Repository navigation
fix(objectql)!: store an object's own actions as their ActionSchema parse, so a raw manifest's requiresFeature is lowered - #25981
Conversation
…rse at registerObject A code package's object registered through SchemaRegistry.registerObject (a raw manifest, a strict: false stack, a programmatic registerApp) now keeps its own actions as their ActionSchema parse, so a declared requiresFeature / requiresMembershipReach is lowered into visible. An action that does not parse is refused with INVALID_METADATA (422) before anything is stored. One parse helper (parsePackageAction) serves both the standalone action item and the object's own actions. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012FM5zuP45LkwYGZfazCj5z
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012FM5zuP45LkwYGZfazCj5z
The registry now stores an object's own actions as their ActionSchema parse, so the reader-seam pin's host-handler script actions name their target and the MCP discovery fixture's AI-exposed ping carries a 40-character description. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012FM5zuP45LkwYGZfazCj5z
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 88053174692167dcb376d8c32d33754139d87f79 && git checkout 88053174692167dcb376d8c32d33754139d87f79
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cdd4eea93811522cca145a0f24858def51443671 cf6bf094b94601e90a9912f0c0b426e1352f6600 && git checkout -B drift-repro cdd4eea93811522cca145a0f24858def51443671 && git merge --no-ff cf6bf094b94601e90a9912f0c0b426e1352f6600
node scripts/docs-audit/affected-docs.mjs --json cdd4eea93811522cca145a0f24858def51443671
|
Contract reviewServed-tier: ① Derived judgmentsRead-only: the card body and its three comments (triage 6393644756, claim 6409400948, os-dev-report 6410946806), the PR body, the five-file diff CI on the head, at review time. 50 check-runs; 42 concluded, 0 non-green; 8 still in progress ( 1. The accept-set change, named. 2. Which registrations are now parsed, and what refuses them. Judged (parsed, or refused 422 3. Untouched as claimed. A bare 4. Stored shape equals the strict path's. 5. Placement. The parse sits after the field- 6. The two runtime fixture edits are fixes of off-contract fixtures, not loosening. 7. The producer measurement. 21 objects / 154 actions / 0 changed / 4 refused, all four on ② Semver levelThe changeset moves ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | draft, base main, Fixes #25744, Clause-②: no (narrowing: …) and Metadata-protocol: none in body and changeset, @objectstack/objectql: patch with the BREAKING banner and ! on the title. |
| Files (5, +265/−8) | objectql/src/registry.ts (registerObject parses a code package's object actions through ActionSchema under registerItem's #25649 guard), one new suite, two runtime test fixtures made spec-valid (a script action's target, a 40+ character ai.description), one changeset. No packages/spec/**; not governed (check-governed-merges --pr 25981, 273 changed lines). |
| Fix | a raw-manifest or strict: false object action that fails ActionSchema is refused 422 INVALID_METADATA with actions.N.KEY paths before any registry write; a passing one is stored exactly as strict defineStack stores it (requiresFeature / requiresMembershipReach lowered into visible). Tenant (_provenance: 'org') and package-less registrations are untouched. |
| Evidence | red first, ablation, derived gates green per the report; the contract review read every door that reaches registerObject. |
| Carried | FLS_CONTRACT_OBJECT is off-contract but no door registers it under a package: acceptance note, no card. The doubled period in parsePackageAction's message predates this PR: polish, unfiled. At the nested-plugin and metadata-service reload doors the refusal is a warn, the same as every registry refusal there. |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready + auto-merge through the relay; auto-merge waits for the last three Test Core shards. |
Generated by Claude Code
Requested by Leehom · project thread
Fixes #25744
Clause-②: no (narrowing: an object's own action that fails
ActionSchemais refused at the runtime registration door, as #25649 did for standalone actions)Metadata-protocol: none
BREAKING for a package that registers an object whose own action
ActionSchemadoes not accept. Such an object used to be stored as written, with the action's declared gates inert. Now the object's registration is refused withINVALID_METADATA(422) and nothing of it is stored. The error names the action, its object and its package, andissues[]locates each failure asactions.N.KEY. An object that passesos validateor a strictdefineStackis unaffected. ADR-0087 disposition (in the changeset):not-required (no-migration-prescription).ActionSchemaandObjectSchemaare byte-unchanged, so there is nothing forobjectstack migrate metato rewrite.What changed
SchemaRegistry.registerObject(packages/objectql/src/registry.ts) now stores a code package's object with its ownactionsas theirActionSchemaparse. If an action does not parse, the registry refuses the object loudly. Before, the actions were stored as the package wrote them.actionitem,parsePackageAction. It gains an optionalowner({ object, index }), so an object action's refusal names its object and prefixes each issue path withactions.N. A newparsePackageObjectActionsmaps it over the object'sactionsand returns a shallow copy; the caller's body is never mutated.typedoor and the twoextendrefusals, and before any materialization or the contributor list. So a refused object leaves nothing behind, and a refused re-registration leaves the object already registered under that package in place (pinned).registerItem's action-parse test (packageIdpassed andisCodeArtifactBody), asked of the body asapplyProtectionwill stamp it later in the method: the package id it does not already carry, and'package'provenance. A registration with nopackageId, thesys_metadatasentinel, and a tenant-authored body (_provenance: 'org': the save door's write-through and the boot rehydration) are not judged here.ObjectSchema'sactionsisActionSchema[], so a platform object (ObjectSchema.create) and a strictdefineStackalready hand this method parsed actions, and they are stored deep-equal to what arrived (pinned as the control).INVALID_METADATA/ 422 (ADR-0112), the code this package already uses for metadata that fails the spec. No new error code.Producers measured
Measured at
9ca36b163ewith every in-repo producer of an object withactions: each object's actions run throughActionSchema.safeParseand compared as canonical (key-sorted) JSON. Sources: every non-test file underpackages/**/srcthat declares anactions: [array (connectors, platform objects, plugin-approvals, service-automation, qa fixtures, verify), the four example stacks (app-crm,app-todo,app-showcase,app-multi-package) andplatform-objects' entry.accountobject of the published test fixtureFLS_CONTRACT_OBJECT(@objectstack/metadata-core/testing): itsscriptactions carry nobodyortarget. Its own CHANGELOG already records it as not a spec-valid object. No suite registers it throughregisterObject(its readers inpackages/restandpackages/metadata-coreserve it from a stored map), so this door never meets it. It is left unchanged and named in the acceptance notes.Fixture triage (the refusal's consumer radius)
Two runtime fixtures registered an object action body
ActionSchemarefuses. Each got the missing declaration; nothing pinned the removed behaviour:runtime/src/stored-metadata-reader-contexts.pin.test.ts: the four host-handlerscriptactions onpin_notename theirtarget. Dispatch is by handler key, so the pins' subject is unchanged (171 tests in the two files green).runtime/src/domains/mcp-package-object-discovery.test.ts: the AI-exposedpingaction'sdescriptionis 40 characters or more, the lengthActionSchemarequires.Tests
New,
packages/objectql/src/object-own-action-schema-parse.test.ts, through the realObjectQL.registerApp/registry.registerObjectandprotocol.getMetaItem({ type: 'object' }), the read behindGET /api/v1/meta/objects/:name:requiresFeatureand a CELvisibleis served with the conjunct lowered, with norequiresFeaturekey, equal to whatObjectSchema.creategives the same declaration.visible, the gate alone.requiresMembershipReachis lowered the same way.strict: falsedefineStackhands the body on raw; registered, it is served lowered.registerObjectunder a package is parsed, and the caller's body is not mutated.ObjectSchema.createproduced is served deep-equal to it.requiresFeaturebesidevisible: falseis refusedINVALID_METADATA/ 422; the message namesgated,oa_organdcom.test.raw;issues[]holdsactions.0.requiresFeature; nothing is registered.unrecognized_keys) leaves the registered object in place.Red first. With
registry.tsrestored to the base blob, the new file ran 7 failed / 1 passed; the one green was the control.Ablation (
scripts/ablation-replace.mjs). The test imports./registry.jsfrom source, so no dist leg applies. The anchorschema = parsePackageObjectActions(schema, packageId);hit once and was replaced by a comment; blobca946b02a9tob5fb1f4f77. Result: 7 failed, 1 passed (the control). Restore: blobca946b02a9equals HEAD andgit diff HEADis empty.Local verification
Head
cf6bf094b9.pnpm --filter @objectstack/dogfood test, noOS_TEST_SHARD, no paths), after building the@objectstack/dogfood^...closure:Test Files 296 passed | 3 skipped (299),Tests 2523 passed | 18 skipped (2541),VERDICT command-exit 0. Run at3867ab77; the only later commit edits two runtime test fixtures.@objectstack/objectqlfull:Test Files 470 passed (470),Tests 8738 passed (8738). Typecheck exit 0,check:test-typecheck: OK.@objectstack/runtimefull:Test Files 446 passed (446),Tests 7131 passed | 19 skipped. Typecheck exit 0,check:test-typecheck: OK.@objectstack/restfull:Test Files 322 passed (322),Tests 6466 passed | 326 skipped.@objectstack/metadata-protocolfull:Test Files 264 passed | 3 skipped (267),Tests 21518 passed | 19 skipped.eslint --no-inline-configover the touchedobjectqlfiles: exit 0.dispatch-gates.mjs --commandson this diff derives 73 families; all run,--ran: 73 derived, 73 run, 0 NOT-MEASURED.Acceptance notes
_provenance: 'org') is not parsed at this door. It is the save door's to parse, and the overlay row of a packaged action is outside this card.actions. Only anactionsarray is parsed. A body whoseactionsis not an array is left as it was;ObjectSchemarefuses that shape at the authoring doors.FLS_CONTRACT_OBJECTstays off-contract. Its fourscriptactions carry nobodyortarget(measured above). This door never registers it today; a suite that registers it under a package would now be refused.parsePackageActionjoins each issue's message and then adds a period, so a Zod message that ends in one reads…function name)... The standalone-action refusal already prints this onmain; this PR does not change the sentence.Generated by Claude Code