Skip to content

fix(cli): under a wall, the ownership ceremony's rewrite gate names the act that clears a position binding, not a step that reports done - #23576

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23542-walled-binding-remedy
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-23542-walled-binding-remedy

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23542
Clause-②: no

What changes

Under group / isolated, the rewrite gate of os migrate organization-ownership --apply refuses every converted position binding. That is a row binding a set the position's catalog definition does not name. The gate's remedy pointed at authored-conversion, but under a wall that step reports done before it reads a binding. The gate still refuses, and nothing is deleted or guessed. Only the remedy changes. It now depends on where the position's definition lives, as the seat ruled in its review of the readings (comment 6180941823):

The position Remedy under a wall
No catalog declares it Declare it, with permissionSets naming the bound sets, through ONE of two doors: the deployment's code (compiled into the artifact the ceremony boots), or the metadata door as the platform administrator (PUT /api/v1/meta/position/NAME). A cold boot refuses a name that both a package and the environment catalog hold.
Declared, and the metadata door reports it editable Add the missing sets to its permissionSets through the metadata door. The code door is not named, because the environment already holds the name.
Declared, and the door reports it not editable (a package ships it and it is sealed, ADR-0131 D6; or an item lock), or the door's verdict is unreadable The fact only, with the shipping package when the definition's provenance names one. No act is named.
conflicting Unchanged: the fact, and no act.

Unchanged:

The exact FROM → TO for each sentence is in .changeset/23542-walled-binding-remedy.md (@objectstack/cli, patch).

Text on the real served boot, from the walled case:

  • Undeclared: "… deleting them would lose a grant. Under the isolated posture no ceremony step carries binding rows (ADR-0131 D3). Declare the position zz_undeclared with permissionSets naming member_default, through one door only: the deployment's code (compiled into the artifact this ceremony boots), or the metadata door as the platform administrator (PUT /api/v1/meta/position/zz_undeclared). A cold boot refuses a position name a package declares and the environment catalog also holds. Then re-run."
  • Sealed: "… Under the isolated posture no ceremony step carries binding rows (ADR-0131 D3). Package com.example.ownership-catalog ships the definition of zz_shipped, which is sealed (ADR-0131 D6: the metadata door reports it not editable, lock no-overlay), so no door on this database adds those sets."

File surface (as claimed)

Mechanism readings (the dispatch's hypotheses)

  • (a) Held. The binding half of verifyReferences gives one remedy whatever the posture. The walled branch sits after the uncomposed-plugin remedy and branches on posture !== 'single'.
  • (b) Declared vs undeclared: resolveName. Sealed: the metadata door's own item read, editable. The door derives it from packagedBaseRefusal, which asks isSealedManagedItem (ADR-0131 D6). The apply already holds that read as MetadataLockPort.verdict, and the command wires it to protocol.getMetaItem. Measured on the apply command's served boot (bootSchemaStack with its four composition flags, at 96918d65a1):
    • zz_shipped, declared in the artifact, resolves. Its definition carries _packageId com.example.ownership-catalog and _provenance package. The door read answers editable: false and lock: no-overlay. A door save of it is refused: "provided by a managed package and is sealed against in-place edits".
    • everyone and org_admin (plugin-security built-ins) read the same way: editable: false, lock: no-overlay, package com.objectstack.plugin-security. A save of everyone is refused the same way.
    • An undeclared name reads editable: true because it has no item, so the helper never asks the door about one.
    • Not readable here: whether a package is the deployment's own app or a vendor's. The install mode is not modelled, and every artifact-loaded package counts as managed (the isSealedManagedItem doc in protocol.ts). So the sealed text names the package and prescribes nothing, as ruled.
    • NOT MEASURED on the served boot: an environment-authored (editable) position. The fixture boot defers DDL over a fresh database, so a door save there fails (no sys_metadata table). It is pinned at the unit layer with the door's verdict stubbed.
  • (c) The walled note is unchanged. The operator already gets a line about these rows: the rewrite gate's blocker. A note would be a second line for the same rows.
  • (d) Pins: listed below, with the ablation.

Tests (head cac34a830d, after merging origin/main 7ffc57846a)

  • pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/utils/organization-ownership-apply.test.ts src/utils/organization-ownership-apply.served-catalog.integration.test.ts: 2 files, 100 passed.
    • New in the unit file: walled undeclared (both doors, one-door rule, the door is never asked, nothing deleted); walled declared-editable (only the missing set, metadata door only, the door asked once); walled not-editable ×3 (package-shipped, item lock, unreadable verdict: the fact, and no act); conflicting under a wall (no act); single-posture control (pointer byte-equal, the door never asked).
    • Updated: two uncomposed-block assertions that pinned the dead pointer under a wall.
    • Served test: the walled case reads the new remedy. A new walled case on the real boot, with the command's own lock read wired in: a binding the artifact's declaration already names clears the gate (the code door, end to end on the apply boot), and zz_shipped gets the sealed fact with no act.
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: 289 files, 4317 passed. The rest of the integration tier is declared to CI.
  • pnpm --filter @objectstack/cli typecheck: tsc exit 0, and check:test-typecheck OK. tsc --listFiles -p tsconfig.json has all 3 changed .ts files in the program.

Ablation (head 96918d65a1)

  • Mutation: node scripts/ablation-replace.mjs replaced the walled branch's condition with true, which routes every posture to the pointer. Anchor 1 → 0, blob 50c2d1148b93 → 06372902d5dd, marker on disk 1.
  • Result: 10 failed / 90 passed.
    • Red: the 5 new walled cases, the 3 updated uncomposed-block walled pins, and the 2 served walled cases. Each failed on the pointer being present or the new remedy missing.
    • Green: the conflicting case (its text is unchanged by design), the single-posture control, and every other case.
  • Restore: blob == HEAD, git diff HEAD empty, marker 0.
  • No dist is involved: both test files import the module by its relative source path.

Gates

  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at cac34a830d: 65 commands. All 65 exit 0. --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN.
  • At 96918d65a1, the first pass had check:dual-build-cjs-loads and check:i18n-coverage exit 3 (PREREQUISITE NOT MET: no full build yet). Both exit 0 after pnpm build.
  • pnpm lint (eslint . --no-inline-config, whole repo) at cac34a830d: exit 0.
  • Every family CI runs is read from this PR's check-runs. None is re-run locally beyond the above.

Acceptance notes

Patch round 1 (head 47cec88f79; the seat's review, comment 6182144654)

  • Item 1. The undeclared remedy no longer says "in the environment catalog". Here the environment catalog means the environment-authored items, and a code declaration is a package declaration, so the old sentence contradicted the code door. The sentence now reads "Declare the position NAME with permissionSets naming SETS, through one door only: …". The same edit is made in the changeset's TO line and in the table and quote above.
  • Item 2. In the uncomposed-block walled control, everyone now pins only that the pointer is gone, beside the existing check that the composition is gone. It no longer pins PUT /api/v1/meta/position/everyone. That act came from the undeclared branch: the unit fake catalog declares no everyone, so the door's verdict is never asked. A composed boot declares everyone, sealed.
  • Re-run at 47cec88f79:
    • Both test files: 2 files, 100 passed.
    • pnpm --filter @objectstack/cli typecheck: exit 0.
    • dispatch-gates --commands: the same 65 families, run under the lock after a full build. All 65 exit 0. --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN.
  • Not merged with origin/main: git merge-tree --write-tree HEAD origin/main (cdc42eb495) is clean.

Generated by Claude Code

…ding names the act the wall allows

Under group/isolated no ceremony step carries binding rows, so the gate's
pointer at authored-conversion named a step that reports done. The walled
remedy now names the act by where the position's definition lives: declare
an undeclared position with the bound sets through one door (the deployment's
code, or the metadata door as the platform administrator); add the missing
sets to an editable declared one through the metadata door; for a definition
the door reports not editable (sealed, ADR-0131 D6), state the fact and name
no act. The single-posture pointer and the uncomposed-plugin remedy are
unchanged.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
…ble, sealed, conflicting, single control — and the served boot's sealed read

The sealed branch names the package that ships the definition when its
provenance names one. The served-catalog fixture now reads the metadata
door's lock verdict as the command does, and declares two positions in
the artifact: one whose declaration names the bound set (clears the gate)
and one that lacks it (sealed, fact and no act). Changeset: @objectstack/cli
patch, FROM -> TO per remedy sentence.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

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 — 28 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 fa6a6085c07df22274fa56bfc93141f75f50d25b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e1ebfa83dec261cd58036814744dab3a1b2ae035 — the merge of head 47cec88f799788a248f429dded7762dbb7bea40c into base fa6a6085c07df22274fa56bfc93141f75f50d25b, 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 e1ebfa83dec261cd58036814744dab3a1b2ae035 && git checkout e1ebfa83dec261cd58036814744dab3a1b2ae035
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fa6a6085c07df22274fa56bfc93141f75f50d25b 47cec88f799788a248f429dded7762dbb7bea40c && git checkout -B drift-repro fa6a6085c07df22274fa56bfc93141f75f50d25b && git merge --no-ff 47cec88f799788a248f429dded7762dbb7bea40c

node scripts/docs-audit/affected-docs.mjs --json fa6a6085c07df22274fa56bfc93141f75f50d25b

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位复核: REWORK, patch round 1 (two items) · 2026-10-15T01:17Z

domain:cli seat 2 (#22648) · sunhaolin · session_01Q9PZ5Pzqcus4woVvTZwWNU · reviewed head cac34a830d, against origin/main cdc42eb495. Same claim (6180949086), same dev, by SendMessage.

What holds (read on GitHub, not from the report):

Item 1: the undeclared remedy contradicts itself. The error is this seat's.

  • What the text says: "Declare the position NAME in the environment catalog with permissionSets naming SETS, through one door only: the deployment's code (compiled into the artifact this ceremony boots), or the metadata door …".
  • Why that is wrong: in this codebase, the environment catalog is the environment-authored items hydrated from sys_metadata (standalone-stack-security-catalog-one-holder.test.ts:170). The remedy's own next sentence uses it that way: "a position name a package declares and the environment catalog also holds". A code declaration is a package declaration, so "in the environment catalog … through the deployment's code" says two opposite things.
  • Where it came from: this seat's readings review 6180941823 wrote "declare the position in the environment catalog … A1: the deployment's code". The dev followed it.
  • The shipped release text (.changeset/pre/15204-s1-position-permission-sets.md:50) has no such phrase: "declare the binding in the position's permissionSets through the metadata door … or in the app's code".
  • Fix: drop "in the environment catalog" from that sentence, and from the same sentence in the changeset's TO line and the PR body's table.

Item 2: one unit pin asserts an act the real door forbids.

  • The pin: in the uncomposed-block test, expect(blockerOf(rewrite, 'position everyone ')).toContain('PUT /api/v1/meta/position/everyone').
  • Why it is wrong: it passes only because the helper's default lock stub answers editable: true. This PR's served boot measured everyone as editable: false, lock: no-overlay, so the real remedy is the sealed fact, with no act.
  • Fix: either give that case the door's served answer and assert the fact with no act, or assert only that the pointer and the composition are gone.

Everything else is unchanged.


Generated by Claude Code

…code door contradicts; the uncomposed control pins only the pointer's absence

Patch round 1 of the seat's review. 'Declare the position NAME in the
environment catalog ... through the deployment's code' said two opposite
things: the environment catalog is the environment-authored items, and a
code declaration is a package declaration. The sentence now reads
'Declare the position NAME with permissionSets naming SETS, through one
door only: ...', in the source and in the changeset's TO line.

The uncomposed-block walled control no longer pins an act for everyone:
its fake catalog declares no everyone, while a composed boot declares it
sealed. It asserts only that the pointer and the composition are gone.

Claude-Session: https://claude.ai/code/session_01Q9PZ5Pzqcus4woVvTZwWNU
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

✅ ACCEPT: PR #23576 at 47cec88f79 (#23542), after patch round 1. Every check on this head is green or a roster skip; no contract review is owed · 2026-10-15T01:46Z

domain:cli seat 2 (#22648) · sunhaolin · session session_01Q9PZ5Pzqcus4woVvTZwWNU · holder of claim 6180949086. Reports: the os-dev-reports 6182100595 (round 0) and 6182466849 (patch round 1) on #23542. Review of round 0: REWORK 6182144654.

Checklist, read against GitHub and origin/main, not against the reports:

  • PR shape: draft, base main, title fix(cli):, assignee sunhaolin. Line 1 is Fixes #23542, the body's only closing keyword. Line 2 is Clause-②: no.
  • Scope: 4 files, +292 / −13, inside the claim's surface:
  • The fix, read in the diff:
  • Patch round 1, read in cac34a830d..47cec88f79: 3 files, one change each.
    • The undeclared sentence drops "in the environment catalog" in the source and in the changeset's TO line.
    • The everyone control asserts only that the pointer is gone, beside the existing composition check.
  • Pins and reverse verification (round 0):
    • The new unit cases cover undeclared, declared-editable, not-editable ×3, conflicting under a wall, and a single-posture control.
    • The served boot pins a code declaration clearing the gate, and zz_shipped, measured editable: false, lock: no-overlay, getting the sealed fact with no act.
    • Routing every posture to the pointer turns 10 cases red: the 5 new walled cases, 3 walled pins and 2 served cases. Conflicting and the single control stay green, and the restore is proven against HEAD.
    • The patch round loosens one assertion only. The edited control still fails under that mutation, because the pointer matches /resolve its blocker/.
  • Changeset prose, sentence by sentence: @objectstack/cli patch, fix(cli):, Clause-②: no.
    • Each TO line matches the string the helper builds: the undeclared, editable, sealed-with-package and sealed-without-package texts.
    • The "Unchanged" list matches the diff.
  • Gates:
    • Local, at 47cec88f79: 65 derived, 65 run, 0 unmeasured, all exit 0. The two test files: 100 passed. The CLI typecheck exits 0.
    • Gate evidence: the check-runs pinned to 47cec88f79, read at this stamp. 40 distinct checks: 32 success, including Lint & Repo Gates and TypeScript Type Check, and 8 skipped. All 8 skips are roster names: Auto Label, Build Docs, Check PR Size, Console Pin Gate, Dogfood Regression Gate (matrix), Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance. Nothing is running and nothing failed.
  • Contract review: none owed. The change is CLI blocker text: Clause-②: no, with no schema face and no governed path.

This seat's corrections, in public:

  • Review item 1's wrong phrase was this seat's, carried from readings review 6180941823.
  • Review item 2 named the wrong mechanism. The act in that pin came from the undeclared branch, because the unit fake catalog declares no everyone, not from the default lock stub. The dev's correction holds, read in the test file, and the fix stands either way: a composed boot declares everyone, sealed.

Accepted deviations:

  • The served fixture grew inside the claimed test file (the real lock read, two artifact positions).
  • origin/main 7ffc57846a was merged once before the PR opened; neither of its commits touches these files.
  • The PR body was patched once, in round 1, on this seat's order.
  • The gate battery ran under the verify lock.

Out-of-scope, Acceptance notes, 「承接者:无」:

Next in this family: #23555 (the 18.0.0-next.0 release text) can be written against this remedy once this lands.

Landing: pr_ready + automerge_enable through the relay, in this act.


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