test(fixtures): derive PLATFORM_OBJECTS from the installed platform rosters - #1574
Merged
Conversation
The set was sixteen platform object names typed out by hand, citing "the
16.1.0 bundles in node_modules" against a 17.2.0 pin. Both dangling-reference
guards use it as half of their resolvability predicate, so a machine roster
kept by hand fails them in both directions once it drifts.
It had drifted: nothing registers `sys_approval`. plugin-approvals exports
SysApprovalRequest / Action / Approver / Delegation and no SysApproval, and the
only occurrence of the bare token in any installed bundle is a
`startsWith('sys_approval')` prefix guard. So the allowlist admitted a name
matching nothing at runtime — the class it exists to reject.
Now read from six packages, each loaded by something this stack declares,
filtered to values carrying both `name` and `fields`. 16 names become 65: 50
enter, one leaves. Same export name, still a Set<string>, so no reader
changes. sys_audit_log / sys_activity / sys_comment stay hand-listed with the
reason — plugin-audit provisions them at init and exports no descriptor.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDbLQQiy7ew8sdwiCQEZwC
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
The derivation commit left two consecutive `/**` openers above the PLATFORM_OBJECTS docblock — an off-by-one in the splice that produced it, which kept the original opener and added a new one. It is legal JavaScript: the stray opener sits inside the block comment it accidentally opens, so it is just text, which is why every gate passed and was right to. But it mis-renders in any editor or doc tool that folds on `/**`, and this file's whole job in this PR is to be more trustworthy than the hand-maintained list it replaces. Comment text only — the delimiter is now balanced at 7 openers and 7 closers. No code, no wording, no changeset change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YDbLQQiy7ew8sdwiCQEZwC
os-musk
marked this pull request as ready for review
September 3, 2026 17:00
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1481
test/helpers/metadata-fixtures.tsexported sixteen platform object names typed out by hand, citing "verified against the 16.1.0 bundles in node_modules" against a 17.2.0 pin. This derives the set from the installed packages' own rosters instead. Same export name, still aSetof strings, so no reader needed a line changed — the two files fenced to #1434 (test/metadata-references.test.ts,test/readonly-write-semantics.test.ts) and the unfencedtest/action-references.test.tsare all untouched.All measurements on
origin/main@18444c8, verified at5180990, ObjectStack 17.2.0 installed.The unconfirmed hypothesis, measured first: nothing registers
sys_approvalConfirmed.
@objectstack/plugin-approvals17.2.0 exportsSysApprovalRequest,SysApprovalAction,SysApprovalApproverandSysApprovalDelegation— and noSysApproval. The only occurrence of the bare token in any installed bundle is a prefix guard:A control confirms the search was live rather than vacuously empty: the same
name: '...'pattern hits 6 files forsys_approval_requestand 75 forsys_user, and 0 forsys_approval.So the allowlist admitted a name that matches nothing at runtime — precisely the class the set exists to reject. That is the card's strongest sentence and it is now proven rather than reported.
src/apps/crm.app.tscarried the same stale claim in a comment; it now names the four objects that exist. Comment only, no code.The predicate, its basis, and its control
A roster entry is an
ObjectSchema.create(...)descriptor, and what makes it an object rather than some other metadata is that it carries afieldsmap. So the filter is "value carrying bothnameandfields".Basis measured: 62 exported values across the six packages carry both, and all 62 names are
sys_*— no non-platform name is admitted.The control is the near miss. 7 exported values carry a
namebut nofields, and they are exactly the things that are not objects — so the predicate excludes precisely them:nameACCOUNT_APP,SETUP_APP,STUDIO_APPaccount,setup,studioSysOrganizationDetailPage,SysPositionDetailPage,SysUserDetailPagesys_*_detailSystemOverviewDashboardsystem_overviewNothing exports
fieldswithout aname(0 cases). The derivation is therefore anchored to registration, not to "everything the package exports".Which packages, and why these
Each roster is loaded by something this stack declares, so a name it registers really does resolve at runtime. The capability-to-package map lives in the CLI's
CAPABILITY_PROVIDERSand is not exported (checked: no such export, and the identifier is absent from the CLI dist), so the packages — not the object names — are the part still written out.@objectstack/platform-objects@objectstack/plugin-approvalsrequires: 'approvals'@objectstack/plugin-sharingrequires: 'sharing'@objectstack/service-automationrequires: 'automation'@objectstack/service-messaging@objectstack/service-storageplugin-audit)The set difference, counted
16 names become 65: 50 enter, exactly 1 leaves.
Leaves (1):
sys_approval— registered by nothing, as measured above.Enters (50):
sys_accountsys_api_keysys_audit_logsys_automation_runsys_business_unitsys_business_unit_membersys_device_codesys_flow_dispatchsys_http_deliverysys_import_jobsys_invitationsys_jobsys_job_queuesys_job_runsys_jwkssys_membersys_metadatasys_metadata_auditsys_metadata_historysys_migrationsys_migration_journalsys_notification_deliverysys_notification_preferencesys_notification_receiptsys_notification_subscriptionsys_notification_templatesys_oauth_access_tokensys_oauth_applicationsys_oauth_client_assertionsys_oauth_client_resourcesys_oauth_consentsys_oauth_refresh_tokensys_oauth_resourcesys_record_sharesys_report_schedulesys_saved_reportsys_scim_providersys_secretsys_sessionsys_settingsys_setting_auditsys_share_linksys_sharing_rulesys_sso_providersys_team_membersys_two_factorsys_upload_sessionsys_user_preferencesys_verificationsys_view_definitionAll 15 surviving hand-listed names are present in the derived set (checked by reading the real module, not the probe: size 65, every one of the 15 present,
sys_approvalabsent).Nothing currently red turns green spuriously
2 files, 46 tests passed— and are green after, with the identical 46. Nosys_*reference in today's app metadata changes verdict in either direction.plugin-audit's own descriptors.sys_*tokenssrc/mentions, 7 are newly admitted (sys_audit_log,sys_business_unit_member,sys_http_delivery,sys_member,sys_record_share,sys_saved_report,sys_sharing_rule), each backed by a roster entry above.sys_approval_process,sys_queue,sys_user_position,sys_webhook,sys_fetch_previous_updateandsys_approvalremain outside the set — nothing gained admission without registering.Why widening is the correct direction. The old docblock called the set a deliberate subset, but its stated contract is "a reference to a
sys_*name outside this set matches nothing at runtime". A name the platform really registers does resolve, so withholding it makes the guard wrong in the false-red direction — the first failure mode the card names, and the one that costs an unrelated PR a red run. Accuracy here happens to be permissive. No guard was weakened and the derived set was not hand-curated back down.The residue: expected, and not a failure
@objectstack/plugin-auditregisterssys_audit_log,sys_activityandsys_commentby callingsyncObjectSchema()on each fromprovisionSystemTables()at plugin init. Its descriptors are module-private —'SysActivity' in require('@objectstack/plugin-audit')isfalse, its exports are 10 functions and 1 string — and itsexportsmap offers only".", so no deep subpath reaches them. They are not statically derivable from any public surface and stay hand-listed with that reason beside them.This matters rather than being cosmetic:
sys_activityis live app metadata (ctx.api.object('sys_activity').insert(...)in two action bodies), so a derivation that silently dropped it would have been a false red.sys_audit_logis included on the same basis — it registers, so a reference to it resolves.Reverse verification
The derivation is load-bearing, not decorative. Making the registration predicate match nothing (
return false) turns the guards red in the predicted direction:The mutation was confirmed on disk before the run (injected marker present 1, original predicate line gone 0) rather than trusted to the editor's exit code, and the mutated file is first-party TypeScript that vitest transpiles from source, so there is no
distleg to rebuild. Restore wasgit checkout HEAD --on an absolute path, proved three ways:git diff HEADempty, worktree blob755b910bequal to the HEAD blob (both non-empty), and 0 ablation markers left.Gates
Full
pnpm verifychain, green at5180990:validatecolSpanadvisories only)typechecklintlint:i18n-gatei18n/missing-*hygienehygiene:tokensbuildtesttypecheckwas confirmed to actually cover the change rather than passing vacuously:tsc --listFilesincludes both edited files and resolves all six roster.d.tsfiles.Scope
Three files, exactly the declared surface:
test/helpers/metadata-fixtures.ts, thesrc/apps/crm.app.tscomment, and one changeset (patch).No new gate was added. Deriving an existing list is not a gate; a check that the list matches the registry would be, and that belongs to the platform.
Generated by Claude Code