fix(spec): retire page.assignedProfiles and answer profiles: with the permission-set route - #17835
Conversation
…e false records Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…oute Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
…ur locale bundles Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
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
📓 Docs Drift CheckThis PR changes 3 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 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
|
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>
…signedprofiles-removal
`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>
|
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 What was red, and what fixed it
⭐ 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 —
|
Correction to this seat's previous comment (
|
|
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 1. The three FAIL grounds — each verified on the treeF1 — closed, both halves.
F2 — closed. F3 — closed, both records. The retired-keys entry no longer claims a strict-shape deletion with a 2. The self-caught defect — restoration verified complete and correct
3. What the two
|
Clause-② provenance — landing
Records: the FAIL on head
Tier: both rounds served at
Pre-landing checks
Path face from ⭐ What the re-review proved that a gate could notThe patch round's own merge resolution had silently dropped The re-review did not take the fix on trust. It ran two ablations:
⇒ 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 This defect class is filed as #18047 (with this evidence transplanted onto it). Generated by Claude Code |
|
Landing state — complete and verified, blocked on one platform action.
All three pre-landing checks pass on head
⭐ 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 |
|
Addendum — the platform outage cleared; both PRs are now in their final states.
#17811 is deliberately left for the maintainer's hand. #17189 is
⭐ One platform fact re-confirmed today, worth keeping: a bare Generated by Claude Code |
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>
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
guidanceprescriptions stopping naming the key) stands and is not redone: both prescriptions onorigin/mainalready omit it, and this branch leaves their text alone.The six ruled items, one by one
page.zod.ts:assignedProfilesremoved;profiles/assignedTobecome refusals naming the permission-set route; the two guidance strings rewrittenpage.form.tshelpText and its four locale bundles removedmajorchangeset + an ADR-0087 semantic migration entry; key stripped onmigrate meta --storedwith a structured TODOmajorgrade is refused by a standing repo-wide gate — see One ruled item the tree refusesClause-②: noItem 1 — route correction: a
retiredKey()tombstone, not a bare shape deletionThe retirement playbook offers two routes and keys the choice on whether the schema is strict:
retiredKey()for a non-strict schema, delete-plus-guidancefor a strict one.PageSchemais astrictObject, so the first attempt took the strict route — and the build refused it:scripts/build-schemas.tscheck (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/Pageis reachable from thepageroot and keeps emitting, so none holds. The route the tree actually permits here is the tombstone, which is also what the siblingview.pageNameretirement took two days ago.This is not a softening of the ruling. The key is unwritable:
tsctypes itnever, and a value reaching a parse raises the prescription. It simply stays in the walked shape, which is why its liveness row stays (asdead) 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:linewas re-derived by sentence rather than trusted. Two of the three resolved as written; the second did not.packages/spec/liveness/page.json—liveciting a non-existent objectui bridgepackages/spec/liveness/view.json:125— the "page audience gate" justificationgit grepover that file finds zero hits foraudience,assignedProfilesorpage audience(lit control:pageNamereads 3 lines in the same file; dark control 0). The #17063pageNameretirement rewrote that row on 2026-09-10 and the justification left with it. The same assertion is live atpackages/spec/src/api/protocol.zod.ts(SearchAllPageHitSchema's TSDoc) — that is the one corrected herepackages/metadata-protocol/src/protocol.ts— "enforced at page render"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.
packages/spec/liveness/page.json— wasstatus: "live", note: "profile-scoped page audience; objectui bridges it (react/src/spec-bridge/bridges/page.ts) to PageLayout.assignedProfiles." Nowstatus: "dead"with averifiedAtand a note recording that the cited path does not exist in objectui (nor does anyspec-bridgedirectory), 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 adeadverdict.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.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/**isdomain: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.mjsbefore and after, same script both times.Before (
origin/maincontent, built):After (this branch, rebuilt):
That is the actual refusal text, not a claim that one exists. Note the refusal moved channel:
unrecognized_keysat the page →invalid_typelocated at["assignedProfiles"], which is what az.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_keyspath. 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" insidestrictUnknownKeyError— andshared/alias-integrity.test.tsstates the mechanism as the premise of the gate it implements: "an alias only ever runs from theunrecognized_keyspath, so a key the shape declares can never reach it."The before-probe is the direct evidence: rows B and C above are
REFUSEDwithcode=unrecognized_keysbefore any change. SoprofilesandassignedTowere 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.tsasserts 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:
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).
Restore is proven by the blob hash against the HEAD blob and by an empty
git diff HEAD+ emptygit status --porcelain, never by an exit code. No rebuild leg is needed and none is claimed:page.test.tsimports./page.zod— a relative source path inside the same package — so this ablation never resolves throughdist.alias lineoccurrence count printed empty because thegrep -cwas 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 controlaliases: {= 6, dark control = 0).Changeset — it reaches a published
dist, with both controlsBuilt first, then measured (
npm pack --dry-run --jsonbefore a build readsdistas empty — that trap is avoided).dist/files and indist/index.d.ts(3 hits), so a consumer'stscand runtime both see it.src/ui/page.zod.tsis itself in the packed list.src/ui/page.test.tsandscripts/build-schemas.tsare NOT in the packed list (2012 files packed; 216dist/, 201src/, 38liveness/).//comment inpage.zod.tsreads 18 hits indist/.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-protocolcorrectly carries no changeset entry: itsfilesis["dist","README.md","CHANGELOG.md"], the corrected sentence is an inline body comment, and it reads 0 files in that package'sdist(lit controlCLOSURE_CONTEXT_KEY_BY_TYPE= 2, so the instrument fired).majorgradeRuling item 4 fixes the changeset at
major. The changeset in this PR ismajor, as ruled. A standing repo-wide gate refuses it: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:
17.xto18.0.0;scripts/sync-protocol-version.mjskeysPROTOCOL_VERSIONoff the spec package major, so the bump also flips the handshake to18and activates the 24 othertoMajor: 18conversions already waiting in the registry.⛔ I have therefore not applied
allow-major, and I have not silently regraded the changeset tominor. 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:major⇒ add theallow-majorlabel, and this PR is the one that cuts18.0.0; orminor⇒ 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 siblingview-page-mountretirement (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.tsis 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 bygen:migration-registryand sorted by id (entries/README.mdis the authority). This PR adds two such files and never edits between the markers:entries/retired-keys/18.ui__Page__assignedProfiles.ts→RETIRED_KEYS_BY_MAJOR[18]entries/semantic/18.page-assigned-profiles-audience-to-permission-set.ts→MIGRATIONS_BY_MAJOR[18].semanticTwo hand-edited lines remain, both appends at the tail and neither renumbering anything:
step18.conversionIdsgains'page-assigned-profiles-removed'after'view-page-mount-removed', andstep18.rationalegains a paragraph. ⛔ No other PR's entry is renumbered or reordered.The merge, and what it actually collided with.
origin/mainwas merged immediately before opening this PR: merge commit8774a8c115, parents3a1be112ff(this branch) +c1078a5591(origin/mainat that moment). The collision was real but it was not a number — it wasstep18.rationale, where #17260's landedobject-kanban.quickAddretirement and this card had each appended a paragraph to the same prose field. Resolved semantically, both intents kept, main's paragraph first:"ui/ObjectKanbanProps:quickAdd [RETIRED]"baseline marker — the os-regen driver's documented exit-0-while-dropping-a-side behaviour.scripts/pm/os-regen-merge.sh's order was followed (merge committed first, regeneration as its own commit,pre-commitdeferral discharged), and the regeneration put that marker back. That restoration is visible as its own commit;scheduledcache-warmup strategy — the cron it selected left in this same major (ADR-0049) #17638 or feat(spec): refuse a duration key whose JSDoc names a unit its describe does not #17635 was renumbered, reordered or touched.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-declaresassignedProfileson 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 ⛔nois not flipped toyesto 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.shwithOS_VERIFY_LOCK_SLOT=issue-16929; the verdict quoted is the wrapper's ownVERDICTline, or a per-partecho "$?"marker where parts were sequenced.All of the following at
626ca34833, the merged head.pnpm --filter @objectstack/spec buildVERDICT command-exit 0pnpm --filter @objectstack/spec check:generatedcontent/docs/references/**andliveness/state-counts.md— regenerated with--fix)test+typecheck+ platform-objectstest+ metadata-protocoltypecheck, joined with&&so one verdict covers all fourVERDICT command-exit 0— spec: Test Files 471 passed (471), Tests 13375 passed (13375); platform-objects: 39 files, 561 testsnpx eslint . --no-inline-config --format json— the full repo union, no narrowing claimedESLINT_EXIT=0— 6647 files received per--format json, 0 errors, 0 warningsnode scripts/pm/dispatch-gates.mjs --ran … --repo objectstack-ai/objectstackEXIT=0— 114 derived, 112 run, 2 NOT-MEASURED, 0 UNRUNnode scripts/check-i18n-bundles.mjs --writeEXIT=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 isEXIT=0, all nine bundles regeneratedpnpm check:nul-bytesEXIT=0— 8463 files scanned, no raw control bytes. Own sweep over the 20 changed paths withgrep -naPfinds none; lit control on an injected byte firesnode scripts/check-changeset-no-major.mjs --base origin/mainEXIT=1— deliberate, see aboveThe 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— wantsturbo 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 unbuiltpackages/client-react/dist, which was then built) andcheck:react-declaration-parity, run as CI runs it withMANIFEST="$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-erasurefirst readexit 2. That run happened whilesrc/migrations/registry.tsbriefly held a merge-resolution syntax error, and the gate parses that very file. Re-run on the fixed tree it isexit 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.md—page23 live → 22 live + 1 dead (total 24 unchanged); repo total 850→849 live, 93→94 deadcontent/docs/references/ui/page.mdx— the row's type becomesneverand carries the[REMOVED]prescription*.metadata-forms.generated.tsbundles lose theassignedProfilesblock (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/refsand#+digits all read 0;Opus/Sonnet/Haikuread 0. Lit controlClaudereads 8, so the sweep reaches. The onlyclaude-/anthropichits are the mandated trailer pair.验收备注
docs/adr/0010-nl-to-flow-authoring.mdmentionsassignedProfilesin 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.mdanddocs/audits/2026-06-security-identity-property-liveness.mdboth 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 prescribingassignedProfilesas a page gate #17401's, still pending) states "assignedProfilesremains 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:specexecution seat'sos-devround, sessionsession_01MkQhmuuJAVDjmeWNixwDDH, on branchclaude/issue-16929-assignedprofiles-removalat626ca34833.Generated by Claude Code