Skip to content

fix(objectql)!: store an object's own actions as their ActionSchema parse, so a raw manifest's requiresFeature is lowered - #25981

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-gq9dl5
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-gq9dl5

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Fixes #25744

Clause-②: no (narrowing: an object's own action that fails ActionSchema is 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 ActionSchema does 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 with INVALID_METADATA (422) and nothing of it is stored. The error names the action, its object and its package, and issues[] locates each failure as actions.N.KEY. An object that passes os validate or a strict defineStack is unaffected. ADR-0087 disposition (in the changeset): not-required (no-migration-prescription). ActionSchema and ObjectSchema are byte-unchanged, so there is nothing for objectstack migrate meta to rewrite.

What changed

SchemaRegistry.registerObject (packages/objectql/src/registry.ts) now stores a code package's object with its own actions as their ActionSchema parse. If an action does not parse, the registry refuses the object loudly. Before, the actions were stored as the package wrote them.

  • One parse path. The object path reuses the helper PR fix(objectql)!: store a code package's action as its ActionSchema parse, so a bound action's requiresFeature is lowered #25699 added for a standalone action item, parsePackageAction. It gains an optional owner ({ object, index }), so an object action's refusal names its object and prefixes each issue path with actions.N. A new parsePackageObjectActions maps it over the object's actions and returns a shallow copy; the caller's body is never mutated.
  • Where in the method. Right after the field-type door and the two extend refusals, 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).
  • Which registrations. The test is registerItem's action-parse test (packageId passed and isCodeArtifactBody), asked of the body as applyProtection will stamp it later in the method: the package id it does not already carry, and 'package' provenance. A registration with no packageId, the sys_metadata sentinel, and a tenant-authored body (_provenance: 'org': the save door's write-through and the boot rehydration) are not judged here.
  • Idempotent over parsed input. ObjectSchema's actions is ActionSchema[], so a platform object (ObjectSchema.create) and a strict defineStack already hand this method parsed actions, and they are stored deep-equal to what arrived (pinned as the control).
  • Refusal envelope. 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 9ca36b163e with every in-repo producer of an object with actions: each object's actions run through ActionSchema.safeParse and compared as canonical (key-sorted) JSON. Sources: every non-test file under packages/**/src that declares an actions: [ array (connectors, platform objects, plugin-approvals, service-automation, qa fixtures, verify), the four example stacks (app-crm, app-todo, app-showcase, app-multi-package) and platform-objects' entry.

  • 21 objects carrying actions, 154 actions. Refused: 4, all on one object. Changed by the parse: 0.
  • The 4 refused are the account object of the published test fixture FLS_CONTRACT_OBJECT (@objectstack/metadata-core/testing): its script actions carry no body or target. Its own CHANGELOG already records it as not a spec-valid object. No suite registers it through registerObject (its readers in packages/rest and packages/metadata-core serve it from a stored map), so this door never meets it. It is left unchanged and named in the acceptance notes.
  • No shipped product object fails the parse.

Fixture triage (the refusal's consumer radius)

Two runtime fixtures registered an object action body ActionSchema refuses. Each got the missing declaration; nothing pinned the removed behaviour:

  • runtime/src/stored-metadata-reader-contexts.pin.test.ts: the four host-handler script actions on pin_note name their target. 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-exposed ping action's description is 40 characters or more, the length ActionSchema requires.

Tests

New, packages/objectql/src/object-own-action-schema-parse.test.ts, through the real ObjectQL.registerApp / registry.registerObject and protocol.getMetaItem({ type: 'object' }), the read behind GET /api/v1/meta/objects/:name:

  • A raw manifest's object action with requiresFeature and a CEL visible is served with the conjunct lowered, with no requiresFeature key, equal to what ObjectSchema.create gives the same declaration.
  • With no visible, the gate alone. requiresMembershipReach is lowered the same way.
  • A strict: false defineStack hands the body on raw; registered, it is served lowered.
  • A direct registerObject under a package is parsed, and the caller's body is not mutated.
  • Control: an object ObjectSchema.create produced is served deep-equal to it.
  • Refusal: requiresFeature beside visible: false is refused INVALID_METADATA / 422; the message names gated, oa_org and com.test.raw; issues[] holds actions.0.requiresFeature; nothing is registered.
  • A refused re-registration (an undeclared key, unrecognized_keys) leaves the registered object in place.

Red first. With registry.ts restored 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.js from source, so no dist leg applies. The anchor schema = parsePackageObjectActions(schema, packageId); hit once and was replaced by a comment; blob ca946b02a9 to b5fb1f4f77. Result: 7 failed, 1 passed (the control). Restore: blob ca946b02a9 equals HEAD and git diff HEAD is empty.

Local verification

Head cf6bf094b9.

  • Dogfood battery, full, under the verify lock with CI's command (pnpm --filter @objectstack/dogfood test, no OS_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 at 3867ab77; the only later commit edits two runtime test fixtures.
  • @objectstack/objectql full: Test Files 470 passed (470), Tests 8738 passed (8738). Typecheck exit 0, check:test-typecheck: OK.
  • @objectstack/runtime full: Test Files 446 passed (446), Tests 7131 passed | 19 skipped. Typecheck exit 0, check:test-typecheck: OK.
  • @objectstack/rest full: Test Files 322 passed (322), Tests 6466 passed | 326 skipped.
  • @objectstack/metadata-protocol full: Test Files 264 passed | 3 skipped (267), Tests 21518 passed | 19 skipped.
  • eslint --no-inline-config over the touched objectql files: exit 0.
  • dispatch-gates.mjs --commands on this diff derives 73 families; all run, --ran: 73 derived, 73 run, 0 NOT-MEASURED.

Acceptance notes

  • Boundary kept: tenant overlay rows. A tenant-authored object body (_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.
  • A non-array actions. Only an actions array is parsed. A body whose actions is not an array is left as it was; ObjectSchema refuses that shape at the authoring doors.
  • FLS_CONTRACT_OBJECT stays off-contract. Its four script actions carry no body or target (measured above). This door never registers it today; a suite that registers it under a package would now be refused.
  • Doubled period in the refusal text. parsePackageAction joins 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 on main; this PR does not change the sentence.

Generated by Claude Code

…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
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
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 3 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/kernel/error-handling.mdx (via registerObject (symbol, a method of class SchemaRegistry))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cdd4eea93811522cca145a0f24858def51443671 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 88053174692167dcb376d8c32d33754139d87f79 — the merge of head cf6bf094b94601e90a9912f0c0b426e1352f6600 into base cdd4eea93811522cca145a0f24858def51443671, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs cdd4eea93811522cca145a0f24858def51443671 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cf6bf094b94601e90a9912f0c0b426e1352f6600
Local-runs: none
Scope: packages/objectql/src/registry.ts, packages/objectql/src/object-own-action-schema-parse.test.ts, packages/runtime/src/domains/mcp-package-object-discovery.test.ts, packages/runtime/src/stored-metadata-reader-contexts.pin.test.ts + packages/objectql/src/registry.ts (parsePackageAction, registerItem's #25649 parse guard, registerObject through its applyProtection call), packages/metadata-core/src/code-artifact-provenance.ts (isCodeArtifactBody, isTenantAuthored), packages/spec/src/shared/protection.zod.ts (applyProtection), packages/spec/src/ui/action.zod.ts (ActionSchema: the script body-or-target refine, the ai.description min-40 rule, the lowerRequiresFeature / lowerRequiresMembershipReach transform), packages/spec/src/data/object.zod.ts (ObjectSchema.actions, ServiceObject), packages/objectql/src/engine.ts (registerApp, stampObjectPackage, nested-plugin object door, executeAction), packages/objectql/src/plugin.ts (residual-object door, metadata-service reload doors, objectContributionKind), packages/objectql/src/metadata-facade.ts (registerObjectBothPlaces), packages/metadata-protocol/src/protocol.ts (the save door write-through and the boot rehydration registerObject calls), packages/runtime/src/action-execution.ts (isHeadlessInvokableAction), packages/metadata-core/src/object-schema-fls-contract.ts (FLS_CONTRACT_OBJECT) and its five in-repo readers, scripts/check-changeset-no-major.mjs (ruling A), scripts/check-adr-0087-registration.mjs (prescription detectors). Prose face read for ②: .changeset/25744-object-own-action-schema-parse.md.

① Derived judgments

Read-only: the card body and its three comments (triage 6393644756, claim 6409400948, os-dev-report 6410946806), the PR body, the five-file diff origin/main...cf6bf094b9 (merge base 9ca36b163e), and the head's check-runs. Nothing built or run.

CI on the head, at review time. 50 check-runs; 42 concluded, 0 non-green; 8 still in progress (Test Core shards 1 to 7 of 8, Build Core, Type Check · workspace). Concluded success and bearing on this review: Check Changeset, Lint · CI, release & engine gates (the ADR-0087 registration gate's family), Governed Surface Queue Guard, No other open PR may claim the same single-writer path, The card this PR closes must claim this branch, Dogfood Regression Gate, Test Core (8/8), Type Check · source gates, Type Check · consumer gates, Lint · ESLint. The judgments below are rendered on what has concluded; the pending shards are the derived test families the dev reports green locally (objectql 470 files / 8738 tests, runtime 446 / 7131, rest, metadata-protocol), and a red there would stand on its own as a gate verdict.

1. The accept-set change, named. SchemaRegistry.registerObject(schema, packageId, …) now, when packageId is truthy AND isCodeArtifactBody({ _packageId: schema._packageId ?? packageId, _provenance: schema._provenance ?? 'package' }) holds, maps ActionSchema.safeParse over schema.actions (array, non-empty) and either stores the parsed array on a shallow copy or throws the first failure as INVALID_METADATA / status: 422 / httpStatus: 422 with issues[] paths prefixed actions.N. and a message naming action, object and package (registry.ts 205 to 236, 257 to 266, 3503 to 3508 at the head). Right. The guard is the exact test registerItem applies at its #25649 seam (type === 'action' && packageId && isCodeArtifactBody(item), registry.ts 5302), transposed to run BEFORE applyProtection: applyProtection (protection.zod.ts 238 to 255) stamps _packageId only when undefined and _provenance = 'package' only when undefined and a package id is in hand, so the two ?? fallbacks reproduce byte for byte what isCodeArtifactBody would read after the stamp at registry.ts 3689. One parse path for both doors, as the card wanted.

2. Which registrations are now parsed, and what refuses them. Judged (parsed, or refused 422 INVALID_METADATA): ObjectQL.registerApp for a raw manifest, array and map branches (engine.ts 9303 / 9316, packageId = manifest.id; a manifest with no version is still judged because the guard falls back to the packageId argument, and stampObjectPackage is identity without a version); a strict: false defineStack handed to registerApp; the nested-plugin object door (engine.ts 9479 / 9489, owner = parent id); AppPlugin's residual-object door (plugin.ts 2840); the public ObjectQL.registerObject(schema, packageId) proxy; MetadataFacade.registerObjectBothPlaces when a real _packageId arrives (metadata-facade.ts 191 to 199); the plugin's two metadata-service reload doors (plugin.ts 1361 / 1439, _packageId ?? 'metadata-service', body not tenant-stamped). Right, and one reader note the PR body does not carry: at the nested-plugin door and the two reload doors the throw lands in a pre-existing try/catch that logs warn and skips the object (engine.ts 9492, plugin.ts 1369), so "refused loudly" is a warning line there. That is how every registry refusal on those doors already behaves (the field-type door, ownership conflicts), so it is parity, not a fault of this diff.

3. Untouched as claimed. A bare registerObject(schema) with no packageId: the guard's first conjunct fails; applyProtection leaves it clean. The sys_metadata sentinel: isCodeArtifactBody answers false on it (code-artifact-provenance.ts 59), and the facade's package-less copy is stamped _provenance: 'org' under RUNTIME_AUTHORED_PACKAGE_ID = 'sys_metadata' (metadata-facade.ts 31, 193 to 196). Tenant rows: the only two registerObject calls in metadata-protocol are the save door's write-through (protocol.ts 17824) and the boot rehydration (29256), and both spread _provenance: 'org' onto the body before the call, so isTenantAuthored is true and the parse never runs; the hydrateOverlayIntoRegistry grafting path registers through registerItem, not registerObject. The tenant overlay row of a packaged action is therefore outside this door, which is what the card and the claim's ⛔ ask. Right. An extend body cannot carry actions at any authoring door (ObjectExtensionSchema guidance, object.zod.ts 4095) and the merge carries none, so the parse running on an extend contribution is inert.

4. Stored shape equals the strict path's. ObjectSchema.actions is z.array(ActionSchema) (object.zod.ts 3267) and nothing on ObjectSchema transforms an action after that element parse (every objectName reference in the file is a field diagnostic), so the registry now stores for a raw manifest exactly what ObjectSchema.create / strict defineStack store for the same declaration: requiresFeature / requiresMembershipReach removed and lowered into visible (grade gate first, feature gate last, action.zod.ts 2193 to 2198), defaults filled, CEL string wrapped. The new test pins the lowering against ObjectSchema.create on the same declaration and the registry's own getObject body against the served one; the control pins idempotence over already-parsed input (deep-equal). Right. The caller's body is not mutated by the parse (shallow copy, fresh parsed.data); the later in-place applyProtection stamp on that copy is pre-existing behaviour.

5. Placement. The parse sits after the field-type door and the two extend refusals and before materializeTreeSelfReferences, applySystemFields, registerNamespace and openObjectEntry (registry.ts 3489 to 3526), so a refused object leaves no contributor entry, no namespace claim and no short-name key; a refused re-registration never reaches the ownership block, so the sitting owner stays. The eighth test pins exactly that. Right.

6. The two runtime fixture edits are fixes of off-contract fixtures, not loosening. stored-metadata-reader-contexts.pin.test.ts: four type: 'script' actions on pin_note carried neither body nor target, which ActionSchema refuses (action.zod.ts 1970 to 1976). The edit adds target equal to the action name. Dispatch is engine.executeAction → this.actions.get(objectName:actionName) (engine.ts 6136), the handler PIN_HANDLER_PLUGIN registers by that key; target is never read for dispatch, only by isHeadlessInvokableAction as an executability bit (action-execution.ts 1008), and AppPlugin binds no handler for a target-bound script (plugin.ts 3699 / 3776), exactly as it bound none for a body-less one. The pins' assertions are untouched. mcp-package-object-discovery.test.ts: ai: { exposed: true, description: 'Ping' } violated .min(40) on ai.description (action.zod.ts 788); the edit supplies a 44-character description; the listed set and every assertion are unchanged. Both right.

7. The producer measurement. 21 objects / 154 actions / 0 changed / 4 refused, all four on FLS_CONTRACT_OBJECT: its regrade, rename, invite, escalate are script with no body or target (object-schema-fls-contract.ts 102 to 110). Its five in-repo readers (rest meta-object-fls, meta-item-save-capability-gate, meta-compound-save-and-reset-capability-gate, runtime meta-object-fls, metadata-core object-schema-fls-references) serve it from a stored map, a mocked registry.getObject, or applyObjectSchemaMask / stageIssues; none calls registerObject or registerApp. Right: this door never meets it today.

② Semver level

The changeset moves @objectstack/objectql: patch and nothing else; the runtime hunks are test files and packages/spec/** is untouched (the claim's ⛔ held). Under ruling A (check-changeset-no-major.mjs 905 to 925) the level is decided by the Metadata-protocol: line alone: none ⇒ every introduced entry patch; the (narrowing) arm keeps the BREAKING carriers and the ADR-0087 disposition and has no level coupling. The changeset carries all three carriers: the ! on its summary, the **BREAKING** banner with Before / Now / Fix, and Clause-②: no (narrowing: an object's own action that fails ActionSchema is refused at the runtime registration door, as #25649 did for standalone actions), byte-identical to the PR body's line. Metadata-protocol: none is true: ActionSchema and ObjectSchema are byte-unchanged and no authorable spelling moves. The ADR-0087 disposition not-required (no-migration-prescription) is honest: the body has no ## Migration / 迁移 header, no table whose header cells match OLD_COLUMN_RE / NEW_COLUMN_RE / FROM / TO; Before / Now / Fix bullets are a behaviour description, not a rewrite prescription. The PR title's fix(objectql)!: bang matches the banner. Check Changeset and Lint · CI, release & engine gates are success on the head. The declared level matches what the diff publishes: patch, narrowing, breaking-flagged.

③ Boundary flags

  • Card's open choice (refuse vs diagnostic). The seat ruled refusal in claim 6409400948; the PR implements refusal, 422 INVALID_METADATA, and declares Clause-②: no (narrowing) on body and changeset as the card and triage required. Answered.
  • Tenant overlay boundary (claim ⛔, card ⛔). Kept; both protocol doors stamp _provenance: 'org' (① item 3). Answered.
  • open_questions: []. None to answer.
  • Out-of-scope 1: FLS_CONTRACT_OBJECT off-contract. Verified (① item 7). Its package CHANGELOG already records "not otherwise a spec-valid object (its script actions carry no body or target)" (metadata-core CHANGELOG.md 647). No door registers it under a package, so it has no reach today; a future suite that does will be refused with the actions.N.body issue, which is the contract working. Accepted as an acceptance note; no card owed.
  • Out-of-scope 2: doubled period. Confirmed from parsePackageAction (join('; ') then '. Every action…'), pre-existing on main for standalone actions, no field of the envelope depends on it. Polish; accepted unfiled.
  • Deviation 1: no merge of origin/main before pr_create. main since the base touched engine.ts (realtime refactor refactor(objectql): move the realtime publish bodies out of engine.ts into engine-realtime-publish.ts (#25522 cut 4) #25948) and a security fix (fix(security)!: a hook that runs as system and an action body write no identity assignment #25954), not registry.ts, action.zod.ts, code-artifact-provenance.ts or protection.zod.ts; the merge queue validates the merge. Accepted.
  • Deviation 2: ablation overlapped two ADR-0087 gate runs. Those read changesets, not registry.ts, and the head's Lint · CI, release & engine gates re-answers them green. Accepted.
  • Reader note, not a flag the report raised. At the nested-plugin door and the metadata-service reload doors the new refusal is swallowed into a warn by pre-existing catches (① item 2). Parity with every other registry refusal on those doors; named here so the next reader of "refused loudly" knows where it is a log line.
  • Pending CI. Seven Test Core shards, Build Core and Type Check · workspace had not concluded at render time; they are gate verdicts of their own and this record does not stand in for them.

Implemented-by: claude/project-thread-gq9dl5
Reviewed-by: session_01R1q2xdtYgU6FKrZ7LgjPCC

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 26, 2026 08:06
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 26, 2026 08:06
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-26T08:20Z

Head cf6bf094b9, report 6410946806 (on #25744), claim 6409400948, contract review 6411122522 (PASS, narrowing face).

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants