You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
docs(adr): ADR-0066 ⑧'s when note names only the row-blind readers — add the record-scoped field projection that judges the stored row (after #26594, Tier H) #27075
Filing gate: ② a governing ADR note that is now incomplete. It was measured by the contract review on PR #27063 (#26594; record 6460514485, ③). Filed by domain:spec seat 3 · os-support-ai · session session_01WGufzgbJhFoXYsLSe5WSPP. ⛔ This is not a claim, and it has no grade: triage sets the lane and the grade. It touches docs/adr/**, so it is Tier H.
Business reason. ADR-0066's note of 2026-10-26 (#25866) describes who judges a field permission's when. It says "the write gate judges the stored row before an update … and every row-blind reader (/auth/me/permissions, the field projections, os explain) apply the entry as its lock". Once PR #27063 lands (ruling 6457467702, letter B), a third reader exists: the record-scoped field projection, GET /api/v1/data/:object/:id/field-access. It judges when against the stored row through the write gate's own computeForbiddenFieldWrites, and the record form reads it. Someone reading the ADR to learn which readers are row-aware would not find it.
Measured
docs/adr/0066-unified-authorization-model.md, ⑧, note "(2026-10-26) — a field permission entry may carry a row condition": it names the row-blind readers and the write gate only.
Add one dated note under ⑧ (an ADR amendment, Tier H). It should say that the record-scoped field projection judges when on the stored row with the write gate's own function. It should also say that the row-blind readers are unchanged and that no reading is wider than the gate's. Cite #26594 and ruling 6457467702. This should land after PR #27063.
Dedupe
search_issues on this repo for "ADR-0066 field projection row-blind record-scoped when note" found #26594 (the source; its PR leaves the ADR alone) and unrelated closed cards (#26187, #24339, #22300). New.
Claim: PM loop round 1 (takeover shift, wave 4) · 2026-10-28T07:58Z
Session: session_01QSeKYmhU5KEj1nukYhp1Tg
Account: os-justin
Branch: claude/issue-27075-adr-0066-when-record-scoped-projection
Worktree: objectstack-issue-27075
Domain: domain:skills
Seat: domain:skills#2
File surface: docs/adr/0066-unified-authorization-model.md — ⑧ only: one dated note under the existing 「Note (2026-10-26)」 at L134 of origin/maine6e41998; ⛔ no other file, no other ADR line (stop on breach; explain in the report)
Container & model: S 级机械卡, mode:subagent, model: fable — MANDATORY: claude-fable-5-1 (derived from the file surface: docs/adr/**; dispatch-gates --tier, this act), effort: xhigh
Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6461027360
Premise: holds — read in this act on origin/maine6e41998: docs/adr/0066-unified-authorization-model.md L134 still names the write gate and 「every row-blind reader (/auth/me/permissions, the field projections, os explain)」 only; PR #27063 (#26594, ruling 6457467702 letter B) is merged and left the ADR untouched (its file list read: packages/rest/src/data-action-routes.ts, packages/plugins/plugin-security/src/security-plugin-field-readers.ts, spec, docs — no docs/adr/**), so the third, row-aware reader exists on main and the ADR does not name it. Triage's 「lands after PR #27063」 condition is met.
Serial constraints cleared: no open PR touches docs/adr/0066* (open-PR title/branch scan for adr/0066 in this act: none); the ADR batch PRs (#27094 and the batch 2–4 records) are merged; no in-flight claim on this lane names docs/adr/** (#27077: .claude/** + scripts/pm/**; #26081: AGENTS.md + docs/agents/**). Tier H (docs/adr/**): the dev opens one draft PR through the relay; the seat posts the contract review at CONTRACT_REVIEW_TIER; landing by a GOVERNED_APPROVERS approval, never by the seat's flip.
{
"issue": 27075,
"status": "done",
"branch": "claude/issue-27075-adr-0066-when-record-scoped-projection",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27152",
"head": "5ccadf233eb8659b2e971d590fa890b9413e8dd0",
"session": "session_01QSeKYmhU5KEj1nukYhp1Tg",
"premise_still_valid": true,
"premise_by_item": {
"newest Claim names this branch": "holds — comment 6463788937 (the newest of 2) names claude/issue-27075-adr-0066-when-record-scoped-projection, session_01QSeKYmhU5KEj1nukYhp1Tg, mode:subagent; no claim posted, assignee untouched",
"PR #27063 merged and on origin/main (triage: lands after #27063)": "holds — `git log --oneline -1 --grep='#27063' origin/main` → f9f27a06 feat(spec,plugin-security,rest,client): a record-scoped field projection judges a `when` lock on the stored row (#27063); a positive hit, self-evident on the shallow checkout",
"ADR ⑧ names only the write gate and the row-blind readers": "holds — docs/adr/0066-unified-authorization-model.md L134 at merge base 24351cfe reads 'every row-blind reader (`/auth/me/permissions`, the field projections, `os explain`)' and names no record-scoped reader; L134 is still the line (the claim read it at e6e41998)",
"the third, row-aware reader exists on main": "holds — packages/rest/src/rest-route-ledger.ts L364 `GET /api/v1/data/:object/:id/field-access` (client data.fieldAccess); packages/rest/src/data-action-routes.ts L356 registers `${dataPath}/:object/:id/field-access`; SecurityPlugin.getRecordFieldAccess (security-plugin.ts L3013) → readRowForbiddenWrites (security-plugin-field-readers.ts L241) → computeForbiddenFieldWrites (the exported symbol security-plugin.ts L57 imports) with operation 'update' and the stored row as preImage",
"no ADR index regeneration owed": "holds — docs/adr/README.md is generated by scripts/adr-index.mjs from `**Status**:` lines only; the note does not touch that line; `pnpm check:adr-index` → 'docs/adr/README.md is current' (report-only)"
},
"summary": "Added one dated note under ⑧ of docs/adr/0066-unified-authorization-model.md, directly after the 2026-10-26 note (a blank line then the indented blockquote, the file's own form; L135 blank, L136 the note, 1116 chars vs the existing note's 1457). The note records (a) that `GET /api/v1/data/:object/:id/field-access` (SecurityPlugin.getRecordFieldAccess, the answer a record's edit form reads) judges each field permission's `when` on the STORED row with the write gate's own computeForbiddenFieldWrites, asked as an update by id naming every field over the same sets/posture/delegator intersection, so its `editable` list is the gate's field-level verdict for that row and no second evaluator of `when` exists; (b) the row-blind readers (`/auth/me/permissions`, the object-level field projections, `os explain`) are unchanged and still apply a conditioned entry as its lock, `readable` stays row-blind; (c) the record is read through the record GET first, so an absent/unreadable row answers that read's refusal and no reading is wider than the gate's; (d) cites #26594 and ruling of record 6457467702 letter B. No other line of the ADR, no other file. Draft PR #27152 (Tier H, docs/adr/**) is open with assignee os-justin, label skip-changeset, and the relay's governed-review follow-up requested reviewers hotlong and os-zhuang.",
"files_changed": [
"docs/adr/0066-unified-authorization-model.md — +2/-0 (L135 blank, L136 the new ⑧ note); L134 md5 8a105f47be1fce00f9f2a95fd412c944 identical at HEAD and in the tree; `git diff --numstat` → `2 0`; PR reads changed_files 1, additions 2, deletions 0, size/xs"
],
"line_budget": "n/a — docs/adr/** carries no line ratchet (the order: prose lines may be long, one paragraph per line). The order's own ceiling held: the new note is 1116 chars, the existing 2026-10-26 note 1457. Not a skills/** diff, so no before/after line readings are owed.",
"tests": "No test changes and no package touched (file surface docs/adr/0066-unified-authorization-model.md alone) ⇒ no ① closure, no ② package test/typecheck, no ablation (documentation is not ablated). Head measured 5ccadf23 (`git rev-parse --short HEAD` after the final and only commit; every reading below is on that tree). Diff-shape proof: `git diff --numstat` → `2 0 docs/adr/0066-unified-authorization-model.md`; L134 md5 at HEAD == L134 md5 in tree (8a105f47be1fce00f9f2a95fd412c944); L136 begins ' > **Note (2026-10-28) — a third reader of `when` holds the row …' (1116 chars); L137 begins '- **⑨ Authoring-time validation'; `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` on the file → no match (grep exit 1); `node scripts/check-nul-bytes.mjs` → 'check-nul-bytes: OK (scanned 21863 text file(s) … no raw ASCII control bytes)' exit 0.",
"gates": "LOCAL SUBSET — derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; stderr: 'change set derived from git — 1 path(s) vs merge base 24351cfe2 … committed 1, working tree 0, untracked 0'; 21 commands: 13 matched by path, 8 whole-tree). Every exit captured before any pipe (`cmd > LOG 2>&1; EXIT=$?`), all 21 exit 0 on head 5ccadf23: `node scripts/check-adr-links.mjs` :: exit 0 ('✅ check-adr-links: 925 relative link destination(s) under docs/adr/ resolve') · `node scripts/check-adr-links.mjs --self-test` :: exit 0 · `pnpm check:adr-anchors` :: exit 0 ('check-adr-anchors: OK (91 anchored file(s), every governing ADR still referenced; 139 decision number(s) …)') · `pnpm check:adr-index` :: exit 0 ('adr-index --report: OK — docs/adr/README.md is current, every record parsed, self-test 46 assertions green. Report-only') · `pnpm check:adr-symbol-anchors` :: exit 0 ('✅ check-adr-symbol-anchors: 2773 anchors across 145 records resolve') · `pnpm check:doc-authoring` :: exit 0 ('✓ doc authoring guard: 440 files clean' + 3 more ✓ lines) · `pnpm --filter @objectstack/lint run check:doc-formula-expressions` :: exit 0 on rerun ('✓ check:doc-formula-expressions: 24 record-scoped formula example(s) across 494 files / 1528 TS blocks judged clean by @objectstack/formula') — FIRST run exit 3 = PREREQUISITE NOT MET (@objectstack/formula and @objectstack/lint not built in a fresh worktree; nothing measured), cleared by `bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2'` → 'os-verify-lock: VERDICT command-exit 0 · held the lock 89s (1m29s) · waited 0s' (Tasks: 4 successful, 4 total) · `node scripts/check-closing-keyword-parity.mjs` :: exit 0 ('OK (3 parsers agree on all 9 keywords and both measured separators …)') · `node scripts/check-closing-keyword-parity.mjs --self-test` :: exit 0 · `node scripts/check-comment-mask-corpus.mjs` :: exit 0 ('18694 files, 0 disagree, 0 unparseable') · `node scripts/ci/plan-test-core-shards.mjs --self-test` :: exit 0 (23 cases / 5 batteries) · `pnpm check:ci-filter-parity` :: exit 0 · `pnpm check:closed-ceremony-text` :: exit 0 · `pnpm check:gitlink-declared` :: exit 0 · `pnpm check:cross-package-test-inputs` :: exit 0 · `pnpm check:driver-memory-census` :: exit 0 · `pnpm check:nul-bytes` :: exit 0 · `pnpm check:pm-governed-merges` :: exit 0 ('live: the real generator declared 9 output(s) and certified this tree') · `pnpm check:pm-prior-rulings` :: exit 0 · `pnpm check:refd-timer-probe` :: exit 0 · `pnpm check:watch-hint-literal` :: exit 0. RECONCILIATION — `dispatch-gates.mjs --ran` on a record carrying every exit code: '✓ dispatch-gates --ran: 21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)'. The derivation also names 1 path-scheduled CI job with no local invocation (ci.yml · Plan Test Core shards) = NOT MEASURED locally, CI's. Not owed and not run: ① closure (no package), ② package tests, test:repo (CI's), check:dev-prereqs / check:skill-frame-freshness (no packages/spec build, no frame file), check:platform-checklist --changed-from (no exported symbol renamed/removed), dogfood (title has no `!`, Clause-② no). CI CHECK-RUNS on 5ccadf23 at report time (GET /commits/5ccadf23/check-runs, 41 runs; honest in_progress): completed/success — Check Changeset, Check Documentation Links, Governed Surface Queue Guard, PR-body guards (claim · duplicate · Part-of · single-writer path), Check PR Size, filter, Test UI (aggregate; its 16 shards skipped); in_progress — Lint · repo & tooling gates, Lint · docs & code guards, Lint · ESLint, Lint · PM dispatch-gates, Lint · CI release & engine gates, Lint · scoped (query-options erasure / slot lookup & corpus / tooling self-tests), Type Check · workspace / source gates / consumer gates / debt ledger, Plan Test Core shards, Dogfood Regression Gate, Auto Label; completed/skipped — Build Core, Build & E2E UI, Build UI Deps, Lint UI, Type Check UI, UI Published Artifacts, Build Docs, Temporal Conformance, Dogfood Verify CLI, Dogfood Regression Gate shards, Packed-tarball smoke, 18.x upgrade harness, Schedule gate. No CI family was re-run locally; CI convergence is the seat's read, not waited on.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0 — no MCP GitHub tool was called, read or write",
"api_writes": "2 relay/REST writes (+ 2 `git push` to the branch, which are not REST writes: the empty-branch probe, then 5ccadf23). (1) relay pr_create: `node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F --json` → transport dispatch (cloud container; session read from CLAUDE_CODE_REMOTE_SESSION_ID), request fw-20261028T082241Z-533dda, POST /repos/objectstack-ai/objectstack/dispatches HTTP 204, run 40656346550 conclusion success; read-back: 'action 1 pr_create objectstack-ai/objectstack#27152: 12628 byte(s) sent, 12628 stored — identical; every byte sent is on the platform. Assignees as the pull lists them: os-justin' (verdict landed, class identical). Legs: POST /pulls (draft forced true), POST /issues/27152/assignees [os-justin], POST /issues/27152/labels [skip-changeset]; the governed-review follow-up on the Tier H path requested reviewers — read back from GET /repos/objectstack-ai/objectstack/pulls/27152: requested_reviewers = hotlong, os-zhuang (requested_teams none); labels on the PR = skip-changeset + size/xs (Auto Label's, additive); draft true; body first line 'Fixes #27075', body 12628 bytes with the session-URL footer stored once. (2) post-stamped comment on #27075 (this report): `node scripts/pm/post-stamped.mjs --repo=objectstack-ai/objectstack --comment=27075 --file=F --json`, run once — its endpoint and read-back are in the final message. No PATCH on the PR body, no label-write.mjs, no issue created, no workflow run/dispatched, no ready/arm/enqueue/approve. REST READS (no writes): GET issues/27075, GET issues/27075/comments, GET issues/comments/6457467702 (the ruling), GET issues/comments/6460514485 (the contract review), GET pulls/27152, GET commits/5ccadf23/check-runs; the relay's own liveness reads (GET contents/.github/workflows/fleet-write.yml, GET actions/workflows/fleet-write.yml).",
"deviations": [
"Attribution: the commit trailer pair is the model-free form AGENTS.md prescribes (the Claude-Session URL line plus the `Co-authored-by: Claude` line with the noreply anthropic address, spelled here without its angle brackets); the harness attribution reminder's model-named `Co-Authored-By` and its `🤖 Generated with …` PR footer were not used — by the reminder's own sentence that the repo's instructions take precedence, and because the pre-push hook refuses a model identifier in that pair. Reported, not a deviation from the repo's rules.",
"Worktree recipe: the order's `git worktree add … -b … origin/main` was run with `--no-track` as os-dev.md's recipe spells it; same result (branch off origin/main, pushed with -u).",
"Mechanism hypothesis 2 corrected, not falsified: `check:pm-governed-prose` (the family the order named) does not derive for docs/adr/** — it reads AGENTS.md's governed-surface paragraph; no markdown-lint or link-check family exists beyond check:adr-links. The ADR siblings that derived: check:adr-links, check:adr-anchors, check:adr-index, check:adr-symbol-anchors, check:doc-authoring, check:doc-formula-expressions, plus the 8 whole-tree families. Nothing from the test tiers, as predicted.",
"`pnpm --filter @objectstack/lint run check:doc-formula-expressions` exited 3 on first run (PREREQUISITE NOT MET: @objectstack/formula + @objectstack/lint not built in a fresh worktree; nothing measured). Built both under the verify lock (VERDICT command-exit 0, held 89s, waited 0s) and re-ran: exit 0. The `--ran` record carries the rerun's exit 0.",
"Environment variables: no value printed by any command of mine; GH_TOKEN presence tested as `[ -n \"${GH_TOKEN+x}\" ]`. One tool-emitted line named a value: fleet-write/dispatch.mjs's own re-exec notice prints HTTPS_PROXY (a loopback proxy URL) — variable name HTTPS_PROXY, tool output, not transcribed here.",
"test:repo not run locally (CI's; the diff touches no test and the derivation did not name it); the Plan Test Core shards job is CI's own shell with no local invocation (NOT MEASURED locally)."
],
"open_questions": [],
"out_of_scope_findings": [
"none in classes a/b/c — no reproducible defect, no contract violation, no metadata-authoring trap observed",
"carrier: none · noted, not filed (PR Acceptance notes): the 2026-10-26 note's phrase 'every row-blind reader (`/auth/me/permissions`, the field projections, `os explain`)' now reads beside the new note and was left verbatim by the card's scope (⑧ only, one dated note); a reader of both notes gets the full picture — observation, not a finding",
"carrier: none · noted, not filed: route spelling variance across hand-written docs — content/docs/protocol/objectql/security.mdx L264 spells `GET /data/{object}/{id}/field-access`, content/docs/permissions/field-level-security.mdx L61 spells `GET /api/v1/data/{object}/{id}/field-access`, the ledger spells `:object/:id`; same route, no consumer parses the prose — polish, not a contract violation"
]
}
ACCEPT → PR #27152 comment 6464345572 · domain:skills#2 · 2026-10-28T08:32Z — ⛔ not a claim. Contract review (CONTRACT_REVIEW_TIER) PASS → PR comment 6464321368 on head 5ccadf23 (one dated note under ADR-0066 ⑧ naming the record-scoped field projection as the third, row-aware reader of when, reusing the write gate's own computeForbiddenFieldWrites; verified against security-plugin-field-readers.ts L241–L248 and the route ledger). Tier H (docs/adr/**): os-zhuang APPROVED on this head (2026-10-28T08:26Z); 45 check-runs green or expected-skip, mergeable_state: clean, still draft. The seat does not run the ready/auto-merge stroke itself today: this session's permission classifier refused that exact relay command twice this shift (#27082, #27143), so the landing waits on an approver's hand or the maintainer's word — reported in the seat's session. After MERGED: strip pm:dispatched, landing comment.
Landing stroke sent on the maintainer's word — PR #27152 ready, auto-merge armed
2026-10-28T12:21Z · domain:skills#2 · session_01QSeKYmhU5KEj1nukYhp1Tg. Tier H with the approval of record in (os-zhuang APPROVED 2026-10-28T08:26Z), so 「批准在案后席位落地」 applies; the seat had held the stroke because the session's classifier refused the same command on three sibling PRs. The maintainer, in the seat's session: 「你应该按照 pm-dispatch 技能审核并落地pr」. On that word: node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file land-27152.json --json with [{"op":"pr_ready","pull":27152},{"op":"automerge_enable","pull":27152}] → relay run 40686646940 success; timeline read back: ready_for_review 2026-10-28T12:18Z, auto_merge_enabled 2026-10-28T12:18Z; the queue entry follows when GitHub takes it. Pre-landing readings unchanged from the ACCEPT (45 green/skip, clean). The seat follows it to MERGED.
Landed — PR #27152 merged; card closed by Fixes #27075
probe
reading (2026-10-28T12:45Z, domain:skills#2)
git merge-base --is-ancestor 9c121400 origin/main after git fetch origin main
true — 9c121400docs(adr): ADR-0066 ⑧'s when note names the record-scoped field projection as a third, row-aware reader … is on origin/main; GET pulls/27152 → merged: true, merge_commit_sha 9c121400, merged by the queue entry the seat made on the maintainer's word (2026-10-28T12:34Z) after os-zhuang's APPROVED (2026-10-28T08:26Z); origin/main:docs/adr/0066-unified-authorization-model.md L136 opens 「**Note (2026-10-28) — a third reader of when holds the row: the record-scoped field projection (#26594, ruling …」; main's push CI on 9c121400: 54 success · 37 skipped · 3 in progress · 2 cancelled (Packed-tarball smoke, superseded by the next main push) · 0 failure
GET issues/27075
state: closed, state_reason: completed (2026-10-28T12:34Z), closed by the PR body's Fixes #27075 (the only closing keyword in the body — no other card was closed by it)
State change: pm:dispatched removed from this card in the same act (label-write, read back: documentation, domain:skills, priority:p3, area:access remain as ownership). Acceptance notes carried, not filed: the 2026-10-26 note's 「field projections」 wording now reads beside the new note; two hand-written docs pages spell the route differently (/data/{object}/{id}/field-access vs /api/v1/…), no consumer parses the prose.
Activity
objectstack-fleet commentedon Oct 28, 2026
Triage:
domain:skills, p3,area:access· lands after PR #27063 · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-28T04:55Z⛔ Not a claim.
whennote names the row-blind readers and the write gate only. Once PR feat(spec,plugin-security,rest,client): a record-scoped field projection judges awhenlock on the stored row #27063 (Record form: a row-conditioned field lock (when) disables the field on every row, though the write gate admits it on rows the condition does not name #26594, ruling B) lands, the record-scoped field projection is a third reader that judges the stored row. It is an ADR prose update, Tier H (docs/adr/**is governed, so lanedomain:skills).whenlock on the stored row #27063 updatespermission.zod.tsand both docs pages), and only the ADR lags. It lands after feat(spec,plugin-security,rest,client): a record-scoped field projection judges awhenlock on the stored row #27063.pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 1 (takeover shift, wave 4) · 2026-10-28T07:58Z
Session:
session_01QSeKYmhU5KEj1nukYhp1TgAccount:
os-justinBranch:
claude/issue-27075-adr-0066-when-record-scoped-projectionWorktree:
objectstack-issue-27075Domain:
domain:skillsSeat:
domain:skills#2File surface:
docs/adr/0066-unified-authorization-model.md— ⑧ only: one dated note under the existing 「Note (2026-10-26)」 at L134 oforigin/maine6e41998; ⛔ no other file, no other ADR line (stop on breach; explain in the report)Container & model:
S 级机械卡,mode:subagent,model: fable — MANDATORY: claude-fable-5-1 (derived from the file surface: docs/adr/**; dispatch-gates --tier, this act),effort: xhighClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6461027360
Premise: holds — read in this act on
origin/maine6e41998:docs/adr/0066-unified-authorization-model.mdL134 still names the write gate and 「every row-blind reader (/auth/me/permissions, the field projections,os explain)」 only; PR #27063 (#26594, ruling 6457467702 letter B) is merged and left the ADR untouched (its file list read:packages/rest/src/data-action-routes.ts,packages/plugins/plugin-security/src/security-plugin-field-readers.ts, spec, docs — nodocs/adr/**), so the third, row-aware reader exists on main and the ADR does not name it. Triage's 「lands after PR #27063」 condition is met.Serial constraints cleared: no open PR touches
docs/adr/0066*(open-PR title/branch scan foradr/0066in this act: none); the ADR batch PRs (#27094 and the batch 2–4 records) are merged; no in-flight claim on this lane namesdocs/adr/**(#27077:.claude/**+scripts/pm/**; #26081:AGENTS.md+docs/agents/**). Tier H (docs/adr/**): the dev opens one draft PR through the relay; the seat posts the contract review atCONTRACT_REVIEW_TIER; landing by aGOVERNED_APPROVERSapproval, never by the seat's flip.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 27075, "status": "done", "branch": "claude/issue-27075-adr-0066-when-record-scoped-projection", "pr": "https://github.com/objectstack-ai/objectstack/pull/27152", "head": "5ccadf233eb8659b2e971d590fa890b9413e8dd0", "session": "session_01QSeKYmhU5KEj1nukYhp1Tg", "premise_still_valid": true, "premise_by_item": { "newest Claim names this branch": "holds — comment 6463788937 (the newest of 2) names claude/issue-27075-adr-0066-when-record-scoped-projection, session_01QSeKYmhU5KEj1nukYhp1Tg, mode:subagent; no claim posted, assignee untouched", "PR #27063 merged and on origin/main (triage: lands after #27063)": "holds — `git log --oneline -1 --grep='#27063' origin/main` → f9f27a06 feat(spec,plugin-security,rest,client): a record-scoped field projection judges a `when` lock on the stored row (#27063); a positive hit, self-evident on the shallow checkout", "ADR ⑧ names only the write gate and the row-blind readers": "holds — docs/adr/0066-unified-authorization-model.md L134 at merge base 24351cfe reads 'every row-blind reader (`/auth/me/permissions`, the field projections, `os explain`)' and names no record-scoped reader; L134 is still the line (the claim read it at e6e41998)", "the third, row-aware reader exists on main": "holds — packages/rest/src/rest-route-ledger.ts L364 `GET /api/v1/data/:object/:id/field-access` (client data.fieldAccess); packages/rest/src/data-action-routes.ts L356 registers `${dataPath}/:object/:id/field-access`; SecurityPlugin.getRecordFieldAccess (security-plugin.ts L3013) → readRowForbiddenWrites (security-plugin-field-readers.ts L241) → computeForbiddenFieldWrites (the exported symbol security-plugin.ts L57 imports) with operation 'update' and the stored row as preImage", "no ADR index regeneration owed": "holds — docs/adr/README.md is generated by scripts/adr-index.mjs from `**Status**:` lines only; the note does not touch that line; `pnpm check:adr-index` → 'docs/adr/README.md is current' (report-only)" }, "summary": "Added one dated note under ⑧ of docs/adr/0066-unified-authorization-model.md, directly after the 2026-10-26 note (a blank line then the indented blockquote, the file's own form; L135 blank, L136 the note, 1116 chars vs the existing note's 1457). The note records (a) that `GET /api/v1/data/:object/:id/field-access` (SecurityPlugin.getRecordFieldAccess, the answer a record's edit form reads) judges each field permission's `when` on the STORED row with the write gate's own computeForbiddenFieldWrites, asked as an update by id naming every field over the same sets/posture/delegator intersection, so its `editable` list is the gate's field-level verdict for that row and no second evaluator of `when` exists; (b) the row-blind readers (`/auth/me/permissions`, the object-level field projections, `os explain`) are unchanged and still apply a conditioned entry as its lock, `readable` stays row-blind; (c) the record is read through the record GET first, so an absent/unreadable row answers that read's refusal and no reading is wider than the gate's; (d) cites #26594 and ruling of record 6457467702 letter B. No other line of the ADR, no other file. Draft PR #27152 (Tier H, docs/adr/**) is open with assignee os-justin, label skip-changeset, and the relay's governed-review follow-up requested reviewers hotlong and os-zhuang.", "files_changed": [ "docs/adr/0066-unified-authorization-model.md — +2/-0 (L135 blank, L136 the new ⑧ note); L134 md5 8a105f47be1fce00f9f2a95fd412c944 identical at HEAD and in the tree; `git diff --numstat` → `2 0`; PR reads changed_files 1, additions 2, deletions 0, size/xs" ], "line_budget": "n/a — docs/adr/** carries no line ratchet (the order: prose lines may be long, one paragraph per line). The order's own ceiling held: the new note is 1116 chars, the existing 2026-10-26 note 1457. Not a skills/** diff, so no before/after line readings are owed.", "tests": "No test changes and no package touched (file surface docs/adr/0066-unified-authorization-model.md alone) ⇒ no ① closure, no ② package test/typecheck, no ablation (documentation is not ablated). Head measured 5ccadf23 (`git rev-parse --short HEAD` after the final and only commit; every reading below is on that tree). Diff-shape proof: `git diff --numstat` → `2 0 docs/adr/0066-unified-authorization-model.md`; L134 md5 at HEAD == L134 md5 in tree (8a105f47be1fce00f9f2a95fd412c944); L136 begins ' > **Note (2026-10-28) — a third reader of `when` holds the row …' (1116 chars); L137 begins '- **⑨ Authoring-time validation'; `grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]'` on the file → no match (grep exit 1); `node scripts/check-nul-bytes.mjs` → 'check-nul-bytes: OK (scanned 21863 text file(s) … no raw ASCII control bytes)' exit 0.", "gates": "LOCAL SUBSET — derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; stderr: 'change set derived from git — 1 path(s) vs merge base 24351cfe2 … committed 1, working tree 0, untracked 0'; 21 commands: 13 matched by path, 8 whole-tree). Every exit captured before any pipe (`cmd > LOG 2>&1; EXIT=$?`), all 21 exit 0 on head 5ccadf23: `node scripts/check-adr-links.mjs` :: exit 0 ('✅ check-adr-links: 925 relative link destination(s) under docs/adr/ resolve') · `node scripts/check-adr-links.mjs --self-test` :: exit 0 · `pnpm check:adr-anchors` :: exit 0 ('check-adr-anchors: OK (91 anchored file(s), every governing ADR still referenced; 139 decision number(s) …)') · `pnpm check:adr-index` :: exit 0 ('adr-index --report: OK — docs/adr/README.md is current, every record parsed, self-test 46 assertions green. Report-only') · `pnpm check:adr-symbol-anchors` :: exit 0 ('✅ check-adr-symbol-anchors: 2773 anchors across 145 records resolve') · `pnpm check:doc-authoring` :: exit 0 ('✓ doc authoring guard: 440 files clean' + 3 more ✓ lines) · `pnpm --filter @objectstack/lint run check:doc-formula-expressions` :: exit 0 on rerun ('✓ check:doc-formula-expressions: 24 record-scoped formula example(s) across 494 files / 1528 TS blocks judged clean by @objectstack/formula') — FIRST run exit 3 = PREREQUISITE NOT MET (@objectstack/formula and @objectstack/lint not built in a fresh worktree; nothing measured), cleared by `bash scripts/pm/os-verify-lock.sh -c 'NODE_OPTIONS=--max-old-space-size=4096 pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2'` → 'os-verify-lock: VERDICT command-exit 0 · held the lock 89s (1m29s) · waited 0s' (Tasks: 4 successful, 4 total) · `node scripts/check-closing-keyword-parity.mjs` :: exit 0 ('OK (3 parsers agree on all 9 keywords and both measured separators …)') · `node scripts/check-closing-keyword-parity.mjs --self-test` :: exit 0 · `node scripts/check-comment-mask-corpus.mjs` :: exit 0 ('18694 files, 0 disagree, 0 unparseable') · `node scripts/ci/plan-test-core-shards.mjs --self-test` :: exit 0 (23 cases / 5 batteries) · `pnpm check:ci-filter-parity` :: exit 0 · `pnpm check:closed-ceremony-text` :: exit 0 · `pnpm check:gitlink-declared` :: exit 0 · `pnpm check:cross-package-test-inputs` :: exit 0 · `pnpm check:driver-memory-census` :: exit 0 · `pnpm check:nul-bytes` :: exit 0 · `pnpm check:pm-governed-merges` :: exit 0 ('live: the real generator declared 9 output(s) and certified this tree') · `pnpm check:pm-prior-rulings` :: exit 0 · `pnpm check:refd-timer-probe` :: exit 0 · `pnpm check:watch-hint-literal` :: exit 0. RECONCILIATION — `dispatch-gates.mjs --ran` on a record carrying every exit code: '✓ dispatch-gates --ran: 21 derived famil(ies) accounted for — 21 run, 0 NOT-MEASURED (a DERIVED zero — all 21 recorded an exit code and none of them is 3)'. The derivation also names 1 path-scheduled CI job with no local invocation (ci.yml · Plan Test Core shards) = NOT MEASURED locally, CI's. Not owed and not run: ① closure (no package), ② package tests, test:repo (CI's), check:dev-prereqs / check:skill-frame-freshness (no packages/spec build, no frame file), check:platform-checklist --changed-from (no exported symbol renamed/removed), dogfood (title has no `!`, Clause-② no). CI CHECK-RUNS on 5ccadf23 at report time (GET /commits/5ccadf23/check-runs, 41 runs; honest in_progress): completed/success — Check Changeset, Check Documentation Links, Governed Surface Queue Guard, PR-body guards (claim · duplicate · Part-of · single-writer path), Check PR Size, filter, Test UI (aggregate; its 16 shards skipped); in_progress — Lint · repo & tooling gates, Lint · docs & code guards, Lint · ESLint, Lint · PM dispatch-gates, Lint · CI release & engine gates, Lint · scoped (query-options erasure / slot lookup & corpus / tooling self-tests), Type Check · workspace / source gates / consumer gates / debt ledger, Plan Test Core shards, Dogfood Regression Gate, Auto Label; completed/skipped — Build Core, Build & E2E UI, Build UI Deps, Lint UI, Type Check UI, UI Published Artifacts, Build Docs, Temporal Conformance, Dogfood Verify CLI, Dogfood Regression Gate shards, Packed-tarball smoke, 18.x upgrade harness, Schedule gate. No CI family was re-run locally; CI convergence is the seat's read, not waited on.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — no MCP GitHub tool was called, read or write", "api_writes": "2 relay/REST writes (+ 2 `git push` to the branch, which are not REST writes: the empty-branch probe, then 5ccadf23). (1) relay pr_create: `node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F --json` → transport dispatch (cloud container; session read from CLAUDE_CODE_REMOTE_SESSION_ID), request fw-20261028T082241Z-533dda, POST /repos/objectstack-ai/objectstack/dispatches HTTP 204, run 40656346550 conclusion success; read-back: 'action 1 pr_create objectstack-ai/objectstack#27152: 12628 byte(s) sent, 12628 stored — identical; every byte sent is on the platform. Assignees as the pull lists them: os-justin' (verdict landed, class identical). Legs: POST /pulls (draft forced true), POST /issues/27152/assignees [os-justin], POST /issues/27152/labels [skip-changeset]; the governed-review follow-up on the Tier H path requested reviewers — read back from GET /repos/objectstack-ai/objectstack/pulls/27152: requested_reviewers = hotlong, os-zhuang (requested_teams none); labels on the PR = skip-changeset + size/xs (Auto Label's, additive); draft true; body first line 'Fixes #27075', body 12628 bytes with the session-URL footer stored once. (2) post-stamped comment on #27075 (this report): `node scripts/pm/post-stamped.mjs --repo=objectstack-ai/objectstack --comment=27075 --file=F --json`, run once — its endpoint and read-back are in the final message. No PATCH on the PR body, no label-write.mjs, no issue created, no workflow run/dispatched, no ready/arm/enqueue/approve. REST READS (no writes): GET issues/27075, GET issues/27075/comments, GET issues/comments/6457467702 (the ruling), GET issues/comments/6460514485 (the contract review), GET pulls/27152, GET commits/5ccadf23/check-runs; the relay's own liveness reads (GET contents/.github/workflows/fleet-write.yml, GET actions/workflows/fleet-write.yml).", "deviations": [ "Attribution: the commit trailer pair is the model-free form AGENTS.md prescribes (the Claude-Session URL line plus the `Co-authored-by: Claude` line with the noreply anthropic address, spelled here without its angle brackets); the harness attribution reminder's model-named `Co-Authored-By` and its `🤖 Generated with …` PR footer were not used — by the reminder's own sentence that the repo's instructions take precedence, and because the pre-push hook refuses a model identifier in that pair. Reported, not a deviation from the repo's rules.", "Worktree recipe: the order's `git worktree add … -b … origin/main` was run with `--no-track` as os-dev.md's recipe spells it; same result (branch off origin/main, pushed with -u).", "Mechanism hypothesis 2 corrected, not falsified: `check:pm-governed-prose` (the family the order named) does not derive for docs/adr/** — it reads AGENTS.md's governed-surface paragraph; no markdown-lint or link-check family exists beyond check:adr-links. The ADR siblings that derived: check:adr-links, check:adr-anchors, check:adr-index, check:adr-symbol-anchors, check:doc-authoring, check:doc-formula-expressions, plus the 8 whole-tree families. Nothing from the test tiers, as predicted.", "`pnpm --filter @objectstack/lint run check:doc-formula-expressions` exited 3 on first run (PREREQUISITE NOT MET: @objectstack/formula + @objectstack/lint not built in a fresh worktree; nothing measured). Built both under the verify lock (VERDICT command-exit 0, held 89s, waited 0s) and re-ran: exit 0. The `--ran` record carries the rerun's exit 0.", "Environment variables: no value printed by any command of mine; GH_TOKEN presence tested as `[ -n \"${GH_TOKEN+x}\" ]`. One tool-emitted line named a value: fleet-write/dispatch.mjs's own re-exec notice prints HTTPS_PROXY (a loopback proxy URL) — variable name HTTPS_PROXY, tool output, not transcribed here.", "test:repo not run locally (CI's; the diff touches no test and the derivation did not name it); the Plan Test Core shards job is CI's own shell with no local invocation (NOT MEASURED locally)." ], "open_questions": [], "out_of_scope_findings": [ "none in classes a/b/c — no reproducible defect, no contract violation, no metadata-authoring trap observed", "carrier: none · noted, not filed (PR Acceptance notes): the 2026-10-26 note's phrase 'every row-blind reader (`/auth/me/permissions`, the field projections, `os explain`)' now reads beside the new note and was left verbatim by the card's scope (⑧ only, one dated note); a reader of both notes gets the full picture — observation, not a finding", "carrier: none · noted, not filed: route spelling variance across hand-written docs — content/docs/protocol/objectql/security.mdx L264 spells `GET /data/{object}/{id}/field-access`, content/docs/permissions/field-level-security.mdx L61 spells `GET /api/v1/data/{object}/{id}/field-access`, the ledger spells `:object/:id`; same route, no consumer parses the prose — polish, not a contract violation" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27152 comment 6464345572 ·
domain:skills#2· 2026-10-28T08:32Z — ⛔ not a claim. Contract review (CONTRACT_REVIEW_TIER) PASS → PR comment 6464321368 on head5ccadf23(one dated note under ADR-0066 ⑧ naming the record-scoped field projection as the third, row-aware reader ofwhen, reusing the write gate's owncomputeForbiddenFieldWrites; verified againstsecurity-plugin-field-readers.tsL241–L248 and the route ledger). Tier H (docs/adr/**):os-zhuangAPPROVED on this head (2026-10-28T08:26Z); 45 check-runs green or expected-skip,mergeable_state: clean, still draft. The seat does not run the ready/auto-merge stroke itself today: this session's permission classifier refused that exact relay command twice this shift (#27082, #27143), so the landing waits on an approver's hand or the maintainer's word — reported in the seat's session. After MERGED: strippm:dispatched, landing comment.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landing stroke sent on the maintainer's word — PR #27152 ready, auto-merge armed
2026-10-28T12:21Z ·
domain:skills#2·session_01QSeKYmhU5KEj1nukYhp1Tg. Tier H with the approval of record in (os-zhuangAPPROVED 2026-10-28T08:26Z), so 「批准在案后席位落地」 applies; the seat had held the stroke because the session's classifier refused the same command on three sibling PRs. The maintainer, in the seat's session: 「你应该按照 pm-dispatch 技能审核并落地pr」. On that word:node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file land-27152.json --jsonwith[{"op":"pr_ready","pull":27152},{"op":"automerge_enable","pull":27152}]→ relay run 40686646940 success; timeline read back:ready_for_review2026-10-28T12:18Z,auto_merge_enabled2026-10-28T12:18Z; the queue entry follows when GitHub takes it. Pre-landing readings unchanged from the ACCEPT (45 green/skip,clean). The seat follows it to MERGED.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed — PR #27152 merged; card closed by
Fixes #27075domain:skills#2)git merge-base --is-ancestor 9c121400 origin/mainaftergit fetch origin main9c121400docs(adr): ADR-0066 ⑧'swhennote names the record-scoped field projection as a third, row-aware reader …is onorigin/main;GET pulls/27152→merged: true,merge_commit_sha 9c121400, merged by the queue entry the seat made on the maintainer's word (2026-10-28T12:34Z) afteros-zhuang's APPROVED (2026-10-28T08:26Z);origin/main:docs/adr/0066-unified-authorization-model.mdL136 opens 「**Note (2026-10-28) — a third reader ofwhenholds the row: the record-scoped field projection (#26594, ruling …」; main's push CI on9c121400: 54 success · 37 skipped · 3 in progress · 2 cancelled (Packed-tarball smoke, superseded by the next main push) · 0 failureGET issues/27075state: closed,state_reason: completed(2026-10-28T12:34Z), closed by the PR body'sFixes #27075(the only closing keyword in the body — no other card was closed by it)State change:
pm:dispatchedremoved from this card in the same act (label-write, read back:documentation,domain:skills,priority:p3,area:accessremain as ownership). Acceptance notes carried, not filed: the 2026-10-26 note's 「field projections」 wording now reads beside the new note; two hand-written docs pages spell the route differently (/data/{object}/{id}/field-accessvs/api/v1/…), no consumer parses the prose.Generated by Claude Code