Skip to content

metadata: migrate-stored fails to heal a draft-only permission set that needs a conversion when it grants on an object with no active row, because the unknown-grant refusal reads the active row #29279

Closed

Description

@objectstack-fleet

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

立卡门类别: ① 产品缺陷 — 具名落点 (packages/metadata-protocol/src/protocol.ts, the unknownGrantObjectSaveRefusal block in saveMetaItem, whose held read is readMetaItem({ type: 'permission', name }), active-only by default) + 复现. Follow-up owed by landed work: should-fix 1–3 of contract review 6574758254 on PR #29093 (card #28990).
reach: 公开入口实测 — the stored-metadata healing pass (migrateStoredMetadata, source: 'migrate-stored') on the real kernel at PR head 8604d1dc: a DRAFT-only permission set (no active row) granting on an object that has only a draft, then that object draft removed, with a body that needs a conversion (legacy managedBy key). Preview reports outcome: pending; apply reports outcome: failed with the 422 unknown-grant sentence (failed: 1). Control: with the object draft still present, apply reports rewritten. A canonical body is not re-saved and is not affected. Measured by the #28990 thread with a throwaway test, deleted after.
业务理由: the healing pass exists so that rows stored before a rule keep converting after it lands; #28952 carves writeSource !== 'migrate-stored' out of its own save-door refusal for exactly this reason. Here a pre-rule draft that needs a conversion is reported failed and stays unconverted, so an operator who runs migrate-stored sees a failure they cannot act on from the pass, and the draft keeps its legacy shape. Not doing it: the changeset's "not refused on re-save" is untrue for this row class.
动手的读者: the domain:server seat (metadata-protocol), after triage grades it; os-dev. Lands after PR #29093.
查重: migrate-stored draft permission refused → 91 hits, migrateStoredMetadata draft row active read → 28 (incl. closed): none is this. Nearest: #28990 (the rule, PR #29093), #28952 (the migrate-stored carve-out precedent, PR #29066).

Fix (one PR, three items from the record).

  1. In the save door, judge the unknown-grant refusal against the row being re-saved: carve migrate-stored out as metadata-protocol: an upsert seed whose externalId names a field the object lacks is accepted and inserts a duplicate row on every publish, silently #28952 does, or, when the re-save is a draft, read the held keys from the draft row (state: 'draft'). Pin the measured case (draft-only row + conversion → rewritten), and keep the control.
  2. Finding wording on the publish path (promoteDraftForPublish): "none is saved here" reads true only on the save path. Say "and no published object, or object draft of this batch, carries it". Keep the path and the first sentence, which tests and Studio key on.
  3. Changeset: one line saying that a draft stored before the rule is refused at its publish (as 26724-object-unknown-datasource-refused.md does).

Level. Item 1 widens what migrate-stored admits back to the pre-rule behaviour for that row class (Clause-②: no, measure). Items 2–3 are wording.

Activity

  1. added
    bugSomething isn't working
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p3 · pm:queue · 2026-11-02T11:14Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 4 · 2026-11-02T11:43Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread (session named in its first report)
    Account: os-steve
    Branch: the card thread's harness-pinned branch, named in its first report (it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    File surface: the unknownGrantObjectSaveRefusal block and the publish-path finding in packages/metadata-protocol/src/protocol.ts (and protocol-save-door-refusals.ts if the sentence lives there), tests, a @objectstack/metadata-protocol patch changeset. ⛔ No packages/spec/**, no new error code, no change to the refusal on a user save (stop on breach; explain in the report)
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: yes (widening) (expected: the migrate-stored healing pass admits a draft re-save it refused; the dev measures)
    Metadata-protocol: none (expected)
    Responsibility: the code is the #28990 save-door refusal · the platform path is the stored-metadata healing pass · reached by every operator who runs migrate-stored over a pre-rule draft permission set
    Thread-read: 6575742201
    Premise: as filed and measured; the dev re-measures on current main first
    Serial constraints cleared: seat 5's #25467 E0 cut moves the protocol.ts preamble; the dev merges origin/main before the PR

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29279-delta.md).

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 4 (revised: branch and dev session named) · 2026-11-02T11:47Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread session_01ADHkmRrBmeYea4FHGY5wro
    Account: os-steve
    Branch: claude/project-thread-vqzr9r (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    File surface: the unknownGrantObjectSaveRefusal block and the publish-path finding in packages/metadata-protocol/src/protocol.ts (and protocol-save-door-refusals.ts if the sentence lives there), tests, a @objectstack/metadata-protocol patch changeset. ⛔ No packages/spec/**, no new error code, no change to the refusal on a user save (stop on breach; explain in the report)
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: yes (widening) (expected: the migrate-stored healing pass admits a draft re-save it refused; the dev measures)
    Metadata-protocol: none (expected)
    Responsibility: the code is the #28990 save-door refusal · the platform path is the stored-metadata healing pass · reached by every operator who runs migrate-stored over a pre-rule draft permission set
    Thread-read: 6575742201
    Premise: as filed and measured; the dev re-measures on current main first
    Serial constraints cleared: seat 5's #25467 E0 cut moves the protocol.ts preamble; the dev merges origin/main before the PR

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29279-delta.md).

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmRrBmeYea4FHGY5wro
    os-dev-report

    {
      "issue": 29279,
      "status": "done",
      "branch": "claude/project-thread-vqzr9r",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29350",
      "session": "session_01ADHkmRrBmeYea4FHGY5wro",
      "premise_still_valid": true,
      "summary": "Premise re-measured on main a19759ac with a real-kernel test (ObjectKernel + ObjectQLPlugin): a draft-only permission set with the retired managedBy key, granting on an object draft that is then removed, is reported failed by migrateStoredMetadata apply (422 unknown-grant sentence); with the object draft present it is rewritten. Fix 1 copies the #28952 carve-out (writeSource !== 'migrate-stored') onto the #28990 block in saveMetaItem, chosen because writeSource is server-stated and every author door (save, publish promotion, restore) is judged as on main; the draft is still refused at its publish. Fix 2: the promotion's finding says 'no package registers an object of that name, and no published object, or object draft of this batch, carries it' via a door parameter; path and leading sentence unchanged. Fix 3: own patch changeset carrying the 'refused at its publish' line.",
      "tests": "Base a19759ac real-kernel file: 1 failed | 3 passed (measured case outcome failed). Head f7ee536e: metadata-protocol full suite 301 files passed, 4 skipped; 22129 tests passed, 26 skipped; typecheck exit 0. objectql protocol-duplicate-package-* + new file: 5/5 files, 24/24 tests; typecheck exit 0. Dogfood permission-unknown-grant-object-refusal + wall-catalog-refusal: 2/2 files, 24/24 tests. Ablations (fix committed first, os-verify-lock VERDICT lines read): A1 carve-out removed, rebuilt, dist preflight marker in index.js and index.cjs: metadata-protocol 1 failed | 17 passed, objectql 1 failed | 3 passed; restore rebuilt, preflight --absent exit 0, tree clean. A2 widened to all drafts: 3 failed | 15 passed. A3 promotion stops asking: 4 failed | 14 passed. Each leg restored with git checkout HEAD, blob hash equal to HEAD, git diff HEAD empty.",
      "gates": "dispatch-gates --commands at f7ee536e: 62 derived, all 62 exit 0 (teardown-shape --self-test and dual-build-cjs-loads first exit 3, prerequisites met then exit 0). --ran: 62 run, 0 NOT-MEASURED (DERIVED zero). pnpm lint: neither touched package has a lint script (ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT); eslint --no-inline-config on the 4 touched files exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "2 — mcp__github__issue_read (get_comments, card claim), mcp__github__pull_request_read (get, body read-back); no write tools",
      "api_writes": "2 — fleet relay pr_create (PR #29350, run 41446582958), post-stamped os-dev-report comment on #29279; plus git push (not REST)",
      "open_questions": [
        {
          "question": "The pending #28990 changeset (28990-permission-grant-unknown-object.md) still lists only the save-path refusal wording and does not say a draft stored before the rule is refused at its publish; this PR puts that line in its own changeset rather than editing a pending changeset (which reds check-empty-changeset as a deliberate correction). Keep it there or move it?",
          "options": [
            "A keep the line in the 29279 changeset",
            "B also amend the 28990 changeset and declare the deliberate correction"
          ],
          "recommendation": "A, because both changesets ship in the same release and A needs no gate exception"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [],
      "files_changed": [
        ".changeset/29279-migrate-stored-draft-permission-grant.md",
        "packages/metadata-protocol/src/protocol-save-door-refusals.ts",
        "packages/metadata-protocol/src/protocol.ts",
        "packages/metadata-protocol/src/protocol.unknown-grant-object-refusal.test.ts",
        "packages/objectql/src/protocol-migrate-stored-draft-permission-grant.test.ts"
      ],
      "line_budget": "+299 / -19 over 5 files (source +26 / -11; tests +261 / -8; changeset +12)",
      "deviations": [
        "The harness's assign/review-request step on the PR was skipped: os-dev forbids PR assignees and GitHub MCP writes.",
        "Lint ran as eslint on the touched files because neither package declares a lint script.",
        "The real-kernel premise test clears the planted row's checksum so the converted re-save is not a no-op (explained in the PR's Acceptance notes)."
      ]
    }

    Stamped 2026-11-02T12:41Z.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmRrBmeYea4FHGY5wro
    os-dev-report

    {
      "issue": 29279,
      "status": "done",
      "branch": "claude/project-thread-vqzr9r",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29350",
      "session": "session_01ADHkmRrBmeYea4FHGY5wro",
      "premise_still_valid": true,
      "summary": "Premise re-measured on main a19759ac with a real-kernel test (ObjectKernel + ObjectQLPlugin): a draft-only permission set with the retired managedBy key, granting on an object draft that is then removed, is reported failed by migrateStoredMetadata apply (422 unknown-grant sentence); with the object draft present it is rewritten. Fix 1 copies the #28952 carve-out (writeSource !== 'migrate-stored') onto the #28990 block in saveMetaItem, chosen because writeSource is server-stated and every author door (save, publish promotion, restore) is judged as on main; the draft is still refused at its publish. Fix 2: the promotion's finding says 'no package registers an object of that name, and no published object, or object draft of this batch, carries it' via a door parameter; path and leading sentence unchanged. Fix 3: own patch changeset carrying the 'refused at its publish' line.",
      "tests": "Base a19759ac real-kernel file: 1 failed | 3 passed (measured case outcome failed). Head f7ee536e: metadata-protocol full suite 301 files passed, 4 skipped; 22129 tests passed, 26 skipped; typecheck exit 0. objectql protocol-duplicate-package-* + new file: 5/5 files, 24/24 tests; typecheck exit 0. Dogfood permission-unknown-grant-object-refusal + wall-catalog-refusal: 2/2 files, 24/24 tests. Ablations (fix committed first, os-verify-lock VERDICT lines read): A1 carve-out removed, rebuilt, dist preflight marker in index.js and index.cjs: metadata-protocol 1 failed | 17 passed, objectql 1 failed | 3 passed; restore rebuilt, preflight --absent exit 0, tree clean. A2 widened to all drafts: 3 failed | 15 passed. A3 promotion stops asking: 4 failed | 14 passed. Each leg restored with git checkout HEAD, blob hash equal to HEAD, git diff HEAD empty.",
      "gates": "dispatch-gates --commands at f7ee536e: 62 derived, all 62 exit 0 (teardown-shape --self-test and dual-build-cjs-loads first exit 3, prerequisites met then exit 0). --ran: 62 run, 0 NOT-MEASURED (DERIVED zero). pnpm lint: neither touched package has a lint script (ERR_PNPM_RECURSIVE_RUN_NO_SCRIPT); eslint --no-inline-config on the 4 touched files exit 0. CI check-runs: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "2 — mcp__github__issue_read (get_comments, card claim), mcp__github__pull_request_read (get, body read-back); no write tools",
      "api_writes": "2 — fleet relay pr_create (PR #29350, run 41446582958), post-stamped os-dev-report comment on #29279; plus git push (not REST)",
      "open_questions": [
        {
          "question": "The pending #28990 changeset (28990-permission-grant-unknown-object.md) still lists only the save-path refusal wording and does not say a draft stored before the rule is refused at its publish; this PR puts that line in its own changeset rather than editing a pending changeset (which reds check-empty-changeset as a deliberate correction). Keep it there or move it?",
          "options": [
            "A keep the line in the 29279 changeset",
            "B also amend the 28990 changeset and declare the deliberate correction"
          ],
          "recommendation": "A, because both changesets ship in the same release and A needs no gate exception"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [],
      "files_changed": [
        ".changeset/29279-migrate-stored-draft-permission-grant.md",
        "packages/metadata-protocol/src/protocol-save-door-refusals.ts",
        "packages/metadata-protocol/src/protocol.ts",
        "packages/metadata-protocol/src/protocol.unknown-grant-object-refusal.test.ts",
        "packages/objectql/src/protocol-migrate-stored-draft-permission-grant.test.ts"
      ],
      "line_budget": "+299 / -19 over 5 files (source +26 / -11; tests +261 / -8; changeset +12)",
      "deviations": [
        "The harness's assign/review-request step on the PR was skipped: os-dev forbids PR assignees and GitHub MCP writes.",
        "Lint ran as eslint on the touched files because neither package declares a lint script.",
        "The real-kernel premise test clears the planted row's checksum so the converted re-save is not a no-op (explained in the PR's Acceptance notes)."
      ]
    }

    Stamped 2026-11-02T12:42Z.


    Generated by Claude Code

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Landed · 2026-11-02T13:47Z

    Probe Reading
    PR #29350 MERGED 2026-11-02T13:47Z, merge commit f2f6bf0359, through the merge queue
    #29279 closed completed by Fixes #29279

    pm:dispatched is removed. Seat records: review PASS 6577844100, ACCEPT 6577869788.

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions