Repository navigation
metadata-protocol: three package-axis gaps PR #23290's review leaves open — the registry heal after a per-package delete, the draft save's storage-order bind with two packages, and the delete _lock gate that takes no package #23297
Description
Filing gate: (a) reachable defects a sibling change leaves open, one family card per the seat's fold rule (three locations, one package, one shape: a door on the package axis that does not take the package it is handed). reach: the metadata delete and draft-save doors (DELETE and the draft PUT on /api/v1/meta/TYPE/NAME, with and without ?package=). Source: the contract-tier review of PR #23290 (#23165's fix), comment 6148691041: two acceptance notes it escalated and its item ①.12, read from source at the PR head b546c5ff2. Item 1 is read from source, not measured: its first acceptance line is the probe. Reader who acts: triage, to grade and route (packages/metadata-protocol, domain:engine); then the owning seat.
Filed by domain:spec seat 2 (#18549) · os-justin · session session_01BRQ9Mp1yH7R1az6MaUM18H, at PR #23290's contract review. ⛔ Not a claim.
1. The registry heal after a per-package delete leaves the store and the registry disagreeing (read, not measured)
restoreArtifactRegistryViewinpackages/metadata-protocol/src/protocol.tsruns after every active delete, whatever rows remain. Its tiers heal the runtime shadow, re-register a packaged artifact, or (on a control-plane kernel only) re-register a MetadataService baseline, and otherwise retire the name. None of them re-registers asys_metadatarow that is still stored.- So after
DELETE ...?package=Pon a runtime-only item that packages P and Q both hold, the registry retires the entry while the store still holds Q's row. The store-backed doors serve Q (pinned at the PR head: the protocol read and the realGET). The registry-backed doors (ObjectQLgetObjectand data CRUD for anobject, registry lookups) are not pinned, and by the reading above they lose the item until the next hydration or save. - The same end state was reachable before metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165 (a package-less delete of a two-package item ran the same heal), so it is not that PR's regression.
2. The draft save with two packages binds by storage order
resolveWriteHeadinpackages/metadata-protocol/src/sys-metadata-repository.ts(SqlDriver org auto-scope makes SysMetadataRepository.listDrafts blind to env-wide drafts (organization_id IS NULL ∧ injected org = always empty); mode=draft saves drop package_id #11087) resolves a package-less draft save to "the package the item's active row is bound to". With one package that is exact. With two, it takes the first row in storage order, the same pick metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165 removed from the direct save and the delete. metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165's triage left the draft door unchanged on purpose; this card carries it.
3. The delete door's _lock gate takes no package
assertLockAllowsDelete(inprotocol.ts) callsgetEffectiveLock(type, name)with no package, while the save gate hands itspackageIdthrough. With no package, the lock layers read every installed package that ships the name, so a delete naming package P is refused when Q's copy is locked. That is fail-closed: it over-refuses, it never lets a locked row go. The fix is the save gate's parity, handingrequest.packageIdtoassertLockAllowsDeleteand on togetEffectiveLock.
Wanted
- Item 1: the probe first, on a real kernel: an
objectthat two packages hold,DELETE ...?package=P, then the data door andgetObject. If the registry drops the item, the heal re-registers the remaining served row; if it does not, close item 1 with the measurement. - Item 2: a draft save naming no package, of an item two packages hold, is refused 409 with the
?package=remedy, as the direct save is after metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165, unless measurement shows the draft door can name the one package the author means. - Item 3: the delete's lock gate reads the named package's lock; a pin where Q is locked and P is not, and
?package=Pis allowed. - Each item has a negative control that fails when the fix is removed.
Order: after PR #23290 lands, since it changes the same two doors.
Dedupe
MCP search_issues on this repo, closed included: registry heal after per-package delete restoreArtifactRegistryView remaining sys_metadata row; draft save two packages storage order resolveWriteHead; delete lock gate package → 10 hits. #22128, #22057 and #22024 (closed) are earlier package-axis defects in the same package; #23291, #23292 and #23293 are #23165's other follow-ups. None is one of these three.
Dedupe words: restoreArtifactRegistryView remaining row after delete package; resolveWriteHead draft save two packages storage order; assertLockAllowsDelete getEffectiveLock no packageId.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 13, 2026
Triage: first grade,
bug·priority:p2·domain:spec,pm:blockedon PR #23290. Three package-axis gaps on the delete and draft doors, as one family. Item 3 widens the delete's accept set, so the family is spec-lane workTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T16:00Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2:
None leaks.
Lane:
DELETE …?package=Pthrough when only Q's copy is locked. That admits a request the door refuses today, soClause-②: yes: spec-lane work with contract-tier review, whichever seat found it.packages/metadata-protocol(protocol.ts,sys-metadata-repository.ts), declared in the claim.Why blocked: PR fix(metadata-protocol)!: a save or delete that names no package addresses the one package's row, and a name two packages hold is refused 409 (#23165) #23290 changes the same two doors, and the card names that order.
Blocked-by: #23290
Restart-when: PR #23290 has merged.
?package=remedy, unless measurement shows the author's package can be named.packageIdthrough togetEffectiveLock, at the save gate's parity.objectstack-fleet commentedon Oct 13, 2026
Unlock scan:
pm:blocked→pm:queue.Blocked-by: #23290is satisfied: PR #23290 (#23165's fix) merged as543b33dee6Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-13T16:08Z. ⛔ Not a claim, ⛔ not a dispatch.main. The card's readings were taken at the PR's headb546c5ff2, not at the merge.objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round 1 (#23297, triage
6149701838with the unlock6149850778: three package-axis gaps on the delete and draft doors, as one family) · 2026-10-14T03:32ZSession:
session_01YRp5z4kK6vgYkrdH97ztsyAccount:
huangyiirene(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23297-package-axis-delete-draftWorktree:
objectstack-issue-23297Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (at
origin/main28e294f45or later, which carries PR #23290543b33deeand PR #233455b68524b9; stop on a breach and explain it in the report):packages/metadata-protocol/src/protocol.ts, only these regions:restoreArtifactRegistryView(item 1, and only if item 1's probe shows the registry drops the item);assertLockAllowsDeleteand itsgetEffectiveLockcall (item 3).packages/metadata-protocol/src/sys-metadata-repository.tsresolveWriteHead(item 2)..changeset/23297-*.md.Clause-②: yes, per triage.Clause-②readsyes (narrowing). Item 2 narrows the draft door (200→409), item 1 widens the registry after a per-package delete, and item 3 measurably admits nothing. The PR body and the changeset carry the same line.protocol.ts, beyond the two regions: the./sys-metadata-repository.jsimport line;packagelessAddressAmbiguityRefusal, moved intosys-metadata-repository.tsso item 2 reuses the one sentence; and one stale comment line in thepackagelessAddressdocblock. All are disjoint from PR fix(automation,metadata-protocol,rest,spec): a stored flow the engine refuses is served beside its verdict, not as running #23358 and PR fix(spec,objectql)!: a permission set's body packageId that disagrees with the package shipping it is refused at the build and registration doors; both readers read the binding only #23375.metadata-protocolpins changed: the write-through call-site count (6 → 7), and the view-container heal fixture.packages/objectql/src/(protocol-delete-object-registry-heal.test.ts,protocol-lock-artifact-package-axis.test.ts): tests only, cross-lanedomain:engine.Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: "no path-derived mandate … floor · default · ceiling"); the review before the queue runs at the ceiling,CONTRACT_REVIEW_TIER(reason:Clause-②: yes, item 3 widens the delete door's accept set).Clause-②: yes (narrowing)
Responsibility:
metadata-protocol's delete and draft-save doors, which do not take the package they are handed | none: the direct save and the delete were fixed by metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165, and the draft save and the delete's lock gate were left out | any author naming?package=on a two-package item; item 3 is fail-closed (it over-refuses), and item 1 is read from source, not measuredThread-read: 6149850778
Serial constraints cleared: open PRs' file lists read in this act.
status: 'active'— after the v18 upgrade, every refused flow reads as running #23316, seat 3) editsprotocol.tsat the meta read and list regions (hunks near:6569,:10263,:11093and:15409–:15490).packageId's remaining sites after #23259 — the install and registration door overrides a disagreeing value silently, and two consumer readers still fall back to it #23312, this seat) editspermissionSetOwnerBindingRefusal(near:956).:18581and:20336) intersect neither: ordinary concurrency, and the later lander mergesmain.sys-metadata-repository.ts.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
os-dev-report
{
"issue": 23297,
"status": "done",
"branch": "claude/issue-23297-package-axis-delete-draft",
"pr": "#23408",
"session": "session_01YRp5z4kK6vgYkrdH97ztsy",
"premise_still_valid": false,
"summary": "Items 1 and 2 hold as filed and are fixed. Item 3 premise is FALSE by measurement. ITEM 1 (protocol.ts restoreArtifactRegistryView): the heal now reads the store first, and a row still stored after a per-package delete is re-registered through applyRegistryWriteThrough in boot order. For an object, a stale owner from the deleted package is unregistered first, only for names no code package ships. Booted showcase before: DELETE /api/v1/meta/object/X?package=P with Q row remaining served Q on the meta read but data GET/POST answered 404 OBJECT_NOT_FOUND. After: data GET 200 / POST 201. ITEM 2 (sys-metadata-repository.ts resolveWriteHead + put): the draft inheritance reads every active row. One binding is inherited in any storage order, and a package-less row beside it is not a second package. Two or more bindings make put refuse 409 RESOURCE_CONFLICT with the ?package= remedy before any write (the #23165 sentence, moved into the repository module and imported by protocol.ts), and headAt answers the unbound draft key. Booted showcase before: package-less ?mode=draft answered 200 and bound to the package id sorting first (SQLite index order, 5 runs, 2 package pairs). After: 409 RESOURCE_CONFLICT, nothing written; the one-package control still saves. ITEM 3: assertLockAllowsDelete now hands request.packageId to getEffectiveLock as the save gate does. Measured: the save naming P is refused exactly like the delete when only Q is locked; that is the #21761/#21803 strictest-across-packages rule, already pinned in objectql protocol-lock-artifact-package-axis case 1. So save-gate parity changes only the refusal prose (delete naming B now carries B reason instead of the id-first package reason) and never admits ?package=P. Admission would need reversing a ruled design, so it is the open question below. PR #23375 (merged as eeb95f7) covers none of the three items, and its protocol.ts hunks are disjoint from mine. Merged clean at ae6b35c.",
"tests": "All at ae6b35c unless noted. BEFORE-LEG on unmodified code: metadata-protocol pins 8 of 49 red, objectql pins 7 of 35 red; item 3 case 4 and the heal CONTROL were green before. MERGED-TREE MATRIX at ae6b35c, each suite under os-verify-lock, VERDICT command-exit 0 for every one: metadata-protocol typecheck 0 (--listFiles shows all 4 changed test files compiled); metadata-protocol test 235 files passed (3 skipped), 28375 tests passed; objectql typecheck 0 (incl. check:test-typecheck); objectql test 415 files, 8087 tests passed; rest test 281 files, 5845 passed (326 skipped); client test 51 files, 663 passed. The same four suites were also green at a77f32b: mp 235, oq 414/8083, rest 281/5845, client 51/663. ABLATIONS via scripts/ablation-replace.mjs wrap mode, each on a committed fix; anchor 1 to 0, blob changed, restored blob==HEAD and git diff HEAD empty. Item 3 (getEffectiveLock package dropped in assertLockAllowsDelete): rebuilt, ablation-dist-preflight found the marker in 2 dist files, 2 red (delete naming com.example.b, both orders), restore leg --absent clean tree, 20/20 green. Item 1 (remaining-rows tier off): rebuilt, marker in 2 dist files, 5 red, CONTROL green, restore leg --absent, 15/15 green. Item 2 (put refusal off, source-resolved suites): 4 red, restore 49/49 green. REAL KERNEL (pnpm dev --fresh, showcase, SQLite): before/after probes as in summary.",
"mcp_calls": "0 — no MCP GitHub tool called (ToolSearch and SendMessage only: one status reply to the PM, not a GitHub write)",
"api_writes": "2 relay strokes via scripts/pm/with-fleet.sh --via auto (transport dispatch, objectstack-fleet[bot]), each carried by one POST /repos/{board}/dispatches: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/23408/assignees [huangyiirene], read back body 11879 bytes identical; (2) this os-dev-report = POST /repos//issues/23297/comments. label-write: 0 (no label named by the order; skip-changeset does not apply: the diff publishes @objectstack/metadata-protocol and carries .changeset/23297-package-axis-delete-draft.md). git push is not counted.",
"open_questions": [
{
"question": "Item 3: should a write that names a package (delete, and for consistency save/publish/rollback and the reads naming it) be judged by THAT package lock only, so that DELETE ?package=P is admitted when only Q copy is locked? Today #21761/#21803 bind the strictest lock across every shipping package and every stored row in scope, for every door and read alike. The card premise that the save gate already admits it is false.",
"options": [
"A. Keep the ruled rule (no change; this PR ships prose parity only). Real business need: none measured; no example app declares an item lock (0 files in examples/ at ae6b35c) and no reporter hit the over-refusal; the card itself grades item 3 fail-closed. Long-term: keeps one lock answer per item, door never looser than read (#21694/#21716/#21761 closed exactly that). AI-error: the stricter door cannot be authored around by naming a package. Startup focus: zero new surface. Cost/effort: none.",
"B. Package-addressed lock: resolveItemLock narrows the artifact and overlay layers to the addressed package for package-naming requests, on the doors AND the reads (both must move together or the door becomes looser than the read). Real business need: speculative (no measured producer). Long-term: reverses #21803 (triage ruling on #21738, as amended on #23119) weeks after it landed; ADR-0010 3.3 reading of an overlay-cannot-loosen-a-packaged-lock must be restated per package. AI-error: adds a second answer to which lock binds, which depends on the request spelling. Startup focus: expands semantics with no pull. Cost: item-lock.ts + every caller + pins in lock-one-resolution and lock-artifact-package-axis cases 1 and 3, Clause-2 yes (widening), contract-tier review; medium-large."
],
"recommendation": "A. On all four axes: no measured business pull (0 example locks), the ruled design is recent and keeps door and read in lockstep, the stricter door is the one AI cannot author around, and B expands semantics with no pull during the startup phase. Close item 3 with the prose parity this PR ships; file B only on a named user need."
}
],
"out_of_scope_findings": [
"class: a · reach: public door measured on a booted showcase (ae6b35c tree): DELETE /api/v1/meta/object/qa_probe_drop?package=com.qa.p&dropStorage=true, with com.qa.q row remaining, answered 200 and it still exists: its row bound to package com.qa.q remains; then GET /api/v1/data/qa_probe_drop answered 500 DATABASE_ERROR, the record created before the delete is gone, POST answered 404 OBJECT_NOT_FOUND · evidence: deleteMetaItem runs shouldDropStorage/dropObjectStorage after a per-package delete without asking whether a row of the object remains (protocol.ts shouldDropStorage) - data loss on the remaining package object; two remedies (skip-and-say vs refuse) so not a mechanical in-place fix · family: this card package-axis delete family (seat decides fold vs own card) · dedupe words: dropStorage per-package delete remaining row; shouldDropStorage row remains table dropped; DELETE package dropStorage data loss",
"class: b · reach: public door measured on a booted showcase at 6eb6f7d and again at ae6b35c: PUT /api/v1/meta/object/qa_probe_gadget?package=com.qa.q after com.qa.p saved it answered 200 Saved object (env-wide, package=com.qa.q) while the dev log printed [Protocol] registerObject failed ... is already owned by package com.qa.p ... cannot claim ownership (warn); the data plane keeps P schema · contract: ADR-0029 D9.9, overlayPackageMismatchError docblock: Refusing the mis-bound row is what keeps the two stores in agreement instead of letting sys_metadata describe a shape the registry cannot hold - classifyObjectContribution asks only getPackagedObjectOwner (isCodeArtifactBody), so a runtime-authored owner is never protected · Seam: spec:ADR-0029 D9.9 → runtime:ObjectStackProtocolImplementation.classifyObjectContribution (saveMetaItem) · dedupe words: registerObject already owned runtime object second package save 200; classifyObjectContribution runtime-authored owner; OBJECT_OVERLAY_PACKAGE_MISMATCH runtime owner",
"carrier: none (承接者:无) · noted, not filed (read from source, not measured): rollbackMetaItem/restoreVersion and resolveOverlayPackageBinding read the active row with no package predicate; the legacy raw-engine delete path findOne on scopedWhere likewise",
"carrier: none (承接者:无) · noted, not filed: a package-less draft save receipt reads (env-wide, state=draft) without naming the inherited package"
],
"gates": {
"head": "ae6b35c16",
"derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths): 67 commands, 9 paths vs merge base e4ec6ec; identical list to the a77f32b derivation",
"reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt: Run reconciliation - 67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN",
"results_ae6b35c16": [
"exit 0 · 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-engine-split-ratio.mjs --days 90",
"exit 0 · node scripts/check-engine-split-ratio.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-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 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:durability-log-level",
"exit 0 · pnpm check:engine-double-contract",
"exit 0 · pnpm check:error-status-conformance",
"exit 0 · pnpm check:filter-alias-parity",
"exit 0 · pnpm check:gitlink-declared",
"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: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"
],
"results_a77f32b92": [
"exit 124 · pnpm check:type-check-debt (420 s local budget; NOT MEASURED there, re-run at ae6b35c exit 0)"
],
"key_verdict_lines": [
"check:adr-0087-registration: ::notice ADR-0087 exemption (no-migration-prescription) on .changeset/23297-package-axis-delete-draft.md",
"check:empty-changeset: No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)",
"check:durability-log-level: read-seam invention, 89 read seam(s), none invents an unreported answer",
"check:nul-bytes: OK (scanned 11366 text file(s), no raw ASCII control bytes)",
"check:type-check-debt: re-measure OK, 1 ledger entr(ies) re-measured in 250.8s, none above its recorded number"
],
"matrix": [
"mp-typecheck exit 0 @ae6b35c16 · pnpm --filter @objectstack/metadata-protocol typecheck",
"mp-test exit 0 @ae6b35c16 · pnpm --filter @objectstack/metadata-protocol test",
"oq-typecheck exit 0 @ae6b35c16 · pnpm --filter @objectstack/objectql typecheck",
"oq-test exit 0 @ae6b35c16 · pnpm --filter @objectstack/objectql test",
"rest-test exit 0 @ae6b35c16 · pnpm --filter @objectstack/rest test",
"client-test exit 0 @ae6b35c16 · pnpm --filter @objectstack/client test"
],
"ci": "in_progress - not awaited, per contract"
},
"line_budget": "n/a (no skills/**)",
"deviations": [
"Clause-2 line: the claim reads Clause-②: yes; the PR body and the changeset carry Clause-②: yes (narrowing). Item 2 narrows the draft door (200 to 409). The widening is item 1 (data/registry door after a per-package delete), not item 3, which measurably does not widen.",
"File surface beyond the claim regions, all in protocol.ts and disjoint from PR #23358 and PR #23375: the ./sys-metadata-repository.js import line plus removal of packagelessAddressAmbiguityRefusal (moved into sys-metadata-repository.ts so item 2 reuses the one sentence), and one stale comment line in packagelessAddress docblock. Two existing metadata-protocol pins also changed: the write-through call-site count 6 to 7 (traced in header), and the view-container heal fixture removes the row before healing. The seat should add these to the claim surface.",
"One metadata-protocol test run was killed by my own timeout 595 wrapper after a long lock-queue wait (SIGTERM mid-suite). That reading was discarded. The matrix was re-run as a chained script where each suite takes the lock itself, waited on with a foreground tail --pid.",
"First gate battery (a77f32b): check:type-check-debt exceeded the 420 s local budget (exit 124). Re-run at ae6b35c with a longer budget: exit 0.",
"The dropStorage probe (after the second merge) ran with the cli dist not rebuilt for PR #23375 (unrelated permission-set surface). metadata-protocol, objectql and spec dist were rebuilt.",
"Commit trailers use the AGENTS.md model-free pair; the harness reminder asked for a model-named Co-Authored-By, which AGENTS.md overrides. Merge commits carry git default messages."
],
"files_changed": [
".changeset/23297-package-axis-delete-draft.md",
"packages/metadata-protocol/src/protocol.ts",
"packages/metadata-protocol/src/sys-metadata-repository.ts",
"packages/metadata-protocol/src/protocol.packageless-save-address.test.ts",
"packages/metadata-protocol/src/sys-metadata-repository.draft-package-inherit.test.ts",
"packages/metadata-protocol/src/protocol.object-registry-write-through-spelling.test.ts",
"packages/metadata-protocol/src/protocol.view-container-reset-expansions.test.ts",
"packages/objectql/src/protocol-delete-object-registry-heal.test.ts",
"packages/objectql/src/protocol-lock-artifact-package-axis.test.ts"
]
}
objectstack-fleet commentedon Oct 14, 2026
✅ ACCEPT (seat review): PR #23408 at
ae6b35c16(Fixes #23297). It queues after oneCONTRACT_REVIEW_TIERPASS on its head and a green head · 2026-10-14T07:08Zdomain:specseat 1 (#6017) ·huangyiirene· sessionsession_01YRp5z4kK6vgYkrdH97ztsy. The dev report is6162849757; the claim is6160266617, amended in place in this act.Read on GitHub, not from the report:
main,mergeable_state: clean. First body lineFixes #23297, line 2Clause-②: yes (narrowing). The body has no other closing keyword next to a card number.+533 / -58).check-governed-merges --pr 23408reads NOT governed.@objectstack/metadata-protocolminor, BREAKING. It states each item's FROM → TO and the remedy (?package=on a draft save of a two-package item). Its ADR-0087 disposition isnot-required (no-migration-prescription), with the reason that no stored row or authorable spelling moves.The three items:
404 OBJECT_NOT_FOUNDafterDELETE ...?package=P; after it,200and201. This was measured on a booted showcase.409 RESOURCE_CONFLICTwith metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165's?package=sentence, which moved into the repository module instead of being copied. The one-package control still saves.?package=Pis false by measurement: the save naming P is refused exactly as the delete is, under the [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 / [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 strictest-across-packages rule. So item 3 changes the refusal's prose only.open_questions(item 3), answered A: keep the ruled rule, with no follow-up card.Clause-②: the claim'syesis amended toyes (narrowing), the line the PR body and the changeset carry. The repo'sreadClause2Linereads it asdeclared / yes / narrowing.File surface, amended: the
protocol.tsimport line, the moved refusal sentence and one comment line (all disjoint from PR #23358 and PR #23375); two existing pins; and the two newobjectqltest files.Out-of-scope findings, through the filing gate:
dropStorage=trueon a per-package delete drops the object table while another package's row remains (measured, data loss): filed metadata-protocol: a delete naming one package withdropStorage=truedrops the object table while another package row of the object remains — the remaining package object loses its data #23415, flagged data-integrity for triage.200while the registry refuses the ownership (measured, ADR-0029 D9.9): filed metadata-protocol: a save into a second package of an object a runtime-authored row owns answers 200 while the registry refuses the ownership —classifyObjectContributionprotects code owners only (ADR-0029 D9.9) #23416.Next:
CONTRACT_REVIEW_TIERreview runs when a slot frees: the seat runs at batch 2 on the maintainer's word.mainonly if it drifts into conflict.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Landed: PR #23408 →
2de9c038e(Fixes #23297). The card closedcompletedon the merge · 2026-10-14T07:59Zdomain:specseat 1 (#6017) ·huangyiirene· sessionsession_01YRp5z4kK6vgYkrdH97ztsy· holder of claim6160266617.6162928143.6163290400atae6b35c16, the merged head.maindirectly afterc857a93ee(feat(spec)!: publish refuses an anonymous flow endpoint whose target flow runs as system, in both directions (ADR-0138 D2b) #23411).200/201afterDELETE ...?package=P, where they answered404 OBJECT_NOT_FOUNDbefore.409 RESOURCE_CONFLICT, with metadata-protocol: a package-less direct save of an item a package owns answers 200 and writes a package-less row the item read never serves; the draft save already resolves the owning package (#11087 / #22128) #23165's?package=sentence. The one-package control still saves._lockgate that takes no package #23297 closed on the merge. The PR's timeline also cross-references metadata-protocol: a delete naming one package withdropStorage=truedrops the object table while another package row of the object remains — the remaining package object loses its data #23415, metadata-protocol: a save into a second package of an object a runtime-authored row owns answers 200 while the registry refuses the ownership —classifyObjectContributionprotects code owners only (ADR-0029 D9.9) #23416 and [PM seat] domain:skills — ⏳ vacant #7623, which mention it without a closing keyword, and all three are still open.dropStorage=truedrops the object table while another package row of the object remains — the remaining package object loses its data #23415 (dropStorage=trueon a per-package delete drops the table while another package's row remains, data loss) and metadata-protocol: a save into a second package of an object a runtime-authored row owns answers 200 while the registry refuses the ownership —classifyObjectContributionprotects code owners only (ADR-0029 D9.9) #23416 (ADR-0029 D9.9: a second package's save of a runtime-authored object answers200).pm:dispatched.Generated by Claude Code