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
finding(showcase): seven showcase objects are exposed in navigation with no permission set granting read, and showcase_field_zoo is a detail object with no CRUD grant — the showcase build warns on every run #14453
Observed by the domain:spec seat (session session_01GDA48PuRFrHyRfdkBz8m21) in the CI build log of PR #14449 (#13723) and reported by that PR's dev as pre-existing and unmoved by the change. Filed unassigned for first-touch grading; no severity claimed.
Measured (CI job Dogfood Regression Gate (2/3), @objectstack/example-showcase:build, head e9c424e7; identical on origin/main per the dev's before/after run of objectstack validate — 33 advisory warnings both trees)
⚠ app "showcase_app" · nav "nav_<x>": navigation exposes object "<x>", but no permission set this stack declares grants read on it — the entry renders, and opening it fails for showcase_preference, showcase_expense_report, showcase_team, showcase_category, showcase_business_unit, showcase_field_zoo, showcase_cascade (each with the prescription: add the object to a permission set's objects with allowRead: true / viewAllRecords, or gate the nav entry with requiredPermissions / visible).
⚠ object "showcase_field_zoo": detail object (master_detail "f_master_detail" → "showcase_project") has no object-level CRUD grant in any permission set — rule security-master-detail-ungranted at objects[9].fields.f_master_detail.
Why it is worth a card
The showcase is the reference app; a navigation entry that renders and fails on open is a user-visible defect in the demo, and a permanent advisory on every build hides the next real one. Each warning carries its own prescription, so this is corpus hygiene, not design.
Shape of the work
Read the showcase permission sets and decide per object: grant read (and CRUD for the detail object alongside its master showcase_project) or gate the nav entry; the instrument is the showcase build / objectstack validate warning count dropping by the number of entries fixed. Do not widen into the other advisory classes in the same log.
Landing point read on origin/main at 15:18Z rather than taken from the title: the showcase is examples/app-showcase, and the file the repair edits is examples/app-showcase/src/security/permission-sets.ts (plus the app's nav declarations for any object gated instead of granted).
Why domain:services and not devx. The lane table routes examples/* by the subsystem the example exercises, ⛔ not to a single examples owner. What is broken here is the permission-set model: seven nav entries reference objects no permission set grants read on, and showcase_field_zoo is a detail object with no object-level CRUD grant beside its master. Deciding grant read versus gate the nav entry, per object, is a judgement about the permission and sharing semantics — the domain:services lane owns those (plugin-security / plugin-sharing folded in on 2026-08-19). A seat without that model would be guessing on eight independent calls.
Bug by the mechanical boundary test: nothing widens: a nav entry that renders and then fails on open is declared-not-delivered, and the repair restores agreement.
priority:p3 — the card claims no severity and is right not to. It is the reference app rather than the product, the build stays green (advisory tier), and every warning ships with its own prescription. ⚠️ The reason it is not p4-and-forget is the second-order cost the card names: 33 advisory warnings on every showcase build is where the next real one goes to hide.
Scope is fixed by the card and worth keeping: read the showcase permission sets, decide per object, and ⛔ do not widen into the other advisory classes in the same log. The instrument is the objectstack validate / showcase-build warning count dropping by exactly the number of entries fixed — a countable before/after, so the PR can prove it rather than assert it.
⚠️ Premise age: the reading is from PR #14449's CI log at head e9c424e7 (2026-09-02), corroborated by that dev's before/after objectstack validate run showing 33 advisory warnings on both trees. ⛔ Re-run the validate before editing — the count is the acceptance instrument, so a stale baseline makes the PR unprovable.
Branch: claude/issue-14453-showcase-permission-gaps (pushed, empty, from origin/main at 53cbad9f7)
Worktree: dedicated, per worktree-first discipline.
Re-read the comments before claiming; only the triage comment was present, no competing claim. Per repo convention the assignee field is the PM's — this comment is the claim.
Plan: re-run the showcase build / objectstack validate on origin/main first and quote the actual warning population before touching anything (the card's counts are a lead, not a spec), then decide per object between granting read and gating the nav entry, treating showcase_field_zoo's master-detail grant as a potentially separate disposition.
{
"issue": 14453,
"status": "done",
"branch": "claude/issue-14453-showcase-permission-gaps",
"pr": "https://github.com/objectstack-ai/objectstack/pull/16069",
"premise_still_valid": true,
"summary": "Premise holds and the instrument reproduced, but the card's count is LOW on one of its two classes. Measured on origin/main 53cbad9f7: `pnpm --filter @objectstack/example-showcase build` exits 0 and prints `⚠ 33 author-time warning(s)`; the rule-id histogram sums to exactly 33, so it is the whole population. `nav-object-ungranted` is 7 — the card's 'seven' is exact. `security-master-detail-ungranted` is 3, not 1: the card names only showcase_field_zoo, and the build also names showcase_expense_line and showcase_project_membership. Real population: 10 findings over 9 distinct objects (showcase_field_zoo draws both). A measurement changed the shape of the work: the nav rule's own hint offers three remedies, but validate-nav-access.ts reads neither `requiredPermissions` nor `visible` (probed against the shipped rule with both controls firing), so gating does NOT clear the finding and only grant-or-drop do. Dropping seven working demo surfaces is the silencing failure mode, so all seven are granted — distributed by persona, not pooled: read on the `everyone` baseline for every object the shared nav exposes (navigation is an access claim), reference-data curation on showcase_ops, expense filing and the showcase_project children on showcase_contributor. Teaching point stated in the PR: all nine objects were ALREADY public_read_write (or controlled_by_parent) and still unreachable, because object-level CRUD (gate 1) and the OWD (gate 2) are independent and gate 1 runs first — the only principal it admits ungranted is the platform's wildcard admin, i.e. exactly who an author browses as. Two object doc comments actively taught the opposite ('without a bespoke permission set', 'intentionally absent from the access-matrix snapshot') and were rewritten. After: 23 warnings, both target rules 0, every other class unchanged, no new class. access-matrix.json regenerated: +14 rows, 0 removed, 0 modified, no VAMA bit anywhere, single delete on the ops-held junction. ⚠ HALF-STATE FOR PM: the `skip-changeset` label did NOT land. Two MCP whole-set writes returned success (the title set in the same second call DID take effect, so the call reached GitHub); a comparative read-back via the zero-quota web channel shows no labels at all, with a control proving the parser is live (it reads all four labels off #14453). Whether the label exists in the repo is NOT MEASURED — that page returns 403 and the control query is equally silent. Please apply `skip-changeset` (the diff is examples-only and private, so it publishes nothing); without it `Check Changeset` may go red.",
"tests": "All exit codes captured bare (redirect, then read), never through a pipe; verdict lines quoted from each tool. BASELINE 53cbad9f7: showcase build exit 0, '⚠ 33 author-time warning(s) — see above'; histogram 10 approval / 7 nav-object-ungranted / 5 react-prop / 3 security-master-detail-ungranted / 2 private-no-readscope / 2 liveness / 1 row-color / 1 title / 1 loop / 1 draft = 33, with a negative control (a nonexistent rule id greps 0) and a positive one (nav rule greps 7). AFTER at 00c12c3b6: build exit 0, '⚠ 23 author-time warning(s)'; nav-object-ungranted 0, security-master-detail-ungranted 0, control rule react-prop-deprecated still 5 (grep is live, not matching nothing). Snapshot gate fired as an ERROR before regen: exit 1, '✗ Access matrix drift (14 changes) — capability changes must be reviewed'; regenerated with --update-access-matrix (exit 0), and the tree is stable afterwards (git status on access-matrix.json empty). PIN examples/app-showcase/test/nav-and-detail-grants.test.ts runs the SHIPPED validateNavAccess/validateSecurityPosture against the real stack: exit 0, 'Test Files 1 passed (1) / Tests 4 passed (4)' — 2 assertions + 2 controls that strip a real grant and demand the rule name that object back (matched by content, not array position). It deliberately does not assert 'build exits 0': the build exited 0 on the broken tree too. ABLATION: fix committed FIRST (774832baa), then mutated. Mutation confirmed on disk before reading anything — PRE hash 7573d923875e91f989e2a2ec8869de6e017cce2a equal to the HEAD blob, 14 grant lines removed, MUT hash 9ddcfccb12cdcd7dac268ad69be60dcd4441b6f0 (changed), and a grep count of 0 for the nine reverted objects with an untouched control (showcase_private_note) unchanged at 3. Pin then exit 1, naming all 7 nav offenders and all 3 detail offenders — the RED population equals the reproduction, not a sample. No rebuild leg was needed: the pin resolves lint via a vitest alias to src/ and reads the config export directly, so nothing in it comes from dist. RESTORE via trap '...' EXIT INT TERM with an absolute REPO_ROOT path, spelled `git checkout HEAD -- ABSOLUTE_PATH` so it reads the HEAD blob not a mutated index; proved by blob equality (7573d923... == HEAD blob, neither empty) AND an empty `git diff HEAD`. TYPECHECK exit 0, echoed 'tsc --noEmit' (a real run, not a zero-package match); coverage itself checked with `tsc --noEmit --listFiles` — the new pin and all three edited source files are in the program, and @objectstack/lint resolves to packages/lint/src/* not dist. SUITE: showcase test exit 0, 'Test Files 28 passed (28) / Tests 379 passed (379)'. LINT: repo-wide `pnpm lint` (eslint . --no-inline-config) exit 0 — the whole population, no narrowing argument needed. GATES: dispatch-gates --repo objectstack-ai/objectstack reconciles at 44 families (Reconciliation line, not the 32-family local-gate block). All 44 ran on the first commit, which is how `pnpm check:type-source-resolution` was caught RED — my new lint import resolved its TYPES through dist; fixed with the tsconfig `paths` rule the gate itself prescribes (it explicitly refuses the registry-widening alternative), then 'check-type-source-resolution OK — 125 tsc program(s) across 78 packages scanned'. `pnpm check:dual-build-cjs-loads` first answered exit 3 'PREREQUISITE NOT MET ... ⛔ This is NOT a pass: nothing was measured' — reported as NOT MEASURED, not as a failure; after building the 7 packages it named it is exit 0, '✓ 103 published require entry point(s) across 66 package(s) load'. Final union: all 44 re-run at 00c12c3b6 on a clean tree with the family set re-derived at that head and byte-identical — 44/44 exit 0, no failure output. One earlier union was DISCARDED rather than reported: it was started at 5a31720ea and two comment files were edited under it mid-sweep, so its later gates graded a tree its earlier ones had not; a partly-stale union reads exactly like a clean one, so it was stopped and re-run from scratch. Supporting measurements, each with a control: the nav-gating probe (CONTROL ungranted=1, CONTROL granted=0, PROBE requiredPermissions=1, PROBE visible:false=1); the OWD read-back from the built artifact (0 of the 9 private-or-unset, CONTROL showcase_private_note='private') proving no new security-private-no-readscope could appear; and describeAnchorForbiddenBits (CONTROL read/create/edit on everyone = null, PROBE allowDelete = \"delete/purge/transfer on 'b'\") proving the delete bit belongs on ops rather than the baseline. PR body read back in full over the zero-quota web channel: every H2 section, all 4 tables, all 14 code blocks and the footer survived the sanitizer intact. NOT MEASURED: no app booted and no request issued (runtime is unverified — the 403 is what the rule REPORTS, not something observed); the other example apps (app-crm, app-todo, app-multi-package) were not examined; the other 23 warnings are untouched by card scope; whether `skip-changeset` exists as a repo label (403 on that page, control equally silent); CI's own job conclusions.",
"mcp_calls": "11 — issue_read get + get_comments (#14453), add_issue_comment (claim), search_issues (one targeted de-dup query; REST was 403 in this session and `gh` is absent, so the channel switch is declared here), issue_write create (#16065), create_pull_request (#16069), issue_read get_labels (returned a rate-limit error), issue_write update x2 (label attempts), get_label (rate-limit error), add_issue_comment (this report). Bulk reads went to git and the zero-quota web channel instead.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #16065: `nav-object-ungranted`'s hint prescribes gating the nav entry with `requiredPermissions`/`visible`, but `validateNavAccess` reads neither key, so a gated entry keeps warning — only grant-or-drop clear it. Measured against the shipped rule with both controls firing. De-duped with one targeted search (nearest neighbours #15135, #7544, #4651 are all runtime gating, not the lint hint). No labels applied, no assignee — left for PM triage."
]
}
Claim: PM loop round R1 — the clause-② declaration in the documented shape
Session: session_01XpTx2tbq3pZRYAdoGt6E6Y
Branch: claude/issue-14453-showcase-permission-gaps
Domain: domain:services
File surface: examples/app-showcase/
Container & model: M, mode:subagent
Clause-②: no — the entire diff is inside @objectstack/example-showcase, which is private: true and is never published; no platform package is touched
Serial constraints cleared: none in flight on examples/app-showcase
Filed ahead of review. Same carrier defect as the five cards @os-zhuang named; root cause is mine and is documented on #16097 (5557652999).
My own reading, measured rather than assumed: the local merge-base diff is nine files, all under examples/app-showcase/ except a three-line pnpm-lock.yaml touch — permission sets, two object definitions, an access matrix, a test and its config. examples/app-showcase/package.json declares "private": true, so nothing here is published to a registry and no consumer can import it. It grants permissions within a demo app; it moves no accept/reject boundary the platform enforces and widens no public surface ⇒ no.
⚠️ Not yet PM-verified — this comment fixes the carrier only and is ⛔ not an acceptance of PR #16069.
domain:services PM seat · claim carrier filed at the documented spelling before the review
{
"issue": 14453,
"status": "done",
"branch": "claude/issue-14453-showcase-permission-gaps",
"pr": "https://github.com/objectstack-ai/objectstack/pull/16069",
"premise_still_valid": true,
"summary": "Not a new card — this round pushes the existing draft PR 16069 to green. Merged origin/main (dacb73f4f) into the branch, then repaired the Dogfood Regression Gate (3/3) failure. Cause: packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts is DERIVED from examples/app-showcase/access-matrix.json, so the 14 grant rows this PR added pulled 9 previously-unreachable objects into the sweep, and its PAYLOAD table had no create-payload builder for any of them (PAYLOAD[object] is not a function). Grew the fixture instead of shrinking the sweep: enumerated the OBJECTS (20) vs PAYLOAD (11) key sets and filled all 9 of the difference, seeded the two new lookup parents, generalised the EDIT probe so the pure junction object can name its own field/value pair, and re-stated the verdict census arithmetic row by row (54/54 to 87/77). File surface expanded from the dispatch fence examples/app-showcase/ into packages/qa/dogfood/ — authorised by the maintainer's direction to follow this PR through to merge, and declared in the PR body in its own section. Clause 2 stays 'no': @objectstack/example-showcase and @objectstack/dogfood are both private:true, no published package's manifest or output moves, skip-changeset label already on the PR and confirmed present. Head is now 2f5268f29.",
"tests": "All at final head 2f5268f29, on a clean tree; every exit code captured on the statement after a single redirect, never through a pipe. DOGFOOD, all three shards (not only the failing one, since a new PAYLOAD entry can move a sibling shard): 'pnpm --filter @objectstack/dogfood test --shard=1/3' EXIT=0, 'Test Files 45 passed (45)' / 'Tests 345 passed (345)'; '--shard=2/3' EXIT=0, 'Test Files 44 passed (44)' / 'Tests 288 passed | 1 skipped (289)'; '--shard=3/3' EXIT=0, 'Test Files 43 passed | 1 skipped (44)' / 'Tests 404 passed | 2 skipped (406)' — the reported failure was '1 failed | 42 passed | 1 skipped (44)'. Each run echoed '> vitest run --shard=N/3', so the argument really reached vitest. TARGETED PROOF the repaired file itself ran: 'pnpm --filter @objectstack/dogfood exec vitest run --reporter=verbose test/showcase-crud-persona-matrix.dogfood.test.ts' EXIT=0, 'Test Files 1 passed (1)' / 'Tests 46 passed (46)', with all 14 new rows named individually in the verbose output (e.g. 'showcase_ops × showcase_project_membership: all four verbs answer the matrix (baseline-unioned)') plus the census, baseline-flip and exclusion assertions. TYPECHECK: 'pnpm --filter @objectstack/dogfood typecheck' EXIT=0, echoed 'tsc --noEmit'; coverage checked rather than assumed — 'tsc --noEmit --listFiles' has exactly 1 hit for showcase-crud-persona-matrix.dogfood.test.ts, so the green is about the edited file and not about a config that excludes tests. SHOWCASE, the PR's original result preserved: 'pnpm --filter @objectstack/example-showcase build' EXIT=0, its own verdict line '⚠ 23 author-time warning(s) — see above', grep count for nav-object-ungranted = 0 and for security-master-detail-ungranted = 0; rule histogram sums to 23 (10 approval-approvers-may-resolve-empty, 5 react-prop-deprecated, 2 security-private-no-readscope, 2 liveness-planned-property, 1 view, 1 title-unresolvable, 1 flow-loop-body-uncontained, 1 flow-draft-status-ambiguous). 'pnpm --filter @objectstack/example-showcase typecheck' EXIT=0 (echoed 'tsc --noEmit'); 'pnpm --filter @objectstack/example-showcase test' EXIT=0, 'Test Files 28 passed (28)' / 'Tests 379 passed (379)'. GATES: family set re-derived on the final head with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (stderr confirms 'derived from the tree of objectstack-ai/objectstack at commit 2f5268f29'), Reconciliation line 58 families; harvested runnably from --commands (58 lines) rather than from the prose so no section could be dropped, and all 58 run with exit codes recorded one per line. Result 58/58 exit 0. One of them needed a second reading and is reported as such: 'pnpm check:type-check-debt' first returned EXIT=3 with the gate's own text '⛔ This is NOT a pass and NOT a finding: nothing was measured' after a V8 heap OOM at the 4096 MB NODE_OPTIONS ceiling; re-run with --max-old-space-size=10240 it returned EXIT=0 and printed 'check-type-check-coverage --re-measure: OK — 12 ledger entr(ies) re-measured in 100.3s, 140 raw tsc error(s) total, none above its recorded number'. ESLINT: the repo-wide population, not a narrowing — 'pnpm lint' (node --stack-size=4000 eslint . --no-inline-config) EXIT=0, no findings. CONTROL BYTES: 'pnpm check:nul-bytes' green inside the 58, plus a direct scan of the edited file, grep -naP over the control-byte class, exit 1 (no hits). Every heavy command ran through 'bash scripts/pm/os-verify-lock.sh' and the conclusions are read from its VERDICT lines, not from a bare $?. NOT MEASURED: no browser or live server was driven, no other example app was built, and CI's own job conclusions are not predicted here.",
"mcp_calls": "5 — pull_request_read(get) x2 (one before the body edit, one read-back), update_pull_request x1, add_issue_comment x1, issue_read(get_comments) x1 for the comment read-back. REST was probed first in-container and answered HTTP 403, and no gh CLI is present, so the write channel is MCP by measurement rather than by preference.",
"open_questions": [],
"out_of_scope_findings": []
}
Left untouched: bug, domain:services, priority:p3 — ownership, priority and outcome are not state claims.
The label set was read back after the write and matched.
A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34055667490 · trigger schedule
Activity
os-zhuang commentedon Sep 4, 2026
Triage: lands in
examples/app-showcase⇒ routeddomain:services. GradedBug·priority:p3.Landing point read on
origin/mainat 15:18Z rather than taken from the title: the showcase isexamples/app-showcase, and the file the repair edits isexamples/app-showcase/src/security/permission-sets.ts(plus the app's nav declarations for any object gated instead of granted).Why
domain:servicesand notdevx. The lane table routesexamples/*by the subsystem the example exercises, ⛔ not to a single examples owner. What is broken here is the permission-set model: seven nav entries reference objects no permission set grants read on, andshowcase_field_zoois a detail object with no object-level CRUD grant beside its master. Deciding grant read versus gate the nav entry, per object, is a judgement about the permission and sharing semantics — thedomain:serviceslane owns those (plugin-security/plugin-sharingfolded in on 2026-08-19). A seat without that model would be guessing on eight independent calls.Bugby the mechanical boundary test: nothing widens: a nav entry that renders and then fails on open is declared-not-delivered, and the repair restores agreement.priority:p3— the card claims no severity and is right not to. It is the reference app rather than the product, the build stays green (advisory tier), and every warning ships with its own prescription.Scope is fixed by the card and worth keeping: read the showcase permission sets, decide per object, and ⛔ do not widen into the other advisory classes in the same log. The instrument is the
objectstack validate/ showcase-build warning count dropping by exactly the number of entries fixed — a countable before/after, so the PR can prove it rather than assert it.e9c424e7(2026-09-02), corroborated by that dev's before/afterobjectstack validaterun showing 33 advisory warnings on both trees. ⛔ Re-run the validate before editing — the count is the acceptance instrument, so a stale baseline makes the PR unprovable.Generated by Claude Code
os-warren commentedon Sep 5, 2026
Claiming this card.
https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Yclaude/issue-14453-showcase-permission-gaps(pushed, empty, fromorigin/mainat53cbad9f7)Re-read the comments before claiming; only the triage comment was present, no competing claim. Per repo convention the assignee field is the PM's — this comment is the claim.
Plan: re-run the showcase build /
objectstack validateonorigin/mainfirst and quote the actual warning population before touching anything (the card's counts are a lead, not a spec), then decide per object between granting read and gating the nav entry, treatingshowcase_field_zoo's master-detail grant as a potentially separate disposition.Generated by Claude Code
os-warren commentedon Sep 5, 2026
os-dev-report
{ "issue": 14453, "status": "done", "branch": "claude/issue-14453-showcase-permission-gaps", "pr": "https://github.com/objectstack-ai/objectstack/pull/16069", "premise_still_valid": true, "summary": "Premise holds and the instrument reproduced, but the card's count is LOW on one of its two classes. Measured on origin/main 53cbad9f7: `pnpm --filter @objectstack/example-showcase build` exits 0 and prints `⚠ 33 author-time warning(s)`; the rule-id histogram sums to exactly 33, so it is the whole population. `nav-object-ungranted` is 7 — the card's 'seven' is exact. `security-master-detail-ungranted` is 3, not 1: the card names only showcase_field_zoo, and the build also names showcase_expense_line and showcase_project_membership. Real population: 10 findings over 9 distinct objects (showcase_field_zoo draws both). A measurement changed the shape of the work: the nav rule's own hint offers three remedies, but validate-nav-access.ts reads neither `requiredPermissions` nor `visible` (probed against the shipped rule with both controls firing), so gating does NOT clear the finding and only grant-or-drop do. Dropping seven working demo surfaces is the silencing failure mode, so all seven are granted — distributed by persona, not pooled: read on the `everyone` baseline for every object the shared nav exposes (navigation is an access claim), reference-data curation on showcase_ops, expense filing and the showcase_project children on showcase_contributor. Teaching point stated in the PR: all nine objects were ALREADY public_read_write (or controlled_by_parent) and still unreachable, because object-level CRUD (gate 1) and the OWD (gate 2) are independent and gate 1 runs first — the only principal it admits ungranted is the platform's wildcard admin, i.e. exactly who an author browses as. Two object doc comments actively taught the opposite ('without a bespoke permission set', 'intentionally absent from the access-matrix snapshot') and were rewritten. After: 23 warnings, both target rules 0, every other class unchanged, no new class. access-matrix.json regenerated: +14 rows, 0 removed, 0 modified, no VAMA bit anywhere, single delete on the ops-held junction. ⚠ HALF-STATE FOR PM: the `skip-changeset` label did NOT land. Two MCP whole-set writes returned success (the title set in the same second call DID take effect, so the call reached GitHub); a comparative read-back via the zero-quota web channel shows no labels at all, with a control proving the parser is live (it reads all four labels off #14453). Whether the label exists in the repo is NOT MEASURED — that page returns 403 and the control query is equally silent. Please apply `skip-changeset` (the diff is examples-only and private, so it publishes nothing); without it `Check Changeset` may go red.", "tests": "All exit codes captured bare (redirect, then read), never through a pipe; verdict lines quoted from each tool. BASELINE 53cbad9f7: showcase build exit 0, '⚠ 33 author-time warning(s) — see above'; histogram 10 approval / 7 nav-object-ungranted / 5 react-prop / 3 security-master-detail-ungranted / 2 private-no-readscope / 2 liveness / 1 row-color / 1 title / 1 loop / 1 draft = 33, with a negative control (a nonexistent rule id greps 0) and a positive one (nav rule greps 7). AFTER at 00c12c3b6: build exit 0, '⚠ 23 author-time warning(s)'; nav-object-ungranted 0, security-master-detail-ungranted 0, control rule react-prop-deprecated still 5 (grep is live, not matching nothing). Snapshot gate fired as an ERROR before regen: exit 1, '✗ Access matrix drift (14 changes) — capability changes must be reviewed'; regenerated with --update-access-matrix (exit 0), and the tree is stable afterwards (git status on access-matrix.json empty). PIN examples/app-showcase/test/nav-and-detail-grants.test.ts runs the SHIPPED validateNavAccess/validateSecurityPosture against the real stack: exit 0, 'Test Files 1 passed (1) / Tests 4 passed (4)' — 2 assertions + 2 controls that strip a real grant and demand the rule name that object back (matched by content, not array position). It deliberately does not assert 'build exits 0': the build exited 0 on the broken tree too. ABLATION: fix committed FIRST (774832baa), then mutated. Mutation confirmed on disk before reading anything — PRE hash 7573d923875e91f989e2a2ec8869de6e017cce2a equal to the HEAD blob, 14 grant lines removed, MUT hash 9ddcfccb12cdcd7dac268ad69be60dcd4441b6f0 (changed), and a grep count of 0 for the nine reverted objects with an untouched control (showcase_private_note) unchanged at 3. Pin then exit 1, naming all 7 nav offenders and all 3 detail offenders — the RED population equals the reproduction, not a sample. No rebuild leg was needed: the pin resolves lint via a vitest alias to src/ and reads the config export directly, so nothing in it comes from dist. RESTORE via trap '...' EXIT INT TERM with an absolute REPO_ROOT path, spelled `git checkout HEAD -- ABSOLUTE_PATH` so it reads the HEAD blob not a mutated index; proved by blob equality (7573d923... == HEAD blob, neither empty) AND an empty `git diff HEAD`. TYPECHECK exit 0, echoed 'tsc --noEmit' (a real run, not a zero-package match); coverage itself checked with `tsc --noEmit --listFiles` — the new pin and all three edited source files are in the program, and @objectstack/lint resolves to packages/lint/src/* not dist. SUITE: showcase test exit 0, 'Test Files 28 passed (28) / Tests 379 passed (379)'. LINT: repo-wide `pnpm lint` (eslint . --no-inline-config) exit 0 — the whole population, no narrowing argument needed. GATES: dispatch-gates --repo objectstack-ai/objectstack reconciles at 44 families (Reconciliation line, not the 32-family local-gate block). All 44 ran on the first commit, which is how `pnpm check:type-source-resolution` was caught RED — my new lint import resolved its TYPES through dist; fixed with the tsconfig `paths` rule the gate itself prescribes (it explicitly refuses the registry-widening alternative), then 'check-type-source-resolution OK — 125 tsc program(s) across 78 packages scanned'. `pnpm check:dual-build-cjs-loads` first answered exit 3 'PREREQUISITE NOT MET ... ⛔ This is NOT a pass: nothing was measured' — reported as NOT MEASURED, not as a failure; after building the 7 packages it named it is exit 0, '✓ 103 published require entry point(s) across 66 package(s) load'. Final union: all 44 re-run at 00c12c3b6 on a clean tree with the family set re-derived at that head and byte-identical — 44/44 exit 0, no failure output. One earlier union was DISCARDED rather than reported: it was started at 5a31720ea and two comment files were edited under it mid-sweep, so its later gates graded a tree its earlier ones had not; a partly-stale union reads exactly like a clean one, so it was stopped and re-run from scratch. Supporting measurements, each with a control: the nav-gating probe (CONTROL ungranted=1, CONTROL granted=0, PROBE requiredPermissions=1, PROBE visible:false=1); the OWD read-back from the built artifact (0 of the 9 private-or-unset, CONTROL showcase_private_note='private') proving no new security-private-no-readscope could appear; and describeAnchorForbiddenBits (CONTROL read/create/edit on everyone = null, PROBE allowDelete = \"delete/purge/transfer on 'b'\") proving the delete bit belongs on ops rather than the baseline. PR body read back in full over the zero-quota web channel: every H2 section, all 4 tables, all 14 code blocks and the footer survived the sanitizer intact. NOT MEASURED: no app booted and no request issued (runtime is unverified — the 403 is what the rule REPORTS, not something observed); the other example apps (app-crm, app-todo, app-multi-package) were not examined; the other 23 warnings are untouched by card scope; whether `skip-changeset` exists as a repo label (403 on that page, control equally silent); CI's own job conclusions.", "mcp_calls": "11 — issue_read get + get_comments (#14453), add_issue_comment (claim), search_issues (one targeted de-dup query; REST was 403 in this session and `gh` is absent, so the channel switch is declared here), issue_write create (#16065), create_pull_request (#16069), issue_read get_labels (returned a rate-limit error), issue_write update x2 (label attempts), get_label (rate-limit error), add_issue_comment (this report). Bulk reads went to git and the zero-quota web channel instead.", "open_questions": [], "out_of_scope_findings": [ "filed as #16065: `nav-object-ungranted`'s hint prescribes gating the nav entry with `requiredPermissions`/`visible`, but `validateNavAccess` reads neither key, so a gated entry keeps warning — only grant-or-drop clear it. Measured against the shipped rule with both controls firing. De-duped with one targeted search (nearest neighbours #15135, #7544, #4651 are all runtime gating, not the lint hint). No labels applied, no assignee — left for PM triage." ] }Generated by Claude Code
os-warren commentedon Sep 6, 2026
Claim: PM loop round R1 — the clause-② declaration in the documented shape
Session:
session_01XpTx2tbq3pZRYAdoGt6E6YBranch:
claude/issue-14453-showcase-permission-gapsDomain:
domain:servicesFile surface:
examples/app-showcase/Container & model: M,
mode:subagentClause-②: no — the entire diff is inside
@objectstack/example-showcase, which isprivate: trueand is never published; no platform package is touchedSerial constraints cleared: none in flight on
examples/app-showcaseFiled ahead of review. Same carrier defect as the five cards @os-zhuang named; root cause is mine and is documented on #16097 (5557652999).
My own reading, measured rather than assumed: the local merge-base diff is nine files, all under
examples/app-showcase/except a three-linepnpm-lock.yamltouch — permission sets, two object definitions, an access matrix, a test and its config.examples/app-showcase/package.jsondeclares"private": true, so nothing here is published to a registry and no consumer can import it. It grants permissions within a demo app; it moves no accept/reject boundary the platform enforces and widens no public surface ⇒no.domain:servicesPM seat · claim carrier filed at the documented spelling before the reviewGenerated by Claude Code
os-warren commentedon Sep 6, 2026
os-dev-report
{ "issue": 14453, "status": "done", "branch": "claude/issue-14453-showcase-permission-gaps", "pr": "https://github.com/objectstack-ai/objectstack/pull/16069", "premise_still_valid": true, "summary": "Not a new card — this round pushes the existing draft PR 16069 to green. Merged origin/main (dacb73f4f) into the branch, then repaired the Dogfood Regression Gate (3/3) failure. Cause: packages/qa/dogfood/test/showcase-crud-persona-matrix.dogfood.test.ts is DERIVED from examples/app-showcase/access-matrix.json, so the 14 grant rows this PR added pulled 9 previously-unreachable objects into the sweep, and its PAYLOAD table had no create-payload builder for any of them (PAYLOAD[object] is not a function). Grew the fixture instead of shrinking the sweep: enumerated the OBJECTS (20) vs PAYLOAD (11) key sets and filled all 9 of the difference, seeded the two new lookup parents, generalised the EDIT probe so the pure junction object can name its own field/value pair, and re-stated the verdict census arithmetic row by row (54/54 to 87/77). File surface expanded from the dispatch fence examples/app-showcase/ into packages/qa/dogfood/ — authorised by the maintainer's direction to follow this PR through to merge, and declared in the PR body in its own section. Clause 2 stays 'no': @objectstack/example-showcase and @objectstack/dogfood are both private:true, no published package's manifest or output moves, skip-changeset label already on the PR and confirmed present. Head is now 2f5268f29.", "tests": "All at final head 2f5268f29, on a clean tree; every exit code captured on the statement after a single redirect, never through a pipe. DOGFOOD, all three shards (not only the failing one, since a new PAYLOAD entry can move a sibling shard): 'pnpm --filter @objectstack/dogfood test --shard=1/3' EXIT=0, 'Test Files 45 passed (45)' / 'Tests 345 passed (345)'; '--shard=2/3' EXIT=0, 'Test Files 44 passed (44)' / 'Tests 288 passed | 1 skipped (289)'; '--shard=3/3' EXIT=0, 'Test Files 43 passed | 1 skipped (44)' / 'Tests 404 passed | 2 skipped (406)' — the reported failure was '1 failed | 42 passed | 1 skipped (44)'. Each run echoed '> vitest run --shard=N/3', so the argument really reached vitest. TARGETED PROOF the repaired file itself ran: 'pnpm --filter @objectstack/dogfood exec vitest run --reporter=verbose test/showcase-crud-persona-matrix.dogfood.test.ts' EXIT=0, 'Test Files 1 passed (1)' / 'Tests 46 passed (46)', with all 14 new rows named individually in the verbose output (e.g. 'showcase_ops × showcase_project_membership: all four verbs answer the matrix (baseline-unioned)') plus the census, baseline-flip and exclusion assertions. TYPECHECK: 'pnpm --filter @objectstack/dogfood typecheck' EXIT=0, echoed 'tsc --noEmit'; coverage checked rather than assumed — 'tsc --noEmit --listFiles' has exactly 1 hit for showcase-crud-persona-matrix.dogfood.test.ts, so the green is about the edited file and not about a config that excludes tests. SHOWCASE, the PR's original result preserved: 'pnpm --filter @objectstack/example-showcase build' EXIT=0, its own verdict line '⚠ 23 author-time warning(s) — see above', grep count for nav-object-ungranted = 0 and for security-master-detail-ungranted = 0; rule histogram sums to 23 (10 approval-approvers-may-resolve-empty, 5 react-prop-deprecated, 2 security-private-no-readscope, 2 liveness-planned-property, 1 view, 1 title-unresolvable, 1 flow-loop-body-uncontained, 1 flow-draft-status-ambiguous). 'pnpm --filter @objectstack/example-showcase typecheck' EXIT=0 (echoed 'tsc --noEmit'); 'pnpm --filter @objectstack/example-showcase test' EXIT=0, 'Test Files 28 passed (28)' / 'Tests 379 passed (379)'. GATES: family set re-derived on the final head with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (stderr confirms 'derived from the tree of objectstack-ai/objectstack at commit 2f5268f29'), Reconciliation line 58 families; harvested runnably from --commands (58 lines) rather than from the prose so no section could be dropped, and all 58 run with exit codes recorded one per line. Result 58/58 exit 0. One of them needed a second reading and is reported as such: 'pnpm check:type-check-debt' first returned EXIT=3 with the gate's own text '⛔ This is NOT a pass and NOT a finding: nothing was measured' after a V8 heap OOM at the 4096 MB NODE_OPTIONS ceiling; re-run with --max-old-space-size=10240 it returned EXIT=0 and printed 'check-type-check-coverage --re-measure: OK — 12 ledger entr(ies) re-measured in 100.3s, 140 raw tsc error(s) total, none above its recorded number'. ESLINT: the repo-wide population, not a narrowing — 'pnpm lint' (node --stack-size=4000 eslint . --no-inline-config) EXIT=0, no findings. CONTROL BYTES: 'pnpm check:nul-bytes' green inside the 58, plus a direct scan of the edited file, grep -naP over the control-byte class, exit 1 (no hits). Every heavy command ran through 'bash scripts/pm/os-verify-lock.sh' and the conclusions are read from its VERDICT lines, not from a bare $?. NOT MEASURED: no browser or live server was driven, no other example app was built, and CI's own job conclusions are not predicted here.", "mcp_calls": "5 — pull_request_read(get) x2 (one before the body edit, one read-back), update_pull_request x1, add_issue_comment x1, issue_read(get_comments) x1 for the comment read-back. REST was probed first in-container and answered HTTP 403, and no gh CLI is present, so the write channel is MCP by measurement rather than by preference.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
github-actions commentedon Sep 6, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:dispatched.3e270d4e29, merged intomain.bug,domain:services,priority:p3— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34055667490 · trigger
scheduleGenerated by Claude Code