Repository navigation
finding(plugin-security): a Setup edit of a package-declared position answers 200, and the next boot silently restores the declared label and description #22360
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsTriage: first grade,
priority:p2·domain:services·area:access·pm:queue(findingremoved). Direction: the seeder stamps package provenance, so the existing row gate refuses the edit loudlyTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T22:54Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-security(bootstrapDeclaredPositions, withassertSystemRowWriteGateunchanged) ⇒domain:services. Rationale: plugin-security belongs to that lane.- Why p2: an administrator's edit is answered
200and then silently lost on the next boot. This is measured on a showcasesingleboot over two boots of one database. No exposure, but a write the platform claims and then reverts. - Execution, not a decision. The accepted text already picks the first of the card's two options:
- ADR-0131 D6: no door edits a managed definition.
- D3: managed sets are read-only and clonable.
- The metadata door already refuses the same edit (
403 NOT_OVERRIDABLE). - So the seeder stamps package provenance on the rows it declares, and the data door refuses the edit through the gate that already protects built-ins.
- ⛔ Keeping the edit and having the seeder stop overwriting it is not taken, because it would make a managed definition editable.
- Narrowing: the data door goes from "accepted, then reverted" to refused.
Clause-②: no (narrowing). The changeset names the remedy: clone the position under a new name, or change it in the package. - Order with feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196 (in flight, C2): land this before that card's S8b reader switch.
- Once declared-position edits are refused at both doors, S8b reading definitions loses no Setup-edited
delegatableon such a position. - The S8b question the card raises is then answered by this fix, not by a separate ruling.
- Holder: read this before cutting S8b.
- Before cutting, check whether an open feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196 stage PR touches the seeder file; if one does, this card goes after it.
- Once declared-position edits are refused at both doors, S8b reading definitions loses no Setup-edited
- Pins:
- A data-door edit of a declared position's label is refused, with the gate's code, and the row is unchanged after a reboot.
- Control: an administrator-authored position stays editable.
- C3 (refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write under
singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204) later retires this seeder whole. This fix is what stops the silent revert until then.
- Why p2: an administrator's edit is answered
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardspriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-08T23:15Z
Session:session_01WkL6Eijt432S1Y7ekb6ovQ
Account:os-bill(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-22360-declared-position-provenance
Worktree:objectstack-issue-22360
Domain:domain:services
Seat:domain:services#1(seat post #6021)Executes triage's direction (
6070667786): the declared-position seeder stamps package provenance on the rows it declares, so the existing row-write gate (assertSystemRowWriteGate, unchanged) refuses a data-door edit of a package-declared position, as it already does for built-ins.File surface at
origin/mainb4601539:-
packages/plugins/plugin-security/src/bootstrap-declared-positions.ts: the provenance stamp on insert, and on an existing declared row whose stamp is missing (an upgraded deployment). Alsonormalize-managed-by.tsonly if the stamp must pass through it. -
Tests:
plugin-securitytests, and one dogfood pin across two boots of one database (adomain:clipath, declared on [PM seat] domain:cli — ⏳ vacant #6024 when the PR shows it). -
One
@objectstack/plugin-securitychangeset (BREAKING; the remedy is to clone the position under a new name, or change it in the package). -
Seat append, 2026-10-09T00:21Z, from the REWORK
6071670550:packages/plugins/plugin-security/src/security-plugin.ts, in theassertSystemRowWriteGateregion (near:7365) only, and its tests.
Exclusions:
assertSystemRowWriteGatechanges only as the seat's REWORK6071670550rules: a row-state-only patch (active,is_default) on apackagesys_positionrow passes, as permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的active存储列喂进了 #4001 之后严格化的 permission spec #4669 does for packaged permission sets. Everything else on the gate is unchanged.- ⛔ No change to built-ins (S2), and none to the permission-set seeder.
- ⛔ No
packages/spec, nopackages/core. - ⛔ Nothing in feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196 stage S7's modules (the position write-through and its backfill, in flight).
Stop on breach; explain in the report.
Container & model:S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier)
Clause-②: no (narrowing)- A data-door edit of a package-declared position, answered
200and silently reverted at the next boot until now, is refused with the gate's existing code.
Responsibility:this repository's own code: the declared-position seeder writes its rows without package provenance, so the row-write gate does not protect them and the next re-seed silently reverts an administrator's edit | none: no gate refuses the edit at the data door | administrators editing a package-declared position in Setup
Thread-read: 6070667786
Serial constraints cleared: - feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196 stage S7 (this seat, in flight, no PR yet) adds a
sys_positionwrite-through that stands down for a name a package holds (its Q2 = A). Its plan does not touch the seeder file. Its pin on that stand-down is moved to the write-through's own level, so this card's refusal does not flip it; whichever lands later mergesmain. - Triage's order: this card lands before feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196's S8b reader switch.
-
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22360,
"status": "done",
"branch": "claude/issue-22360-declared-position-provenance",
"pr": "#22378",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ",
"premise_still_valid": true,
"summary": "Claim 6070926925 names this branch (checked before the first write). The declared-position seeder (bootstrap-declared-positions.ts) now stamps managed_by 'package' on the row of a position a code package holds. It stamps on insert, and it corrects an existing row that carries no gate-managed value (admin, none, legacy user); that write carries managed_by and nothing else, apart from the label/description refresh the seeder always made. 'A code package holds the name' is answered by registry.getArtifactItem('position', name): the lookup the metadata door refuses a save from with 403 NOT_OVERRIDABLE (the same lookup S7's stand-down reads). A registry without that lookup falls back to the door's own filter: the definition names a package other than the sys_metadata sentinel. assertSystemRowWriteGate is unchanged and now refuses data-door edits and deletes of these rows with 403 PERMISSION_DENIED. Environment-authored definitions keep an unmanaged row. H1 (reproduced on origin/main b460153, showcase single, two boots of one file database): all 10 declared rows were managed_by admin, organization null. Each catalog entry came from the registry with package com.example.showcase, and getArtifactItem agreed. PUT /meta/position/manager answered 403 NOT_OVERRIDABLE. PATCH /data/sys_position/ID of manager's label answered 200, and the next boot restored 'Project Manager'. The gate judged the rows unmanaged ('admin' is outside its managed set). With the change: 403 PERMISSION_DENIED, and the row is unchanged across the boot. H2: #2909 T2's text ('seed 只刷 label/description', never touches permissions/bindings/delegatable 等) does not forbid the stamp. A position declaration has no managed_by key, so the stamp projects no content. managed_by is readonly and the gate refuses an admin-door platform/package payload, so the 'admin' on a declared row was only ever the object default, and the re-stamp overwrites no administrator edit. Pinned: active, is_default and delegatable survive the re-stamp. The value is 'package', which the gate refuses and the permission-set and capability seeders stamp. normalize-managed-by.ts is untouched: it rewrites only system/config/user. Measured: a Setup-created position whose name a package later declares IS locked. Organization-bound row p22360_taken, created and edited at 200; a third boot whose stack declares the name re-stamps it 'package' and the next edit answers 403 (on main: relabelled, left admin, edit 200). Not widened. H3: see tests. The uninstall cleanup never reads or deletes sys_position, so it deletes no new rows.",
"tests": "All at HEAD 8c3e68a unless stated. PINS, unit (plugin-security src/bootstrap-declared-positions.test.ts, 7 new cases, real SchemaRegistry, resolved from source). Seeder reverted to the b460153 blob (hash checked on disk): 'Tests 4 failed | 16 passed (20)'. The 4 red: a new row stamped; an upgraded admin row corrected with exactly {id, managed_by}; display refresh and stamp in one write; the no-getArtifactItem registry branch. The 3 controls stay green by design: a second pass writes nothing; platform/system/config kept; an environment-authored definition stays unmanaged. Ablation of the re-stamp line only (node scripts/ablation-replace.mjs: anchor x1 to x0, blob 85be9d2de139 to cff574133726, 'ok mutation landed'): 'Tests 2 failed | 18 passed (20)', exactly the two re-stamp cases; 'ok restored: blob == HEAD (85be9d2de139) and git diff HEAD is empty'. With the change: 'Tests 20 passed (20)'. PINS, dogfood (packages/qa/dogfood/test/declared-position-provenance.dogfood.test.ts; plugin-security resolves through dist/). Against the pre-change dist/ (ablation-dist-preflight --absent: 'dist/: marker absent from all 6 built files'): 'Tests 3 failed | 4 passed (7)'. The 3 red: the declared row's provenance; the refused edit with the row unchanged (main answered 200 and relabelled); the cold boot where the upgraded row is re-stamped and refused. The 4 controls green: the precondition (metadata door 403 NOT_OVERRIDABLE); an administrator-authored position edit 200; the built-in edit 403 PERMISSION_DENIED; administrator- and environment-authored rows after the boot, admin and edit 200. The seeder's system write refreshes a drifted label on a package row: green. Dist ablation of the re-stamp line (ablation-replace, then plugin-security rebuild; preflight --absent on 'packageProvenanceStamp(heldByPackage, existing)': 'dist/: marker absent from all 6 built files'): 'Tests 1 failed | 6 passed (7)', the cold-boot case. Restore leg: rebuild, 'dist/: marker present in 2 built files', 'Tests 7 passed (7)'. Envelope asserted: status and code (403 + PERMISSION_DENIED / NOT_OVERRIDABLE), read from the response's top-level code. SUITES. pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2: 'Test Files 186 passed (186) / Tests 3891 passed | 45 skipped (3936)', VERDICT command-exit 0. plugin-security typecheck (tsc + tsconfig.scripts + check:test-typecheck): VERDICT command-exit 0; tsconfig.test.json --listFiles counts the edited test (1). Dogfood, all 18 files that read or write positions (git grep sys_position|sys_user_position|/meta/position, the new pin included): 'Test Files 18 passed (18) / Tests 168 passed (168)', VERDICT command-exit 0. Dogfood typecheck: VERDICT command-exit 0; --listFiles counts the pin (1). GATES. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run twice (once after git fetch): 69 commands, byte-identical to the seat's gates-22360.txt, all exit 0. pnpm check:dual-build-cjs-loads first exited 3, PREREQUISITE NOT MET (8 packages had no dist/); after turbo built them (44/44 cached) it exited 0: '✓ check:dual-build-cjs-loads — 107 published require entry point(s) across 66 package(s) load'. --ran: 'Run reconciliation — 69 derived, 69 run, 0 NOT-MEASURED, 0 UNRUN'. By hand, pnpm check:error-status-conformance: '✓ every derivable runtime status is documented, and every documented status is reachable.' Changeset grade, @objectstack/plugin-security minor (BREAKING under the launch-window convention): check-changeset-no-major '✓ This diff introduces nomajorbump.' With a pull_request event carrying the body's declaration: '✓ LEVEL AXIS: this PR declares clause-②no (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch.' check-adr-0087-registration '✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition' (not-required, no-migration-prescription). LINT, narrowed: eslint --no-inline-config --format json on the 3 changed .ts files. (1) Population from eslint's own config: --print-config resolves all 3, none ignored. (2) The JSON reports 3 files, 0 errors, 0 warnings. (3) Invariance: parserOptions.project and projectService are null (no type-aware linting), so the diff cannot move a verdict on any untouched file. Repo-wide lint is left to CI. H3 census, read from source at b460153. sys_position.managed_by readers: (1) assertSystemRowWriteGate, the one answer that changes; (2) the reserved_identity_name CEL rule, which exempts only platform/system, unchanged; (3) normalize-managed-by, legacy values only, unchanged; (4) the seeder itself. cleanup-package-permissions (the only plugin-security entry on the uninstall seam; the other is runtime's package jobs) selects sys_permission_set by package_id + managed_by and deletes sys_position_permission_set/sys_audience_binding_suggestion. It never touches sys_position, so no new deletes. The explain engine, the delegated-admin gate, resolve-authz-context and the sharing services never read sys_position.managed_by. Setup UI: highlightFields shows managed_by (now 'Package'). The Activate/Deactivate/Set as Default actions carry no managed_by condition, so they answer 403 on these rows, as on built-ins. objectui at the pinned .objectui-sha a58626c8 (read-only fetch into the scratchpad): its one row-level managed_by reader is recordDelete, on sys_permission_set only. Post-change probe (showcase single): all 10 declared rows package; PATCH label/active/delegatable and DELETE each 403 PERMISSION_DENIED; Setup create 201 and edit 200; builtin 403. Not merged with origin/main: 2 upstream commits (service-storage, spec error-code ledger, a storage dogfood file), disjoint from this diff.",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "3 REST writes, each one repository_dispatch to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches; the runs executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (PR #22378, draft; read back as 10962 bytes stored, identical to those sent); (2) label-write --assign os-bill → POST /repos//issues/22378/assignees (read back: assignee os-bill; the labeler's documentation/size/m/tests/tooling were not this write's); (3) this os-dev-report comment → POST /repos//issues/22360/comments. Git pushes (not REST): 4 to claude/issue-22360-declared-position-provenance (empty-branch probe, fix, pin, changeset), none forced.",
"files_changed": [
"packages/plugins/plugin-security/src/bootstrap-declared-positions.ts",
"packages/plugins/plugin-security/src/bootstrap-declared-positions.test.ts",
"packages/qa/dogfood/test/declared-position-provenance.dogfood.test.ts",
".changeset/22360-declared-position-package-provenance.md"
],
"deviations": [
"Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and a robot-emoji PR footer. Commits carry the model-free pair (Claude-Session + Co-authored-by: Claude) and the PR body ends with the session-URL footer, per AGENTS.md and the os-dev definition.",
"origin/main was not merged: it moved 2 commits after the branch point (#22359, #22341), touching packages disjoint from this diff. CI's merge ref and the queue validate the join.",
"The verify lock queued about 18 minutes behind the #15196 S7 sibling's final-union holder (pid 27469, alive). No check was narrowed; the dogfood typecheck ran after it released.",
"The Setup-UI census read objectui at the pinned .objectui-sha via an anonymous read-only git fetch into the scratchpad (no add_repo, no write)."
],
"open_questions": [
{
"question": "NON-BLOCKING; the PR implements option A as ruled. The ruled narrowing is wider than its one-line description ('accepted, then reverted' becomes refused). Measured on main: data-door PATCH of active:false and of delegatable:true on a declared row answered 200 and PERSISTED across a reboot, and DELETE answered 200 (the row came back at boot). With the stamp and the unchanged gate, all of them are 403 PERMISSION_DENIED. So Setup's Activate, Deactivate and Set as Default no longer work on a package-declared position, and no door is left to deactivate one (the metadata door answers NOT_OVERRIDABLE). The changeset states all of this. Confirm that A stands?",
"options": [
"A. As shipped: the gate is unchanged, so the whole row is read-only like a built-in. Remedy: change it in the package, or clone it under a new name.",
"B. Re-rule: the gate refuses only the definition columns (name, label, description) on package sys_position rows and lets active/is_default/delegatable through. That changes assertSystemRowWriteGate, which is outside this card's fence."
],
"recommendation": "A. Business need: the showcase deactivates none of its 10 declared positions, and a real deployment's count is NOT MEASURED. No producer was measured relying on toggling a declared position. Long-term: ADR-0131 D6 seals managed definitions, D3 makes managed items read-only and clonable, and D13 retires sys_position rows. B would add a per-column exception to a gate that is headed for removal. The triage ruling already chose to lock delegatable ahead of the S8b reader switch. AI-error resistance: A gives one loud rule, the built-ins' 403 at the door. Under B an AI writing Setup automation cannot tell which columns of a managed row stay writable. Startup scope: A adds no surface; B adds a column allowlist."
}
],
"out_of_scope_findings": [
"carrier: #15196 S7 (branch claude/issue-15196-s7-position-write-through at 6d127ed, no PR yet). Its dogfood case 'Q2: a Setup edit of a position the showcase package declares lands as before' expects PATCH of manager's label to answer 200; with this PR on main it answers 403 PERMISSION_DENIED. Whichever lands later reconciles. · noted, not filed",
"carrier: #15196 S7 (position write-through). A position the environment authored through the metadata door keeps an unmanaged row, and a data-door edit of it answers 200 (measured in the pin). By inference from the seeder's per-boot label refresh, the next boot rewrites that label from the definition: the same silent-revert shape, for environment-authored definitions. S7 writes Setup edits through to the definition. The revert itself was NOT MEASURED end to end. · noted, not filed",
"carrier: #15204 (C3 retires this seeder; ADR-0131 D13 retires sys_position). The comment in packages/plugins/plugin-security/src/objects/sys-position.object.ts above the reserved_identity_name rule says the declared seeder 'stamps no provenance at all (the row defaults to admin)'. That premise is now stale, though the rule's verdict is unchanged ('package' is not exempt). The file is outside this card's fence; recorded in the PR's Acceptance notes. · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsSeat review: REWORK, PR #22378 at
8c3e68a2b5· seatdomain:services#1(#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-09T00:20ZChecked against the branch and the dev report.
The stamp is right and stays.
- The seeder stamps
managed_by: 'package'on a position a code package holds, judged byregistry.getArtifactItem, the same lookup the metadata door refuses from. - It corrects an upgraded
adminrow with a write ofmanaged_byalone. - Audit sibling declared-metadata↔record two-store types (sys_position, sys_sharing_rule, sys_capability) per ADR-0094 addendum #2909 T2 is not crossed: a declaration carries no
managed_by, andactive,is_defaultanddelegatablesurvive the stamp (pinned). - The uninstall cleanup never touches
sys_position(measured).
The dev's non-blocking question decides a rework. With the stamp and the gate unchanged, every write to a package position row is refused, row state included:
- deactivate and activate, both of which persisted across a reboot on
main; - Set as Default.
That contradicts the standing rule for the sibling catalog type.
permission-set-projection.ts's write door (#4669, kept under the 2026-08-24 "lock the base, clone to customize" ruling) passes a bare{ active }patch on a packaged permission set straight through, because "switching a packaged set off is not a customization of it, … and a lock that swallowed it would break the surface it exists to protect". Under Q2 = A on #15196 (6039027082),sys_position.activestays row-authoritative.Ruling (four axes): a package position row is locked the way a packaged permission set is.
- Refused: its definition columns (
name,label,description,delegatable), delete, and the other lifecycle ops. - Open: a patch that touches only row state (
active,is_default, neither of which is aPositionSchemakey) passes, as permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的active存储列喂进了 #4001 之后严格化的 permission spec #4669's carve-out does.
Platform (built-in) rows stay fully refused, as today: switching off
everyoneis not an operator lever.The axes:
- Business need: switching a package's position off without uninstalling the package is the activation lever; option A removes it with no replacement door.
- Long-term: one rule across the two catalog types until C3 decides where
activelives. - AI-error-proofing: one stated carve-out (row state passes; definition and delete refuse), the same on both types, instead of two different locks.
- Startup scope: no new key and no new gate. It is a narrow branch in the existing gate.
delegatablestays refused, as triage intended ahead of S8b.The rework:
assertSystemRowWriteGate(security-plugin.ts, near:7365): forsys_positionrows stampedpackageonly, a single-id update whose patch touches onlyactiveand/oris_defaultpasses.- A filtered or multi-row update on such rows passes only if the patch is row-state-only.
- Delete, and any definition column, stay refused with today's code.
platformrows andsys_capabilityare unchanged. - The claim is amended: the gate is no longer "unchanged".
Clause-②: no (narrowing)stands: compared withmain, only definition edits and delete become refused.
Pins to add: on a package-declared position, deactivate, activate and Set as Default each answer
200and persist across a reboot. A label ordelegatableedit and a delete answer403. A built-in's{ active }patch stays403. A multi-row row-state patch passes, and a multi-row label patch is refused. Ablate the carve-out.Also:
- The
sys-position.object.tscomment premise ("the declared seeder stamps no provenance") is now stale. It rides this PR only if the file is touched; otherwise it goes on [PM seat] domain:services — 🟢 zhuangjianguo · session_013j5gkUCpqQiti4GgPqqmnt #6021's ride-along list. - The two S7 notes are with that stage's review.
- The seeder stamps
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22360,
"status": "done",
"branch": "claude/issue-22360-declared-position-provenance",
"pr": "#22378",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ",
"premise_still_valid": true,
"round": "rework 1, per the seat's REWORK 6071670550 and the claim's Seat append (read first; the claim still names this branch)",
"summary": "REWORK done. The stamp is unchanged. origin/main 16096e8 was merged first (merge 73c02fd, clean; no os-regen deferral). assertSystemRowWriteGate (security-plugin.ts, the gate region only) gains carve-out (d). For a sys_position row stamped 'package', an update whose patch is ONE object naming at least one of the named allowlist columns (PACKAGE_POSITION_ROW_STATE_COLUMNS = active, is_default) and nothing else passes. 'id' is ignored as the row identifier. The allowlist is a private static with a comment citing #4669 and #15196 Q2 = A. Single-id: the pre-image is read as before, and only then does a row-state-only patch on a package row return early. Filtered or multi-row: a row-state-only patch asks the managed-row probe only about the values the carve-out does not cover (platform/system), so package rows in the filter no longer refuse it and a built-in in the filter still does. An array payload is never admitted by (d). Still refused, with today's code and message: name, label, description, delegatable, any other column, mixed patches, an empty patch, delete, transfer/restore/purge. platform rows (even a bare { active }), sys_capability, and the provenance-stamp refusal (a) are unchanged. INTERPRETATION, flagged in deviations: the legacy pre-A4 spelling 'config' gets the carve-out too, because SYSTEM_ROW_PROVENANCE guards it as the same owner ('an application package') and the normalizer comment requires the two vocabularies in lockstep. Pinned, as the 'legacy config' case. The changeset now says that definition edits (incl. delegatable) and delete are refused, row state stays switchable (Activate/Deactivate/Set as Default persist), and the remedy stays; Clause-② no (narrowing) stands. The seeder's comments now name the carve-out (comment-only commit). sys-position.object.ts was not touched, so its stale comment is left, per the seat. The PR BODY IS NOW STALE and was not patched (this round's budget has no PR write): see deviations for the corrections the seat should write.",
"tests": "FINAL HEAD c345f93 (pushed; ls-remote agrees). UNIT gate pins, 13 new cases in packages/plugins/plugin-security/src/store-fault-fail-closed.test.ts, on its existing ledgered store double, through the real SecurityPlugin middleware. Single-id deactivate/activate/Set as Default/both/id-in-filter admitted. label/description/delegatable/name/mixed/unknown-column, the empty patch, and delete/transfer/restore/purge all refused, with code PERMISSION_DENIED, status 403, and today's 'provided by an application package' message. Legacy config admitted for row state, refused for label. Built-in { active } and { is_default } refused ('provided by the platform'). sys_capability package { active } refused. Admin row label+delegatable admitted. Forged managed_by with active refused ('cannot stamp'). Filtered row-state over package rows admitted. Filtered label refused. A filtered row-state patch also reaching a built-in refused, and so is a whole-table one. Filtered delete refused. Run: 'Test Files 2 passed (2) / Tests 45 passed (45)' (with the seeder file), VERDICT command-exit 0. ABLATION of the carve-out (node scripts/ablation-replace.mjs: anchor 'const rowStateOnly = SecurityPlugin.isPackagePositionRowStatePatch(opCtx);' to 'const rowStateOnly = false;', 'ok mutation landed: anchor 1 -> 0, blob 05177cb0b619 -> 4ece06ce6bd3'): 'Tests 3 failed | 22 passed (25)'. The 3 red are exactly the admit cases (single-id row state, legacy config row state, filtered row state); 'ok restored: blob == HEAD (05177cb0b619) and git diff HEAD is empty'. Red on main: main's gate has no carve-out, so the same 3 cases; the refusal cases are main's gate behaviour, pinned. DOGFOOD pin packages/qa/dogfood/test/declared-position-provenance.dogfood.test.ts, rewritten (showcase single, two boots of one file). Cases: provenance; label and delegatable edits 403 with the row unchanged; delete 403 with the row staying; Deactivate (contributor), Activate (legal, after a system deactivation) and Set as Default (ops) 200 and landed; updateMany row-state over finance+client_liaison 200 and landed; an updateMany label patch carries PERMISSION_DENIED with the label unchanged; admin-authored and env-authored controls; built-in { active } and label 403; after a cold boot, the declared label stands, the deleted row stays, every row-state write persisted, the upgraded admin row is re-stamped and refused, and the drifted label is refreshed. With the change: 'Tests 10 passed (10)', VERDICT command-exit 0. Dist ablation of the carve-out, against the same anchor. The mutated plugin-security build emitted ESM and CJS ('ESM ⚡️ Build success', 'CJS ⚡️ Build success') and then failed DTS: 'src/security-plugin.ts(7500,18): error TS6133: isPackagePositionRowStatePatch is declared but its value is never read', the expected consequence of the mutation. The suite runs the JS, and ablation-dist-preflight --absent '.isPackagePositionRowStatePatch(opCtx)' gave 'marker absent from all 4 built files'. Result: 'Tests 3 failed | 7 passed (10)'. The 3 red: Deactivate/Activate/Set as Default; the updateMany row-state case; the cold-boot persistence case. Restore leg: full rebuild ('DTS ⚡️ Build success', 'marker present in 2 built files'), then 'Tests 10 passed (10)'. Main leg: both plugin-security sources restored to the origin/main 16096e8 blobs (hash checked), then rebuilt; markers 'restampedPackageProvenance' and '.isPackagePositionRowStatePatch(opCtx)' absent from all 6 built files. Result: 'Tests 5 failed | 5 passed (10)'. The 5 red: provenance; label/delegatable refused; delete refused; the updateMany label refusal; the cold boot. Green on main by design: the precondition, the row-state 200s (main's behaviour, kept), and the admin, built-in and post-boot controls. Then 'RESTORED: both files == HEAD blobs; git diff HEAD empty', and the HEAD rebuild has both markers 'present in 2 built files'. The seeder unit pins from round 1 are unchanged: 20/20. SUITES. plugin-security full suite at 649d1b6 (plugin-security src as at c345f93, which differs only in test-file placement): 'Test Files 188 passed (188) / Tests 3915 passed | 45 skipped (3960)'. At c345f93: the two touched test files 45/45; plugin-security typecheck incl. check:test-typecheck OK, and dogfood typecheck, together VERDICT command-exit 0. Dogfood, the 18 files that read or write positions, at 649d1b6 against that build (the shipped source is identical at c345f93): 'Test Files 18 passed (18) / Tests 171 passed (171)'. GATES at c345f93: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gave 70 commands (round 1's 69 plus pnpm check:error-status-conformance, now derived because security-plugin.ts moved), all exit 0. --ran: 'Run reconciliation — 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. Verdict lines: error-status-conformance '✓ every derivable runtime status is documented, and every documented status is reachable.' engine-double-contract 'check-engine-double-contract: OK — 990 pinned, 129 in the DEBT ledger, 3 exempt.' dual-build-cjs-loads '✓ … 107 published require entry point(s) across 66 package(s) load' (the 8 extra packages were built first). nul-bytes 'check-nul-bytes: OK'. check-adr-0087-registration '✓ 1 declared-breaking changeset(s), each carrying an ADR-0087 disposition.' check-changeset-no-major '✓ This diff introduces nomajorbump.' With the pull_request event: '✓ LEVEL AXIS: this PR declares clause-②no (narrowing), and no package whosepackages/**/src/**it moves is gradedpatch.' At 649d1b6, a first run of the same 70 had engine-double-contract exit 1: 'RETAINED [findOne]: …system-row-write-gate-package-position.test.ts pins 1 engine double(s) that the pinned ledger does not record'. The pins moved into the existing ledgered file and that file was deleted, so there was no ledger write; the gate then answered OK. LINT, narrowed: eslint --no-inline-config --format json on the 5 changed .ts files gives 'files 5 errors 0 warnings 0'. --print-config resolves all 5 (none ignored), with parserOptions.project and projectService null, so linting is not type-aware and untouched files cannot move.",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "This round: 1 REST write, this os-dev-report comment: POST /repos//issues/22360/comments, via one repository_dispatch to the fleet-write relay (scripts/pm/post-stamped.mjs). No PR write and no label write. Round 1 made 3 (pr_create #22378, label-write --assign os-bill on #22378, the round-1 report comment 6071639081). Git pushes this round (not REST): 4 fast-forward pushes to claude/issue-22360-declared-position-provenance (16fe3da merge+carve-out, f9d3a1f pin+changeset, 649d1b6 seeder comments, c345f93 pin move), none forced.",
"files_changed": [
"packages/plugins/plugin-security/src/bootstrap-declared-positions.ts",
"packages/plugins/plugin-security/src/bootstrap-declared-positions.test.ts",
"packages/plugins/plugin-security/src/security-plugin.ts",
"packages/plugins/plugin-security/src/store-fault-fail-closed.test.ts",
"packages/qa/dogfood/test/declared-position-provenance.dogfood.test.ts",
".changeset/22360-declared-position-package-provenance.md"
],
"deviations": [
"PR body STALE, not patched (no PR write in this round's budget; dev writes a body once). The seat should rewrite these parts. (1) 'What this changes': the gate is no longer unchanged; add carve-out (d) and its named allowlist active/is_default on package (and legacy config) sys_position rows. (2) The Diff line: add security-plugin.ts (gate region) and store-fault-fail-closed.test.ts. (3) H1 table, 'this branch' column: active:false is 200 and persists; delegatable is 403; DELETE is 403. (4) The Pins section: the round-2 counts in tests. (5) The Changeset section: definition edits and delete refused, row state switchable. (6) Verification: HEAD c345f93, 70 gates. (7) Acceptance note 1 ('narrowing wider than…') is resolved by this rework; replace it with the carve-out statement.",
"INTERPRETATION: the carve-out covers 'config' (legacy pre-A4 package) besides 'package', for vocabulary lockstep with SYSTEM_ROW_PROVENANCE. The ruling names 'package'. Dropping 'config' is a one-entry change to PACKAGE_POSITION_PROVENANCE plus its pin, if the seat prefers the letter.",
"Pin placement: the gate pins first went into a new test file, which tripped check:engine-double-contract (a new findOne double the pinned ledger does not record). Per the os-dev rule (reuse a file's ledgered double rather than pin a new one; leave the ledger alone), they moved into store-fault-fail-closed.test.ts, which already tests this gate. Its header was left as is, and the new describe block carries its own docblock.",
"The seeder file got a comment-only commit (649d1b6) so its provenance notes stay true under the carve-out. The file is in the claim's original fence.",
"The verify lock queued about 19 minutes in total behind the #15196 S7 sibling's final-union holder (pid 23159, alive). No check was narrowed.",
"Attribution: commits carry the model-free trailer pair, not the harness's model-named Co-Authored-By, per AGENTS.md and the os-dev definition."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: #15196 S7 (branch claude/issue-15196-s7-position-write-through, no PR as of this round). Its dogfood case 'Q2' expects PATCH of manager's LABEL to answer 200; with this PR on main it answers 403 PERMISSION_DENIED. A row-state-only patch would still answer 200. Whichever lands later reconciles. · noted, not filed",
"carrier: #15196 S7 (position write-through). A data-door label edit of an environment-authored position's row answers 200 (measured in the pin). That the next boot rewrites it from the definition is an inference, NOT MEASURED end to end. · noted, not filed",
"carrier: domain:services seat #6021 ride-along list (per the seat's REWORK). The packages/plugins/plugin-security/src/objects/sys-position.object.ts comment above reserved_identity_name says the declared seeder 'stamps no provenance at all'. That premise is stale and the verdict is unchanged; the file was not touched in this PR. · noted, not filed"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsSeat ACCEPT: PR #22378 at
c345f93b73(rework round 1) · seatdomain:services#1(#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-09T01:37ZChecked against GitHub and the branch, not the report's prose (round-1 report
6071639081, rework report on this card).- Form:
- The PR is a draft. Its first line is
Fixes #22360, with a line-startClause-②: no (narrowing). The PR assignee isos-bill. - The stale parts of the body are superseded by a "Rework round 1" section, written by the seat through the relay.
check-governed-merges: NOT governed.origin/mainis merged in clean.
- The PR is a draft. Its first line is
- The stamp (round 1, unchanged):
- The seeder stamps
managed_by: 'package'on a position a code package holds (registry.getArtifactItem, the lookup the metadata door refuses from). - It corrects an upgraded
adminrow with a write ofmanaged_byalone. active,is_defaultanddelegatablesurvive (pinned). Audit sibling declared-metadata↔record two-store types (sys_position, sys_sharing_rule, sys_capability) per ADR-0094 addendum #2909 T2 is not crossed, and the uninstall cleanup never touchessys_position.
- The seeder stamps
- The carve-out (the REWORK
6071670550), read by the seat:- Carve-out (d) in
assertSystemRowWriteGate: anupdatewhose patch is one object naming onlyactive/is_defaultpasses on apackage(or legacyconfig)sys_positionrow. - A filtered row-state patch passes over package rows; a built-in in the filter still refuses it.
- Platform rows,
sys_capability, delete, the lifecycle ops, definition columns, mixed and empty patches, and the provenance-stamp refusal are unchanged. - The allowlist is a named static, citing permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的
active存储列喂进了 #4001 之后严格化的 permission spec #4669 and Q2 = A, marked "widening this set is a ruling, not an edit". configis accepted:SYSTEM_ROW_PROVENANCEguards it as the same owner, and the vocabularies move in lockstep.
- Carve-out (d) in
- Evidence:
- Gate pins: 13 cases. The ablation turns exactly the 3 admit cases red.
- Dogfood, across a cold boot: 10/10 with the change. On
main, 5 are red: the provenance, the label/delegatablerefusal, the delete refusal, theupdateManylabel refusal and the cold boot. The row-state 200s aremain's behaviour, kept. The dist ablation of the carve-out turns 3 red. plugin-security3915 passed. The position dogfood files 18/18. Gates 70 of 70 derived, with--rana derived zero.check:engine-double-contractis OK: the pins reuse a ledgered double rather than pin a new one.
- Deviations, accepted:
- The
configinterpretation. - The pin placement.
- The seeder's comment-only commit.
- The
- Out-of-scope notes:
- S7's dogfood Q2 label case: the S7 dev was told at
6070926925's dispatch to pin the stand-down at the write-through's own level. The seat checks it at S7's review. - The environment-authored position's possible boot revert: inferred only, carried to S7's review.
- The stale
sys-position.object.tscomment: added to [PM seat] domain:services — 🟢 zhuangjianguo · session_013j5gkUCpqQiti4GgPqqmnt #6021's ride-along list.
- S7's dogfood Q2 label case: the S7 dev was told at
- Landing to-do:
- Every check on
c345f93b73must be green or an expected skip. - Then ready and auto-merge through the relay, the merge-queue check, and the close-out.
- Triage's order holds: this lands before feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196's S8b.
- Every check on
- Form:
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded ·
domain:servicesseat 1 (#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-09T02:13Z.PR #22378 merged through the merge queue as
00bee272. Onorigin/main,@objectstack/plugin-security(BREAKING narrowing):- The declared-position seeder stamps
managed_by: 'package'on a position a code package holds, and corrects an upgraded row's stamp at boot. - The row-write gate refuses a data-door edit of a package position's definition (
name,label,description,delegatable) and its delete, with403 PERMISSION_DENIED. The administrator is told at the write, instead of the next boot silently reverting it. - Row state stays switchable: Activate, Deactivate and Set as Default on a package position pass and persist, as permission-set backfill (ADR-0094 D4) 现在 100% 失败:行里的
active存储列喂进了 #4001 之后严格化的 permission spec #4669 allows for packaged permission sets. - Built-in positions are fully refused, as before.
The PR's
Fixesline closed the cardcompleted. This note also removespm:dispatchedand the assignee.- feat(core,objectql,plugin-security,plugin-sharing): the catalog is read from the registry; assignment tables reference it by name (ADR-0131 D2/D3/D4) #15196's S8b can now read
delegatablefrom definitions without losing a Setup edit (triage's order).
- The declared-position seeder stamps
- added a commit that references this issue
on Oct 9, 2026
Filing gate: ① a product defect, class (a): a write answered as saved that the platform silently reverts. reach: measured on a showcase
singleboot at1cb0edb82d(two boots of one database), by #15196 stage S7's measurement round (os-dev-report6070148106). Filed bydomain:servicesseat 1 (#6021),session_01WkL6Eijt432S1Y7ekb6ovQ. ⛔ Not graded or routed here; ⛔ not a claim.Measured
PATCHon asys_positionrow) answers200for a new label on a position a package declares (the showcase's declared positions), and the row carries the new label.bootstrapDeclaredPositions,plugin-security) writes the row's label and description from the declaration again. The administrator's edit is gone, with no message.managed_by(admin).assertSystemRowWriteGaterefuses update and delete only onplatform/packagerows, so it does not protect them. The metadata door refuses the same edit (403 NOT_OVERRIDABLE).Direction (for triage)
Choose one of these; the claimant measures:
Note: #15196's S7 ruling (
Q2 = A) leaves this path exactly as today, and the S8b reader switch reads definitions. Sodelegatableon such a position, which is row-authoritative and Setup-editable today, needs the same decision before S8b.Dedupe: MCP
search_issues「declared position label edit reverts on boot seeder overwrites Setup edit managed_by admin」 gave 2 hits, #21785 (closed, email templates) and #21486 (closed, the seed-ownership claim). Neither is this.Dedupe words: declared position label edit reverts on boot · sys_position seeder overwrites Setup edit · package position row managed_by admin