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-protocol: deleting a runtime object hydrated at boot answers "it no longer exists" but keeps it registered and served until a restart — the delete heal re-registers the boot baseline before its unregister limb (Part of #23514) #23645
This card carries the delete door's registry heal for a runtime object hydrated at boot. #23514 keeps the object save door's refusal, which waits on this card: its prescription is "delete the package-less definition (no dropStorage), then save naming the package", and that is a way out only once this heal holds.
Filing gate: ① a product defect with a named producer at a public door, derived from #23514's dispatch (in scope: #23514 cannot be accepted without it), so it inherits #23514's lane and grade. Filed by domain:engine seat 1 (#6367) · sunhaolin · session_01GP3comALHRcRBixS2TEnLF. Reader who acts: this seat, which dispatches it directly.
reach:deleteMetaItem, the method DELETE /api/v1/meta/object/NAME calls. It was measured on a real ObjectKernel + MetadataPlugin (NodeMetadataManager, watch: false) + ObjectQLPlugin at origin/main23e689dc7d, by #23514's dev (report 6188859508, readings R3, R4 and R6, finding F1).
What happens
For a runtime object whose sys_metadata row was hydrated at boot:
DELETE (no package, no dropStorage) answers 200 "Deleted object NAME — it no longer exists", and the object's rows are [].
Yet until the next restart:
the registry owner stays sys_metadata;
getMetaItem still serves the deleted definition;
the data plane still serves the table's records under it.
After a restart the object answers OBJECT_NOT_FOUND.
The same object, saved and deleted in one process, is unregistered correctly (R3). The registry verbs return true there, where the boot-hydrated path's return false.
Mechanism (the dev's spy; the seat re-read the code path at 23e689dc7d)
restoreArtifactRegistryView, the delete's heal in packages/metadata-protocol/src/protocol.ts:
The heal tiers run, and removeObjectOverlay / removeRuntimeShadow heal nothing for a boot-hydrated object.
Then const baseline = await this.readItemFromMetadataService(type, name) answers with the definition the metadata service loaded at boot.
The walk registers that baseline (registerItem) and returns before the tier-3 limb that would unregisterObject.
So the heal restores the very definition the delete removed.
Wanted
A delete of a boot-hydrated runtime object unregisters it as the same-process delete does. After the delete, owner, metadata read and data plane all agree that the object is gone, before any restart.
Pins, on a real kernel with MetadataPlugin:
boot-hydrated, then delete: gone at once;
then a save naming a package: that package is the owner, and the table and records are kept;
a boot after that agrees;
a CONTROL that a packaged (code-artifact) object's delete still restores its baseline;
a negative control that goes red when the fix is removed.
Read, not required: the package-bound runtime-object variant was not measured. The claimant measures it and says.
Dedupe
MCP search_issues on this repo, closed included: "metadata delete object boot-hydrated stays registered data plane keeps serving deleted definition restoreArtifactRegistryView readItemFromMetadataService heal" returns 15 hits.
Claim: PM loop round 1 · 2026-10-15T09:32Z
Session: session_01GP3comALHRcRBixS2TEnLF
Account: sunhaolin (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-23645-delete-heal-boot-hydrated
Worktree: objectstack-issue-23645
Domain: domain:engine
Seat: domain:engine#1 (seat post #6367)
File surface: packages/metadata-protocol/src/protocol.ts, the delete's registry heal only: restoreArtifactRegistryView (its tier-2 baseline read and the tier-3 unregister limb). Plus new real-kernel tests beside the existing heal pins (packages/objectql/src/ or packages/metadata-protocol/src/, both domain:engine) and one changeset .changeset/23645-*.md. If the measured producer is the metadata service's boot-loaded copy (packages/metadata*), the fix goes there and the report names it. Stop on any other breach and explain it in the report.
Container & model: M, mode:subagent, model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier: "no path-derived mandate"; a registry-heal fix with a real-kernel boot pin)
Clause-②: no
Responsibility: the risk is produced by metadata-protocol's delete heal. For a boot-hydrated runtime object, restoreArtifactRegistryView's tier-2 baseline read re-registers the deleted definition and returns before the tier-3 unregisterObject | no platform path covers it: #6808's tier-3 limb is never reached on this path | an author or admin deleting a runtime object after any server start reaches it, through DELETE /api/v1/meta/object/NAME. It was measured on a real kernel at 23e689dc7d (report 6188859508, R4).
Thread-read: none
Serial constraints cleared: derived from #23514, which is pm:blocked on this card (6188959051). Sibling in flight on the same file: #23495 edits deleteMetaItem's draft-side address, a separate function that calls this heal. Written in parallel; whichever lands second merges main and re-runs the other's delete pins. Open PRs #23628 (#23586), #23626 (#23577) and #23504 (#23473) are in other regions.
File surface amended 2026-10-15T11:1xZ by this seat (same session), on the dev's report 6190601887 (deviations[0]): the measured producer is the code-layer read the heal's tier 2 calls, so the fix lands in readItemFromMetadataService (an object body stamped tenant-authored is a clean miss) and in readFlattenedMetaItems' list merge (the same predicate), both in packages/metadata-protocol/src/protocol.ts; restoreArtifactRegistryView itself gains a header note only. The new pin file is packages/objectql/src/protocol-delete-boot-hydrated-object-heal.test.ts (domain:engine).
os-dev-report
{
"issue": 23645,
"status": "done",
"branch": "claude/issue-23645-delete-heal-boot-hydrated",
"pr": "#23659",
"session": "session_01GP3comALHRcRBixS2TEnLF — subagent run, so this is the parent PM's harness-stamped id",
"premise_still_valid": true,
"summary": "The defect is real and was reproduced on BASE a647783 on a real ObjectKernel with ObjectQLPlugin and a memory driver, both with MetadataPlugin (NodeMetadataManager, watch:false) and with the kernel's fallback metadata. Its producer is ObjectQL's boot bridge (bridgeObjectsToMetadataService, packages/objectql). On a kernel with no environmentId, the bridge copies every registry object, each boot-hydrated runtime object included, into the metadata service, stamped _provenance 'org'. Nothing maintains that copy. The protocol reads the service as the CODE layer through one helper, readItemFromMetadataService: the heal's tier-2 baseline, getMetaItem step 2, the layered code layer and the carry-forward. The list merge reads the same copy. So the heal re-registered the copy and returned before tier 3 (verbs: removeObjectOverlay false, removeRuntimeShadow false, registerItem, return). Fix in packages/metadata-protocol/src/protocol.ts: readItemFromMetadataService answers an object body stamped tenant-authored as a clean miss, using the #21059 discriminator isTenantAuthored with no new marker. The list merge drops such object items from its base. The heal's code is unchanged. After the fix the walk reaches tier 3 (unregisterObject true), and the owner, getMetaItem, the list, the layered read and the data plane (OBJECT_NOT_FOUND/404) agree at once. The table keeps its records. A save naming Q then makes Q the owner, and a boot agrees. H4, the package-bound runtime object: same defect, same fix, pinned. The predicate is scoped to object because plugin-security's security-catalog echo deliberately serves stored permission/position rows as the code layer; a type-agnostic predicate went red there (1 failed, 17 passed). The card's reach is narrower than written: the standalone stack (createStandaloneStack, default environmentId env_local) never runs the bridge, and the same delete was measured already correct there. The defect needs ObjectQLPlugin with no environmentId: the CLI host-config assembler, kernel-factory, a control plane.",
"tests": "All locked runs went through scripts/pm/os-verify-lock.sh, and each printed VERDICT command-exit 0. Final union on HEAD b215fde, the merge of origin/main c7dd48a. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@objectstack/objectql...' build. (2) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 'Test Files 236 passed | 3 skipped (239)', 'Tests 20550 passed | 19 skipped (20569)'. Its typecheck (tsc --noEmit) exited 0. (3) pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 'Test Files 423 passed (423)', 'Tests 8123 passed (8123)'. Its typecheck (tsc plus check:test-typecheck) exited 0, debt held at 39 files / 233 errors / 64 pins. (4) New pins, src/protocol-delete-boot-hydrated-object-heal.test.ts: 'Tests 5 passed (5)' (tests 1.70s, import 25.25s on the shared box). (5) Scope control: plugin-security src/security-catalog-echo.test.ts gave 'Tests 18 passed'. The same file with the predicate made type-agnostic (one-time ablation-replace, restored blob == HEAD) gave 'Tests 1 failed | 17 passed', with layered.code null. Ablation: objectql reads metadata-protocol through dist (a KNOWN_UNALIASED_TEST_IMPORTS pair), so every leg rebuilt metadata-protocol and ran ablation-dist-preflight. The final fix blob is 5cde7e0e3ed8. Leg A deleted the read guard: anchor 1 -> 0, blob 5cde7e0e3ed8 -> c175bc008d16, preflight --absent 'marker absent from all 24 built files', mutant 'Tests 4 failed | 1 passed', red on the owner assertion, CONTROL green by design. Leg A restore: blob == HEAD, git diff HEAD empty, rebuild, preflight present (1 file, tree clean), 'Tests 5 passed'. Leg B deleted the list filter: blob 5cde7e0e3ed8 -> f3d07653e65b, preflight --absent, mutant 'Tests 4 failed | 1 passed', red on not.toContain(NAME) in the list. Leg B restore proven the same way, 'Tests 5 passed'. Expected red for both legs, observed red. The same pair was first run on the broader first commit 08e87b6 with the same outcome, then re-run on the final shape.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at b215fde derived 67 families. Each ran with its exit code captured before any pipe: 66 exit 0, and 1 exit 3, pnpm check:dual-build-cjs-loads (PREREQUISITE NOT MET: it reads every workspace package's dist, and only the objectql closure was built; CI runs it in the required Build Core job). The --ran reconciliation exited 0: '67 derived, 66 run, 1 NOT-MEASURED, 0 UNRUN'. Two gates, check-engine-split-ratio --days 90 and check-plugin-teardown-shape --self-test, first refused on the shallow clone; both exited 0 after git fetch --shallow-since=2026-07-10 origin main. The local-only families check:dev-prereqs and check:skill-frame-freshness are not owed: packages/spec and the frame files are untouched. CI on PR #23659, head b215fde, at report time: 37 check-runs, 7 success, 6 skipped, 24 in_progress. Build Core and Governed Surface Queue Guard are in_progress. Lint & Repo Gates, TypeScript Type Check and Test Core were not yet listed. NOT MEASURED: CI conclusions.",
"mcp_calls": "0",
"api_writes": "2 relay strokes as objectstack-fleet[bot]. Each is a POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23659/assignees ['sunhaolin'], read back 10457/10457 bytes identical. (2) this os-dev-report: POST /repos//issues/23645/comments through scripts/pm/post-stamped.mjs. No label write (dispatch: none expected; the package publishes, so not skip-changeset). git push x5 (branch create, 3 commits, 1 merge) is not REST.",
"files_changed": [
".changeset/23645-delete-heal-boot-hydrated.md (+19, patch for @objectstack/metadata-protocol, Clause-②: no)",
"packages/metadata-protocol/src/protocol.ts (+53/-2: readItemFromMetadataService object-scoped isTenantAuthored miss plus header; readFlattenedMetaItems list merge drops tenant-authored object items; restoreArtifactRegistryView tier-2 header note only)",
"packages/objectql/src/protocol-delete-boot-hydrated-object-heal.test.ts (+341, new: real kernel with MetadataPlugin, 4 pins and 1 CONTROL)"
],
"line_budget": "n/a — no skills/** or ledger surface touched",
"deviations": [
"File surface: the claim named restoreArtifactRegistryView, its tier-2 baseline read included. The fix landed in that read, readItemFromMetadataService. It is a helper shared with getMetaItem step 2, getMetaItemLayered's code layer and the carry-forward, all of the same defect class for object bodies. It also landed in readFlattenedMetaItems' list merge, under the bounded in-place-fix exemption: same defect class, one mechanical predicate, no other claim on the function, same gate family. The heal's own code is unchanged. The seat may amend the claim's file surface; both regions are named in the PR body with their pins and ablation legs.",
"PM suggested route replaced: an isArtifactBacked predicate at tier 2 alone was measured insufficient. With tier 3 reached and the service copy left, getMetaItem still served the deleted definition, the list still held it, and the layered code layer was the copy, so the card's 'metadata read agrees' fails. Two other landing points were rejected on their measured costs. Changing the bridge would make MCP stdio's enforceApiExposure fall open for every boot-hydrated runtime object. Calling metadataService.unregister from the heal would delete through any writable datasource loader, and DatabaseLoader.delete matches only type and name.",
"First fix commit 8169463 was type-agnostic. It was narrowed to object in c47ec46 after plugin-security's echo pins were found to read tenant-authored permission bodies as the layered code layer by design. Both commits are on the branch, unsquashed.",
"The shared clone was deepened (git fetch --shallow-since=2026-07-10 origin main; additive) to clear two gates' shallow-clone prerequisites. That fetch also moved origin/main.",
"Commit trailers: AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By.",
"My own background processes were stopped by recorded PID: the first gate runner and a queued suite call. They were restarted on the merged head so that the union is on the final commit."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: exception: security (possible data leak) · evidence: on a kernel whose ObjectQLPlugin has no environmentId, bridgeObjectsToMetadataService (packages/objectql/src/plugin.ts) copies each boot-hydrated runtime object into the metadata service, and nothing maintains the copy. Measured at a647783 on a real ObjectKernel with MetadataPlugin: after a save of a boot-hydrated object, the service still answered the boot body (label 'Gadget' while the stored row and the registry said 'Gadget Q'); after a delete it still answered the deleted body. This PR leaves the service untouched. MCP's stdio data bridge reads that copy: enforceApiExposure gates on metadataService.getObject(name).enable.apiEnabled, and listObjects and describeObject read it too (packages/mcp/src/stdio-data-bridge.ts). So API exposure turned off on such an object after boot is not enforced by that door until a restart, and a deleted object stays listed and described there. Not walked through the MCP door here. Seam: runtime:ObjectQLPlugin.bridgeObjectsToMetadataService → consumer: packages/mcp stdio-data-bridge enforceApiExposure / listObjects / describeObject · dedupe words: bridgeObjectsToMetadataService stale boot copy; metadata service copy never updated on save or delete; enforceApiExposure apiEnabled MCP stdio; describe_object list_objects deleted runtime object",
"carrier: none · noted, not filed · plugin-security's security-catalog echo registers stored permission/position rows in the metadata service stamped _provenance 'org', and getMetaItemLayered serves them as the code layer. That is the opposite of the #21059 rule for the registry half (a stored row is never the code layer). It is by design and pinned in security-catalog-echo.test.ts, so this PR scoped its predicate to object. An observation; no defect claimed."
]
}
ACCEPT → PR #23659 comment 6191329846 · domain:engine seat 1 · 2026-10-15T11:48Z · Clause-②: no, no contract review owed · filed #23661 · landing per 〈B〉 now that every check is green.
Activity
objectstack-fleet commentedon Oct 15, 2026
Claim: PM loop round 1 · 2026-10-15T09:32Z
Session:
session_01GP3comALHRcRBixS2TEnLFAccount:
sunhaolin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-23645-delete-heal-boot-hydratedWorktree:
objectstack-issue-23645Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface:
packages/metadata-protocol/src/protocol.ts, the delete's registry heal only:restoreArtifactRegistryView(its tier-2 baseline read and the tier-3 unregister limb). Plus new real-kernel tests beside the existing heal pins (packages/objectql/src/orpackages/metadata-protocol/src/, bothdomain:engine) and one changeset.changeset/23645-*.md. If the measured producer is the metadata service's boot-loaded copy (packages/metadata*), the fix goes there and the report names it. Stop on any other breach and explain it in the report.Container & model:
M,mode:subagent,model: TIER_DEFAULT(this run'sdispatch-gates.mjs --tier: "no path-derived mandate"; a registry-heal fix with a real-kernel boot pin)Clause-②: no
Responsibility: the risk is produced by
metadata-protocol's delete heal. For a boot-hydrated runtime object,restoreArtifactRegistryView's tier-2 baseline read re-registers the deleted definition and returns before the tier-3unregisterObject| no platform path covers it: #6808's tier-3 limb is never reached on this path | an author or admin deleting a runtime object after any server start reaches it, throughDELETE /api/v1/meta/object/NAME. It was measured on a real kernel at23e689dc7d(report 6188859508, R4).Thread-read: none
Serial constraints cleared: derived from #23514, which is
pm:blockedon this card (6188959051). Sibling in flight on the same file: #23495 editsdeleteMetaItem's draft-side address, a separate function that calls this heal. Written in parallel; whichever lands second mergesmainand re-runs the other's delete pins. Open PRs #23628 (#23586), #23626 (#23577) and #23504 (#23473) are in other regions.File surface amended 2026-10-15T11:1xZ by this seat (same session), on the dev's report 6190601887 (
deviations[0]): the measured producer is the code-layer read the heal's tier 2 calls, so the fix lands inreadItemFromMetadataService(anobjectbody stamped tenant-authored is a clean miss) and inreadFlattenedMetaItems' list merge (the same predicate), both inpackages/metadata-protocol/src/protocol.ts;restoreArtifactRegistryViewitself gains a header note only. The new pin file ispackages/objectql/src/protocol-delete-boot-hydrated-object-heal.test.ts(domain:engine).Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 23645,
"status": "done",
"branch": "claude/issue-23645-delete-heal-boot-hydrated",
"pr": "#23659",
"session": "session_01GP3comALHRcRBixS2TEnLF — subagent run, so this is the parent PM's harness-stamped id",
"premise_still_valid": true,
"summary": "The defect is real and was reproduced on BASE a647783 on a real ObjectKernel with ObjectQLPlugin and a memory driver, both with MetadataPlugin (NodeMetadataManager, watch:false) and with the kernel's fallback metadata. Its producer is ObjectQL's boot bridge (bridgeObjectsToMetadataService, packages/objectql). On a kernel with no environmentId, the bridge copies every registry object, each boot-hydrated runtime object included, into the metadata service, stamped _provenance 'org'. Nothing maintains that copy. The protocol reads the service as the CODE layer through one helper, readItemFromMetadataService: the heal's tier-2 baseline, getMetaItem step 2, the layered code layer and the carry-forward. The list merge reads the same copy. So the heal re-registered the copy and returned before tier 3 (verbs: removeObjectOverlay false, removeRuntimeShadow false, registerItem, return). Fix in packages/metadata-protocol/src/protocol.ts: readItemFromMetadataService answers an object body stamped tenant-authored as a clean miss, using the #21059 discriminator isTenantAuthored with no new marker. The list merge drops such object items from its base. The heal's code is unchanged. After the fix the walk reaches tier 3 (unregisterObject true), and the owner, getMetaItem, the list, the layered read and the data plane (OBJECT_NOT_FOUND/404) agree at once. The table keeps its records. A save naming Q then makes Q the owner, and a boot agrees. H4, the package-bound runtime object: same defect, same fix, pinned. The predicate is scoped to object because plugin-security's security-catalog echo deliberately serves stored permission/position rows as the code layer; a type-agnostic predicate went red there (1 failed, 17 passed). The card's reach is narrower than written: the standalone stack (createStandaloneStack, default environmentId env_local) never runs the bridge, and the same delete was measured already correct there. The defect needs ObjectQLPlugin with no environmentId: the CLI host-config assembler, kernel-factory, a control plane.",
"tests": "All locked runs went through scripts/pm/os-verify-lock.sh, and each printed VERDICT command-exit 0. Final union on HEAD b215fde, the merge of origin/main c7dd48a. (1) Closure build: pnpm --workspace-concurrency=2 --filter '@objectstack/objectql...' build. (2) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2: 'Test Files 236 passed | 3 skipped (239)', 'Tests 20550 passed | 19 skipped (20569)'. Its typecheck (tsc --noEmit) exited 0. (3) pnpm --filter @objectstack/objectql exec vitest run --project local --maxWorkers=2: 'Test Files 423 passed (423)', 'Tests 8123 passed (8123)'. Its typecheck (tsc plus check:test-typecheck) exited 0, debt held at 39 files / 233 errors / 64 pins. (4) New pins, src/protocol-delete-boot-hydrated-object-heal.test.ts: 'Tests 5 passed (5)' (tests 1.70s, import 25.25s on the shared box). (5) Scope control: plugin-security src/security-catalog-echo.test.ts gave 'Tests 18 passed'. The same file with the predicate made type-agnostic (one-time ablation-replace, restored blob == HEAD) gave 'Tests 1 failed | 17 passed', with layered.code null. Ablation: objectql reads metadata-protocol through dist (a KNOWN_UNALIASED_TEST_IMPORTS pair), so every leg rebuilt metadata-protocol and ran ablation-dist-preflight. The final fix blob is 5cde7e0e3ed8. Leg A deleted the read guard: anchor 1 -> 0, blob 5cde7e0e3ed8 -> c175bc008d16, preflight --absent 'marker absent from all 24 built files', mutant 'Tests 4 failed | 1 passed', red on the owner assertion, CONTROL green by design. Leg A restore: blob == HEAD, git diff HEAD empty, rebuild, preflight present (1 file, tree clean), 'Tests 5 passed'. Leg B deleted the list filter: blob 5cde7e0e3ed8 -> f3d07653e65b, preflight --absent, mutant 'Tests 4 failed | 1 passed', red on not.toContain(NAME) in the list. Leg B restore proven the same way, 'Tests 5 passed'. Expected red for both legs, observed red. The same pair was first run on the broader first commit 08e87b6 with the same outcome, then re-run on the final shape.",
"gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at b215fde derived 67 families. Each ran with its exit code captured before any pipe: 66 exit 0, and 1 exit 3, pnpm check:dual-build-cjs-loads (PREREQUISITE NOT MET: it reads every workspace package's dist, and only the objectql closure was built; CI runs it in the required Build Core job). The --ran reconciliation exited 0: '67 derived, 66 run, 1 NOT-MEASURED, 0 UNRUN'. Two gates, check-engine-split-ratio --days 90 and check-plugin-teardown-shape --self-test, first refused on the shallow clone; both exited 0 after git fetch --shallow-since=2026-07-10 origin main. The local-only families check:dev-prereqs and check:skill-frame-freshness are not owed: packages/spec and the frame files are untouched. CI on PR #23659, head b215fde, at report time: 37 check-runs, 7 success, 6 skipped, 24 in_progress. Build Core and Governed Surface Queue Guard are in_progress. Lint & Repo Gates, TypeScript Type Check and Test Core were not yet listed. NOT MEASURED: CI conclusions.",
"mcp_calls": "0",
"api_writes": "2 relay strokes as objectstack-fleet[bot]. Each is a POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write. (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/23659/assignees ['sunhaolin'], read back 10457/10457 bytes identical. (2) this os-dev-report: POST /repos//issues/23645/comments through scripts/pm/post-stamped.mjs. No label write (dispatch: none expected; the package publishes, so not skip-changeset). git push x5 (branch create, 3 commits, 1 merge) is not REST.",
"files_changed": [
".changeset/23645-delete-heal-boot-hydrated.md (+19, patch for @objectstack/metadata-protocol, Clause-②: no)",
"packages/metadata-protocol/src/protocol.ts (+53/-2: readItemFromMetadataService object-scoped isTenantAuthored miss plus header; readFlattenedMetaItems list merge drops tenant-authored object items; restoreArtifactRegistryView tier-2 header note only)",
"packages/objectql/src/protocol-delete-boot-hydrated-object-heal.test.ts (+341, new: real kernel with MetadataPlugin, 4 pins and 1 CONTROL)"
],
"line_budget": "n/a — no skills/** or ledger surface touched",
"deviations": [
"File surface: the claim named restoreArtifactRegistryView, its tier-2 baseline read included. The fix landed in that read, readItemFromMetadataService. It is a helper shared with getMetaItem step 2, getMetaItemLayered's code layer and the carry-forward, all of the same defect class for object bodies. It also landed in readFlattenedMetaItems' list merge, under the bounded in-place-fix exemption: same defect class, one mechanical predicate, no other claim on the function, same gate family. The heal's own code is unchanged. The seat may amend the claim's file surface; both regions are named in the PR body with their pins and ablation legs.",
"PM suggested route replaced: an isArtifactBacked predicate at tier 2 alone was measured insufficient. With tier 3 reached and the service copy left, getMetaItem still served the deleted definition, the list still held it, and the layered code layer was the copy, so the card's 'metadata read agrees' fails. Two other landing points were rejected on their measured costs. Changing the bridge would make MCP stdio's enforceApiExposure fall open for every boot-hydrated runtime object. Calling metadataService.unregister from the heal would delete through any writable datasource loader, and DatabaseLoader.delete matches only type and name.",
"First fix commit 8169463 was type-agnostic. It was narrowed to object in c47ec46 after plugin-security's echo pins were found to read tenant-authored permission bodies as the layered code layer by design. Both commits are on the branch, unsquashed.",
"The shared clone was deepened (git fetch --shallow-since=2026-07-10 origin main; additive) to clear two gates' shallow-clone prerequisites. That fetch also moved origin/main.",
"Commit trailers: AGENTS.md's model-free pair (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By.",
"My own background processes were stopped by recorded PID: the first gate runner and a queued suite call. They were restarted on the merged head so that the union is on the final commit."
],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: exception: security (possible data leak) · evidence: on a kernel whose ObjectQLPlugin has no environmentId, bridgeObjectsToMetadataService (packages/objectql/src/plugin.ts) copies each boot-hydrated runtime object into the metadata service, and nothing maintains the copy. Measured at a647783 on a real ObjectKernel with MetadataPlugin: after a save of a boot-hydrated object, the service still answered the boot body (label 'Gadget' while the stored row and the registry said 'Gadget Q'); after a delete it still answered the deleted body. This PR leaves the service untouched. MCP's stdio data bridge reads that copy: enforceApiExposure gates on metadataService.getObject(name).enable.apiEnabled, and listObjects and describeObject read it too (packages/mcp/src/stdio-data-bridge.ts). So API exposure turned off on such an object after boot is not enforced by that door until a restart, and a deleted object stays listed and described there. Not walked through the MCP door here. Seam: runtime:ObjectQLPlugin.bridgeObjectsToMetadataService → consumer: packages/mcp stdio-data-bridge enforceApiExposure / listObjects / describeObject · dedupe words: bridgeObjectsToMetadataService stale boot copy; metadata service copy never updated on save or delete; enforceApiExposure apiEnabled MCP stdio; describe_object list_objects deleted runtime object",
"carrier: none · noted, not filed · plugin-security's security-catalog echo registers stored permission/position rows in the metadata service stamped _provenance 'org', and getMetaItemLayered serves them as the code layer. That is the opposite of the #21059 rule for the registry half (a stored row is never the code layer). It is by design and pinned in security-catalog-echo.test.ts, so this PR scoped its predicate to object. An observation; no defect claimed."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #23659 comment 6191329846 ·
domain:engineseat 1 · 2026-10-15T11:48Z ·Clause-②: no, no contract review owed · filed #23661 · landing per 〈B〉 now that every check is green.Generated by Claude Code