Skip to content

[finding] GET /meta/:type/:name/diff serves PENDING draft content to a member with no authoring capability: its history versions include draft saves, and it is the one draft-serving door the #20338 gate leaves open #20378

Closed

Description

@objectstack-fleet

Ruled: 5865708652 · letter B · 2026-09-28T07:49Z

Filing gate: ① a defect with a named landing site, packages/rest/src/rest-server.ts, the GET /meta/:type/:name/diff handler (registerPerItemRoute → diffMetaItem). Finding class (b), with reach: measured at a public HTTP door; security exception: it leaks unpublished content.

Found by the os-dev round on #20338 (PR #20373). The order for that round forbade gating this door, because it is one of ruling B's stored-version doors (#20156). Verified at source and filed by the domain:cli execution seat (#6024, session_01UYBdGBzWSrAMzpW8ah3GbP). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens (measured)

The dev drove the real REST stack at 8bc02e48b (the PR #20373 branch; this door is unchanged from origin/main there). The caller was a member with no authoring capability (none of system, studio.access, setup.access or manage_metadata) who may open the app.

door answer
GET /meta/app/NAME/diff?from=0&to=2 on a published app with a pending draft 200, carrying the draft's label and a draft-only navigation entry
the same on a draft-only app 404
GET /meta/app/NAME/history events only, no bodies

The seat's reading at source (origin/main)

  • sys_metadata_history stores a full body for every draft save: the repository's single put appends a history row for state: 'draft' saves too (sys-metadata-repository.ts). diffMetaItem (packages/metadata-protocol/src/protocol.ts) reads versions with where { organization_id, type, name } and no state filter.

  • For app, doc and book (gatesPerCaller), the REST handler judges every side with metaItemReadGate(..., RestServer.STORED_VERSION_DOOR_POLICY). That gate decides who may open the item and prunes by audience. It never asks whether the caller may read drafts.

  • For every other type (for example view), /diff fetches no current document and applies no per-caller gate. So the draft-only 404 above holds only for those three types. This is a reading at source, confirmed by the contract review of PR fix(rest, runtime): serve pending metadata drafts only to a caller with an authoring capability #20373 (5863212700); it was not measured.

  • The runtime dispatcher (packages/runtime/src/domains/meta.ts) has no /diff route. This door is REST-only.

  • Once PR fix(rest, runtime): serve pending metadata drafts only to a caller with an authoring capability #20373 lands, every door that takes a draft switch asks isObjectSchemaMaskExempt first:

    • the item read's ?state=draft and ?preview=draft;
    • the list's ?preview=draft;
    • the dataset preview's previewDrafts;
    • the dispatcher's ?preview=draft.

    /diff still serves draft bodies to the same member.

Governing text

Who acts on it

Triage grades and routes it. If it lands in the domain:cli lane, the landing site is the /diff handler in rest-server.ts, a hot file on that lane's serial queue.

Duplicate check

Searched the board, open and closed:


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #234 item 2 · letter B · maintainer 「同意」 2026-09-28T07:48Z

    Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意」 to batch #234 as presented — this card item 2, recommendation B with /history folded in, fallback A, the full four-axis body in chat.

    Ruled: B. GET /meta/:type/:name/diff and GET /meta/:type/:name/history are authoring doors: a caller mayReadPendingDrafts (rest-server.ts:1701) does not admit is refused exactly as GET /meta/_drafts refuses — 403, decided on the caller before any item or version is read, so the door is no existence oracle; a builder reads what they read today. Ruling B on #20156 (5856774816) item 2 is narrowed for these two doors only: /layers and ?layers=true read the active row and keep the pruned plain-read answer as ruled. A (a lifecycle-state column on sys_metadata_history with a permanent legacy-row policy), C (inferring drafts from stored rows) and D (serving only provably-active rows) are not taken; C and D are measured wrong both ways.

    Readings that decided it:

    • Long-term: sys_metadata_history is the authoring commit log (ADR-0067); its readers are the commit timeline and 「revert to this」, both authoring surfaces. Mainstream (from memory): Salesforce compares Flow versions in Flow Builder under Manage Flow and gates Setup Audit Trail on View Setup; ServiceNow's version compare is administrator-only. Members meet the published product, not the definition's history. A would add a system column and a legacy policy for a member capability with no mainstream analogue.
    • Real use: zero member readers of /diff — objectui's MetadataClient.diff has no caller at the pin, Studio's DraftReviewPanel computes its diff client-side, the SDK's diffItem has no in-repo caller and one docs example. B withdraws nothing anyone uses, and it is the fastest close of a p1 leak that is live now.
    • AI-safety: B is a loud, uniform refusal — one status per caller. A's withheld versions must answer as nonexistent, which an AI client reads as a true all-added / all-removed diff.
    • Startup scope: one guard reusing the existing predicate and the existing 403 shape; A spans three packages.
    • Why the [finding] class closure: the alternate read doors of /meta/:type/:name (/layers, ?layers=true, /published) serve a set-gated doc's BODY to a non-holder; they skip every per-caller read gate the plain read applies #20156 premise moved: that ruling assumed a stored version could be pruned per audience like a plain read. The history store cannot tell a draft save from an active save (no state / lifecycle / is_draft column on sys-metadata-history.object.ts; diffMetaItem filters by nothing but organization, type and name), so the pruned published-only answer does not exist without A. A changed premise, not a reversal.
    • /history rides the same ruling (一次裁清): it lists draft-save events without bodies to the same member, from the same log; the same predicate closes it in the same commit, and no second decision card is left.

    Execution parameters (ruled in the same stroke):

    • The domain:cli seat re-claims this card and dispatches one patch: /diff and /history handlers ask mayReadPendingDrafts first and refuse with the /meta/_drafts 403 shape before any read; pins — a member without authoring capability is refused on both doors for app and for view; a builder reads both as today; the refusal carries no item or version detail; /layers and ?layers=true unchanged (lit control). The docs example beside client.meta.diffItem (content/docs/api/client-sdk.mdx) states the authoring-only rule. Changeset @objectstack/rest patch, Clause-②: no (the declared contract, ADR-0106 D4 「draft/preview reads are admin-gated upstream」, is pulled back in). The ruling text goes into the ACCEPT.
    • The sibling card already filed (the default range mislabels versions) proceeds unchanged.
    • ADR-0106 D4 needs no edit; a one-line note under ruling B on [finding] class closure: the alternate read doors of /meta/:type/:name (/layers, ?layers=true, /published) serve a set-gated doc's BODY to a non-holder; they skip every per-caller read gate the plain read applies #20156 (comment there, not a body rewrite) records the narrowing for the two doors.

    State: needs-user-decision → pm:queue in the same stroke; the Ruled line goes on the body.

  2. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3, re-claim after the ruling (batch 3; beside the #20331 and #20320 patch rounds)
    Session: session_01UYBdGBzWSrAMzpW8ah3GbP
    Account: os-litant
    Branch: claude/issue-20378-diff-draft-versions
    Worktree: objectstack-issue-20378
    Domain: domain:cli
    Seat: domain:cli#1
    File surface (the ruling's execution parameters, 5865708652):

    • packages/rest/src/rest-server.ts: the GET /meta/:type/:name/diff and GET /meta/:type/:name/history handlers only, a guard at the head of each;
    • content/docs/api/client-sdk.mdx: one line beside the client.meta.diffItem example, plus any content/docs sentence that tells a non-author they can read /diff or /history;
    • tests in packages/rest/src/;
    • one .changeset/20378-*.md.

    Read-only: packages/metadata-core/**, packages/metadata-protocol/** (#20397 owns diffMetaItem's labels), packages/spec/** and packages/runtime/**. Stop on breach and explain in the report
    Container & model: S, mode:subagent, model: default tier.
    Clause-②: no
    Thread-read: 5865708652
    Serial constraints cleared: PR #20404 (#20320, in its patch round) holds other regions of rest-server.ts (the list chain, isPublicAudienceRead, the item read); the /diff and /history handlers are disjoint, so this is ordinary same-file concurrency and the dev merges main on conflict. #20397 (the version labels, protocol.ts) is not in flight. The branch exists empty at 15bf186f5 from round 1

    Taken 2026-09-28T07:59Z, origin/main dcd3bceaa. Executes ruling 5865708652 (letter B, the maintainer's 「同意」 through the director seat): /diff and /history become authoring doors, refused exactly as GET /meta/_drafts refuses, before any read. Ruling B on #20156 item 2 is narrowed for these two doors only. Clause-②: no: the declared contract (ADR-0106 D4) is pulled back in.


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's new session: a takeover of claim 5865865833, same branch (maintainer arbitration, seat post 5869029598)
    Session: local_1d2a197c-c20e-4e90-9be8-413d4d432289
    Account: hotlong
    Branch: claude/issue-20378-diff-draft-versions
    Worktree: objectstack-issue-20378
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: unchanged from claim 5865865833, which carries the ruling's execution parameters (5865708652):

    • packages/rest/src/rest-server.ts: the GET /meta/:type/:name/diff and GET /meta/:type/:name/history handlers only, a guard at the head of each;
    • content/docs/api/client-sdk.mdx: one line beside the client.meta.diffItem example, plus any content/docs sentence that tells a non-author they can read /diff or /history (the pushed head edits content/docs/ui/apps.mdx under this clause);
    • tests in packages/rest/src/;
    • one .changeset/20378-*.md.

    Read-only: packages/metadata-core/**, packages/metadata-protocol/**, packages/spec/** and packages/runtime/**. Stop on breach and explain in the report
    Container & model: S, mode:subagent, model: default tier (dispatch-gates --tier on this surface at dbddf02c: no path-derived mandate).
    Clause-②: no
    Thread-read: 5865865833
    Serial constraints cleared: PR #20404 (#20320) holds other regions of rest-server.ts (the list chain, isPublicAudienceRead, the item read) and lands in this round; the /diff and /history handlers are disjoint, so this is ordinary same-file concurrency and the dev merges main after it lands or on conflict. The file lists of all 14 open PRs were read in this act: no other PR touches rest-server.ts, client-sdk.mdx or apps.mdx (PR #20423 touches only packages/rest/src/import-integration.test.ts). #20397 (the version labels, protocol.ts) is not in flight
    Release: session session_01UYBdGBzWSrAMzpW8ah3GbP (os-litant), claim 5865865833 · reason: the claimant is unreachable, its seat session ran out of tokens · destination: this claim, same branch; assignee os-litant → hotlong in this stroke · provenance: the maintainer, in live chat with desktop session local_1d2a197c-c20e-4e90-9be8-413d4d432289 after /pm-dispatch cli, verbatim 「前任没有token了,你接管,包括他的pr」

    Hand-over record: the branch's last pushed sha is 24c384d8, a merge of origin/main over the dev's 5a09278b (the guard) and 1f4a1faf (pins, docs, changeset). No PR was opened and no report came back. A continuation dev reads every pushed commit hunk by hunk as UNVERIFIED work, re-runs the whole verification, and opens the draft PR. Taken 2026-09-28T11:37Z, origin/main dbddf02c.

  4. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20378,
      "status": "done",
      "branch": "claude/issue-20378-diff-draft-versions",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20440",
      "session": "local_1d2a197c-c20e-4e90-9be8-413d4d432289 (the takeover claim 5869082029's session; this run is its dev)",
      "premise_still_valid": true,
      "summary": "Continued the predecessor's pushed branch as ruling 5865708652 (letter B) orders. GET /meta/:type/:name/diff and /history now ask mayReadPendingDrafts at the head of each handler. They refuse a caller it does not admit with the /meta/_drafts 403 FORBIDDEN nested envelope, before resolveProtocol, the query parse or any item, event or version read. Every pushed hunk was read against the card and ruling and kept, except one: a TS2345 in the new pin file that reddened check:test-typecheck, corrected in db05a9e270. Merged origin/main twice (f4c601e889; then c0675573df, carrying PR #20404's landing at 95f729a1 on the same file). #20404 touched neither handler; both guards still sit first, and the branch delta against main is the same 6 files. H1-H4 were measured and hold; H1 has one nuance, below. Draft PR #20440 is open, assignee hotlong. CI was in_progress at report time (32 check runs: 10 completed, 20 in_progress) and was not awaited.",
      "tests": "All at head c0675573df. BUILD: pnpm --workspace-concurrency=2 --filter '@objectstack/rest^...' build gave VERDICT command-exit 0. pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2 gave VERDICT command-exit 0 (71/71 tasks). REST TESTS: pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2 gave VERDICT command-exit 0, Test Files 215 passed (215), Tests 3904 passed / 26 skipped (3930). The --project repo run gave 1 file passed, 8 tests passed. TYPECHECK: pnpm --filter @objectstack/rest typecheck gave VERDICT command-exit 0, 'check:test-typecheck: OK ... 0 file(s) / 0 error(s)'. Before db05a9e270 the same command exited 1 on meta-history-diff-authoring-door.test.ts(273,71) TS2345; tsc --listFiles counts both pin files (2) in tsconfig.test.json. H1: the new real-stack file (better-sqlite3 in memory, real sys_metadata objects, real ObjectStackProtocolImplementation, real routes; stubs only resolveExecCtx and the tenancy probe) pins several things for app and view on both doors. The member gets 403 FORBIDDEN, and its envelope key set equals the member's own GET /meta/_drafts answer. Published, draft-only and missing names answer byte-identically. Spies on getMetaItem, getMetaItemLayered, historyMetaItem and diffMetaItem stay uncalled, with a builder call as the live-spy control. ?from=abc answers the member 403 and a builder 400, so the refusal is decided before the query parse. Nuance: the message is per door ('version history' / 'stored versions'), not the /_drafts message; shape, status and code are identical, and the message is not pinned. H2: the apps.mdx edit (+6/-1) amends the paragraph that told every other caller they read /diff pruned; it is in the claim's docs clause, so it was kept. H3: the census /diff and /history rows gain authoring:true and assert envelope {403, FORBIDDEN}, no secrets, and zero protocol reads. The /layers and ?layers=true rows are byte-unchanged. Two edges moved from non-reader to reader, because the admitted-caller question is open only for a caller who may pass. H4 ABLATION: each leg ran node scripts/ablation-replace.mjs with anchor 'if (!mayReadPendingDrafts(diffCtx|historyCtx)) {' (x1 to x0) and replacement 'if (false && !mayReadPendingDrafts(...)) {'. It wrapped vitest over both pin files; the subject is imported relatively (./rest-server.js), so there is no dist in the path. /diff leg: VERDICT command-exit 1, 14 of 310 red, all /diff refusal pins (9 census non-reader cells, the presenter edge, member app, member view, the range pin, the one-predicate pin). /history leg: VERDICT command-exit 1, 13 of 310 red, all /history refusal pins (9 census cells, member app, member view, the limit pin, the one-predicate pin). In both legs every builder pin and both /layers controls stayed green. Restore proven by the tool: blob e0f7a215dbe0 == HEAD, git diff HEAD empty, git status --porcelain empty. The same ablations at pre-merge head f4c601e889 gave the identical 14/13 red split.",
      "mcp_calls": "0 — none",
      "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #20440, through with-fleet.sh); POST /repos/objectstack-ai/objectstack/issues/20440/assignees (hotlong, through label-write.mjs under with-fleet.sh, read back MATCHES); POST /repos/objectstack-ai/objectstack/issues/20378/comments (this os-dev-report, through post-stamped.mjs under with-fleet.sh). Not REST: three git pushes of the branch (f4c601e889, db05a9e270, c0675573df), each through with-fleet.sh --kind 'git push'.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed · /audit serves sys_metadata_audit rows (actor, time, operation, outcome, no bodies) and the ruling names /diff and /history only; whether a draft save writes an audit row a member then reads is a reading at source, not measured · in PR #20440 Acceptance notes",
        "carrier: 承接者:无 · noted, not filed · objectui at the pin f8a9d0fb05 consumes /history from MetadataResourceHistoryPage on the metadata-designer routes (an authoring surface), and MetadataClient.diff has no caller; a caller without an authoring capability who opens that route now receives the 403 · in PR #20440 Acceptance notes"
      ],
      "gates": [
        "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at c0675573df: 91 commands derived (list identical to the pre-merge derivation at db05a9e270); all 91 run, each exit 0",
        "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.list: 'Run reconciliation — 91 derived, 91 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 91 derived famil(ies) accounted for'",
        "pnpm lint (eslint . --no-inline-config, whole repo): exit 0, 37s",
        "node scripts/check-issue-citations.mjs --base origin/main: exit 0, 'citations judged: 7 across 4 file(s) ... 7 resolves'",
        "pnpm --filter @objectstack/rest typecheck: exit 0; pnpm --filter @objectstack/rest test (local 215 files, repo 1 file): exit 0",
        "Pre-merge run at db05a9e270: three gates first answered PREREQUISITE NOT MET (exit 3), each fixed by building its prerequisite, never counted as a measurement. check:skill-examples was green after building client and client-react; check:dual-build-cjs-loads and check:type-check-debt were green after the all-packages build. All three are green in the final 91/91 run"
      ],
      "line_budget": "n/a — no skills/** diff",
      "deviations": [
        "Three git pushes instead of one: a WIP push after each merge and after the type fix, as AGENTS.md asks before minutes-long steps. None is a force-push.",
        "The first label-write call ran outside with-fleet.sh and refused with PREREQUISITE NOT MET (no token), so nothing was read or written. The rerun under with-fleet.sh made the one budgeted assignee write.",
        "The Claude-Session trailer on my three commits is spelled https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289. That follows this repo's precedent for local sessions (14 prior commits use session_local_), because the order names the id local_1d2a197c-... and AGENTS.md's template is session_ plus id. The PR footer uses the same URL.",
        "The refusal messages are per door rather than /meta/_drafts' own message (H1 said byte-shape). The pushed hunk's reason was kept: a message worded about drafts would read as 'this item has one'. Status, code and envelope keys are identical and pinned.",
        "os-verify-lock.sh ran in its declared UNLOCKED mode (no flock on this macOS host) for every build, test and ablation. Its disclosure is copied verbatim into the PR body."
      ],
      "files_changed": [
        ".changeset/20378-diff-history-authoring-doors.md",
        "content/docs/api/client-sdk.mdx",
        "content/docs/ui/apps.mdx",
        "packages/rest/src/meta-alternate-door-read-gates.test.ts",
        "packages/rest/src/meta-history-diff-authoring-door.test.ts",
        "packages/rest/src/rest-server.ts"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #20440 at c0675573, R1 of the domain:cli seat's new session

    domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · review of record, written 2026-09-28T13:02Z

    Reviewed against GitHub and origin/main, not against the report.

    The ruling this PR executes (5865708652, the director seat, the maintainer's 「同意」, letter B), quoted as the ruling asks:

    Ruled: B. GET /meta/:type/:name/diff and GET /meta/:type/:name/history are authoring doors: a caller mayReadPendingDrafts (rest-server.ts:1701) does not admit is refused exactly as GET /meta/_drafts refuses — 403, decided on the caller before any item or version is read, so the door is no existence oracle; a builder reads what they read today. Ruling B on #20156 (5856774816) item 2 is narrowed for these two doors only: /layers and ?layers=true read the active row and keep the pruned plain-read answer as ruled. A (a lifecycle-state column on sys_metadata_history with a permanent legacy-row policy), C (inferring drafts from stored rows) and D (serving only provably-active rows) are not taken; C and D are measured wrong both ways.

    Checklist:

    • PR shape: draft, base main. The first line is Fixes #20378, followed by a line-initial Clause-②: no, as the ruling fixed it. A scan of the whole body finds one closing keyword, Fixes #20378.

    • Scope: 6 files, all inside claim 5869082029:

      • the /diff and /history handlers in packages/rest/src/rest-server.ts;
      • one line beside client.meta.diffItem in content/docs/api/client-sdk.mdx, and the content/docs/ui/apps.mdx paragraph that told non-authors they could read /diff;
      • two test files in packages/rest/src/;
      • one changeset (@objectstack/rest patch).

      check-governed-merges --pr 20440: NOT governed; +529 −14. The read-only fences (metadata-core, metadata-protocol, spec, runtime) are untouched.

    • Takeover: the predecessor's two content commits (5a09278b, 1f4a1faf) were read hunk by hunk and kept. One TS2345 in the new pin file was corrected in db05a9e2. origin/main was merged twice, the second time over PR fix(rest, runtime): the dispatcher's /meta reads answer what RestServer's answer (#20320) #20404's landing on the same file; neither handler was touched by it.

    • Review route: docs and changeset prose are contract-review surfaces. The at-tier record 5870389172 is PASS at this head. The seat verified its transcript: served at CONTRACT_REVIEW_TIER, read-only, no GitHub write.

    • Evidence (the dev's report 5870104344, checked against the PR):

      • A real-stack pin file shows a member refused 403 FORBIDDEN on both doors, for app and for view, with the envelope keys of the member's own /meta/_drafts answer.
      • Published, draft-only and missing names answer byte-identically.
      • The four protocol readers stay uncalled for the member, with a builder control.
      • ?from=abc answers the member 403 and a builder 400, so the refusal comes before the query parse.
      • Ablation of each guard reddens only that door's refusal pins (14 and 13 of 310), and the restore was proven.
    • Deviation, accepted: the refusal message names the door, not /meta/_drafts's wording. The status, code and envelope match the ruling's "403 shape", and the message carries no item, version or draft detail.

    • CI on head c0675573: 35 check-runs, 33 success and 2 skipped, 0 red. The Vercel commit status is still pending, and landing waits for it.

    • Commits: every non-merge commit carries the model-free trailer pair. This session's commits spell the session session_local_…, as the repo's precedent for local sessions does.

    Out-of-scope findings, one line each:

    • /audit serves draft-save events (actor, time, note: draft) to a member the predicate does not admit. It was read at source by the review and the seat, not measured. Filed as [finding] GET /meta/:type/:name/audit lists pending draft-save events (actor, time, note: draft) to a member who may not read drafts #20441 under the filing gate's possible-data-leak exception, bare for triage.
    • objectui's MetadataResourceHistoryPage reads /history on the metadata-designer routes, so a non-authoring caller who opens that route now gets 403. Acceptance notes: the ruling covers it (the commit timeline is an authoring surface). Worth a line at the next objectui pin bump.
    • The rest-route-ledger.ts rows for /history and /diff carry no gate note, where publish and rollback do. Dropped: the note is not a structured field and no gate reads it.

    The ruling's other two parameters are already met: the note under ruling B on #20156 is 5865901551, and the sibling card for the version labels (#20397) is untouched.

    Next: landing when the Vercel status settles, as markPullRequestReadyForReview + enablePullRequestAutoMerge (SQUASH) as objectstack-fleet[bot].

  6. objectstack-fleet commented on Sep 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20440 → 7fa3e3e07cc67877ae2f5d49eac42665c0515fda

    domain:cli execution PM seat #6024 · session local_1d2a197c-c20e-4e90-9be8-413d4d432289 · landing record, written 2026-09-28T13:44Z

    • Merged through the merge queue: readied and armed 2026-09-28T13:18Z, merged 2026-09-28T13:42Z. The landing is a squash (git rev-list --parents -n 1 gives 2 fields), and it is an ancestor of origin/main.
    • Content read on origin/main: packages/rest/src/rest-server.ts carries both guards, mayReadPendingDrafts(diffCtx) and mayReadPendingDrafts(historyCtx) (2 hits). Ruling 5865708652 (letter B) is executed: /diff and /history are authoring doors.
    • Card: closed completed by the PR's Fixes #20378. pm:dispatched is stripped in this stroke; security, area:access, domain:cli and the type label stay.
    • Lane reconcile: the open domain:cli set went from 12 to 11 cards, and the only card missing is this one.
    • Remainder of the family: /audit serves draft-save events to the same member. It was read at source by the review and the seat, and filed as [finding] GET /meta/:type/:name/audit lists pending draft-save events (actor, time, note: draft) to a member who may not read drafts #20441 (bare, for triage).
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:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions