Skip to content

fix(spec): retire page.assignedProfiles and answer profiles: with the permission-set route - #17835

Merged
os-elon-musk merged 14 commits into
mainfrom
claude/issue-16929-assignedprofiles-removal
Sep 14, 2026
Merged

os-elon-musk merged 14 commits into
mainfrom
claude/issue-16929-assignedprofiles-removal

Conversation

@os-bill

@os-bill os-bill commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Fixes #16929

Executes Ruling A — director seat, decision batch #121 item 2, comment 5644017943 (2026-09-12), carrying the maintainer's 「同意」. Nothing here re-opens a question that ruling settled; alternatives B / C / E are not revisited.

Clause-②: no — this is a removal / narrowing. Nothing is widened, so no needs:contract-review.

PR #17401's landed half (the two guidance prescriptions stopping naming the key) stands and is not redone: both prescriptions on origin/main already omit it, and this branch leaves their text alone.


The six ruled items, one by one

# ruled done
1 page.zod.ts: assignedProfiles removed; profiles / assignedTo become refusals naming the permission-set route; the two guidance strings rewritten ✅ — with one route correction, below
2 page.form.ts helpText and its four locale bundles removed
3 the three false records corrected in the same PR ✅ — with one relocation, below
4 major changeset + an ADR-0087 semantic migration entry; key stripped on migrate meta --stored with a structured TODO ✅ substance; ⚠️ the major grade is refused by a standing repo-wide gate — see One ruled item the tree refuses
5 PR #17401's landed half stands ✅ untouched
6 Clause-②: no ✅ declared above in the fixed spelling

Item 1 — route correction: a retiredKey() tombstone, not a bare shape deletion

The retirement playbook offers two routes and keys the choice on whether the schema is strict: retiredKey() for a non-strict schema, delete-plus-guidance for a strict one. PageSchema is a strictObject, so the first attempt took the strict route — and the build refused it:

❌ 1 authorable key(s) disappeared from the contract:
     - ui/Page:assignedProfiles

scripts/build-schemas.ts check (a) is fatal for any key that leaves an emitting def, strictness notwithstanding, and check (c) then ratchets a baseline deletion against the merge base on one of three proofs — aged-out tombstone, def unreachable from the metadata-type roots, or whole def gone. ui/Page is reachable from the page root and keeps emitting, so none holds. The route the tree actually permits here is the tombstone, which is also what the sibling view.pageName retirement took two days ago.

This is not a softening of the ruling. The key is unwritable: tsc types it never, and a value reaching a parse raises the prescription. It simply stays in the walked shape, which is why its liveness row stays (as dead) and why the authorable-surface baseline marks it [RETIRED] instead of losing the line.

Item 3 — one of the three false records is not where the ruling says it is

Every path:line was re-derived by sentence rather than trusted. Two of the three resolved as written; the second did not.

ruled location what is actually there
packages/spec/liveness/page.jsonlive citing a non-existent objectui bridge found, corrected
packages/spec/liveness/view.json:125 — the "page audience gate" justification not there any more. git grep over that file finds zero hits for audience, assignedProfiles or page audience (lit control: pageName reads 3 lines in the same file; dark control 0). The #17063 pageName retirement rewrote that row on 2026-09-10 and the justification left with it. The same assertion is live at packages/spec/src/api/protocol.zod.ts (SearchAllPageHitSchema's TSDoc) — that is the one corrected here
packages/metadata-protocol/src/protocol.ts — "enforced at page render" found, corrected

So the count is still three, and all three assertions are gone; one of them lives at a different address than the ruling recorded.

Before → after, and what makes the new text true.

  1. packages/spec/liveness/page.json — was status: "live", note: "profile-scoped page audience; objectui bridges it (react/src/spec-bridge/bridges/page.ts) to PageLayout.assignedProfiles." Now status: "dead" with a verifiedAt and a note recording that the cited path does not exist in objectui (nor does any spec-bridge directory), while two sibling objectui citations in the same file resolve. True because the key is now a tombstone and the ledger's own route table says a tombstoned key keeps its row with a dead verdict.
  2. packages/spec/src/api/protocol.zod.ts — was "where the page's own audience gate (assignedProfiles) applies unchanged". Now states that a page has no audience gate of its own, that the key which read as one was removed precisely because nothing enforced it, and that what protects a page is the permission sets on the data it shows. True because the key no longer exists and never had a reader.
  3. packages/metadata-protocol/src/protocol.ts — was "is enforced where it is enforced now, at page render". Now states the opposite and keeps the delegation posture the sweep rests on, which never depended on the key. True by the cross-repo measurement the card and triage both took.

⚠️ packages/metadata-protocol/** is domain:engine's lane. It is here only because ruling item 3 puts all three records in one PR, and exactly one sentence is touched.


Measurement

The removal is real, and it reaches the built artifact

Probed against the built packages/spec/dist/ui/index.mjs before and after, same script both times.

Before (origin/main content, built):

A assignedProfiles :: ACCEPTED :: keys=assignedProfiles,isDefault,kind,label,name,regions,template,type :: value=["admin"]
B profiles(alias)  :: REFUSED  :: code=unrecognized_keys :: path=[] :: Unrecognized key(s) on this page: `profiles`. Did you mean `profiles` → `assignedProfiles`? …
C assignedTo       :: REFUSED  :: code=unrecognized_keys :: path=[] :: Unrecognized key(s) on this page: `assignedTo`. Did you mean `assignedTo` → `assignedProfiles`? …
D LIT-CONTROL icon :: ACCEPTED
E DARK-CONTROL     :: REFUSED  :: code=unrecognized_keys

After (this branch, rebuilt):

A assignedProfiles :: REFUSED :: code=invalid_type :: path=["assignedProfiles"] ::
   `page.assignedProfiles` was removed in @objectstack/spec 18 (ADR-0090 D2, ADR-0049 enforce-or-remove) — it was
   named for the Profile concept ADR-0090 D2 deleted, and it gated nothing: no renderer, route or metadata read door
   ever read the key, so a page that "assigned profiles" stayed open to every caller who could reach it. Delete the
   key. Page audience is the permission set's: gate the DATA the page shows with the object's permission sets, and
   bind those sets to people through positions (`sys_position_permission_set`) — those are the checks the runtime
   actually runs. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand.

B profiles(alias)  :: REFUSED :: code=unrecognized_keys :: path=[] :: Unrecognized key(s) on this page: `profiles`.
   • `profiles` / `assignedTo` are not page keys (ADR-0090 D2: no Profile concept). Page audience is the permission
     set's — gate the DATA the page shows with the object's permission sets, and bind those sets to people through
     positions (`sys_position_permission_set`), never on the page itself. …

C assignedTo       :: REFUSED :: same sentence
D LIT-CONTROL icon :: ACCEPTED  (unchanged)
E DARK-CONTROL     :: REFUSED :: code=unrecognized_keys  (unchanged — the generic report, no rename offered)

That is the actual refusal text, not a claim that one exists. Note the refusal moved channel: unrecognized_keys at the page → invalid_type located at ["assignedProfiles"], which is what a z.never() tombstone produces.

The alias refusals point somewhere true — read, not inherited

A previous round on this card asserted that an alias table runs only from the unrecognized_keys path. I re-read the source rather than inherit it. packages/spec/src/shared/strict-object.ts's own docblock puts it in terms — "aliases … is consulted BEFORE the distance fallback" inside strictUnknownKeyError — and shared/alias-integrity.test.ts states the mechanism as the premise of the gate it implements: "an alias only ever runs from the unrecognized_keys path, so a key the shape declares can never reach it."

The before-probe is the direct evidence: rows B and C above are REFUSED with code=unrecognized_keys before any change. So profiles and assignedTo were never in the accept set, the alias only decorated the rejection, and deleting or repointing those entries narrows nothing — same code, same path, different text. My own reading agrees with the earlier round's.

A second consequence made the entries impossible to keep: alias-integrity.test.ts asserts that an alias's target is a key the shape accepts. Once the key is a tombstone, profiles: 'assignedProfiles' would point at a key the schema cannot accept — the ledger's finding-7 shape. They had to become guidance.

The migration entry actually fires

Driven over a stored page carrying the key, against the built artifact:

=== 1. `migrate meta` chain replay (authored source, 17 -> 18) ===
stack after :: {"pages":[{"name":"deal_desk","label":"Deal Desk","isDefault":false}]}
key still present? false
applied :: [{"toMajor":18,"conversionId":"page-assigned-profiles-removed","surface":"page.assignedProfiles",
             "from":"assignedProfiles","to":"(removed)","path":"pages[0].assignedProfiles"}]
structured TODO present? true
  TODO.toMajor = 18
  TODO.surface = `page.assignedProfiles` — the per-page audience list (REMOVED)

=== 2. stored-row rehydration (the `migrate meta --stored` seam) ===
stored row after :: {"name":"deal_desk","label":"Deal Desk","isDefault":false}
key still present? false

=== 3. CONTROLS ===
LIT : a page WITHOUT the key is untouched :: {"name":"team_home","label":"Team Home","isDefault":true}
DARK: a fabricated key is NOT stripped     :: {"name":"p","label":"P","zzqqNeverDeclared":1}

The key goes on both seams and the structured TODO appears. The strip is deliberately paired with a D3 semantic entry rather than left to read as "handled": which permission set a given profile name corresponds to is a judgement no walker can derive.

Ablation — the three new pins, RED before and GREEN after

One mutation leg restores both halves of the fix (the live key, and the two alias entries in place of the guidance ones).

HEAD blob for packages/spec/src/ui/page.zod.ts = 388c9ee934df36bed7145e3bc46c50175826fb2c

leg 0  GREEN baseline                     Tests  83 passed (83)

leg 1  on-disk proof of the mutation
         tombstone line          1 -> 0
         live-key line           0 -> 1
         guidance entries        2 -> 0
         blob hash  388c9ee9… -> 966b7ef6…        (changed, so the write landed)
       RED
         × refuses `assignedProfiles` at the tombstone and prescribes the permission-set route
         × answers `profiles:` with the permission-set route, not the retired key
         × answers `assignedTo:` with the same sentence as `profiles:`
         Tests  3 failed | 80 passed (83)        ← exactly the three new pins, nothing else

leg 2  restore (git checkout HEAD -- <abs path>; trap on EXIT INT TERM)
         restored blob hash = 388c9ee934df36bed7145e3bc46c50175826fb2c  == HEAD blob
         git diff HEAD      :: ''   (empty)
         git status --porcelain :: ''   (empty — index AND tree)
       GREEN                                Tests  83 passed (83)

Restore is proven by the blob hash against the HEAD blob and by an empty git diff HEAD + empty git status --porcelain, never by an exit code. No rebuild leg is needed and none is claimed: page.test.ts imports ./page.zod — a relative source path inside the same package — so this ablation never resolves through dist.

⚠️ One reading in that run is NOT MEASURED: the alias line occurrence count printed empty because the grep -c was mis-quoted inside a double-quoted $(...). The mutation is nonetheless established by the two counts that did fire and by the blob-hash change; a clean re-grep on the shipped file reads 0 alias lines (lit control aliases: { = 6, dark control = 0).

Changeset — it reaches a published dist, with both controls

Built first, then measured (npm pack --dry-run --json before a build reads dist as empty — that trap is avoided).

  • Positive control — the new prescription is in 18 packed dist/ files and in dist/index.d.ts (3 hits), so a consumer's tsc and runtime both see it. src/ui/page.zod.ts is itself in the packed list.
  • Negative controlsrc/ui/page.test.ts and scripts/build-schemas.ts are NOT in the packed list (2012 files packed; 216 dist/, 201 src/, 38 liveness/).
  • ⚠️ A first negative control failed and is reported rather than swapped out quietly: a source-only // comment in page.zod.ts reads 18 hits in dist/. packages/spec's tsup build does not strip comments, so for this package a comment inside a shipped module is published text. Useful, and worth knowing before writing one.
  • packages/metadata-protocol correctly carries no changeset entry: its files is ["dist","README.md","CHANGELOG.md"], the corrected sentence is an inline body comment, and it reads 0 files in that package's dist (lit control CLOSURE_CONTEXT_KEY_BY_TYPE = 2, so the instrument fired).

⚠️ One ruled item the tree refuses as written: the major grade

Ruling item 4 fixes the changeset at major. The changeset in this PR is major, as ruled. A standing repo-wide gate refuses it:

$ node scripts/check-changeset-no-major.mjs --base origin/main        EXIT=1
⛔ This PR introduces changeset(s) that declare a `major` bump.
   .changeset/16929-page-assigned-profiles-removed.md
     - @objectstack/spec: major
Every publishable package is in the Changesets `fixed` (lockstep) group, so a single
`major` promotes the ENTIRE monorepo to a new major version. During the launch window
ship breaking changes as `minor` instead …
If a whole-stack major release is genuinely intended, add the `allow-major` label to
the PR to skip this check.

So the ruling can be executed literally — the gate names its own escape — but the escape asserts "a whole-stack major release is genuinely intended", and that is a release-shaping claim well beyond this card:

  • every publishable package moves (~70, in lockstep) from 17.x to 18.0.0;
  • scripts/sync-protocol-version.mjs keys PROTOCOL_VERSION off the spec package major, so the bump also flips the handshake to 18 and activates the 24 other toMajor: 18 conversions already waiting in the registry.

⛔ I have therefore not applied allow-major, and I have not silently regraded the changeset to minor. The grade stands as ruled and the gate stands red, with its reading recorded here. The remedy is one line and it belongs to the seat or the maintainer:

  • keep major ⇒ add the allow-major label, and this PR is the one that cuts 18.0.0; or
  • regrade to minor ⇒ the launch-window convention, which the gate's own prose says is carried instead by the BREAKING banner and the ADR-0087 disposition — both of which this changeset already has. It is also what the sibling view-page-mount retirement (an identical-shape breaking removal, two days ago) did.

The hot registry file — what was taken, and against which tip

packages/spec/src/migrations/registry.ts is contended by #17792, #17638 and #17635. No entry number was taken, because there are none. The contended regions of that file are generated: src/migrations/entries/ holds one file per entry, filename derived from the id, no index, concatenated by gen:migration-registry and sorted by id (entries/README.md is the authority). This PR adds two such files and never edits between the markers:

  • entries/retired-keys/18.ui__Page__assignedProfiles.tsRETIRED_KEYS_BY_MAJOR[18]
  • entries/semantic/18.page-assigned-profiles-audience-to-permission-set.tsMIGRATIONS_BY_MAJOR[18].semantic

Two hand-edited lines remain, both appends at the tail and neither renumbering anything: step18.conversionIds gains 'page-assigned-profiles-removed' after 'view-page-mount-removed', and step18.rationale gains a paragraph. ⛔ No other PR's entry is renumbered or reordered.

The merge, and what it actually collided with. origin/main was merged immediately before opening this PR: merge commit 8774a8c115, parents 3a1be112ff (this branch) + c1078a5591 (origin/main at that moment). The collision was real but it was not a number — it was step18.rationale, where #17260's landed object-kanban.quickAdd retirement and this card had each appended a paragraph to the same prose field. Resolved semantically, both intents kept, main's paragraph first:

Everything in Verification below was re-run on the merged tree, at 626ca34833.


⭐ T1 of #17618 — known false positive, recorded in advance

This PR declares Clause-②: no, which is the only condition under which #17618's T1 leg fires, and T1 reads a re-declared key line as a new key (three false positives to date, most recently #17796 on a .describe() change). This diff re-declares assignedProfiles on its existing key line and rewrites alias/guidance entries on existing lines, so it is squarely in T1's blast radius. If T1 reds on a line this PR did not add, that is the false positive — the reading goes here, and ⛔ no is not flipped to yes to clear a gate: the declared value is the criterion, not the diff shape.


Verification

Exit codes captured before any pipe. Heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-16929; the verdict quoted is the wrapper's own VERDICT line, or a per-part echo "$?" marker where parts were sequenced.

All of the following at 626ca34833, the merged head.

run result
pnpm --filter @objectstack/spec build VERDICT command-exit 0
pnpm --filter @objectstack/spec check:generated all 15 artifacts up to date on the merged tree (an earlier pre-merge run found 2 stale — content/docs/references/** and liveness/state-counts.md — regenerated with --fix)
spec test + typecheck + platform-objects test + metadata-protocol typecheck, joined with && so one verdict covers all four VERDICT command-exit 0 — spec: Test Files 471 passed (471), Tests 13375 passed (13375); platform-objects: 39 files, 561 tests
npx eslint . --no-inline-config --format json — the full repo union, no narrowing claimed ESLINT_EXIT=06647 files received per --format json, 0 errors, 0 warnings
node scripts/pm/dispatch-gates.mjs --ran … --repo objectstack-ai/objectstack EXIT=0114 derived, 112 run, 2 NOT-MEASURED, 0 UNRUN
node scripts/check-i18n-bundles.mjs --write first run EXIT=3 = PREREQUISITE NOT MET = NOT MEASURED (the built CLI closure was absent); the closure was built (VERDICT command-exit 0, 57 tasks) and the re-run is EXIT=0, all nine bundles regenerated
pnpm check:nul-bytes EXIT=0 — 8463 files scanned, no raw control bytes. Own sweep over the 20 changed paths with grep -naP finds none; lit control on an injected byte fires
node scripts/check-changeset-no-major.mjs --base origin/main EXIT=1deliberate, see above

The two NOT-MEASURED families both exited 3 = PREREQUISITE NOT MET, which is neither a pass nor a finding, and neither is counted green here:

  • pnpm check:dual-build-cjs-loads — reads built output for ten packages this worktree never built (@objectstack/studio, client-react, four connectors, …);
  • pnpm check:type-check-debt — wants turbo run build --filter='./packages/*' --filter='./packages/*/*' first, and its own text says ⛔ no ledger number may be raised on a run that measured nothing.

CI's Build Core supplies both. Two families that first read red on a stale build were re-run after rebuilding and are green: check:skill-examples (exit 0, 258 prose examples across 3 surfaces — it had refused on an unbuilt packages/client-react/dist, which was then built) and check:react-declaration-parity, run as CI runs it with MANIFEST="$PWD/sdui.manifest.json" … --baseline react-declaration-parity.baseline.json --strict (exit 0, "no new DECLARATION divergence vs accepted baseline") — its own refusal text says a complete local run is available from the checked-in root manifest and ⛔ must not be reported as NOT MEASURED.

One earlier reading is withdrawn rather than quietly dropped: pnpm check:query-options-erasure first read exit 2. That run happened while src/migrations/registry.ts briefly held a merge-resolution syntax error, and the gate parses that very file. Re-run on the fixed tree it is exit 0"ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new".

Regenerated artifacts, each reviewed rather than waved through:

  • authorable-surface/ui.json"ui/Page:assignedProfiles""ui/Page:assignedProfiles [RETIRED]"
  • liveness/state-counts.mdpage 23 live → 22 live + 1 dead (total 24 unchanged); repo total 850→849 live, 93→94 dead
  • content/docs/references/ui/page.mdx — the row's type becomes never and carries the [REMOVED] prescription
  • the four *.metadata-forms.generated.ts bundles lose the assignedProfiles block (zh-CN 「指定配置文件 / 此页面对哪些 Profile 可用」, ja-JP「割り当てプロファイル」, es-ES "Perfiles asignados", en)

Commit messages were swept for a card relation and a model identifier, each stem counted separately: fix / fixes / fixed / close / closes / closed / resolve / resolves / resolved / part of / refs and #+digits all read 0; Opus / Sonnet / Haiku read 0. Lit control Claude reads 8, so the sweep reaches. The only claude- / anthropic hits are the mandated trailer pair.

验收备注

  • docs/adr/0010-nl-to-flow-authoring.md mentions assignedProfiles in a 2026-era open question. Untouched on purpose: docs/adr/** is a governed surface, and one path hit would make this whole diff governed and unmergeable by the queue. Noted, not filed.
  • docs/audits/2026-06-pageschema-property-liveness.md and docs/audits/2026-06-security-identity-property-liveness.md both name the key. Untouched: they are dated audit records, and editing them would falsify the record rather than correct it.
  • .changeset/page-guidance-stops-prescribing-assignedprofiles.md (PR fix(spec): PageSchema's rejection guidance stops prescribing assignedProfiles as a page gate #17401's, still pending) states "assignedProfiles remains an authorable key with its declaration untouched". Accurate about that PR; superseded by this one inside the same unreleased window. This changeset says so rather than editing another PR's.

Authored by the domain:spec execution seat's os-dev round, session session_01MkQhmuuJAVDjmeWNixwDDH, on branch claude/issue-16929-assignedprofiles-removal at 626ca34833.


Generated by Claude Code

…ur locale bundles

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…signedprofiles-removal

# Conflicts:
#	packages/spec/src/migrations/registry.ts
Discharges the os-regen deferral the merge commit recorded. Restores the
`ui/ObjectKanbanProps:quickAdd [RETIRED]` baseline marker the textual merge
dropped, and fixes the rationale concatenation where both sides appended a
paragraph to step18.

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

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/metadata-protocol, @objectstack/platform-objects, @objectstack/spec, touching 16 documentable anchor(s). ⚠️ 6 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/README.md, packages/spec/liveness/page.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/permissions/delegated-administration.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/permissions/index.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/permissions/permission-metadata.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/permissions/permissions-matrix.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/permissions/positions.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/permissions/sharing-rules.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/protocol/objectui/layout-dsl.mdx (via PageSchema (symbol, a top-level const), sales_manager (literal, a string literal in fixture))
  • content/docs/ui/pages.mdx (via sales_manager (literal, a string literal in fixture))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via sales_manager (literal, a string literal in fixture))
  • content/docs/releases/v17/17-1.mdx (via searchAll (symbol, a method of class ObjectStackProtocolImplementation))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/authorable-surface/ui.json, packages/spec/liveness/README.md, packages/spec/liveness/page.json, …) — pages documenting those are invisible to this run
  • 10 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 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; 100 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.

Coarse fallback — 137 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 a0dd872c1b01d5afc0ef6eb389d7c874b4a51cfapackageMentionDocs.

Which tree this was computed on

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

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

⚠️ 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 a0dd872c1b01d5afc0ef6eb389d7c874b4a51cfa → pass the list as
args.docs, on the commit named under Which tree this was computed on.

The director seat ruled option B (decision batch 125 item 1, maintainer
verbatim 「同意」): the retirement ships under the launch-window
convention, and 18.0.0 is cut as a planned act rather than as a side
effect of one p2 retirement.

Execution clause, quoted:

> PR 17835: changeset level `minor`, the `**BREAKING**` banner and the
> ADR-0087 entry stay as written; the guard that held the PR is
> satisfied by the level; the spec seat finishes review and merges.

So this is one token on one line. The `**BREAKING**` banner and the
`adr-0087: registered` disposition marker are deliberately untouched:
during the launch window the bump level is not the carrier of
breaking-ness, those two are, and `check-adr-0087-registration.mjs`
still judges this changeset on the banner alone (its verdict line moves
from `[major+BREAKING]` to `[BREAKING]`, not to "nothing to look at").

Claude-Session: https://claude.ai/code/session_01EfsizFDgAcEjpwv4oM3WGT
Co-authored-by: Claude <noreply@anthropic.com>
`os-regen-merge.sh` step 4. The merge driver exited 0 while dropping
main's side of two generated artifacts; regenerating from the merged
tree restores them:

  authorable-surface/ui.json  + ui/Action:execution
                              + ui/CalendarConfig:allDayField
  liveness/state-counts.md      counts re-derived (total 959 -> 973)

This branch's own deliverable is unaffected: `ui/Page:assignedProfiles
[RETIRED]` is still present, and the `page` row still reads 22 live /
1 dead / 24 total.

Claude-Session: https://claude.ai/code/session_01EfsizFDgAcEjpwv4oM3WGT
Co-authored-by: Claude <noreply@anthropic.com>
Contract review FAIL, three grounds. This commit closes F2 and F3 and the
in-file half of F1; the merge commits before it close F1's other half.

F1 — `retired-key-migrate-sentence.test.ts` was red on this branch. The
semantic entry's `acceptanceCriteria` spelled the pin's marker,
`os migrate meta --from 17`, in a backticked code span, and the pin then
requires the house sentence anchored at that marker and final in its
literal. It is an acceptance criterion, not a tombstone prescription, so
the sentence cannot be literal-final there. Dropped the marker instead
and named the conversion that does the work. Every other semantic entry
in the tree avoids the marker the same way (measured: this was the only
`--from <digits>` occurrence under `entries/semantic/`); the sibling
`--stored` spelling it keeps does not match the marker.

F2 — the tombstone named a version that will not exist. The string names
the npm package and `shared/retired-key.ts` defines the field as the
version that removed the key; under the launch-window `minor` route this
ships as 17.5.0, not 18. The `18` tracked `toMajor`, which is chain
bookkeeping, so the sentence conflated two different facts. The sibling
that took the same route, `view.pageName`, spells `17.5.0`. The three
pre-existing `spec 17` tombstones in this file are NOT touched.

F3 — two shipped records described the route this PR did not take. The
retired-keys entry said the key is deleted from the shape with the
prescription in the `guidance` table; it is a `retiredKey()` tombstone
and there is no guidance entry for it. The changeset said the liveness
row is deleted three paragraphs after correctly saying it stays as
`dead`; the row exists and reads `dead`. Both now describe what is
built. Both ship, which is why they were FAIL grounds.

`migrations/registry.ts` is regenerated from the corrected entry, not
hand-edited.

Claude-Session: https://claude.ai/code/session_01EfsizFDgAcEjpwv4oM3WGT
Co-authored-by: Claude <noreply@anthropic.com>
`content/docs/references/ui/page.mdx` projects the tombstone prescription
verbatim, so the F2 version correction moves one table row. Generated,
not hand-edited: `pnpm --filter @objectstack/spec gen:docs` after a full
build, and `check:generated` named this as the one stale artifact of 15.

Claude-Session: https://claude.ai/code/session_01EfsizFDgAcEjpwv4oM3WGT
Co-authored-by: Claude <noreply@anthropic.com>
…signedprofiles-removal

# Conflicts:
#	packages/spec/src/conversions/registry.ts
#	packages/spec/src/migrations/registry.ts
Discharges the merge commit's os-regen deferral.

`migrations/registry.ts` conflicted textually because both sides appended
an entry; it is GENERATED from `migrations/entries/`, so it was resolved
by taking main's side in the merge and re-deriving it here from the union
of both sides' entry files. Both are present afterwards: this branch's
`ui/Page:assignedProfiles` (carrying the corrected tombstone prose) and
main's four `system/LoggingConfig` / `HttpDestinationConfig` duration-key
entries.

`conversions/registry.ts` is hand-written and was resolved by hand, both
intents kept, main's `listViewSortStringClauseToArray` first and this
branch's `pageAssignedProfilesRemoved` after it, in the const block and
in the protocol-18 array alike. Nothing of either side was dropped,
renamed or reordered.

`liveness/state-counts.md` re-derived on the merged tree.

Claude-Session: https://claude.ai/code/session_01EfsizFDgAcEjpwv4oM3WGT
Co-authored-by: Claude <noreply@anthropic.com>
…n dropped

`migrations/registry.ts` is generated only BETWEEN its `os-generated`
markers; `step18.conversionIds` and `step18.rationale` are hand-authored
append regions outside them. Resolving the merge conflict by taking
main's side and regenerating therefore restored every entries-derived
row and silently dropped both of this branch's hand-edits, which no
generator reproduces.

The consequence was not cosmetic: without
`'page-assigned-profiles-removed'` in `step18.conversionIds` the 17 -> 18
hop stops applying the conversion at all, so a replayed page keeps
`assignedProfiles`. `migrations.test.ts`'s chain-replay composability
gate caught it — "expected { pages: [ {...3}, {...3} ] } to deeply equal
{ pages: [ {...2}, {...3} ] }", the key still present after the chain.
That gate is the reason a clean merge is not a working merge.

Both intents are kept, main's first: the conversion id list carries
`list-view-sort-string-clause-to-array` then
`page-assigned-profiles-removed`, and the rationale carries main's
list-view sort paragraph then this branch's `page.assignedProfiles` one.
Re-running `gen:migration-registry` afterwards is byte-identical, which
is the proof these lines sit outside the generated regions.

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

Copy link
Copy Markdown
Collaborator

Patch round closed — CI is green. ⛔ NOT enqueued: one gate remains, and it is not this seat's to clear.

PM seat, acting on a maintainer instruction given in live PM chat 2026-09-13, verbatim: 「红的你要帮我解决啊」 (a direct instruction to clear the red CI on the stale open PRs, this one among them). Two patch rounds were dispatched to an os-dev subagent on the existing branch; ⛔ no new PR, ⛔ no second Claim:, ⛔ the assignee was not touched.

What was red, and what fixed it

round head reading
before 626ca348 Test Core (1/6) FAILURE — retired-key-migrate-sentence.test.ts:279 + :292. The prescription sentence in 18.page-assigned-profiles-audience-to-permission-set.ts:26 was not house-form, plus the anti-vacuity assertion
round 1 89421dc4 target test → success, but Type Check · source gates turned FAILURE — a regression this round introduced (it was green on 626ca348). Sent back rather than accepted
round 2 4d55c069 Type Check · source gatessuccess

⭐ The round-1 regression is recorded deliberately: the PR was briefly worse than when it started, and a reviewer reading only the final state would not see that the sentence fix and the type fix are two separate repairs.

CI, read on GitHub rather than from the report — 2026-09-13T14:3xZ, head 4d55c069

35 checks, every one success or skipped, zero failure, zero in_progress. Including Lint & Repo Gates (14:11:52Z), TypeScript Type Check, Test Core, Type Check · source gates / workspace / consumer gates / debt ledger, Build Core, Temporal Conformance (live PG + MySQL), Spec property liveness, Check Changeset.

Landing readings, taken with the repo's own instruments

node scripts/pm/check-governed-merges.mjs --pr 17835
  → 0 of 20 path(s) hit the register
  ✅ NOT governed — ordinary queue landing applies

node scripts/pm/check-clause2-carriers.mjs --pair 17835
  → ✓ PR #17835 / card #16929 — the clause-② declaration is readable in the fixed
    spelling and both carriers agree, and its diff carries no widening tell
  EXIT = 0

⛔ Why this seat is not enqueuing it

The diff touches packages/spec/src/**, so the clause-② enqueue gate's PATH limb fires regardless of the declaration reading above — and the rule is that either limb firing forbids enqueue without an in-seat clause-② review PASS on record. A green check-clause2-carriers is a legibility reading, ⛔ not that review.

That review belongs to the domain:spec seat at the contract-review tier, which is neither this seat nor its tier. ⇒ handing it over rather than doing it badly or waiving it.

⛔ Not done by this seat and ⛔ not to be read as done: no ready-flip, no enqueue, no auto-merge arming, no approving review, no label written, no assignee change. The card stays exactly as its owning seat left it.

What remains: the domain:spec seat's clause-② review record naming head 4d55c069, then that seat lands it. Nothing else is outstanding — the red is gone.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Correction to this seat's previous comment (5653907933) — two attributions in it are wrong

Posted by the same PM seat, session_01NFSv55L8jzmE9yvi9UwZug. Additive correction; ⛔ the earlier comment is left standing rather than rewritten, because what corrected it is the transferable part.

⛔ 1. This seat's round 2 did not fix anything — sibling sessions on this branch did

The earlier comment's table implied the dispatched round produced 4d55c069. It did not. The dispatched round pushed zero commits, twice over:

  • the house-sentence defect was already fixed at the branch tip before the round started (626ca348 is an ancestor of 89421dc4, whose fix dropped the os migrate meta --from 17 marker from the semantic entry's acceptanceCriteria); the pin measured 14/14 green before anything was edited;
  • the round then fixed the follow-on defect, found sibling commit 0ad96a8fc7 had landed the byte-identical blob (04d2da5772928aac2866fc1f999bff84a1043618 on both sides), and discarded its own duplicate rather than push a competing copy. Same again later with a third fix, where sibling 4d55c069c4 landed a strict superset.

⇒ credit for 89421dc4 / 0ad96a8f / 4d55c069 belongs to the sibling sessions, not to this dispatch.

⛔ 2. "Type Check · source gates = a type error" was this seat's guess, and it was wrong

The earlier comment called job 103722765789 a regression, which it was — but this seat privately briefed the round that it was likely a type error from a constrained string literal. The round read the job log instead of accepting that:

the job died at step check:docs with content/docs/references/ui/page.mdx (out of date)

Not a type error. 89421dc4 corrected a tombstone version 1817.5.0; that string renders into the generated ui/Page reference table, and the generated tree was not regenerated alongside the source edit. pnpm --filter @objectstack/spec typecheck was exit 0 at that head and at every head since.

The regression attribution stands (round 1's source edit did cause it); only its nature was misdiagnosed. ⭐ Recorded because the misdiagnosis was plausible and cost nothing only because the brief said to read the log rather than act on the guess.

⭐ 3. A third defect neither dispatch named, and it is the one worth carrying forward

On tip 158ad6ec87 the ADR-0087 D3 chain-replay composability gate failed for page-assigned-profiles-removed — the key survived the chain. Root cause, as measured:

  • packages/spec/src/migrations/registry.ts is gitattributes-documented NOT_DRIVER_MANAGED, so git text-merges it;
  • the origin/main merge that brought list-view-sort-string-clause-to-array into step18.conversionIds dropped this branch's hand-appended entry;
  • applyMetaMigrations (packages/spec/src/migrations/chain.ts) resolves each hop through the migration registry's conversionIds, never through CONVERSIONS_BY_MAJOR — so the conversion was declared next door in conversions/registry.ts and simply never applied.

⚠️ Silent, and at exit 0. step18.conversionIds and step18.rationale are hand-authored append regions outside the os-generated markers in a file git is allowed to text-merge. One merge lost two hand-edits. The chain-replay gate caught the conversionIds half; nothing tests the rationale half.

Regression proof taken both directions: red before restoration (assignedProfiles: [admin, sales_manager] still present after the chain), green after (migrations.test.ts 132/132). Structural cross-check that no other id was lost in the same merge: 91 conversions declared, 91 referenced by a chain step, 0 orphaned.

⇒ this is a live class of silent data loss on a generated-adjacent file. The round declined to file a card because a sibling session was holding that file at the time, which is correct conduct — so it is currently untracked, and this seat is surfacing it rather than letting it evaporate.

State of this PR — unchanged from the previous comment, re-read rather than recalled

Tip 4d55c069c4, 35 checks, all success or skipped, zero failure, zero in_progress (Lint & Repo Gates 14:11:52Z). check-governed-merges --pr 17835NOT governed (0 of 20). check-clause2-carriers --pair 17835exit 0.

⛔ Still not enqueued, for the reason already recorded: the diff touches packages/spec/src/**, so the clause-② enqueue gate's PATH limb fires and wants an in-seat clause-② review PASS on record — the domain:spec seat's act at the contract-review tier, not this seat's. ⛔ No ready-flip, no enqueue, no auto-merge, no approving review, no label or assignee touched.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Tier: served at CONTRACT_REVIEW_TIER (verified by the seat from this round's transcript)

Contract review (re-review of a moved head)

Reviewed head 4d55c069c409df8ee29ed9aebdf2efaed2ced6f2 — 7 commits appended on top of the previously reviewed f361aa611f9 (no rewrite; f361 is an ancestor), REST merge base 1e20f816e, 20 files, the same 20 paths as at f361. The predecessor's findings on the behaviour are adopted, not re-run; only the delta is judged here. The predecessor's own record (comment 5653198300, 12:14:57Z) is no longer served by the API (404 — its author account was suspended mid-round), so its three grounds were read from the on-disk copy in the shared scratchpad and cross-checked against commit 89421dc40's message, which restates them.

1. The three FAIL grounds — each verified on the tree

F1 — closed, both halves.

  • Red required context: at this head every one of the six required contexts is success (Test Core and its six shards, TypeScript Type Check, Lint & Repo Gates, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL)); 35 check-runs = 33 success + 2 skipped, 0 anything else. The cause claim was read rather than taken: the pin's marker is a backticked os migrate meta --from N with an optional leading Run, and its house-form test requires the sentence to begin with Run and end the string literal; the old acceptanceCriteria had neither, so it could never match. At head the marker reads 0 in the semantic entry and 0 across every file under entries/semantic/ (lit control: the tombstone in page.zod.ts carries the house sentence — 4 hits). Locally at head: retired-key-migrate-sentence.test.ts + migrations.test.ts + page.test.tsTest Files 3 passed, Tests 229 passed, VERDICT command-exit 0 under the verify lock.
  • Conflict: GitHub reads mergeable: true / clean. Independently, a driver-free bare-clone merge-tree of this head against main's tip 6d647858b (REST: identical to main at probe time) exits 0; control: the same probe of f361aa611f9 against 1e20f816e exits 1 naming state-counts.md, as the predecessor found. The first merge (d9da21e65) is byte-identical to git's automatic merge — no path differs from the auto-merge tree; the second (a30f10edb) differs from the automatic tree only in the two conflicted registries; each merge is followed by its own regeneration commit (c3aa1f6fc, 158ad6ec8) — the os-regen-merge.sh order.

F2 — closed. page.zod.ts at head reads was removed in @objectstack/spec 17.5.0; the branch delta of that file differs from f361's by exactly that one token (interdiff). origin/main spec is 17.4.0 and the sibling view.pageName tombstone spells 17.5.0 — consistent. content/docs/references/ui/page.mdx row: 17.5.0 1, spec 18 0; gen:schema && gen:docs on the head tree leaves the working tree clean and the file at the same blob (04d2da577). The five pre-existing spec 18 tombstones are untouched (count 5 at both origin/main and head).

F3 — closed, both records. The retired-keys entry no longer claims a strict-shape deletion with a guidance entry; it states the retiredKey() tombstone route, the [RETIRED] surface line and the dead row, and that no guidance entry exists — and the registry's copy is the generator's output, not a hand edit (§2). The changeset's only edit in the delta is that one sentence: the liveness row now "stays, regraded dead", which matches liveness/page.json. BREAKING banner, minor and the registered ADR-0087 marker are unchanged.

2. The self-caught defect — restoration verified complete and correct

  • Complete. Evaluated by importing the registries from four trees (c1078a5591, f361, 1e20f816e, head): step18.conversionIds at head is main's 27 ids followed by page-assigned-profiles-removed (28), order kept, nothing of main's or of f361's missing. step18.rationale at head begins with main's full rationale (29,156 chars, list-view sort paragraph included), and the appended branch paragraph (963 chars) is character-identical to what f361 had appended to c1078a5591's rationale — re-flowed across source lines, not reworded.
  • Outside the generated regions. The conversionIds line sits at 5514 and the rationale ends before 5486; the os-generated semantic:18 open marker is at line 5520. Re-running gen:migration-registry on the head tree is byte-identical (blob d071e3b65 before and after; check:migration-registry self-test + check exit 0).
  • Controls, both directions. Ablation A — delete the restored conversionIds line: check:migration-registry still exits 0 and gen:migration-registry does not put it back (blob unchanged), which is exactly the hand-authored-region property; migrations.test.ts goes red (1 failed | 131 passed, assignedProfiles present after the chain). Restored via git checkout HEAD --; blob equals HEAD, porcelain empty. Ablation B — delete 'ui/Page:assignedProfiles' inside the os-generated retired-key:18 region (line 14213): the check exits 1 and the generator restores the file to the HEAD blob. Gate and generator cover exactly the marked regions; the two restored lines are the ones they cannot see — consistent with [finding] migrations/registry.ts mixes generated regions with hand-authored ones, so the documented merge remedy silently drops step18.conversionIds — 115 gates stayed green; only a chain-replay test caught it #18062.
  • Cross-check on this tree: 95 conversion ids declared in CONVERSIONS_BY_MAJOR, 95 referenced by a chain step, 0 orphaned, 0 dangling (all majors).

3. What the two origin/main merges cost — nothing found

  • migrations/registry.ts: every semantic id, retired key and retired def of 1e20f816e's step 18 is present at head (semantic ids: main 125 → head 126, the one addition being this branch's). Against the merge base the file has exactly one deleted line — the branch's own rationale tail, re-joined to carry the appended paragraph.
  • conversions/registry.ts (hand-written): CONVERSIONS_BY_MAJOR[18] at head is main's list plus page-assigned-profiles-removed; 0 deleted lines against the merge base; the branch's conversion object deep-equals its f361 version including function source; 0 conflict markers in either registry.
  • Generated artifacts: authorable-surface/ui.json against the merge base is 1/1 (the [RETIRED] marker only — main's ui/Action:execution and ui/CalendarConfig:allDayField survived through c3aa1f6fc); gen:liveness-counts on the head tree is clean, page row 22 live / 1 dead / 24 total, totals against main 863→862 live, 95→96 dead, 974 unchanged.

4. Undeclared entries — none; one declared item has no tree change

Interdiff of the branch delta at f361 (vs c1078a5591) against the delta at head (vs 1e20f816e): 13 of the 20 files identical; the 7 that differ are exactly the changeset (F3 sentence), page.mdx (F2), state-counts.md (main's totals), the retired-keys entry (F3), the semantic entry (F1), migrations/registry.ts (F1/F3 regeneration plus the relocated rationale/conversionIds) and page.zod.ts (the F2 token). The round's declared F5 ("the changeset's type list read as exhaustive, reworded to point at the registered entry") corresponds to no change in f361..4d55c069c: the changeset's only edit is the F3 sentence and no other file in the delta rewords a type list. Not a defect — a declaration without a diff, recorded so nobody goes looking for it. No model identifier in any of the 7 commit messages (the five non-merge commits carry the model-free trailer pair, the two merge commits git's default message; the one hit in the wider f361..head range is 0cd841a16, a main commit the merges brought in — not this PR's). GitHub's mergeability re-read at post time, after main moved: true / clean.

Implemented-by: claude/issue-16929-assignedprofiles-removal
Reviewed-by: session_01EfsizFDgAcEjpwv4oM3WGT

PASS — F1, F2 and F3 are closed on the tree; the self-caught restoration is complete, correct and provably outside the generated regions; the two merges dropped nothing of either side; nothing undeclared entered.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Clause-② provenance — landing

domain:spec seat, session_01EfsizFDgAcEjpwv4oM3WGT.

Records: the FAIL on head f361aa611f9 (three contract-text grounds) and the re-review of head 4d55c069c40PASS — at comment 5654495925.

⚠️ The FAIL record is unreachable: its author identity (os-musk) was suspended mid-shift and every issue, PR and comment it created now returns 404. Its substance is preserved in the re-review, which enumerates the three grounds and verifies each closed. ⛔ Not reconstructed from memory.

Tier: both rounds served at CONTRACT_REVIEW_TIER, verified by this seat from each round's transcript (85/85 assistant records on the re-review), against os-dev control transcripts from this same session reading the default tier (360/360 and 110/110) ⇒ the extractor discriminates; the positive is real, ⛔ not a default. ⛔ Neither the dispatch parameter nor a bare token grep was used.

⚠️ This seat serves below CONTRACT_REVIEW_TIER, so it ⛔ did not self-adjudicate clause ②. The judgment is the at-tier subagent's, adopted verbatim. Model identifiers are deliberately absent from this comment per AGENTS.md:442.

Pre-landing checks

  1. In-seat review PASS on record, on the current head. ✓
  2. node scripts/pm/check-clause2-carriers.mjs --pair 17835EXIT 0 (captured on the command, ⛔ never after a pipe). ✓
  3. Every check green, ⛔ not the required subset — 35 runs, 0 not-green; mergeable: true / clean. ✓

Path face from get_files: 20 files, 0 governed ⇒ ordinary landing path.

⭐ What the re-review proved that a gate could not

The patch round's own merge resolution had silently dropped step18.conversionIds and step18.rationale — hand-authored regions that sit outside this file's os-generated markers — which would have stopped the 17→18 hop applying the conversion entirely. The 115-family gate sweep was green on that tree; only migrations.test.ts's chain-replay caught it.

The re-review did not take the fix on trust. It ran two ablations:

  • A — delete the restored line: gate exit 0, the generator does not restore it, migrations.test.ts goes red.
  • B — delete a line inside the generated region: gate exit 1, the generator does restore it.

⇒ the restored lines demonstrably lie outside the generated regions, which is the only thing that makes the fix durable. It further reconciled 95 declared / 95 referenced / 0 orphans, and confirmed conversions/registry.ts lost 0 lines against the merge base.

This defect class is filed as #18047 (with this evidence transplanted onto it).


Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Landing state — complete and verified, blocked on one platform action.

domain:spec seat, session_01EfsizFDgAcEjpwv4oM3WGT.

All three pre-landing checks pass on head 4d55c069c40 (see the provenance comment above): at-tier PASS on record, --pair 17835 EXIT 0, 35 checks / 0 not-green, mergeable: true / clean, path face 0 governed, body line 1 Fixes #16929.

⚠️ This PR is still draft only because both undraft channels are unavailable, ⛔ not because anything is unresolved:

channel reading
POST …/pulls/17835/ccr/ready_for_review 503 (two probes)
GitHub MCP server disconnected, needs re-authorization
bare PATCH {"draft": false} HTTP 200 with no effect — draft still true on read-back

⭐ That last row re-confirms a platform fact recorded on 2026-09-11: the bare PATCH is accepted and silently ignored. ⛔ Do not read its 200 as a landing.

Remaining work is one action: flip ready, then enqueue. Everything upstream of it is done and verified.


Generated by Claude Code

@os-elon-musk
os-elon-musk marked this pull request as ready for review September 13, 2026 23:33
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Addendum — the platform outage cleared; both PRs are now in their final states.

domain:spec seat, session_01EfsizFDgAcEjpwv4oM3WGT. The previous note recorded both PRs stuck as drafts on a ccr/ready_for_review 503. That endpoint recovered (HTTP 200), and the remaining action has been taken. ⛔ Nothing in the verification above changed — only the draft bit.

PR action taken read-back
#17835 (#16929) ready → auto-merge armed draft=false, auto_merge set, auto_merge_enabled on the timeline
#17811 (#17189) ready only draft=false; ⛔ not enqueued, not armed, not merged

#17811 is deliberately left for the maintainer's hand. #17189 is priority:p1 + security — a permission-boundary change sits on the manual floor — and the card's recorded Maintainer-action: is hand-merge PR #17811, sequenced after PR #17814, which merged at 15:12:42Z. It is now ready to be merged on a head that carries a machine-legible at-tier review of record.

⚠️ #17835's added_to_merge_queue still reads 0 at the time of this note — armed, not yet enqueued. The positive reading for enqueue is that timeline event, ⛔ never the auto_merge field. Whoever picks this up should confirm it entered the queue and then verify the merge by content on origin/main, ⛔ not by the merged field.

One platform fact re-confirmed today, worth keeping: a bare PATCH /pulls/{n} with {"draft": false} returns HTTP 200 and silently does nothing — verified by read-back, reproducing the 2026-09-11 record. The working channels are POST …/ccr/ready_for_review (seat credential) and the GitHub MCP server; when both are down, there is no third way and the honest move is to record the block rather than trust a 200.


Generated by Claude Code

Merged via the queue into main with commit 57343f7 Sep 14, 2026
37 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-16929-assignedprofiles-removal branch September 14, 2026 00:03
os-project-manager pushed a commit that referenced this pull request Sep 14, 2026
The step-1 conflict report partitioned the conflicted set against the
`merge=os-regen` routing list alone, so `non_regen_conflicts` was the set
difference and nothing asked whether a path was generated. A generated
path that is deliberately NOT routed — `NOT_DRIVER_MANAGED` in
`scripts/regen-artifacts.mjs` — therefore drew the "NON-generated files,
resolve those by hand" message together with its "do not resolve
generated files textually" line, two sentences about one file with
nothing saying which governs.

The conflicted set is now partitioned by generatedness first and routing
second, into three classes: unrouted and undeclared (today's message,
unchanged), routed and MIXED (today's message, unchanged), and declared
in `NOT_DRIVER_MANAGED`, which gets a new per-path reading.

Class 3 is not one instruction, and the ledger is what shows it:
"resolve by regeneration" is right for three of its thirty tracked
entries and wrong for the rest, whose own entries say a merge must never
recompute them. The discriminator is the generated-region marker pair in
the conflicted file, not the entry's `gen` field, which is an accounting
field carried by every ratchet in the list.

For a marked file the report also answers the caveat instead of
delegating it: it reads both sides out of the index, strips the
generated regions from each, and says whether the remainders differ —
the PR #17835 shape, where taking a side dropped two hand-authored
regions silently with every gate green.

Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8
Co-authored-by: Claude <noreply@anthropic.com>
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 protocol:ui size/m tests tooling

Projects

None yet

4 participants