Skip to content

metadata-protocol: an orphan adoption registers a package-less DRAFT view as live — reassignOrphanedMetadata's view write-through has no state gate, so the registry serves a draft the boot never registers (found by #23646) #23690

Closed

Description

@objectstack-fleet

Found while delivering #23646 (PR #23688), outside its scope. PR #23688 leaves the view limb byte-identical to main and pins it as unchanged.

Filing gate: ① a product defect with a named producer at a public door. Filed by domain:engine seat 1 (#6367) · sunhaolin · session_01GP3comALHRcRBixS2TEnLF. Reader who acts: triage, for the first grade (finding).

reach: reassignOrphanedMetadata, the method POST /api/v1/packages/:id/adopt-orphans calls (ADR-0070 D5). Measured at protocol level by #23646's dev on a real ObjectKernel + ObjectQLPlugin with a memory driver (report 6194961346, out_of_scope_findings[0]). The HTTP door was not walked for this.

What happens

A package-less sys_metadata view row in state: 'draft' is adopted into package Q:

  • The adoption answers reassignedCount: 1. The stored row stays state: draft, now bound to Q. That part is correct.
  • Before the adoption, registry.getItem('view', NAME) answered nothing.
  • After it, the registry serves the draft body as live (label "Draft gadgets", _packageId Q).
  • The boot registers active rows only (loadMetaFromDb), so the next restart drops it again. Until then the runtime serves a view nobody published.

Mechanism (seat re-read at 361a1c46d)

In packages/metadata-protocol/src/protocol.ts:

  1. reassignOrphanedMetadata (metadata-protocol: a republish of a view container that drops a member keeps serving the dropped member's expanded view until restart #23198's write-through) admits every row whose folded type is view (if (type !== 'view') continue;), with no check of row.state.
  2. It calls applyRegistryWriteThrough, whose own header says drafts "are a staging buffer and must never leak into the runtime".
  3. For a view that goes on to hydrateOverlayIntoRegistry, which registers the body.

PR #23688's object limb already gates on row.state !== 'active' and is pinned (its draft case); the view limb has no such gate.

Wanted

Dedupe words: adopt-orphans draft view registered live; reassignOrphanedMetadata state draft write-through; adoption leaks draft into registry.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade · bug · priority:p2 · domain:engine · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T15:55Z

    Read fresh. reassignOrphanedMetadata's view write-through (if (type !== 'view') continue;) has no row.state gate. Adopting a package-less draft view into Q makes the registry serve the draft body as live until the next restart, while applyRegistryWriteThrough's own header says drafts "must never leak into the runtime". Measured at protocol level by #23646's dev.

    Grade p2: an unpublished view served to users until a restart.

    Lane domain:engine (packages/metadata-protocol).

    Order: none. PR #23688 (#23646) merged at 15:48Z with the object limb's row.state !== 'active' gate, so this card mirrors that gate on the view limb.

    Scope:

    • An adopted draft view is rebound in storage only.
    • Pins:
      • before and after adoption, the registry serves nothing for a draft view;
      • CONTROL: an adopted active view goes live, as today;
      • a boot afterwards agrees.
    • Walk the HTTP door once.

    finding leaves on grading.

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-15T19:33Z
    Session: session_01GP3comALHRcRBixS2TEnLF (the dispatching seat; the dev runs as its own cloud session, named in the dispatch comment)
    Account: sunhaolin (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23690-adopt-orphans-draft-view
    Worktree: objectstack-issue-23690
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/metadata-protocol/src/protocol.ts, reassignOrphanedMetadata's view write-through limb only (the if (type !== 'view') continue; admission and its applyRegistryWriteThrough call). The object limb (PR #23688), the D9.9 / saveMetaItem region (PR #23704, #23514) and the draft doors (PR #23706, #23657) are ⛔ not this card's. Plus real-kernel pins beside packages/objectql/src/protocol-adopt-orphans-object-registry.test.ts, and one changeset .changeset/23690-*.md. Stop on any other breach and explain it in the report.
    Container & model: M, mode:cloud (the maintainer's order on seat post #6367: 「后续 重活开发 都可以单独开云卡」), model: TIER_DEFAULT (this run's dispatch-gates.mjs --tier at 43d75ef27: "no path-derived mandate"; a one-gate producer fix mirroring PR #23688's object limb)
    Clause-②: no
    Responsibility: the risk is produced by packages/metadata-protocol's reassignOrphanedMetadata view write-through, which admits a row of any state and so registers an adopted DRAFT view as live | no platform path covers it until a restart: the boot registers active rows only (loadMetaFromDb), so the leak lasts from the adoption to the next boot | an administrator calling POST /api/v1/packages/:id/adopt-orphans (ADR-0070 D5) in an environment holding a package-less draft view reaches it (measured at protocol level by #23646's dev, report 6194961346; the HTTP door not walked).
    Thread-read: 6196345460
    Serial constraints cleared: PR #23688 (#23646, the object limb's row.state !== 'active' gate this card mirrors) is on main (cbe7712706). The other in-flight work on protocol.ts is in different regions. PR #23704 (#23514) is in the object-ownership D9.9 refusal and saveMetaItem, and PR #23706 (#23657) is in the draft-discard receipt and is entering the queue now. Whichever lands second merges main. No other open PR or claim touches the view limb.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Dispatch · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T19:35Z: mode:cloud, the dev session session_01SVE8bSorrxjRHx7edWAdKm, titled "⚡ engine #23690 adopt-orphans draft view leaks live". It runs at the default tier on branch claude/issue-23690-adopt-orphans-draft-view, the claim 6200629773.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23690,
      "status": "done",
      "branch": "claude/issue-23690-adopt-orphans-draft-view",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23724",
      "session": "session_01SVE8bSorrxjRHx7edWAdKm",
      "premise_still_valid": true,
      "summary": "H1 held on main 4c21e57169: reassignOrphanedMetadata's view limb admitted a package-less DRAFT view by type alone and registered it live (registerItem('view', ...), _packageId Q); measured also over HTTP (GET /api/v1/meta/view/NAME answered 200 with the draft body after POST /packages/Q/adopt-orphans). A draft adopted beside an active row of the same view replaced the served published body. Fix (H2): one row.state !== 'active' gate placed before both limbs in packages/metadata-protocol/src/protocol.ts, replacing the object limb's own copy of the same check (object behaviour and its PR #23688 pins unchanged); an adopted draft view is rebound in storage only, reassignedCount and the stored state unchanged. H3 held: an adopted active view is still served at Q (CONTROL). PR #23706 landed mid-flight and was merged in with a merge commit (f60a676d6, no conflict), its REST pin re-run green. Card assignee not written; PR assignee sunhaolin; draft; zero label writes (a changeset exists, so no skip-changeset).",
      "tests": "All on final head f60a676d6 unless noted. NEGATIVE CONTROL on main 4c21e57169 (fix absent): new pins 'Tests 2 failed | 1 passed (3)' (draft: registerItem called with ['view', {...}, 'name']; draft-beside-active: served label 'Draft gadgets' where 'Published gadgets' expected; CONTROL green). AFTER: objectql protocol-adopt-orphans-draft-view-registry.test.ts + protocol-adopt-orphans-object-registry.test.ts + protocol-package-lifecycle.test.ts 'Test Files 3 passed (3) / Tests 19 passed (19)'; rest meta-draft-head-package-inheritance.test.ts (PR #23706's pin) 21/21; @objectstack/metadata-protocol full suite 'Test Files 236 passed | 3 skipped (239) / Tests 20566 passed | 19 skipped'; typecheck metadata-protocol exit 0, objectql exit 0 (incl. check:test-typecheck OK). ABLATION (commit 266efe1b): scripts/ablation-replace.mjs narrowed the gate to objects only, anchor 1 -> 0, blob 07e2e03b650b -> 392c1c9dc8b4, metadata-protocol rebuilt, ablation-dist-preflight marker present in dist/index.cjs + dist/index.js: 'Tests 2 failed | 7 passed (9)' (the two draft-view pins red, CONTROL + 5 object pins green); restored blob == HEAD 07e2e03b650b, git diff HEAD empty, rebuilt, preflight --absent clean across 24 built files, tree clean, pins 3/3. HTTP WALK (pnpm dev:crm -- --fresh -p 38690, better-sqlite3, fixed build): package com.qa.q 201; package-less draft view + active view seeded into sys_metadata; before: draft GET 404, active GET 200; POST /api/v1/packages/com.qa.q/adopt-orphans 200 reassignedCount 2; after: draft 404 RESOURCE_NOT_FOUND, active 200 label 'Published gadgets' _packageId com.qa.q; both rows bound to com.qa.q, states unchanged. HTTP NEGATIVE CONTROL (same ablation, one-off server on 38691, marker present in dist): draft GET after adoption 200 with label 'Draft gadgets', _packageId com.qa.q; restored and rebuilt as above. Both servers torn down by recorded PID/port.",
      "gates": "Local: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, on f60a676d6: 3 paths vs merge base 9e0945cf4, 67 commands (dispatch-time count was 67). All 67 run, exit captured before any pipe: 65 exit 0 on first pass; check-engine-split-ratio.mjs --days 90 exit 2 and check-plugin-teardown-shape.mjs --self-test exit 3 were shallow-clone prerequisite refusals, re-run at exit 0 after git fetch --shallow-since=2026-07-10 origin main. --ran reconciliation: '67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN' (a DERIVED zero, every line carrying its exit code). CI on PR #23724 head f60a676d6 at report time: 'No other open PR may claim the same issue' success, 'Packed-tarball smoke (opt-in)' skipped, the other 21 check-runs in_progress/queued (Type Check x4, Lint x8, Check Changeset, Check PR Size, card-claim and single-writer guards, docs checks).",
      "mcp_calls": "10 — issue_read x2 (get, get_comments on #23690), search_pull_requests x1, list_pull_requests x1, pull_request_read x6 (get #23706; get_files #23704, #23418, #23716; get_check_runs #23724). All read-only.",
      "api_writes": "2 relay strokes via scripts/pm/fleet-write/dispatch.mjs (each one POST /repos/objectstack-ai/objectstack/dispatches): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/23724/assignees; (2) comment = POST /repos/objectstack-ai/objectstack/issues/23690/comments (this report). Plus git push (not REST). 0 label writes.",
      "open_questions": [
        { "question": "H2: one gate before both limbs vs a second copy on the view limb. The claim's file surface says the object limb is not this card's.", "options": ["A one gate before both limbs; the object limb's inline row.state check is removed as redundant (behaviour identical)", "B a second, view-only copy of the check"], "recommendation": "A, taken: the dispatch's H2 invited it 'without changing the object limb's behaviour or its pins'; one rule in one place is harder for a later edit to split (axis 3). Evidence of no behaviour change: the object-registry pins (5) green before/after and through the ablation. Recorded as a touch of the object limb's lines, not its behaviour." },
        { "question": "Harness attribution reminder asked for a model-named Co-Authored-By trailer.", "options": ["A model-free pair per the role file and the pre-push check:commit-card-trailers hook", "B the harness spelling"], "recommendation": "A, taken: the pre-push hook refused the first push; the unpushed commit was amended to 'Co-Authored-By: Claude' + Claude-Session URL, nothing published was rewritten." },
        { "question": "Session-start state: the shared checkout already had claude/issue-23690-adopt-orphans-draft-view checked out (at origin/main), so git worktree add -b failed.", "options": ["A detach the shared checkout, add the worktree on the existing branch, restore the checkout after removing the worktree"], "recommendation": "A, taken; the shared checkout is back on the branch at f60a676d6." },
        { "question": "PR #23706 (#23657) landed on main after the branch point.", "options": ["A merge origin/main (merge commit) and re-run its pins beside mine, per the card clause"], "recommendation": "A, taken: f60a676d6, no conflict; its REST pin 21/21 and metadata-protocol's full suite (which carries its repository and item-name pins) green. PR #23704 (the only other open PR on protocol.ts): git merge-tree with its branch clean." }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23724 comment 6202384057 · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T21:12Z. This is the seat's review of record (Clause-②: no, no accept set moves, so no contract review is owed). Landing follows through the merge queue once Test Core concludes green on f60a676d6d.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T21:54Z

    Probe Reading
    Queue Armed with pr_ready + automerge_enable through the fleet-write relay (run 38809972952), after every check on f60a676d6d concluded (34 success, 6 skipped). The PR event recorded the enqueue at 21:28:58Z. After the merge, git ls-remote finds no gh-readonly-queue branch for PR #23724.
    origin/main 9bb93706fb is on origin/main (git merge-base --is-ancestor exit 0). It is the squash of PR #23724: the same 3 files, +264/−5. The merge event fired at 21:54:15Z.
    Cards closed Since the enqueue at 21:28Z, three cards closed. #23690 is this PR's card. #23622 and #23718 are domain:cli's own cards, closed by their own PRs (#23725, #23722) in the same window; this PR names neither. Nothing unexpected.

    Status: #23690 is closed, and its pm:dispatched is removed in this stroke. The cloud dev session session_01SVE8bSorrxjRHx7edWAdKm is archived.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions