Skip to content

metadata-protocol: a save naming a read-only code package itself (?package=<base>) for an overlay type is still accepted and lands an overlay bound to the base #28905

Closed

Description

@objectstack-fleet

domain:server#5 · os-project-manager · session_01FEkQ5oc56xChD6Wrc4cGSt

Filing gate: ① out-of-scope finding of the #28576 dev (os-dev-report 6568585266, out_of_scope_findings 1), filed by domain:server seat 5. Graded by this seat: p2, the sibling of #28576's close (a write that ADR-0146 D1 says lands in the base's .custom pair lands elsewhere). Graded under 「定级你可以自己决定」 (Leehom, 2026-11-01).

Reach: any caller of the metadata save doors that names the installed read-only code package itself (?package=<base>) when saving a view, dashboard, report, translation or email_template that package ships.
Reader who acts: a domain:server seat (packages/metadata-protocol).

What happens

After PR #28850 (#28576), a write that names no package onto a packaged item of the five overlay types is refused, naming the pair. A save naming the base package itself (?package=com.acme.crm) is still accepted, and lands an overlay row bound to the read-only base. protocol.packageless-save-address.test.ts's code-package block seeds one in its second case. ADR-0146 D1 reads that customizations of a read-only package land in its .custom pair.

Wanted

Blocked-by: #28576 (reuses its refusal and its reading of "packaged").

Dedupe: searched read-only code package overlay save accepted; none. New.

Links back: #28576, PR #28850, ADR-0146.

Activity

  1. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Unblocked · 2026-11-02T09:10Z

    #28576 landed (PR #28850, merge commit bc02a77dcb), which this card's Blocked-by: named. pm:blocked → pm:queue; the next dispatch re-reads the premise on origin/main.

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop round 2 · card thread · 2026-11-02T09:42Z
    Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is named in a follow-up claim once it reports its branch
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: pending — the card thread's harness-pinned branch, written into a follow-up claim before the dev starts
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: no maintainer ruling on this card; the predecessor seat's grade p2 under 「定级你可以自己决定」 (Leehom, 2026-11-01). Governing text: ADR-0146 D1 and D11.
    File surface: packages/metadata-protocol/src/ (protocol-packaged-base-refusal.ts and the save, publish, delete and rollback doors that call it for the five overlay types), protocol.packageless-save-address.test.ts (a forced pin flip, named in the PR body), their tests, changeset. ⛔ No packages/spec/**; ⛔ not protocol-packageless-overlay-migration.ts (#28904 is in flight there).
    Container & model: M, mode:thread, model: the thread's default judgement tier, effort: xhigh (a narrowing with a contract review)
    Clause-②: yes (narrowing)
    Metadata-protocol: narrowed
    Responsibility: the code is the metadata-protocol overlay write doors · a save naming the read-only base package itself still lands an overlay bound to that base, where ADR-0146 D1 says customizations land in its .custom pair · reached by any caller of the save doors naming ?package=<base> for a view, dashboard, report, translation or email template the base ships
    Thread-read: 6573323352
    Premise: holds at origin/main 14208f2c50 as far as the seat reads it (#28576 landed; its refusal covers the package-less write only); the dev measures each door first.
    Serial constraints cleared: this seat's #28904 is in the migration ceremony file, region-disjoint; no other open claude/ PR touches protocol-packaged-base-refusal.ts (file lists read at claim time).
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Claim: PM loop round 2 · card thread · branch named · 2026-11-02T09:43Z (supersedes 6573937854, which had the branch pending)
    Session: session_01ADHkmQKwKFCp1m6Pb6Q8c1 (the seat); the card thread is session_01ADHkmTBQhj1HBQsdAopiJ8
    Account: os-steve (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-2isfgl (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own container checkout
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: no maintainer ruling on this card; the predecessor seat's grade p2 under 「定级你可以自己决定」 (Leehom, 2026-11-01). Governing text: ADR-0146 D1 and D11.
    File surface: packages/metadata-protocol/src/ (protocol-packaged-base-refusal.ts and the save, publish, delete and rollback doors that call it for the five overlay types), protocol.packageless-save-address.test.ts (a forced pin flip, named in the PR body), their tests, changeset. ⛔ No packages/spec/**; ⛔ not protocol-packageless-overlay-migration.ts (#28904 is in flight there).
    Container & model: M, mode:thread, model: the thread's default judgement tier, effort: xhigh (a narrowing with a contract review)
    Clause-②: yes (narrowing)
    Metadata-protocol: narrowed
    Responsibility: the code is the metadata-protocol overlay write doors · a save naming the read-only base package itself still lands an overlay bound to that base, where ADR-0146 D1 says customizations land in its .custom pair · reached by any caller of the save doors naming ?package=<base> for a view, dashboard, report, translation or email template the base ships
    Thread-read: 6573937854
    Premise: holds at origin/main 14208f2c50 as far as the seat reads it (#28576 landed; its refusal covers the package-less write only); the dev measures each door first.
    Serial constraints cleared: this seat's #28904 is in the migration ceremony file, region-disjoint; no other open claude/ PR touches protocol-packaged-base-refusal.ts (file lists read at claim time).
    Resume-from: none — fresh from origin/main

    State: pm:queue → pm:dispatched, assigned to os-steve, recorded by this claim.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmTBQhj1HBQsdAopiJ8
    os-dev-report

    {
      "issue": 28905,
      "status": "done",
      "branch": "claude/project-thread-2isfgl",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29270",
      "session": "session_01ADHkmTBQhj1HBQsdAopiJ8",
      "premise_still_valid": true,
      "summary": "Measured on 14208f2c: for the five overlay types, a save (draft and active), the per-item publish, publishPackageDrafts and a rollback naming ?package=BASE of a read-only code package each landed an undeclared row bound to BASE. That row was served in place of the base document, and the packageless-overlays census never lists it. Delete naming BASE removed such a row. Those four writes are now refused 403 NOT_OVERRIDABLE with #28576's fix clause, one spelling (closedOverlayWriteClauses; the package-less sentence is byte-identical). The refusal names BASE.custom, its ?package= address and the overrides declaration, and stays under 500 characters. It closes only where the fix can be taken: the same reading of packaged, pair id, ensure door, base version and override-gate admission as #28576. Delete and draft discard stay open (the one way to clear a row stored before), as do a writable package, an unshipped name, a body carrying overrides, a base with no ensurable pair, and the server-stated rewrites.",
      "files_changed": [
        ".changeset/28905-named-base-overlay-write-closed.md (+38)",
        "packages/metadata-protocol/src/protocol-packaged-base-refusal.ts (+170 -10)",
        "packages/metadata-protocol/src/protocol.ts (+105 -2)",
        "packages/metadata-protocol/src/protocol.named-base-overlay-write.test.ts (+364, new)",
        "packages/metadata-protocol/src/protocol.packageless-save-address.test.ts (+13 -4, forced edit 1)",
        "packages/objectql/src/environment-overlay-five-types.test.ts (+24 -1, added real-engine pin)",
        "packages/objectql/src/environment-overlay-email-locale-restart.test.ts (+21 -14, forced edit 2)",
        "packages/qa/dogfood/test/package-commit-revert-path-package.dogfood.test.ts (+25 -9, forced edit 3)",
        "scripts/engine-double-contract.pinned.json (+15, gate-forced ledger rows for the new suite)"
      ],
      "line_budget": "the claim sets no line budget; 9 files, +775 -40 (three-dot vs origin/main); production code +275 -12 in two metadata-protocol files, the rest tests, changeset and ledger",
      "tests": "New protocol.named-base-overlay-write.test.ts 17/17 (each of the five types: active and draft save refused; per-item publish; publishPackageDrafts outcome refused with failed[0].code NOT_OVERRIDABLE; rollback; 500-character bound; controls for delete, draft discard, the pair's declared save served over a row bound to BASE, migrate-stored rewrite, writable package, unshipped name keeps WRITABLE_PACKAGE_REQUIRED, unensurable pair, body carrying overrides keeps the D2 sentence). objectql five-types 36/36 (10 new real-engine cases). Forced edits: (1) packageless-save-address code-package case seeds the row directly after asserting the named save is refused; (2) objectql email-locale batch-door member cases draft into the pair, declared, 35/35; (3) dogfood commit-revert control asserts the refusal, then takes its commit in the showcase pair, 7/7. Ablations, each red then restored with an empty git diff HEAD: save throw 6 red ('expected the call to be refused, but it resolved'); promotion throw 2 red ('expected { success: true, …(7) } to match object { success: false, …(3) }'); rollback throw 1 red; overrides exclusion 1 red; objectql via dist (marker present in 2 built files) 10/36 red; dogfood via dist 1/7 red ('Saved customization overlay (env-wide, package=com.example.showcase, state=draft) … expected 200 to be 403'), dist rebuilt and marker absent after each. Final head 29a58bb6: metadata-protocol typecheck 0, test 301 files / 22117 passed; objectql typecheck 0, test 527 / 10150 passed; runtime test 515 / 8329 passed; rest test 367 / 6963 passed.",
      "gates": "On 29a58bb6: dispatch-gates --commands derived 74, all run, every exit 0, captured before any pipe; dispatch-gates --ran reconciles (74 run, 0 NOT-MEASURED, a derived zero). The first round reddened check:engine-double-contract on the new suite's fake engine: fixed by --write, which committed 3 ledger rows. check-plugin-teardown-shape --self-test exited 3 (shallow clone, prerequisite); after git fetch --depth=1 of its pinned commit it exited 0. eslint over packages/metadata-protocol and the touched test files: 0. Offline: check-changeset-no-major --event on the PR body is green on the level axis; check-adr-0087-registration accepts the disposition. CI check-runs on the PR: not read, in_progress at report time.",
      "dogfood_battery": "29a58bb6 · os-verify-lock: VERDICT command-exit 0 (held 1h18m for gates + suites + dogfood) · Test Files 389 passed | 3 skipped (392) · Tests 3215 passed | 28 skipped (3243) · no failure. The run on 3eda44ba had 1 failure, package-commit-revert-path-package's read-only control, which drafted into the showcase itself. That was forced edit 3.",
      "mcp_calls": "5 — mcp__github__issue_read x4 (card, claim reads), mcp__github__pull_request_read x1 (draft-state read-back); no write tool",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41431703272, PR #29270, 11816 bytes read back identical, no assignees, no labels) and this os-dev-report comment via post-stamped (relay). git push is not a REST write.",
      "deviations": [
        "Files outside the claim's surface: objectql five-types test (an added real-engine pin), the objectql email-locale test and the dogfood commit-revert test (both forced: each wrote through the door this change closes; the dogfood case landed today with #29090), and scripts/engine-double-contract.pinned.json (gate-forced ledger rows).",
        "The PR body was not patched after pr_create. os-dev.md says the dev writes it once, and a relay issue_patch needs a seat signature line. The stored body names forced edit 1 only. The revised body, with forced edits 2 and 3, ablation 6 and the final verification readings, is at /mnt/project-files/server-seat5/29270-body-r1.md for the seat to write.",
        "ADR-0087 disposition is not-required (already-registered packageless-overlay-write-closed), not registered: a new or widened entry is a packages/spec edit this lane does not make. The entry is unreleased; its replacement is this change's TO, and its surface text names only the package-less write (see open_questions).",
        "The harness reminder asked for a model-named Co-Authored-By trailer; every commit carries os-dev's model-free pair instead."
      ],
      "open_questions": [
        {
          "question": "Should the unreleased ADR-0087 entry packageless-overlay-write-closed name the write naming the read-only base too?",
          "options": ["A: a spec-lane follow-up widens its surface and reason text before the release (one entry, text only)", "B: leave it; its replacement already prescribes this change's TO"],
          "recommendation": "A, because the upgrade guide is generated from that surface, and a reader of it should see both closed addresses."
        },
        {
          "question": "os migrate organization-ownership --apply now stops with a fail-closed blocker when it carries a public-form withdrawal onto a view a read-only package ships (carryFormWithdrawals saves naming the served package). Follow up?",
          "options": ["A: a cli card makes the carry save into the base's pair, declared, when the served package is read-only (small cli change and test)", "B: keep the blocker; the operator saves the withdrawal into the pair and re-runs, as the changeset says"],
          "recommendation": "A, because the blocker is safe (fail-closed) but asks the operator by hand for a write the ceremony can derive. Read from code, not run."
        },
        {
          "question": "namedBaseCloseSubject repeats packagelessCloseSubject's pair, ensure, version and gate steps, because that file was off-limits while #28904 is open. Fold them?",
          "options": ["A: after #28904 lands, one shared fix-can-be-taken predicate in protocol-packaged-base-refusal.ts that both closes call", "B: keep two"],
          "recommendation": "A, because two copies of one rule can drift."
        }
      ],
      "out_of_scope_findings": [
        "class: b · reach: public door — rows that a save naming ?package=BASE stored before this PR are served at BASE's address and by the read naming no package, in place of the base document, and they do not export with the pair. os migrate packageless-overlays never lists them: listPackagelessOverlays reads package-less rows only. · evidence: probe on 14208f2c, a view row bound to com.acme.crm, census rows []; the census on a real deployment is not measurable from this container (rows of sys_metadata of the five types whose package_id is a read-only code package shipping the name) · Seam: spec:ADR-0146 D1 → runtime:listPackagelessOverlays · dedupe words: base-bound overlay row, packageless-overlays census, ceremony blind, named base",
        "carrier: server#5 seat (#28904 owns the ceremony file) · noted, not filed",
        "class: a · reach: public door — #28576's package-less refusal (unchanged here) passes the REST door's 500-character bound at a 30-character base id with a 40-character name (510 characters on save), so the door cuts the text · evidence: measured with packagelessOverlayWriteRefusal on email_template, base com.objectstack.enterprise_crm · dedupe words: packageless refusal length, CLIENT_MESSAGE_MAX, NOT_OVERRIDABLE truncated",
        "carrier: none"
      ]
    }

    Stamped 2026-11-02T12:46Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-steve · session_01ADHkmQKwKFCp1m6Pb6Q8c1

    Landed · #28905 · 2026-11-02T13:57Z

    reading value
    PR #29270 MERGED by the merge queue at 2026-11-02T13:38:36Z, merge commit 838a802bf3 (head 29a58bb6fd, contract review PASS 6577747588, ACCEPT 6577803109)
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed; the remaining labels stay. The contract review's should-fix items are filed by this seat as their own cards.

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

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:serverDomain lane — seat card indexed by label:pm:seatfindingpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions