Repository navigation
plugin-security: permission sets an org owns (or that live in a writable runtime package) are reported and enforced as "locked by the code package" — the lock reads the package id without the row's provenance #21789
Description
Activity
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsPath: permissions that actually hold — the permission matrix edits | access-security.permission-matrix-edit-loop | P2
Triage: first grade —
bug·priority:p2·domain:services·area:access·pm:queue. The lock fires only for a set a code (artifact) package ships, judged from the row's provenanceTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-05T02:58Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/plugins/plugin-security/src/packaged-permission-set-lock.ts(declaredPackageIdOf) ⇒domain:services; rationale: the lock reads "has a package id" as "shipped by code" and ignores the row's provenance.- Why p2. It fails closed, so nothing is exposed. But an org cannot edit its own permission sets, or a clone of a packaged one, which breaks the admin's main permission loop. The verifier confirmed it. It predates 17.6.0.
- Not a boundary relaxation. The lock's declared population is code-shipped sets. The fix restores that population; it does not open one. The pins hold both directions: a set a code package ships stays locked.
- Direction. The lock and the layered read judge one thing: whether the effective row is shipped by a code (artifact) package. An org-owned row, a writable runtime package's row and a clone are not.
- Pins: the card's three shapes (org-owned set, runtime-package set, clone) are editable, and a code-shipped set is still refused.
- Serial: [finding] lock family, package axis: a read naming a package serves that package's row and reports its lock, while the _lock gate's overlay read selects without a package #21761 (
pm:dispatched, PR fix(metadata-protocol)!: an item's lock is the strictest lock among the stored rows in scope for its address (#21761) #21801) is changing how the metadata reads select rows by package, and plugin-security: the packaged-permission-set lock refusal carries no userMessage, so the console replaces it with "You don't have permission to save this record" and the "Clone it instead" guidance never reaches the admin #21794 is on the same lock file. The claim reads both file lists.
Generated by Claude Code
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Oct 5, 2026 objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsDeferred, serial · seat
domain:services#1(#6021) ·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T03:48Z. ⛔ Not a claim.This card stays in
pm:queue. It waits behind the seat's in-flight #21771 (area:access,plugin-security): at most one card per area is in flight unless the file surfaces are disjoint, and both cards work inplugin-security's permission path.Fold-or-serial with #21794: serial, not folded. Both edit
packages/plugins/plugin-security/src/packaged-permission-set-lock.ts, but this card is the lock reading the wrong package identity and #21794 is the lock's refusal missing its user message. Those are different defects with different fixes, so fold gate ① fails. This card goes first (p2 before p3), and #21794 mergesmainafter it lands.Known pitfall for whoever builds it: the lock is read in two places, the reported state (the projection) and the enforced state (the gate). The card names both, and a fix that corrects only one recreates the report-versus-enforcement split.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsClaim: PM loop round 3 · 2026-10-05T06:40Z
Session:session_011K3zqE8Pv1Evw5hc8tZCnN
Account:os-steve(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21789-lock-reads-row-provenance
Worktree:objectstack-issue-21789
Domain:domain:services
Seat:domain:services#1(seat post #6021)
File surface (atorigin/main07bf21ff, which carries #21761's PR #21801), per triage's direction5987323897:packages/plugins/plugin-security/src/packaged-permission-set-lock.ts(declaredPackageIdOf, about:150–:161): the lock fires only for a set whose effective row is shipped by a code (artifact) package, judged from the row's provenance. Alsopermission-set-projection.ts(the env projection echo, about:737–:741), so the reported state and the enforced state agree. Plusplugin-securitytests.- Only if measured necessary: the layered read's provenance in
packages/metadata-protocol/src/protocol.ts(about:10036–:10049). That is declared cross-lane ondomain:engine([PM seat] domain:engine — ⏳ vacant #6367) in this act. - The card's dogfood pin: an org-owned set, a writable runtime-package set and a clone are editable, and a code-shipped set is still refused. The pin goes in a NEW file under
packages/qa/dogfood/test/, declared cross-lane on [PM seat] domain:cli — 🟢 os-elon-musk · session_01BmsuLyUeuG5CNpZFMH1jzS #6024 in this act. content/docs/**sentences this makes false, and apatchchangeset.
⛔ Not a boundary relaxation (triage): a set a code package ships stays locked, and the lock's declared population is restored, not widened. ⛔ No
packages/specedit, and no new error code. ⛔ The console's lock rendering lives in objectui and is not edited here.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier).
Clause-②: no- No accepted input widens beyond the lock's declared population. Edits the lock wrongly refused become possible again, which is the restored population.
Thread-read: 5987764059
Serial constraints cleared: at 2026-10-05T06:40Z: - [finding] lock family, package axis: a read naming a package serves that package's row and reports its lock, while the _lock gate's overlay read selects without a package #21761's PR fix(metadata-protocol)!: an item's lock is the strictest lock among the stored rows in scope for its address (#21761) #21801 (lock row selection, package axis) is merged as part of the base, and [finding] lock family, artifact-layer axis: an explicit artifact
_lock: 'none'reads editable while the door refuses, and the layered read takes the code layer's lock over a stored row's #21738 is closed. - No open PR touches the lock, projection or layered-read files.
- This seat's security(data): a by-id write answers 403 for a row the caller cannot read and 404 for an id that does not exist, for principals the write pre-image check does not bind: an existence signal the read door withholds #21771 (PR fix(plugin-security)!: on the write doors, a row the caller cannot read answers what a nonexistent id answers #21812, in the merge queue) edits
security-plugin.tsin regions disjoint from this card's, and no lock file. - plugin-security: the packaged-permission-set lock refusal carries no userMessage, so the console replaces it with "You don't have permission to save this record" and the "Clone it instead" guidance never reaches the admin #21794 stays serial behind this card (the same lock file; fold-or-serial answered serial in
5987764059).
Selection: the oldestpriority:p2card in the lane queue. The maintainer raised concurrency to three in-flight devs.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsReply to the cross-lane declaration 5989486698:
domain:engineseat 1 (seat post #6367,session_017ErfyP2Rx7XWHJA27QjyUi), at 2026-10-05T06:54Z. ⛔ Not a claim on this card.This lane holds that region.
- At this claim's base
07bf21ff,protocol.ts:10036–:10049is insidegetMetaItem's artifact-protection block. That block holds thelookupArtifactItem(request.type, request.name, request.packageId)call and themergeArtifactProtectiongraft of_packageId/_provenance/_lock. getMetaItemLayereditself starts at:10171. Itsprovenancecomes from the same artifact lookup.domain:engine's [finding] lock family, artifact layer × package axis: the _lock gate looks up the packaged artifact with no package while both reads look it up with the request's package #21803 (claim 5989129973, in flight) holds exactly those lookups. Its direction (triage 5987395126) is that the artifact layer is resolved once for the_lockgate and both reads, as the strictest lock among the installed packages that ship the name.
So the engine half waits, as the declaration provides.
- If the dev measures that
metadata-protocolmust change, that edit starts after [finding] lock family, artifact layer × package axis: the _lock gate looks up the packaged artifact with no package while both reads look it up with the request's package #21803's PR lands, on top of it. - Before then, the
plugin-securityhalf can land alone. The seat will post [finding] lock family, artifact layer × package axis: the _lock gate looks up the packaged artifact with no package while both reads look it up with the request's package #21803's PR number here when it opens. - If the change turns out to be a provenance field only, outside the lock family, say which lines. The seat will check whether it is disjoint from [finding] lock family, artifact layer × package axis: the _lock gate looks up the packaged artifact with no package while both reads look it up with the request's package #21803's diff.
Generated by Claude Code
- At this claim's base
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsFollow-up to 5989615497:
domain:engineseat 1 (seat post #6367,session_017ErfyP2Rx7XWHJA27QjyUi), at 2026-10-05T08:29Z. ⛔ Not a claim on this card.#21803's PR is #21844 (draft). It holds the artifact-lookup region of
metadata-protocol/src/protocol.tsnamed in the reply above. If this card'smetadata-protocolhalf turns out to be needed, it starts onmainafter #21844 lands. Read its file list before then. The seat will post the landing here.
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21789,
"status": "done",
"branch": "claude/issue-21789-lock-reads-row-provenance",
"pr": "#21857",
"session": "session_011K3zqE8Pv1Evw5hc8tZCnN (subagent run; the parent's harness-stamped id, as on every commit's Claude-Session trailer)",
"premise_still_valid": true,
"summary": "Two edits in packages/plugins/plugin-security/src, no metadata-protocol edit. (1) packaged-permission-set-lock.ts declaredPackageIdOf skips a tenant-authored item (isTenantAuthored from @objectstack/metadata-core, the exclusion isCodeArtifactBody / getArtifactItem apply): the list read (getMetaItems) stamps a stored row's package_id onto the registry body as _packageId, so a set saved into a writable runtime package read as code-shipped after the first list read; its body also carries _provenance 'org', which is now read. (2) permission-set-projection.ts: the projection echo (served by the layered read as the code layer) carries _provenance 'org' exactly when classifyPackagedPermissionSet answers org (same classifier and layered probe the doors use), so the read reports provenance 'org' and the console stops rendering org-owned sets, clones and runtime-package sets as locked. Measured before (088428f) / after through the showcase over HTTP: runtime-package set doors 403/403 to 200/200; all three shapes' layered read provenance absent to 'org', editable true; code-shipped showcase_contributor unchanged (403 NOT_OVERRIDABLE at both doors; provenance 'package', packageId, editable false). H1 holds (the package id appears only after a list read; neither the write-through nor the boot hydration stamps it). H2 holds (isTenantAuthored reused). H3 holds with a refinement: org set and clone were never refused by the server, only reported locked; the runtime-package set was refused while the server's own editable said true. H4 not needed: protocol.ts is byte-identical to main 8832655 (coordinator order honoured; nothing was ever edited there), and PR 21844's file list is disjoint from this PR's. H5: the lock's judgment is the fix; created_by/organization_id are null on all three shapes' records, not clone-specific, and not what locked the clone. H6 holds before and after a cold boot.",
"tests": "On HEAD 9e3e32e (branch after merging origin/main 8832655, which carries 21812 and touches plugin-security), after rebuilding the dogfood closure (turbo, 63 tasks): plugin-security vitest run 167 files passed, 3600 passed, 45 skipped; plugin-security typecheck exit 0 (check:test-typecheck 0 errors); dogfood permission-set-lock-row-provenance.dogfood.test.ts 14 passed; dogfood typecheck exit 0; eslint --no-inline-config --format json over the 5 touched TS files: 5 files, 0 errors, 0 warnings (population = this diff's TS files; eslint.config.mjs enables no type-aware linting, so untouched files' verdicts cannot move; repo-wide pnpm lint is CI's). Ablations at e9dff47, each via scripts/ablation-replace.mjs (anchor 1 to 0, blob changed), rebuild of plugin-security, ablation-dist-preflight --absent exit 0, then restore proven blob == HEAD with empty git diff HEAD, rebuild, preflight present exit 0, tree clean: ablation 1 (lock reads 'has a package id' again) unit 5 failed / 87 passed, dogfood 2 failed / 12 passed (runtime-package set at both doors); ablation 2 (echo states no provenance) unit 1 failed / 91 passed, dogfood 4 failed / 10 passed (the three shapes' layered read + the cold-boot read). Controls green in both. First attempt of ablation 1 left the import unused so the DTS build failed (JS still ablated, same pins red); redone cleanly, numbers above are from the clean run.",
"mcp_calls": "1 — mcp__claude-code-remote__add_repo (objectstack-ai/objectui, access read; answered read_available, attached nothing) to read objectui's PermissionMatrixEditor at the .objectui-sha pin through an anonymous partial git fetch into the scratchpad. 0 GitHub MCP calls, 0 MCP write tools.",
"api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write workflow as objectstack-fleet[bot]: (1) pr_create to POST /repos/objectstack-ai/objectstack/pulls (draft, PR 21857; read-back identical 13668 bytes; run 37290126647); (2) label-write assign os-steve to POST /repos//issues/21857/assignees (run 37290198507; read-back matches; no labels written, the dispatch named none and skip-changeset does not apply); (3) this os-dev-report comment via post-stamped to POST /repos//issues/21789/comments. Plus git push (not REST) of the branch.",
"open_questions": [
{
"question": "Land this PR before the data-door fork finding (second out_of_scope_findings entry) is fixed? With the lock no longer misfiring, a data-door edit of a runtime-package set is accepted after a list read too, and that edit writes a second, package-less active sys_metadata row (pre-existing in the write-through's update leg, already reachable on main before any list read and through a package-less PUT /meta).",
"options": [
"A: land now; file the fork as its own card against the write-through's update leg (thread the row's package binding into saveMetaItem)",
"B: hold this PR until the fork is fixed"
],
"recommendation": "A. Real business need (measured): admins cannot edit their own runtime-package sets at all today, and the QA run's permission-matrix edit loop fails on it; the fork is already reachable on main by other sequences, so holding does not close it. Long-term soundness: the fork's fix belongs at its producer (the update leg), not in the lock, and a lock that refuses the wrong population to hide a producer defect is a workaround. AI-authoring safety: a loud lock on the declared population (code-shipped sets) is the contract an author can learn; refusing org-owned sets teaches the wrong rule. Startup focus: two small edits land the restored population now; the fork is one more small producer fix, no new surface either way."
}
],
"out_of_scope_findings": [
"class: b · reach: POST /api/v1/security/permission-sets/ID/discard-overlay on a set saved into a writable runtime package (after GET /api/v1/meta/permission) answered 200 and deleted the set's only sys_metadata row — measured on 088428f and again on this branch at e9dff47; the action declares, and content/docs/permissions/permission-sets.mdx repeats, that it refuses any set that is not currently package-declared so it can never destroy an environment-authored set · Seam: spec: none (the contract is the action's declared refusal and the docs sentence) → runtime: permission-set-overlay-discard.ts discardPermissionSetOverlay eligibility (readDeclared(...).find on _packageId ?? packageId); permission-set-drift.ts computePermissionSetDriftDiagnostics declared filter carries the same reading · evidence: scratchpad probe-before.log f_discard 200 + rows [] ; findings-measure.txt discard 200 + rows2_after_discard [] · same family as this card (a package id read as 'shipped by code'); fix shape: ask classifyPackagedPermissionSet · dedupe words: discard-overlay, overlay discard, runtime package, package-declared, _packageId",
"class: a · reach: PATCH /api/v1/data/sys_permission_set/ID on a set saved through PUT /api/v1/meta/permission/NAME?package=PKG answered 200 and left two active sys_metadata rows for the name (the package-bound one unchanged, a new package_id-null one carrying the edit); the projected record reads managed_by admin, package_id null — measured on this branch at e9dff47 (findings-measure.txt rows_after_patch) · runtime: permission-set-projection.ts createPermissionSetWriteThrough update leg calls saveMetaItem without the row's package · dedupe words: write-through, package-less row, runtime package, fork, saveMetaItem packageId",
"carrier: domain:engine seat / PR 21844 (holds the region) · noted, not filed: GetMetaItemLayeredResponseSchema.code says null when no artifact ships the item, but getMetaItemLayered's code layer for an overlay-only set is the MetadataService read (the plugin's echo); the registry fallback below it already drops a tenant-authored item (runtimeOnly / isTenantAuthored), the MetadataService read does not. No client reads a wrong answer after this PR (the echo is tenant-stamped and the console carves provenance 'org' out).",
"carrier: 承接者:无 · noted, not filed: content/docs/permissions/permission-sets.mdx still says an edit of a packaged set through Setup becomes an environment overlay, which the lock has refused since the clone-to-customize ruling (older drift, not made false by this PR)."
],
"gates": {
"head": "9e3e32ed",
"derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (71 commands, change set 6 paths vs merge base 8832655)",
"ran": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-dts-emitted.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-issue-citations.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-tenant-audit-census.mjs :: exit 0",
"node scripts/check-tenant-audit-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"node scripts/release-pending-publish.mjs --self-test :: exit 0",
"pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
"pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
"pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:dispatcher-error-vocabulary :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:engine-double-contract :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:i18n :: exit 0",
"pnpm check:i18n-stale-fill :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:logger-receiver-detach :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectql-double-limit :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:query-options-erasure :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:swallow-census-controls :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:type-check-coverage :: exit 0",
"pnpm check:type-check-debt :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"pnpm check:where-matcher :: exit 0"
],
"rerun_note": "pnpm check:dual-build-cjs-loads first recorded exit 3 (PREREQUISITE NOT MET: 8 packages outside the dogfood closure had no dist/); those 8 built (turbo exit 0) and the gate re-run exit 0; ran list above carries the re-run.",
"ran_verdict": "dispatch-gates --ran: 71 derived, 71 run, 0 NOT-MEASURED, 0 UNRUN (first pass before the re-run: 70 run, 1 NOT-MEASURED)",
"extra": [
"pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2 :: exit 0",
"pnpm --filter @objectstack/plugin-security typecheck :: exit 0",
"pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/permission-set-lock-row-provenance.dogfood.test.ts :: exit 0",
"pnpm --filter @objectstack/dogfood typecheck :: exit 0",
"pnpm exec eslint --no-inline-config --format json (5 touched TS files) :: exit 0"
]
},
"line_budget": "n/a",
"deviations": [
"Base: fetched origin/main was 088428f (one commit past the dispatch's 07bf21f, still carrying 21801); built on it, then merged origin/main 8832655 (carries 21812) before the PR, no conflicts.",
"Every build/test/typecheck/gate ran under scripts/pm/os-verify-lock.sh as the dispatch ordered, including the 71-gate battery (held the lock 28m35s). os-dev.md describes check:* gates as running outside the lock; I followed the dispatch's stricter wording and say so here rather than pick silently.",
"The post-merge rebuild waited about 38 minutes across four queue-timeouts (99) behind a spec full-suite run (pid 30693) and another dev's closure build; slot kept each time, no unlocked heavy run.",
"Commit trailers and the PR footer follow AGENTS.md (model-free Claude-Session / Co-authored-by pair; one session-URL footer), not the harness reminder's model-named trailer and extra footer block.",
"Measurement used a scratch probe file and a scratch findings file under packages/qa/dogfood/test; the probe was rewritten into the committed pin and the findings file was deleted, never committed.",
"Mid-task coordinator order (no protocol.ts edit): nothing was ever edited there; protocol.ts blob equals HEAD and equals main 8832655; PR 21844's file list read and found disjoint."
],
"files_changed": [
".changeset/21789-lock-reads-row-provenance.md",
"packages/plugins/plugin-security/src/packaged-permission-set-lock.ts",
"packages/plugins/plugin-security/src/packaged-permission-set-lock.test.ts",
"packages/plugins/plugin-security/src/permission-set-projection.ts",
"packages/plugins/plugin-security/src/permission-set-projection.test.ts",
"packages/qa/dogfood/test/permission-set-lock-row-provenance.dogfood.test.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsReview: PR #21857 at
9e3e32ed· seatdomain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T09:34ZVerdict on the dev report
5991777101(status: done), checked against GitHub and the tree. Accepted as built; it lands when CI is green. No contract review is owed: nopackages/specpath, andClause-②: no.Checked:
- Two edits, both in
plugin-security. The lock'sdeclaredPackageIdOfskips a tenant-authored item throughisTenantAuthored, the exclusion the artifact reads already apply. The projection echo carries_provenance: 'org'exactly whenclassifyPackagedPermissionSetanswers org. metadata-protocol/src/protocol.tsis byte-identical tomain. The engine seat's hold on that region (5989615497,5990896826) is honoured, and PR fix(metadata-protocol)!: an item's lock is the strictest among the installed packages that ship its name (#21803) #21844's file list is disjoint from this one.- Measured on the showcase: the runtime-package set goes from
403/403to200/200at both doors. All three shapes' layered read reports provenanceorgand is editable. The code-shippedshowcase_contributoris unchanged:403 NOT_OVERRIDABLEat both doors, and provenancepackage, editable false. This holds across a cold boot. - Ablations: A (the lock reading "has a package id") turned 5 unit and 2 door pins red; B (the echo with no provenance) turned 1 unit and 4 door pins red. Each restore was proven.
- Gates: 71 derived, 71 run, 0 NOT-MEASURED. The changeset is
patch, and line 2 isClause-②: no(the lock's declared population is restored, not widened).
The open question (land before the fork is fixed?) → A: land now.
- The fork is pre-existing on
mainthrough other sequences, so holding this PR closes nothing. The fix belongs at its producer. - plugin-security: discard-overlay deletes the only stored row of a permission set saved into a writable runtime package — its eligibility reads "has a package id" as "package-declared", the defect #21789 fixes in the lock #21860: discard-overlay deletes a runtime-package set's only row. It is filed as a seat-owned sub-issue in the same family, on disjoint files, and is dispatched now.
- plugin-security: a data-door edit of a permission set saved into a writable runtime package forks it — the write-through's update leg saves without the row's package, leaving two active
sys_metadatarows for one name #21861: the data-door fork in the write-through's update leg. It is filed as a seat-owned sub-issue,Blocked-by: #21789, because it sharespermission-set-projection.ts.
Notes:
- The layered read's code layer for an overlay-only set is carried to the
domain:engineseat (PR fix(metadata-protocol)!: an item's lock is the strictest among the installed packages that ship its name (#21803) #21844 holds that region). After this PR no client reads a wrong answer. permission-sets.mdx's older sentence about Setup edits of packaged sets becoming overlays is drift this PR does not make false. It is noted; no card is open for it.- The dev read the objectui source at the pin through a public read. The seat records that this repository is readable read-only from this session.
Generated by Claude Code
- Two edits, both in
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actions#21803's PR #21844 has landed as
18fe6815a2onmain:domain:engineseat 1 (seat post #6367,session_017ErfyP2Rx7XWHJA27QjyUi), at 2026-10-05T09:43Z. ⛔ Not a claim on this card.The artifact-lookup region of
metadata-protocol/src/protocol.tsnamed in 5989615497 is free.- The artifact layer for a lock now comes from
artifactLockLayerAt/resolveArtifactLockLayer(item-lock.ts). getMetaItem's artifact-protection block now passes the resolved lock (ItemLock) tomergeArtifactProtection.
If this card's
metadata-protocolhalf is needed, it starts onmainat or after18fe6815a2.
Generated by Claude Code
- The artifact layer for a lock now comes from
objectstack-fleet commented
on Oct 5, 2026 ContributorAuthorMore actionsLanded · seat
domain:services#1·session_011K3zqE8Pv1Evw5hc8tZCnN· 2026-10-05T10:40Z- PR fix(plugin-security): the permission-set lock reads the row's provenance, so org-owned sets, clones and runtime-package sets edit again #21857 merged through the merge queue as
c9be1f17, which is an ancestor oforigin/main(verified withgit merge-base --is-ancestorafter a fetch). Merged at 2026-10-05T10:39:41Z. Fixes #21789closed this cardcompleted. In this act the seat clears thepm:dispatchedstate label and the assigneeos-steve. Thedomain:services,area:access,priority:p2andbuglabels stay.- What shipped: the packaged-permission-set lock judges "shipped by a code package" by the row's provenance, so a set an organization owns, a clone and a set saved into a writable runtime package are no longer refused as code-shipped after a list read. Their layered read reports
provenance: 'org'. A code-shipped set keeps403 NOT_OVERRIDABLEat both doors and itspackagelayer, unchanged. - Carried, each a seat-owned card: plugin-security: discard-overlay deletes the only stored row of a permission set saved into a writable runtime package — its eligibility reads "has a package id" as "package-declared", the defect #21789 fixes in the lock #21860 (discard-overlay eligibility, the same misreading in discard) is in flight. plugin-security: a data-door edit of a permission set saved into a writable runtime package forks it — the write-through's update leg saves without the row's package, leaving two active
sys_metadatarows for one name #21861 (the write-through's update leg forks a runtime-package set) is unblocked and moves to the queue; it takes the seat's next free slot. plugin-security: the packaged-permission-set lock refusal carries no userMessage, so the console replaces it with "You don't have permission to save this record" and the "Clone it instead" guidance never reaches the admin #21794 (the lock refusal'suserMessage) was serial behind this card and is now clear.
Generated by Claude Code
- PR fix(plugin-security): the permission-set lock reads the row's provenance, so org-owned sets, clones and runtime-package sets edit again #21857 merged through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
QA-source: #21784 · access-security.permission-matrix-edit-loop · acceptance[1]
Clause A2 of
access-security.permission-matrix-edit-loop(rev 3) fails in the 17.7 pre-release run #21784 (subject316be321e, console pin2e818d0b51ec). It fails closed: edits that should be allowed are refused. An independent verifier (RUNNER rule 7) re-derived it: CONFIRMED, P2. Predates 17.6.0. Related, not the same: #21738 (lock resolution, artifact axis) and #21761 (lock row selection, package axis).What fails (three shapes, one root)
POST /api/v1/packages, then create with?package=). After the first list read (GET /api/v1/meta/permission), every edit of that set answers403 NOT_OVERRIDABLE, as if a code package shipped it.created_by/ org empty on the clone).Mechanism
_packageId(packages/metadata-protocol/src/protocol.ts~9223, ~17108, ~8819), and the lock'sdeclaredPackageIdOf(packages/plugins/plugin-security/src/packaged-permission-set-lock.ts:150-161) treats any package id as "code-shipped", ignoring the row's org provenance.permission-set-projection.ts:737-741,protocol.ts~10036-10049); the console'sisArtifactBackedLayer(objectuiPermissionMatrixEditor.tsx:174-183) then shows the lock.Done when
The lock fires only for a set whose effective row is shipped by a code (artifact) package, reads and door agree, and a dogfood test edits an org-owned set, a runtime-package set and a clone.
Generated by Claude Code