Skip to content

metadata: a runtime permission-set save accepts a grant on an object that does not exist (200) — the build refuses the same grant (#18202), so a typo silently grants nothing #28990

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbWJDYbbtH6dhJNoboN

立卡门: ① 产品缺陷,具名落点 + 复现(reach: 公开入口 HTTP PUT /api/v1/meta/permission/NAME 实测一次错误结果)
业务理由: 在 Studio 或用 API 运行时编辑权限集的管理员,把对象名写错(例如短名 lead 而不是 crm_lead)会得到 200,以为授权成功,实际什么都没授。#18202 正是为这个场景在构建期加了硬失败(「grant is simply lost … Fail loudly at build time」),但运行时保存门没有同样的检查,同一个错误在两条路上一条报错、一条静默。
动手的读者: domain:server 席,元数据保存门对 permission 的校验(packages/metadata-protocol):复用 packages/spec/src/stack.zod.ts 的 collectPermissionGrantObjectErrors 判据(平台对象照放行),对运行时已注册对象集合判 objects 的键,未知对象 422 点名。
查重: permission set grant unknown object runtime save 121 命中、permission set objects not defined meta PUT 51、collectPermissionGrantObjectErrors 1(均含已关);最近邻 #18202(构建期规则本身,已关),没有运行时门的卡。

QA-source: #28981 · access-security.permission-matrix-edit-loop · observed beside the clauses

What happens

Measured at framework 7fa9873f (main, 2026-11-02), stock showcase boot from a scratch copy, as the seeded admin.

Reproduction

  1. As admin: PUT /api/v1/meta/permission/qa_r3_probe2 with an otherwise valid permission-set body whose objects map carries a grant keyed qa_no_such_object (an object no package registers).
  2. Expected: 422 naming objects.qa_no_such_object as an unknown object, as the build-time rule does.
  3. Actual: 200, stored and served; the same through draft → publish.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · domain:server · area:access · pm:queue · 2026-11-02T06:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    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
  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

    Claim: PM loop round 1 · card thread · 2026-11-02T06:25Z
    Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01FEkQ5oyJ4R1betVYFyp6SD (a new card thread, fresh from main ab085a04)
    Account: os-project-manager (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-gk98zo (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#1
    Ruling-ref: triage#1 graded the card p2 (06:01Z): wrong things refused loudly; not an authorization hole. Route (the dispatching seat's call): refuse at the metadata save door beside #26724's unknown-datasource refusal, with the save door's existing 422 envelope; "known" is the runtime's registered objects, stored rows and same-batch drafts. Dispatched in the seat-1 split of the server queue under 「队列进了很多任务,抓紧派发」 (Leehom, 2026-11-02T05:15Z) and 「定级你可以自己决定」 (Leehom, 2026-11-01T12:11Z).
    File surface: the save-door refusals in packages/metadata-protocol/src/protocol-save-door-refusals.ts and their three call sites (save, publish promotion, restore); their tests and changesets. ⛔ No packages/spec/** (the build rule collectPermissionGrantObjectErrors is read, never exported or edited), no new error code, no new gate.
    Container & model: S, mode:thread, model and effort as the thread was opened
    Clause-②: no (narrowing) (expected: a permission-set save naming an unknown object, accepted 200 on main, is refused; the dev measures)
    Metadata-protocol: per the #26724 precedent, expected narrowed with a minor changeset; measured by the dev
    Responsibility: the code is the metadata save door of @objectstack/metadata-protocol · the platform path is a permission set whose grant names an object that does not exist, saved 200 and granting nothing · reached by every admin editing a permission set in Studio or through PUT /api/v1/meta/permission/:name
    Thread-read: none
    Premise: holds on main 7fa9873f (the card's QA record #28981)
    Serial constraints cleared: #18202 (the build rule) closed. No open server card edits protocol-save-door-refusals.ts; #28952 (seat 1, another thread) edits the seed save door in the same package, so merge origin/main once if it lands first.
    Resume-from: none — fresh from origin/main

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


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC

    Release: server seat 1 shift close · 2026-11-02T07:42Z
    Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat that claimed this card); card thread session_01FEkQ5oyJ4R1betVYFyp6SD (paused)
    Who / what / where: Leehom (os-project-manager) in the server-lane project chat — 「进展中的开发先提交代码然后暂停,先确保已有的pr都合并」 (2026-11-02T07:11Z), 「自动合并中的也交给下一任跟进」 (2026-11-02T07:23Z), 「我说的是pm-dispatch 中车道下班的流程,都要走完」 (2026-11-02T07:33Z). Shift close returns every claim to the queue (seat-post-protocol.md 交接收尾清单 step 2); no stay-behind.
    Resume-from: claude/project-thread-gk98zo@7d6842b30a5006dd6d1edbd2a6858af2104f6611 — PR #29093, ACCEPTed by the seat on this head. Contract review 6571510063, ACCEPT 6571515994; auto-merge armed, checks pending.
    Successor: read the handoff 6571543819 on seat post #6367 first; land PR #29093 on green, write the landing record, close this card. ⛔ Do not re-dispatch the card from scratch.

    State: pm:dispatched → pm:queue, assignee os-project-manager cleared, recorded by this release.


    Generated by Claude Code

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 1 · takeover of a released card · 2026-11-02T08:17Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY
    Account: os-steve (the card's assignee, set in the same round)
    Branch: claude/project-thread-gk98zo
    Worktree: the predecessor card thread's (paused); no new dev is dispatched
    Domain: domain:server
    Seat: domain:server#1
    File surface: unchanged from the predecessor's claim 6570627828 (packages/metadata-protocol/src/protocol-save-door-refusals.ts and its call sites); the open PR #29093 is the surface.
    Container & model: S, mode:thread, landing only (no new build)
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: as in the predecessor's claim (unchanged)
    Thread-read: 6571796395
    Premise: holds (PR #29093 ACCEPTed by the predecessor seat)
    Serial constraints cleared: none new; the seven PRs this seat inherited are its only in-flight surface.
    Resume-from: claude/project-thread-gk98zo@7d6842b30a5006dd6d1edbd2a6858af2104f6611

    Takeover record: the predecessor (os-project-manager, session_01R1q2xdtYgU6FKrZ7LgjPCC) released this card in 6571796395 at its shift close (Leehom: 「自动合并中的也交给下一任跟进」). This seat takes it on the maintainer's summons in the Claude Code project "objectstack spec" (「server 1 号座位你也接手开席位」), seat 2 (#20966) having left it to seat 1. Last pushed head: 7d6842b30a (PR #29093). Auto-merge armed, checks pending; follow to MERGED.

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 1 · revised (a dev is dispatched onto the open PR) · 2026-11-02T08:39Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread session_01ADHkmSjtHK8jHQqWternFp
    Account: os-steve
    Branch: claude/project-thread-gk98zo (unchanged; the dev pushes to it, PR #29093)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    File surface: the PR's surface (packages/metadata-protocol/src/protocol-save-door-refusals.ts and its call sites) plus the fixture of packages/objectql/src/protocol-duplicate-package-objects-key-collision.test.ts and any other consumer test the narrowing breaks. ⛔ No change that weakens the new refusal, no packages/spec/**, no new gate.
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: as in the predecessor's claim 6570627828 (unchanged)
    Thread-read: 6572392758
    Premise: corrected — PR #29093 head 7d6842b30a is red on Test Core (7/8): 2 objectql tests (ADR-0070 D4 duplicatePackage permission-set key collision) seed, through the save door, a grant on an unregistered object, which this PR now refuses. Auto-merge was disabled by this seat; the seat re-checks the new head before re-arming.
    Serial constraints cleared: #28952 (PR #29066, this seat, seed save door in the same package) is in the merge queue; merge origin/main once if it lands first.
    Resume-from: claude/project-thread-gk98zo@7d6842b30a5006dd6d1edbd2a6858af2104f6611

    Orders: the seat's standard order plus server-seat1/28990-fix-delta.md in the project's shared folder.

  7. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

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

    {
      "issue": 28990,
      "status": "done",
      "branch": "claude/project-thread-gk98zo",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29093",
      "session": "session_01ADHkmSjtHK8jHQqWternFp",
      "premise_still_valid": true,
      "summary": "Fix round on PR #29093 (red on Test Core 7/8 at 7d6842b3). Two consumer fixtures granted on an object that does not exist and are now refused by the PR's own save-door refusal; both fixed in the fixture, the refusal untouched: (1) objectql ADR-0070 D4 key-collision test now saves the set with its one known grant through the door and gives the stored sys_metadata row the two-key objects map (the at-rest shape); (2) dogfood wall-catalog-refusal granted CRUD on sys_permission_set / sys_position, the retired catalog row objects the same file pins unregistered; those two grants removed. Merged origin/main twice (second merge resolved conflicts with #28952's seed externalId refusal in protocol.ts, protocol-authoring-gate.ts and the restore-family test: both refusals kept, the test harness's objects stage unified on main's Map). Final head 8604d1dc.",
      "tests": "At 7d6842b3: objectql key-collision file 2 failed / 3 passed (reproduced, the refusal thrown from seed()). After the fixture edit: 5/5. Full suites at 0993e876 (first merge, before the dogfood fixture edit): objectql 527 files / 10126 tests passed; metadata-protocol 299 passed 4 skipped / 22089 passed 26 skipped; plugin-security 238 / 5127 passed 45 skipped; runtime 514 / 8318 passed 19 skipped; rest 367 / 6963 passed 332 skipped; cloud-connection 67 / 813 passed. Full dogfood battery at 0993e876: 1 failed / 387 passed / 3 skipped files, 3195 passed tests; the failure was wall-catalog-refusal beforeAll (expected 422 to be 200) under both walled postures; fixed in 4b79bd63, that file 17/17. At final head 8604d1dc (after the conflict merge), every VERDICT command-exit 0: metadata-protocol 301 passed 4 skipped / 22120 passed 26 skipped; objectql 527 / 10140 passed; metadata-protocol typecheck 0; dogfood wall-catalog-refusal + permission-unknown-grant-object-refusal 2 files / 24 tests passed; eslint (objectql key-collision file, packages/metadata-protocol, both dogfood files, --no-inline-config) exit 0. No ablation: the diff changes fixtures, not a tested behaviour; the red side of each is the CI/dogfood failure above.",
      "gates": "At 8604d1dc: dispatch-gates --commands derived 74 families; all 74 run, every one exit 0 (check-plugin-teardown-shape --self-test after fetching its fixture commit; check:dual-build-cjs-loads after building the 7 packages outside the closure; check:type-check-debt re-run without lock contention). --ran reconciliation: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. pnpm lint: neither touched package declares a lint script (pnpm lint answers Command not found, exit 254), so eslint was run directly as above, exit 0. CI on 8604d1dc: read by the seat (contract review PASS 6574758254, ACCEPT r2 6574786481); not re-read here.",
      "dogfood_battery": "0993e876 · VERDICT command-exit 1 · Test Files 1 failed / 387 passed / 3 skipped (391) · Tests 3195 passed / 44 skipped · failure: wall-catalog-refusal.dogfood.test.ts (both postures), a stale fixture of this change, fixed in 4b79bd63 and re-run 17/17; the full battery was not re-run on 8604d1dc (the conflict merge touched metadata-protocol only; the two dogfood files that exercise this refusal re-ran green there)",
      "mcp_calls": "3 — issue_read get_comments #28990, pull_request_read get_check_runs #29093, pull_request_read get #29093 (reads only); no MCP write",
      "api_writes": "1 — this os-dev-report comment via post-stamped.mjs on #28990; plus 4 git pushes (00306c45, 0993e876, 4b79bd63, 8604d1dc), not REST writes",
      "files_changed": [
        "packages/objectql/src/protocol-duplicate-package-objects-key-collision.test.ts",
        "packages/qa/dogfood/test/wall-catalog-refusal.dogfood.test.ts",
        "merge resolutions: packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-authoring-gate.ts, packages/metadata-protocol/src/protocol.restore-doors-save-refusal-family.test.ts"
      ],
      "line_budget": "this round, branch delta vs origin/main: 10 files, +727 / -7 (was 8 files +705 / -3); the two fixture edits are +27 / -6",
      "deviations": [
        "PR body NOT patched: os-dev.md (the dev writes the PR body once and never PATCHes it) binds over the fix-delta's step 5, and the relay refuses an issue_patch body whose first line is not a seat signature while the delta asks to keep the attribution lines first. The ready-to-write body is /mnt/project-files/server-seat1/29093-body-r2.md for the seat to write.",
        "Merged origin/main a second time after the seat reported the PR dirty (conflicts with #28952); readings re-taken on 8604d1dc as listed; the full dogfood battery and the runtime/rest/plugin-security/cloud-connection suites were taken at 0993e876, not re-run after the conflict merge.",
        "Throwaway probes (two, files deleted, never committed) answered the D4 reachability question and the seat's migrate-stored question."
      ],
      "open_questions": [
        {
          "question": "Can a NEW save still reach ADR-0070 D4's permission-set branch after #28990?",
          "options": [
            "A: yes, it stays a live guard",
            "B: no, only a stored-row guard"
          ],
          "recommendation": "A, measured: with object dupdst_customer saved in the target package, a set granting on dupsrc_customer and dupdst_customer saves 200 and duplicatePackage into that target refuses it in failed[] with the D4 sentence; a held grant on an object deleted since reaches it too. No code change."
        },
        {
          "question": "Seat's ask (review should-fix 1): does migrate-stored fail a draft-only permission set granting on an unknown object?",
          "options": [
            "A: file the follow-up card",
            "B: leave it"
          ],
          "recommendation": "A. Measured at 8604d1dc on the real kernel: a draft-only set (no active row) granting on an object draft that is then removed. Canonical body: scanned 1, canonical 1, no re-save, nothing reported. Body needing a conversion (legacy managedBy key): preview outcome pending, apply outcome failed with the 422 refusal text (failed 1). Control with the object draft still present: apply outcome rewritten. Cause: the held-grant read is readMetaItem of the active row, which a draft-only set does not have."
        }
      ],
      "out_of_scope_findings": [
        "carrier: seat (follow-up card for the migrate-stored draft-only case, readings above) · noted, not filed"
      ]
    }

    Stamped 2026-11-02T10:52Z.

  8. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Landed · 2026-11-02T11:25Z

    Probe Reading
    PR #29093 MERGED 2026-11-02T11:25:11Z, merge commit 78f78ac822, through the merge queue
    #28990 closed completed by Fixes #28990

    pm:dispatched is removed. Seat record: revised claim 6572775812, contract review PASS 6574758254 (triage, head 8604d1dc), seat ACCEPT r2 6574786481. Fix thread report 6575331948.

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 guardsdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions