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 }}
Repository navigation
metadata save door: a hand-authored _lock binds on an environment item with no unlock door (any manage_metadata holder can brick an item); ADR-0010 §3.5 and the spec say the key is never authored; needs a ruling #23119
Filing gate ①, class (b): a private runtime key that authors can set at a public door, with no way back. reach: the public door, measured on a showcase boot at abf86554c9 by #23096's dev:
PUT /api/v1/meta/app/probe_rawlock_app with { name, label, _lock: 'full', _lockReason: 'raw' } answers 200;
GET then reads lock: 'full', editable: false, deletable: false;
a second PUT answers 403 ITEM_LOCKED ("locked (_lock=full, source=overlay)"), and DELETE answers 403 ITEM_LOCKED.
No unlock door exists (ADR-0010 §3.8 is unimplemented). So any manage_metadata holder, or an AI copying the _lock that a served packaged body carries, can make an environment item permanently uneditable and undeletable through the API.
Filed by domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-13T00:47Z. It comes from #23096's dev (os-dev-report 6135259274, out-of-scope findings) and the contract-tier record on PR #23115 (6135514431). ⛔ Not a claim. Lane and grade are triage's.
The texts that disagree
ADR-0010 §3.5: "Tenant org artifacts ignore L3 / L4 entirely (the tenant owns them in full, can also delete them)."
packages/spec/src/shared/protection.zod.ts's guidance: "The _lock* keys are the runtime's PRIVATE envelope, stamped by the loader from this block — never authored."
The save door's DERIVED_PROVENANCE_KEYS deliberately omits _lock*.
Why this needs a ruling, not a fix
A refusal at the save door would reverse the #21738 reading. Honouring the stored lock would need a declared door, an authorisation, and an unlock door (ADR-0010 §3.8). This is a contract choice, so it goes to triage for routing and, if the readings stand, to the decision box.
Related
#23096 (PR #23115) closes the same door for the public protection block: refused at save, per ADR-0010.
Dedupe: raw _lock save door bricks environment item; hand-authored _lock overlay binds no unlock; _lock envelope authored at metadata save door; org item lock ADR-0010 §3.5. A search of the issues found #21738 (the ruling cited above) and no open card for this reach.
Triage: first grade, bug · priority:p2 · domain:engine · area:studio · pm:blocked (finding removed). An author's write never sets or changes a stored lock. Not a fork: ADR-0010 and the spec already say so
That grade called a stored row lock "an administrator's stored lock". No text declares an administrator's lock. That phrase was my premise, and item-lock.ts now cites it.
The texts say otherwise:
ADR-0010 §4.1: the protection fields are stripped on write, and the loader reattaches them on load.
ADR-0010 §3.5: tenant org artifacts ignore L3 and L4.
shared/protection.zod.ts: _lock* is the runtime's private envelope and is never authored.
A stored row lock is one the runtime stamped: a loader's or a plugin's applyProtection, an install, or a migration's re-save. It is not one an author wrote.
Why this is not a decision. The maintainer's standing ruling (#12708, the protocol is the baseline) chooses between the texts. The other reading, an administrator lock, would be a new door. It would need its own permission and an unlock (ADR-0010 §3.8). Nobody has asked for it, so it is not built.
Direction: the save door's author faces never set or change the stored _lock* family.
An author-reachable write (meta-envelope, meta-dispatch) carries the target row's stored _lock* family forward unchanged. The caller's body cannot add one, change one or remove one.
A body whose _lock* equals what the read served for that item is a round-trip echo. The door accepts it and ignores it, because Studio and cloud's AI Studio echo the served body on every edit.
⛔ This card does not unbind any stored lock. Releasing a lock that binds today would loosen an enforced lock, and that goes to the maintainer.
No deployment has reported a stuck item. This card stops new ones. If a stuck item is reported, that report is the ADR-0010 §3.8 card.
Why p2: it matches #23096. The door is reachable by any manage_metadata holder, or by an agent that echoes one item's served lock onto another. The item becomes uneditable and undeletable through the API, with no API way back. It reads no data and grants no privilege.
Why blocked: PR #23115 (#23096, draft, head 94c3c852b9) is reworking the same saveMetaItem refusal position, the same shared/protection.zod.ts guidance and the same changeset family. The unlock scan fires when #23096 closes.
Triage: lands in packages/metadata-protocol/src/protocol.ts (saveMetaItem) (domain:engine, the lock family's door); rationale: the texts agree, and only my reading did not.
Claim: PM loop round 1 · 2026-10-13T02:29Z
Session: session_01ADzJtzYTLUfgrRZHxkagkX
Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23119-save-door-lock-envelope
Worktree: objectstack-issue-23119
Domain: domain:engine
Seat: domain:engine#2 (seat post #20966); the lane's only seated PM
Triage-ref: 6135754247 (binding direction: the save door's author faces never set or change the stored _lock* family; an echo of the served lock is accepted and ignored; a differing _lock* is refused in #23096's refusal family at the same position; server-stated faces keep stamping; ⛔ not a silent strip; ⛔ no stored lock is unbound)
File surface (read on origin/main0d43004100; stop on a breach and explain it in the report). ⛔ Classes, positions and functions only, in every artefact.
packages/metadata-protocol/src/protocol.ts, saveMetaItem's author-reachable arm only: the request.source === undefined && request.writeFace !== 'package-duplicate' branch beside protectionBlockRefusal. The target row's stored _lock* family is carried forward unchanged. A body _lock* equal to what the read serves for that item is accepted and ignored. A differing one (an add, a change or a removal) is refused with the located 400 family protectionBlockRefusal uses, at the same position, naming ADR-0010's door. Triage names the author faces meta-envelope and meta-dispatch; the dev measures every face that reaches this arm and reports it.
packages/metadata-protocol/src/item-lock.ts: the docblock sentence (about :37) that calls a stored row lock "an administrator's stored lock" is corrected to the amended reading (triage 6135754247). Prose only; ⛔ the resolver, the overlay layer and the layer order are untouched.
packages/spec/src/shared/protection.zod.ts: guidance prose only, and only if it must name the save door's refusal (cross-lane domain:spec prose).
Tests beside the code, and one .changeset/23119-*.md.
⛔ Not touched: the server-stated arm (source, package-duplicate), stripUnmanagedImportLock and importUnmanagedPackage, any stored row (no lock is unbound), and .changeset/15213-*.
Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). Ceiling CONTRACT_REVIEW_TIER, reason: the card NARROWS a published accept set (a body _lock* the door stored now answers 400), so it is Clause-②: no, stays in its lane, and owes one contract-review-tier review before the queue; packages/spec/src/** prose may be touched (Clause-② suspect surface, a hint). check-governed-merges --test over the claimed paths: not governed.
Clause-②: no
Responsibility: packages/metadata-protocol protocol.ts saveMetaItem (this lane): the author arm stores a body's _lock* family verbatim, so a hand-authored or echoed lock binds the item | none: the loader stamps _lock* on package items, stripUnmanagedImportLock covers the unmanaged import only, and no unlock door exists (ADR-0010 §3.8 is unimplemented) | any manage_metadata holder, or an agent that echoes one item's served lock onto another; measured on a showcase boot at abf86554c9 by #23096's dev (6135259274): PUT 200, then PUT and DELETE each 403 ITEM_LOCKED
Thread-read: 6136862148
Serial constraints cleared:
os-dev-report
{
"issue": 23119,
"status": "done",
"branch": "claude/issue-23119-save-door-lock-envelope",
"pr": "#23167",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)",
"premise_still_valid": true,
"summary": "Reproduced on base f931e76 (origin/main at worktree creation; 0d43004 plus one spec/ui-only commit) at the protocol door the REST routes forward to: PUT app with _lock 'full' answered 200, GET read lock full / editable false / deletable false, a second PUT and DELETE each 403 ITEM_LOCKED; also measured that an author save sending no _lock* dropped a server-stamped no-delete (fail-open). saveMetaItem's author arm now runs authoredLockFamily beside protectionBlockRefusal: no _lock* key keeps the target row's stored family; a family equal to the item's resolved served lock (resolveItemLock over artifactLockLayerAt + overlayLockLayerAt otherSpelling true, via withItemLockFamily, what getMetaItem / layered effective / list serve) or to the family stored on the row the save writes (repo.headAt at its state/package/member, first draft falls back to the active row; what the draft read and layered overlay serve) is accepted and ignored; anything else (add, change, removal, a family copied from another item) is refused 400 VALIDATION_ERROR with issues[] per key, naming ADR-0010 Phase 4.3's loader door. Server faces (source, package-duplicate) stamp as before, stripUnmanagedImportLock untouched, malformed values still the parse's 422; item-lock.ts docblock sentence amended per 6135754247 (prose only); protection.zod.ts left as is (its guidance stays true). Faces reaching the arm: meta-envelope, meta-dispatch, the face-less in-process doors (judged), external-import and package-unmanaged-import (bodies never carry a well-formed family: carry-forward only); external_catalog (no schema) judged too. No face is server-stated in fact, so no needs_decision.",
"tests": "At 6bbc547 unless stated. New protocol.save-door-lock-family.test.ts: 25 passed. Full metadata-protocol suite (at 2ed18c7, implementation unchanged since): 230 files passed, 1 failed = protocol.package-publish-audit-rows.test.ts (5 tests; fixtures seeded a lock through the author face) → fixtures moved to source 'migrate-stored' in 063ca29; that file + the new one: 2 files / 43 tests passed. Untouched pins re-run: protocol.lock-one-resolution (#21738 resolver pin), protocol.save-door-protection, package-unmanaged-install, protocol.lock-door-read-agree: 4 files / 24129 tests passed. objectql: the 53 test files mentioning saveMetaItem or _lock, against rebuilt metadata-protocol dist (authoredLockFamily x5 in dist/index.js): 53 files / 740 tests passed (protocol-derived-provenance-doors.test.ts updated: the verbatim-store pin now pins kept-stored-family + refused-authored-family). Typecheck: metadata-protocol exit 0 (--listFiles includes both edited test files), objectql exit 0 (tsc + check:test-typecheck). Ablation via scripts/ablation-replace.mjs on the committed tree, trap restore: anchor 'request.item = await this.authoredLockFamily({' 1→0, marker 0→1, blob 5ce2896165→8faf50ed0d; ablated run 18 failed / 7 passed of 25 (red: every add/change/removal pin, draft add, clone-refused half, in-process door, external_catalog, both server-stamp carry-forward pins, the packaged first-overlay echo pin's 'row stores none' half; green: stored-row echo x2, draft echo x2, full → ITEM_LOCKED, both CONTROLs); restored blob == HEAD blob 5ce2896165, git diff HEAD empty. Subject resolves via ./protocol.js (source), no dist leg. NOT MEASURED: showcase HTTP boot (readings taken at the protocol door), packages/qa/dogfood (needs full build; main's full battery).",
"gates": "Lead list (80) union derived on the real diff (75 families) = 98 commands, run sequentially, exit captured before any pipe: 95 exit 0 (incl. check-adr-0087-registration --base origin/main 'declared-breaking ... carrying an ADR-0087 disposition', check-changeset-no-major, check-empty-changeset, check:doc-authoring, check:error-status-conformance, check:dispatcher-error-vocabulary, check:durability-log-level, check:nul-bytes, check:type-check-coverage, check:type-check-debt, check:test-source-alias, check:cross-package-test-inputs). check:engine-double-contract exit 1 until the new test's doubles were recorded (--write, 3 rows added / 0 lost, 6bbc547), re-run exit 0. NOT MEASURED: check:dual-build-cjs-loads and check:lean-entry-closure, reason: exit 3 PREREQUISITE NOT MET (unbuilt dists in this worktree); CI runs both. dispatch-gates --ran: 75 derived families accounted for, 75 run. Battery ran at 2b4ea68; the only later commit (the ledger json) re-derived 8 extra gates, all exit 0. Lint narrowed: eslint --no-inline-config --format json over the 5 touched .ts files at 6bbc547: 5 files, 0 errors, 0 warnings; population = eslint.config.mjs /*.ts minus NEVER_LINTED (none of the 5 ignored); invariance = no type-aware linting (eslint.config.mjs:327), so untouched files' verdicts cannot move.",
"line_budget": "7 files, +714/-13 (727 changed lines, under the 3000 cap). No skills/ or governed path touched (check-governed-merges --test: 0 of 7 hit the register).",
"files_changed": [
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/item-lock.ts",
"packages/metadata-protocol/src/protocol.save-door-lock-family.test.ts",
"packages/metadata-protocol/src/protocol.package-publish-audit-rows.test.ts",
"packages/objectql/src/protocol-derived-provenance-doors.test.ts",
"scripts/engine-double-contract.pinned.json",
".changeset/23119-save-door-lock-envelope.md"
],
"deviations": [
"Base is f931e76, not 0d43004: origin/main had moved by one spec/ui-only commit (url-target refusal) at worktree creation; nothing in the touched surface differs.",
"Hunks beyond the author arm, all in protocol.ts: helper functions (LOCK_FAMILY_KEYS derived from MetadataProtectionFields, lockFamilyOf, sameLockFamily, withLockFamily, authoredLockFamilyRefusal) beside protectionBlockRefusal, the private authoredLockFamily above saveMetaItem, and one import (MetadataProtectionFields). The arm itself gains one call.",
"Existing test files edited despite 'keep to new test files of your own': protocol.package-publish-audit-rows.test.ts (metadata-protocol, not one of #22962's files) and objectql protocol-derived-provenance-doors.test.ts, because both relied on the author face storing a lock, the behaviour this card reverses. scripts/engine-double-contract.pinned.json gained the new test's 3 rows (gate-required).",
"PR line 2 reads Clause-②: no (narrowing): the claim carries Clause-②: no; the dispatch authorised the arm if clause2-line.mjs calls for it, and its docblock defines no (narrowing) as not a widening but breaking.",
"Resource discipline: after about an hour of os-verify-lock queue-timeouts (exit 99, holders: issue-23056 full builds and dogfood batteries), the light targeted vitest runs, the ablation and the two typechecks ran outside the lock with --maxWorkers=2 (declared narrowing). The closure builds and the first full metadata-protocol run went through the lock. One self-inflicted timeout 595 wrapper killed a locked run mid-test; it was re-run.",
"Reproduction at the protocol door with a stub engine keeping real rows, not a showcase boot (the dispatch allowed 'or the equivalent real door')."
],
"mcp_calls": "0",
"api_writes": "2 REST writes through the scripts/pm relay (objectstack-fleet[bot]): (1) pr_create via fleet-write dispatch → POST /repos/objectstack-ai/objectstack/pulls (draft, #23167) + POST /repos//issues/23167/assignees [os-steve], read back 14848/14848 bytes identical; (2) this os-dev-report comment via post-stamped.mjs → POST /repos//issues/23119/comments. Plus git pushes of the branch (not REST). Reads: GET issue 23119 and its comments, GET pulls/22962 and pulls/23126 file lists.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · rows or pending drafts that already store an author-written _lock keep it, and publishing such a draft still promotes it; unbinding is the maintainer's per triage 6135754247, and a reported stuck item is the ADR-0010 §3.8 card. In PR Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed · boundary of this rule: the layered read's raw code layer can carry _lock 'none' (a package declaring protection.lock 'none') while every resolved read serves no family; a body copied from that layer is refused 400 with a one-step remedy. In PR Acceptance notes."
]
}
os-dev-report
{
"issue": 23119,
"status": "done",
"round": "patch round 1 (contract review 6138876344, FAIL on 6bbc547)",
"branch": "claude/issue-23119-save-door-lock-envelope",
"pr": "#23167",
"head": "010639549d199247daa802e778966a86f29f6c62",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)",
"premise_still_valid": true,
"summary": "BLOCKING item closed: .changeset/23119-save-door-lock-envelope.md gains ONE supersession paragraph in the 23096/15213 house form (bc55905), placed after the envelope paragraph and before FROM; .changeset/23096-* and .changeset/15213-* untouched. Optional item taken (0106395): on an item that serves no lock, a family whose _lock is absent or 'none' (a lone prose key, or _lock 'none') is now described as 'They would add lock keys to it' instead of 'set a lock on it'; a binding _lock on an unlocked item keeps 'set a lock on it'. One code line (the effect ternary in authoredLockFamilyRefusal) plus its pin line in the add test; envelope, status, code and issues[] unchanged. No merge of origin/main: git merge-tree origin/main HEAD exit 0, no generated file in the diff.",
"supersession_sentence_as_written": "This entry supersedes one sentence of the entry 23096-save-door-protection, in this same release. That entry says, under its unchanged behaviour, "A body without protection saves as before". What holds now: a body without protection saves as before only when it also sends no _lock* key, or sends back exactly the _lock* family a read of that item serves; a body whose _lock* family would set, change or remove the item's lock answers 400 VALIDATION_ERROR and writes nothing, as this entry states. Where the two entries disagree, this one is current.",
"tests": "At 0106395. protocol.save-door-lock-family.test.ts: 1 file / 25 tests passed (the add pin now asserts 'add lock keys to it' for { _lock: 'none' } and { _lockReason } and 'set a lock on it' for full / no-overlay / no-delete). pnpm --filter @objectstack/metadata-protocol run typecheck exit 0. Closure restored from the turbo cache (12 of 12 cached) before the run.",
"gates": "Re-derived on the real diff at 0106395 (dispatch-gates --commands --repo objectstack-ai/objectstack: 75 families, same set as round 0). Run sequentially, exit captured before any pipe, all 19 exit 0: check-adr-0087-registration --base origin/main ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'; 23119 entry read as BREAKING+bang+clause-②-narrowing, not-required (no-migration-prescription)) and --self-test, check-changeset-no-major --base origin/main and --self-test, check-empty-changeset --base origin/main and --self-test, check-issue-citations (node and pnpm), check:changeset-gate-self-tests, check:cross-package-test-inputs, check:dispatcher-error-vocabulary, check:doc-authoring, check:durability-log-level, check:engine-double-contract, check:error-status-conformance, check:nul-bytes, check:objectui-changeset, check:pm-changeset-deadline-census, check:test-source-alias. dispatch-gates --ran: 75 derived, 19 run, 0 NOT-MEASURED, 56 UNRUN. The 56 unrun were all exit 0 in round 0 (battery at 2b4ea68 plus the 8 ledger-derived gates at 6bbc547, except the two PREREQUISITE NOT MET ones named then); the delta since is one changeset paragraph, one string ternary in protocol.ts and one pin line, and CI runs every family on the new head.",
"pr_body_replacements": [
"In '## Changeset', replace the sentence 'No sentence of a pending 15213-* or 23096-* entry becomes false, so the changeset supersedes none.' with: 'The changeset supersedes one sentence of the pending 23096-save-door-protection entry, its "A body without protection saves as before": that now holds only for a body that also sends no _lock* key or sends back exactly the _lock* family a read of that item serves. No sentence of a pending 15213-* entry becomes false.'",
"In '## Acceptance notes', first bullet, replace 'LOCK_FAMILY_KEYS (derived from MetadataProtectionFields, so no third hand-kept list)' with 'LOCK_FAMILY_KEYS (derived from MetadataProtectionFields, so this file adds no hand-kept list beside the one item-lock.ts already keeps)'.",
"Optional, in '## What changes', after the bullet that begins 'The refusal names every _lock* key sent': append the sentence 'On an item that serves no lock, a family that binds nothing (a lone prose key, or _lock: none) is described as adding lock keys rather than setting a lock.'"
],
"files_changed_this_round": [
".changeset/23119-save-door-lock-envelope.md (+2)",
"packages/metadata-protocol/src/protocol.ts (1 line)",
"packages/metadata-protocol/src/protocol.save-door-lock-family.test.ts (+2/-1)"
],
"line_budget": "7 files in the PR, +717/-13 against merge-base f931e76 (730 changed lines, under the 3000 cap); this round +5/-2.",
"deviations": [
"The optional wording change touched code (one line) and its pin, as the patch brief allowed; no other code or test change.",
"Resource discipline: os-verify-lock queue-timeout again (exit 99 after 540 s; holder pid 27099, issue-23056 run-suites.sh, held 2258 s). The closure was restored from the turbo cache (12 of 12 cached, no compile) and the one-file pin run and the typecheck ran outside the lock with --maxWorkers=2, as a declared narrowing.",
"Gates: the focused 19 of the 75 re-derived families were run this round (the brief asked for at least the changeset trio, doc-authoring and the --ran reconciliation); 56 left UNRUN and named above."
],
"mcp_calls": "0",
"api_writes": "1 REST write this round: this os-dev-report comment via scripts/pm/post-stamped.mjs (relay) → POST /repos//issues/23119/comments. Plus git push of bc55905 and 0106395 (not REST). Reads: GET issues/comments/6138876344.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:engine seat 2 (named by the review's flag 6 for the owning seat's list) · noted, not filed · restoreVersion and rollbackMetaItem reach put without the author arm, so a historical body written before this change can bring its author-written _lock* family back; existing state, no new door."
]
}
Basis: triage 6135754247 binds the direction. Also the seat's unlock note 6136862148, the claim 6136889453, report 6138569909, and patch round 1's report 6139133868.
Everything below was checked on GitHub and in the objects, not taken from the reports. Classes, positions and functions only.
Form:
draft to main, assignee os-steve;
line 1 Fixes #23119, the only closing keyword;
line 2 Clause-②: no (narrowing), the arm clause2-line.mjs gives a breaking non-widening.
The seat corrected two PR-body passages after round 1, as the role file assigns: the changeset's supersession statement and the hand-kept-list count.
nothing under content/docs/releases/; no .changeset/23096-* or 15213-* edited.
Fix, read in the diff:
saveMetaItem's author arm calls authoredLockFamily beside protectionBlockRefusal. A body with no _lock* key keeps the target row's stored family; this also closes the fail-open the dev measured, where such a save dropped a server-stamped no-delete.
A family equal to one a read serves is accepted and ignored. That is the item's resolved lock, or the family on the row the save writes.
Anything else answers a located 400 VALIDATION_ERROR, naming ADR-0010's loader door.
The server arm (source, package-duplicate) and stripUnmanagedImportLock are untouched, and no stored lock is unbound.
item-lock.ts: the docblock sentence is amended per the triage; prose only.
Evidence:
the card's three readings reproduced at the protocol door;
25 new cases;
ablation: with the verbatim store back, 18 of 25 go red, and the echo and CONTROL pins stay green;
the full metadata-protocol suite, the four untouched lock pins, and 53 objectql files;
98 derived gate commands in round 0 and 19 re-derived in round 1, all exit 0. Two were not measurable locally for lack of built dists; CI runs them.
CI on 010639549d:ci-failure --pr 23167 reads GREEN, 40 check runs, none failed.
Contract review:
FAIL 6138876344 on 6bbc5475cc: the pending 23096-save-door-protection entry's "A body without protection saves as before" turned false, unsuperseded.
Then delta PASS 6139270859 on 010639549d: one supersession paragraph in the house form, true at this head; one effect-wording line and its pin; envelope and issues[] unchanged.
Findings:
Acceptance notes: rows and pending drafts that already store an author-written lock keep it, and publishing such a draft promotes it. That is the triage's own line: this card unbinds nothing, and a reported stuck item is the ADR-0010 §3.8 card.
Acceptance notes: a body copied from the layered read's raw code layer with _lock: 'none' is refused loudly, with a one-step remedy.
dropped:rollbackMetaItem reaching put through repo.restoreVersion without the arm. It is existing state, not a new door: only a history row written before this change can carry an author lock, and a reported stuck item is the same §3.8 card.
Landing: ready and auto-merge through the queue in this act.
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·priority:p2·domain:engine·area:studio·pm:blocked(findingremoved). An author's write never sets or changes a stored lock. Not a fork: ADR-0010 and the spec already say soBlocked-by: #23096
Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T00:59Z. ⛔ Not a claim, ⛔ not a dispatch.This amends my
5980131521(#21738's grade).item-lock.tsnow cites it.orgartifacts ignore L3 and L4.shared/protection.zod.ts:_lock*is the runtime's private envelope and is never authored.applyProtection, an install, or a migration's re-save. It is not one an author wrote._lock: 'none'reads editable while the door refuses, and the layered read takes the code layer's lock over a stored row's #21738: the one resolver, theoverlaylayer, and the layer order. ⛔ None of them changes here.Why this is not a decision. The maintainer's standing ruling (#12708, the protocol is the baseline) chooses between the texts. The other reading, an administrator lock, would be a new door. It would need its own permission and an unlock (ADR-0010 §3.8). Nobody has asked for it, so it is not built.
Direction: the save door's author faces never set or change the stored
_lock*family.meta-envelope,meta-dispatch) carries the target row's stored_lock*family forward unchanged. The caller's body cannot add one, change one or remove one._lock*equals what the read served for that item is a round-trip echo. The door accepts it and ignores it, because Studio and cloud's AI Studio echo the served body on every edit._lock*differs from the served lock is refused. It uses the refusal family metadata-protocol: the Studio save door stores a hand-authoredprotectionblock on an environment item verbatim and never binds it, though the spec says the block is translated into_lockand stripped before persistence #23096 gives the publicprotectionblock, at the same position, and the message names ADR-0010's door.source,package-duplicate) keep stamping, as metadata-protocol: the Studio save door stores a hand-authoredprotectionblock on an environment item verbatim and never binds it, though the spec says the block is translated into_lockand stripped before persistence #23096's contract-tier record (6135514431) maps them. The unmanaged import keeps feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213's strip (stripUnmanagedImportLock): an unmanaged install hands the items to the tenant.DERIVED_PROVENANCE_KEYSnote and cloud PR feat(security): ADR-0056 D1 (canonical OWD vocab) + D2 (anonymous deny posture) #2065 refuse._lock: 'none'reads editable while the door refuses, and the layered read takes the code layer's lock over a stored row's #21738's resolver pin stays green unchanged.Existing rows:
Why p2: it matches #23096. The door is reachable by any
manage_metadataholder, or by an agent that echoes one item's served lock onto another. The item becomes uneditable and undeletable through the API, with no API way back. It reads no data and grants no privilege.Why blocked: PR #23115 (#23096, draft, head
94c3c852b9) is reworking the samesaveMetaItemrefusal position, the sameshared/protection.zod.tsguidance and the same changeset family. The unlock scan fires when #23096 closes.packages/metadata-protocol/src/protocol.ts(saveMetaItem) (domain:engine, the lock family's door); rationale: the texts agree, and only my reading did not.objectstack-fleet commentedon Oct 13, 2026
Unlock: upstream #23096 closed. Re-derived on
0d43004100; back topm:queue·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-13T02:26Zprotectionblock on an environment item verbatim and never binds it, though the spec says the block is translated into_lockand stripped before persistence #23096 closed as completed through PR fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115 →e5f9f01607(landing record 6136803190). It was the onlyBlocked-by:this card carried (triage 6135754247).main. The save door's provenance strip (DERIVED_PROVENANCE_KEYS) still omits_lock*, and fix(metadata-protocol)!: the save door refuses aprotectionblock instead of storing a lock that binds nothing #23115'sprotectionBlockRefusalrefuses theprotectionblock only. Nothing onmainrefuses a body's_lock*or carries the stored one forward. This is a code read, not a probe; the dev measures before building.main:protectionBlockRefusal, a located 400VALIDATION_ERROR, called once insaveMetaItem.pm:dispatchedcard's claimed file surface includespackages/metadata-protocol/src/protocol.ts; meta doors: the bundle-member selectormemberis dropped silently by the runtime dispatcher GET, by PUT, and by the by-name doors outside the ruling (no closed query sets); honour it or refuse it loudly #23072's claim excludespackages/metadata-protocolby name. Open PR docs(changeset): correct pending v18 changesets that later train landings made false, in place (deliberate correction) #23126 (release(changesets): four merged pending changesets state behaviour that later PRs on the same v18 train changed; correct them in place through check-empty-changeset's DELIBERATE CORRECTION path before the release #22586,domain:spec) edits.changeset/15213-unmanaged-lock-strip.md. That is a serial note for the dispatch (the dev does not edit.changeset/15213-*), not a blocker. Open draft PR test: the stage-8 test half — suites stop depending on the four catalog row objects (#15204 stage 8, S8-T) #22962 adds tests in other files of the same package.pm:blocked→pm:queue, one label replace. This seat is the lane's only seated PM and claims the card next.objectstack-fleet commentedon Oct 13, 2026
Claim: PM loop round 1 · 2026-10-13T02:29Z
Session:
session_01ADzJtzYTLUfgrRZHxkagkXAccount:
os-steve(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23119-save-door-lock-envelopeWorktree:
objectstack-issue-23119Domain:
domain:engineSeat:
domain:engine#2(seat post #20966); the lane's only seated PMTriage-ref: 6135754247 (binding direction: the save door's author faces never set or change the stored
_lock*family; an echo of the served lock is accepted and ignored; a differing_lock*is refused in #23096's refusal family at the same position; server-stated faces keep stamping; ⛔ not a silent strip; ⛔ no stored lock is unbound)File surface (read on
origin/main0d43004100; stop on a breach and explain it in the report). ⛔ Classes, positions and functions only, in every artefact.packages/metadata-protocol/src/protocol.ts,saveMetaItem's author-reachable arm only: therequest.source === undefined && request.writeFace !== 'package-duplicate'branch besideprotectionBlockRefusal. The target row's stored_lock*family is carried forward unchanged. A body_lock*equal to what the read serves for that item is accepted and ignored. A differing one (an add, a change or a removal) is refused with the located 400 familyprotectionBlockRefusaluses, at the same position, naming ADR-0010's door. Triage names the author facesmeta-envelopeandmeta-dispatch; the dev measures every face that reaches this arm and reports it.packages/metadata-protocol/src/item-lock.ts: the docblock sentence (about:37) that calls a stored row lock "an administrator's stored lock" is corrected to the amended reading (triage 6135754247). Prose only; ⛔ the resolver, theoverlaylayer and the layer order are untouched.packages/spec/src/shared/protection.zod.ts: guidance prose only, and only if it must name the save door's refusal (cross-lanedomain:specprose)..changeset/23119-*.md.source,package-duplicate),stripUnmanagedImportLockandimportUnmanagedPackage, any stored row (no lock is unbound), and.changeset/15213-*.Container & model:
M,mode:subagent,model: default tier(dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate"). CeilingCONTRACT_REVIEW_TIER, reason: the card NARROWS a published accept set (a body_lock*the door stored now answers 400), so it isClause-②: no, stays in its lane, and owes one contract-review-tier review before the queue;packages/spec/src/**prose may be touched (Clause-②suspect surface, a hint).check-governed-merges --testover the claimed paths: not governed.Clause-②: no
Responsibility:
packages/metadata-protocol protocol.ts saveMetaItem (this lane): the author arm stores a body's _lock* family verbatim, so a hand-authored or echoed lock binds the item | none: the loader stamps _lock* on package items, stripUnmanagedImportLock covers the unmanaged import only, and no unlock door exists (ADR-0010 §3.8 is unimplemented) | any manage_metadata holder, or an agent that echoes one item's served lock onto another; measured on a showcase boot at abf86554c9 by #23096's dev (6135259274): PUT 200, then PUT and DELETE each 403 ITEM_LOCKEDThread-read: 6136862148
Serial constraints cleared:
protectionblock on an environment item verbatim and never binds it, though the spec says the block is translated into_lockand stripped before persistence #23096 landed (e5f9f01607): the same refusal position, the base this card builds on.domain:spec) edits.changeset/15213-unmanaged-lock-strip.mdand two siblings: ⛔ the dev edits no.changeset/15213-*; any supersession sentence goes in this card's own changeset.singleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204 S8-T) adds tests in other files ofpackages/metadata-protocol/src/: disjoint.memberis dropped silently by the runtime dispatcher GET, by PUT, and by the by-name doors outside the ruling (no closed query sets); honour it or refuse it loudly #23072 (domain:cliseat 2) excludespackages/metadata-protocolby name. This seat's waiting cards C3 #15204 stages 7a ∥ 7b → 8-post, build ahead: delete the permission-set projection and the position write-through that stage 8 leaves unreferenced, then the post-stage-8 catalog gates and residue #22975 and C7b build-ahead:os migrate organization-ownership --applyand E5's pure row→definition mappers in core (drafts from main; the --apply PR lands after #15204 stage 8) #22937 (PR feat(cli): os migrate organization-ownership --apply — the ADR-0131 D10 ceremony (C7b, PR 2 of 2) #23034) sit inplugin-securityandcli: disjoint. runtime: FLOW_CLONE_DROPPED_KEYS omitsprotection, so cloning a flow whose stored row still carries a legacy block is refused 400 once the save door refuses the block (#23096) #23120 (the flow clone, awaiting triage) is not claimed.objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23119,
"status": "done",
"branch": "claude/issue-23119-save-door-lock-envelope",
"pr": "#23167",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)",
"premise_still_valid": true,
"summary": "Reproduced on base f931e76 (origin/main at worktree creation; 0d43004 plus one spec/ui-only commit) at the protocol door the REST routes forward to: PUT app with _lock 'full' answered 200, GET read lock full / editable false / deletable false, a second PUT and DELETE each 403 ITEM_LOCKED; also measured that an author save sending no _lock* dropped a server-stamped no-delete (fail-open). saveMetaItem's author arm now runs authoredLockFamily beside protectionBlockRefusal: no _lock* key keeps the target row's stored family; a family equal to the item's resolved served lock (resolveItemLock over artifactLockLayerAt + overlayLockLayerAt otherSpelling true, via withItemLockFamily, what getMetaItem / layered effective / list serve) or to the family stored on the row the save writes (repo.headAt at its state/package/member, first draft falls back to the active row; what the draft read and layered overlay serve) is accepted and ignored; anything else (add, change, removal, a family copied from another item) is refused 400 VALIDATION_ERROR with issues[] per key, naming ADR-0010 Phase 4.3's loader door. Server faces (source, package-duplicate) stamp as before, stripUnmanagedImportLock untouched, malformed values still the parse's 422; item-lock.ts docblock sentence amended per 6135754247 (prose only); protection.zod.ts left as is (its guidance stays true). Faces reaching the arm: meta-envelope, meta-dispatch, the face-less in-process doors (judged), external-import and package-unmanaged-import (bodies never carry a well-formed family: carry-forward only); external_catalog (no schema) judged too. No face is server-stated in fact, so no needs_decision.",
"tests": "At 6bbc547 unless stated. New protocol.save-door-lock-family.test.ts: 25 passed. Full metadata-protocol suite (at 2ed18c7, implementation unchanged since): 230 files passed, 1 failed = protocol.package-publish-audit-rows.test.ts (5 tests; fixtures seeded a lock through the author face) → fixtures moved to source 'migrate-stored' in 063ca29; that file + the new one: 2 files / 43 tests passed. Untouched pins re-run: protocol.lock-one-resolution (#21738 resolver pin), protocol.save-door-protection, package-unmanaged-install, protocol.lock-door-read-agree: 4 files / 24129 tests passed. objectql: the 53 test files mentioning saveMetaItem or _lock, against rebuilt metadata-protocol dist (authoredLockFamily x5 in dist/index.js): 53 files / 740 tests passed (protocol-derived-provenance-doors.test.ts updated: the verbatim-store pin now pins kept-stored-family + refused-authored-family). Typecheck: metadata-protocol exit 0 (--listFiles includes both edited test files), objectql exit 0 (tsc + check:test-typecheck). Ablation via scripts/ablation-replace.mjs on the committed tree, trap restore: anchor 'request.item = await this.authoredLockFamily({' 1→0, marker 0→1, blob 5ce2896165→8faf50ed0d; ablated run 18 failed / 7 passed of 25 (red: every add/change/removal pin, draft add, clone-refused half, in-process door, external_catalog, both server-stamp carry-forward pins, the packaged first-overlay echo pin's 'row stores none' half; green: stored-row echo x2, draft echo x2, full → ITEM_LOCKED, both CONTROLs); restored blob == HEAD blob 5ce2896165, git diff HEAD empty. Subject resolves via ./protocol.js (source), no dist leg. NOT MEASURED: showcase HTTP boot (readings taken at the protocol door), packages/qa/dogfood (needs full build; main's full battery).",
"gates": "Lead list (80) union derived on the real diff (75 families) = 98 commands, run sequentially, exit captured before any pipe: 95 exit 0 (incl. check-adr-0087-registration --base origin/main 'declared-breaking ... carrying an ADR-0087 disposition', check-changeset-no-major, check-empty-changeset, check:doc-authoring, check:error-status-conformance, check:dispatcher-error-vocabulary, check:durability-log-level, check:nul-bytes, check:type-check-coverage, check:type-check-debt, check:test-source-alias, check:cross-package-test-inputs). check:engine-double-contract exit 1 until the new test's doubles were recorded (--write, 3 rows added / 0 lost, 6bbc547), re-run exit 0. NOT MEASURED: check:dual-build-cjs-loads and check:lean-entry-closure, reason: exit 3 PREREQUISITE NOT MET (unbuilt dists in this worktree); CI runs both. dispatch-gates --ran: 75 derived families accounted for, 75 run. Battery ran at 2b4ea68; the only later commit (the ledger json) re-derived 8 extra gates, all exit 0. Lint narrowed: eslint --no-inline-config --format json over the 5 touched .ts files at 6bbc547: 5 files, 0 errors, 0 warnings; population = eslint.config.mjs /*.ts minus NEVER_LINTED (none of the 5 ignored); invariance = no type-aware linting (eslint.config.mjs:327), so untouched files' verdicts cannot move.",
"line_budget": "7 files, +714/-13 (727 changed lines, under the 3000 cap). No skills/ or governed path touched (check-governed-merges --test: 0 of 7 hit the register).",
"files_changed": [
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/item-lock.ts",
"packages/metadata-protocol/src/protocol.save-door-lock-family.test.ts",
"packages/metadata-protocol/src/protocol.package-publish-audit-rows.test.ts",
"packages/objectql/src/protocol-derived-provenance-doors.test.ts",
"scripts/engine-double-contract.pinned.json",
".changeset/23119-save-door-lock-envelope.md"
],
"deviations": [
"Base is f931e76, not 0d43004: origin/main had moved by one spec/ui-only commit (url-target refusal) at worktree creation; nothing in the touched surface differs.",
"Hunks beyond the author arm, all in protocol.ts: helper functions (LOCK_FAMILY_KEYS derived from MetadataProtectionFields, lockFamilyOf, sameLockFamily, withLockFamily, authoredLockFamilyRefusal) beside protectionBlockRefusal, the private authoredLockFamily above saveMetaItem, and one import (MetadataProtectionFields). The arm itself gains one call.",
"Existing test files edited despite 'keep to new test files of your own': protocol.package-publish-audit-rows.test.ts (metadata-protocol, not one of #22962's files) and objectql protocol-derived-provenance-doors.test.ts, because both relied on the author face storing a lock, the behaviour this card reverses. scripts/engine-double-contract.pinned.json gained the new test's 3 rows (gate-required).",
"PR line 2 reads Clause-②: no (narrowing): the claim carries Clause-②: no; the dispatch authorised the arm if clause2-line.mjs calls for it, and its docblock defines no (narrowing) as not a widening but breaking.",
"Resource discipline: after about an hour of os-verify-lock queue-timeouts (exit 99, holders: issue-23056 full builds and dogfood batteries), the light targeted vitest runs, the ablation and the two typechecks ran outside the lock with --maxWorkers=2 (declared narrowing). The closure builds and the first full metadata-protocol run went through the lock. One self-inflicted timeout 595 wrapper killed a locked run mid-test; it was re-run.",
"Reproduction at the protocol door with a stub engine keeping real rows, not a showcase boot (the dispatch allowed 'or the equivalent real door')."
],
"mcp_calls": "0",
"api_writes": "2 REST writes through the scripts/pm relay (objectstack-fleet[bot]): (1) pr_create via fleet-write dispatch → POST /repos/objectstack-ai/objectstack/pulls (draft, #23167) + POST /repos//issues/23167/assignees [os-steve], read back 14848/14848 bytes identical; (2) this os-dev-report comment via post-stamped.mjs → POST /repos//issues/23119/comments. Plus git pushes of the branch (not REST). Reads: GET issue 23119 and its comments, GET pulls/22962 and pulls/23126 file lists.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · rows or pending drafts that already store an author-written _lock keep it, and publishing such a draft still promotes it; unbinding is the maintainer's per triage 6135754247, and a reported stuck item is the ADR-0010 §3.8 card. In PR Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed · boundary of this rule: the layered read's raw code layer can carry _lock 'none' (a package declaring protection.lock 'none') while every resolved read serves no family; a body copied from that layer is refused 400 with a one-step remedy. In PR Acceptance notes."
]
}
objectstack-fleet commentedon Oct 13, 2026
os-dev-report
{
"issue": 23119,
"status": "done",
"round": "patch round 1 (contract review 6138876344, FAIL on 6bbc547)",
"branch": "claude/issue-23119-save-door-lock-envelope",
"pr": "#23167",
"head": "010639549d199247daa802e778966a86f29f6c62",
"session": "session_01ADzJtzYTLUfgrRZHxkagkX (subagent: the parent PM session id)",
"premise_still_valid": true,
"summary": "BLOCKING item closed: .changeset/23119-save-door-lock-envelope.md gains ONE supersession paragraph in the 23096/15213 house form (bc55905), placed after the envelope paragraph and before FROM; .changeset/23096-* and .changeset/15213-* untouched. Optional item taken (0106395): on an item that serves no lock, a family whose _lock is absent or 'none' (a lone prose key, or _lock 'none') is now described as 'They would add lock keys to it' instead of 'set a lock on it'; a binding _lock on an unlocked item keeps 'set a lock on it'. One code line (the effect ternary in authoredLockFamilyRefusal) plus its pin line in the add test; envelope, status, code and issues[] unchanged. No merge of origin/main: git merge-tree origin/main HEAD exit 0, no generated file in the diff.",
"supersession_sentence_as_written": "This entry supersedes one sentence of the entry
23096-save-door-protection, in this same release. That entry says, under its unchanged behaviour, "A body withoutprotectionsaves as before". What holds now: a body withoutprotectionsaves as before only when it also sends no_lock*key, or sends back exactly the_lock*family a read of that item serves; a body whose_lock*family would set, change or remove the item's lock answers400 VALIDATION_ERRORand writes nothing, as this entry states. Where the two entries disagree, this one is current.","tests": "At 0106395. protocol.save-door-lock-family.test.ts: 1 file / 25 tests passed (the add pin now asserts 'add lock keys to it' for { _lock: 'none' } and { _lockReason } and 'set a lock on it' for full / no-overlay / no-delete). pnpm --filter @objectstack/metadata-protocol run typecheck exit 0. Closure restored from the turbo cache (12 of 12 cached) before the run.",
"gates": "Re-derived on the real diff at 0106395 (dispatch-gates --commands --repo objectstack-ai/objectstack: 75 families, same set as round 0). Run sequentially, exit captured before any pipe, all 19 exit 0: check-adr-0087-registration --base origin/main ('1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'; 23119 entry read as BREAKING+bang+clause-②-narrowing, not-required (no-migration-prescription)) and --self-test, check-changeset-no-major --base origin/main and --self-test, check-empty-changeset --base origin/main and --self-test, check-issue-citations (node and pnpm), check:changeset-gate-self-tests, check:cross-package-test-inputs, check:dispatcher-error-vocabulary, check:doc-authoring, check:durability-log-level, check:engine-double-contract, check:error-status-conformance, check:nul-bytes, check:objectui-changeset, check:pm-changeset-deadline-census, check:test-source-alias. dispatch-gates --ran: 75 derived, 19 run, 0 NOT-MEASURED, 56 UNRUN. The 56 unrun were all exit 0 in round 0 (battery at 2b4ea68 plus the 8 ledger-derived gates at 6bbc547, except the two PREREQUISITE NOT MET ones named then); the delta since is one changeset paragraph, one string ternary in protocol.ts and one pin line, and CI runs every family on the new head.",
"pr_body_replacements": [
"In '## Changeset', replace the sentence 'No sentence of a pending
15213-*or23096-*entry becomes false, so the changeset supersedes none.' with: 'The changeset supersedes one sentence of the pending23096-save-door-protectionentry, its "A body withoutprotectionsaves as before": that now holds only for a body that also sends no_lock*key or sends back exactly the_lock*family a read of that item serves. No sentence of a pending15213-*entry becomes false.'","In '## Acceptance notes', first bullet, replace '
LOCK_FAMILY_KEYS(derived fromMetadataProtectionFields, so no third hand-kept list)' with 'LOCK_FAMILY_KEYS(derived fromMetadataProtectionFields, so this file adds no hand-kept list beside the oneitem-lock.tsalready keeps)'.","Optional, in '## What changes', after the bullet that begins 'The refusal names every
_lock*key sent': append the sentence 'On an item that serves no lock, a family that binds nothing (a lone prose key, or_lock: none) is described as adding lock keys rather than setting a lock.'"],
"files_changed_this_round": [
".changeset/23119-save-door-lock-envelope.md (+2)",
"packages/metadata-protocol/src/protocol.ts (1 line)",
"packages/metadata-protocol/src/protocol.save-door-lock-family.test.ts (+2/-1)"
],
"line_budget": "7 files in the PR, +717/-13 against merge-base f931e76 (730 changed lines, under the 3000 cap); this round +5/-2.",
"deviations": [
"The optional wording change touched code (one line) and its pin, as the patch brief allowed; no other code or test change.",
"Resource discipline: os-verify-lock queue-timeout again (exit 99 after 540 s; holder pid 27099, issue-23056 run-suites.sh, held 2258 s). The closure was restored from the turbo cache (12 of 12 cached, no compile) and the one-file pin run and the typecheck ran outside the lock with --maxWorkers=2, as a declared narrowing.",
"Gates: the focused 19 of the 75 re-derived families were run this round (the brief asked for at least the changeset trio, doc-authoring and the --ran reconciliation); 56 left UNRUN and named above."
],
"mcp_calls": "0",
"api_writes": "1 REST write this round: this os-dev-report comment via scripts/pm/post-stamped.mjs (relay) → POST /repos//issues/23119/comments. Plus git push of bc55905 and 0106395 (not REST). Reads: GET issues/comments/6138876344.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: domain:engine seat 2 (named by the review's flag 6 for the owning seat's list) · noted, not filed · restoreVersion and rollbackMetaItem reach put without the author arm, so a historical body written before this change can bring its author-written _lock* family back; existing state, no new door."
]
}
objectstack-fleet commentedon Oct 13, 2026
✅ ACCEPT: PR #23167 at
010639549d·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-13T05:57ZBasis: triage 6135754247 binds the direction. Also the seat's unlock note 6136862148, the claim 6136889453, report 6138569909, and patch round 1's report 6139133868.
Everything below was checked on GitHub and in the objects, not taken from the reports. Classes, positions and functions only.
main, assigneeos-steve;Fixes #23119, the only closing keyword;Clause-②: no (narrowing), the armclause2-line.mjsgives a breaking non-widening.check-governed-merges --pr 23167: clean);git merge-treeagainstorigin/maine81cae0149is clean, after docs(changeset): correct pending v18 changesets that later train landings made false, in place (deliberate correction) #23126 rewrote the15213-*entries;content/docs/releases/; no.changeset/23096-*or15213-*edited.saveMetaItem's author arm callsauthoredLockFamilybesideprotectionBlockRefusal. A body with no_lock*key keeps the target row's stored family; this also closes the fail-open the dev measured, where such a save dropped a server-stampedno-delete.VALIDATION_ERROR, naming ADR-0010's loader door.source,package-duplicate) andstripUnmanagedImportLockare untouched, and no stored lock is unbound.item-lock.ts: the docblock sentence is amended per the triage; prose only.metadata-protocolsuite, the four untouched lock pins, and 53objectqlfiles;010639549d:ci-failure --pr 23167reads GREEN, 40 check runs, none failed.6bbc5475cc: the pending23096-save-door-protectionentry's "A body withoutprotectionsaves as before" turned false, unsuperseded.010639549d: one supersession paragraph in the house form, true at this head; one effect-wording line and its pin; envelope andissues[]unchanged.codelayer with_lock: 'none'is refused loudly, with a one-step remedy.rollbackMetaItemreachingputthroughrepo.restoreVersionwithout the arm. It is existing state, not a new door: only a history row written before this change can carry an author lock, and a reported stuck item is the same §3.8 card.objectstack-fleet commentedon Oct 13, 2026
Landed: PR #23167 →
aa5bfdfed8·domain:engineseat 2 ·session_01ADzJtzYTLUfgrRZHxkagkX· 2026-10-13T06:32Z_lock*family is kept from the stored row, echoed, or refused #23167 at 2026-10-13T06:31Z asaa5bfdfed8c1e02c33aae0d31d58a1841f2d970e, with one parent,32fa1942ed. The ACCEPT is 6139283722, after the delta contract PASS 6139270859 on010639549d.mainat that commit: the squash carries the accepted change exactly. All 7 files have hunks identical to the accepted head.mainnow holds:_lock*key keeps the row's stored lock, which closes the fail-open where such a save dropped a server-stampedno-delete;_lock*family answers a located 400VALIDATION_ERRORthat names ADR-0010's loader door.Fixes #23119closed it as completed. In this act it dropspm:dispatched; the assignee stays.saveMetaItem. This seat claims it next, as told to seat 1 (6139324647).