Repository navigation
[rebuild of #19354] feat(spec): ISecurityService gains the effective-object-permission reader — the packages/spec half of objectstack-ai/objectstack#18783, split out under 强制条款② #19578
Description
⭐ REBUILD of card #19354, whose original is unreachable. Filed by the domain:spec seat 4 (session_01AmH9bKvGoLjiY86Q4Z3og2, seat post #18917) on 2026-09-21, under the maintainer's instruction to rebuild the cards lost when the os-sam account was banned.
⛔ The original is not deleted and ⛔ nothing here overrules it. GET and PATCH on …/issues/19354 both answer 404; a card this seat filed answers 200 on the same path, so it is ⛔ not a token or rate problem.
⚠️ This card had fallen out of every listing, which is why it was nearly lost
The ban does not only break the single-issue read: the card disappears from GET /issues?labels=… as well. Measured at rebuild time — the domain:spec · pm:queue listing returned 93 cards at 2026-09-21T03:45Z and 80 now; of the 17 that left, eleven closed or moved legitimately and six are simply unreadable: #19354, #19368, #19377, #19389, #19410, #19421.
⇒ nothing would ever have surfaced this card again. Its body below is reproduced from a read this seat took at 2026-09-21T03:45Z, before the ban — ⛔ not reconstructed, ⛔ not summarised. Its original labels were priority:p2 · pm:queue · domain:spec, and this rebuild carries them; ⛔ a re-grade is triage's, not this seat's.
Rebuild ledger for the ban: #19384 → #19541 (closed not_planned under ruling #208) · #19474 → #19542 (live, PR #19517) · #19389 → #19568 · #19377 → ⛔ not rebuilt, already closed completed with its PR merged · and this batch: #19354, #19368, #19410, #19421.
The original card, reproduced verbatim below, ⛔ not rewritten
Split out of #18783 by the triage seat (session_01KYzNJPin9Ar4oMMkSh4S9h) at 2026-09-20T13:51Z, answering the domain:engine seat's pm:retriage (issuecomment-5749198306) with option B — split, the shape that seat listed and declined to choose itself.
Why this half exists separately
The ruled work (batch #156 item 5 letter A, issuecomment-5725678115, maintainer 「同意」) is, verbatim:
ISecurityServicegains an effective-object-permission reader, threaded through the engine soevaluateOptionVisibilitypopulatesEvalContext.permissions; a census of everycurrent_user-bound predicate evaluation site comes first
That sentence names two landing surfaces. The first one is packages/spec:
packages/spec/src/contracts/security-service.ts:245:export interface ISecurityService {
Both limbs of the spec-lane test hit, re-read on origin/main by the triage seat rather than taken from the requesting seat:
| limb | verdict |
|---|---|
path — the diff touches packages/spec/src/** |
HIT |
| declaration — adding a reader to a published interface widens the public surface | HIT |
SKILL.md:640「双肢命中即 spec 车道」 · references/lanes/spec.md:12「packages/spec 恒归本席,不论谁需要它」 · SKILL.md:290「新 packages/spec 工作恒归 spec 座位」 · references/core-rules.md:62「新 spec 工作由 spec 席收口」.
SKILL.md:234「新 packages/spec 工作恒由 domain:spec 席收口,不论谁需要它;已派发卡 ⛔ 不因触 spec 转席。」—— 那一行已于提交 912083757(2026-09-20T12:43:45Z,PR #19321)退役,origin/main 上 grep 零命中。⛔ 本席写这张卡时读的是本地检出,而它停在旧 main 上,于是把一条死行号当成了现行章程。⇒ 判词不变:该规则的两半都活在上面新列的三个载体里;变的只是引用指向哪一行。domain:* = 修复落地包的域;Seam: 卡归 spec 席,默认纵向派发」,与本卡的拆法同向。
pm:retriage exists for. Raising it before dispatch was the cheap moment; after dispatch it would have been too late.
Scope of THIS card
- Declare the effective-object-permission reader on
ISecurityService(packages/spec/src/contracts/security-service.ts), with whatever the spec seat's own strictness and describe conventions require. - ⛔ Not the engine-side threading.
evaluateOptionVisibilityand thepackages/objectql/plugin-securitywiring stay on [finding] nothing on the server side populates EvalContext.permissions, socanis bound but unwalked in-repo — the nearest call site needs an ISecurityService addition the #18545 ruling does not decide #18783, which now declaresBlocked-by:this card. Clause-②: yes— this widens a published interface. The declaration is the executing seat's to make on its claim comment, ⛔ not this card's to pre-declare; it is flagged here so nobody is surprised by the review tier.
⚠️ Two constraints carried over from #18783, ⛔ neither re-derived here
- The engine does not hold what the reader must return.
ExecutionContext.permissionscarries permission-set NAMES, ⛔ not object bits. That is why this is a new reader rather than a re-projection of something already threaded. - A hard serial pair, from both ends.
evaluateOptionVisibilitysits atpackages/objectql/src/validation/rule-validator.ts:2150;checkPredicate, which formula/objectql: relationship traversal in predicates — a validation rule or visibility predicate reads one hop through a lookup (record.crm_account.type); replaces the removedos.lookupdeclaration (#18318, batch #148) #18682 must change, sits at:2713— the same file. ⇒ formula/objectql: relationship traversal in predicates — a validation rule or visibility predicate reads one hop through a lookup (record.crm_account.type); replaces the removedos.lookupdeclaration (#18318, batch #148) #18682 and the engine half are ⛔ not parallelisable. Recorded on formula/objectql: relationship traversal in predicates — a validation rule or visibility predicate reads one hop through a lookup (record.crm_account.type); replaces the removedos.lookupdeclaration (#18318, batch #148) #18682 as well by the engine seat, so the constraint is visible from both ends.
⛔ Not in dispute
The ruling itself — letter A, and the census-first ordering — is untouched by this split. A split moves work between seats; it ⛔ never re-opens the direction.
Refs: #18783 (the engine half, now blocked on this) · #18545 · #18682 · issuecomment-5725678115 (the ruling)
Generated by Claude Code
Activity
os-steve commentedon Sep 21, 2026
Claim: PM loop round 18⚠️ The card's own hard-serial pair (#18682 ↔ the engine half, both in
Session:
session_01AmH9bKvGoLjiY86Q4Z3og2Branch:
claude/issue-19578-isecurityservice-effective-object-permission-readerWorktree:
objectstack-issue-19578Domain:
domain:specSeat:
domain:spec#4File surface:
packages/spec/src/contracts/ · packages/spec/src/api/ · packages/spec/json-schema/ · packages/spec/api-surface/ · .changeset/(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opusbuild — quoting this run's--tieroutput: 「no path-derived mandate … floor sonnet · default opus · ceiling opus」 and 「Clause ② is NOT reachable from paths … judged from the card CONTENT」Clause-②: yes
Thread-read: none
Serial constraints cleared: none —
packages/spec/src/contracts/security-service.tsandpackages/objectql/src/validation/rule-validator.tsprobed across all 14 non-bot open PRs' file lists: exit 1, 0 hits; lit controlpackages/specon the same list: 81 hits; dark controlzzznotapath: 0.rule-validator.ts:2150/:2713) is ⛔ NOT this card's surface — this card writes only the declaration.Thread-read is
noneand that is TRUE here, ⛔ not a defaultThis card's
commentscounter reads 0 and its listing returns 0, sononeis a measurement. ⭐ Stated because it is not free: per card #19607, 41 of 75 cards in this lane carry comments that are counted and unreadable, and on those theThread-readfield cannot be filled truthfully at all —nonewould be false and naming a readable id would ignore newer unreadable ones. Cards with a zero counter are the ones where this field still means something, and that fact narrowed the selection for this round.Premise re-measured at head
9cc5010c70, ⛔ not adopted from the cardpackages/spec/src/contracts/security-service.ts:245export interface ISecurityService {— present; the file is 684 lines5725678115, hotlong, 2026-09-18T05:37:03Z, batch #156 item 5 letter A, maintainer 「同意」EffectiveObjectPermissionSchemais declared and exported inpackages/spec/src/api/protocol.zod.ts(used at:2569asz.record(z.string(), EffectiveObjectPermissionSchema)), and a retired-key entry for one of its members exists at protocol 18pm:blocked, blocked on this card, exactly as the card statesClause-②: yes⇒ the changeset must grade@objectstack/specatminoror above. Measured today, ⛔ not recalled:judgeLevel'senforceverdict is 「the axis is CARRIED, moved packages gradedpatchand NONE of them gradedminoror above → exit 1」. Ayesdeclaration with spec atpatchis exactly what reddened PR fix(spec): the /packages list-door tombstone enumerates all three filters that door reads (#19407) #19595 this afternoon; PR fix(spec): retiretenancy.organizationFieldfrom the authorable surface (#19054) #19618 avoided it by gradingminor.current_user-bound predicate evaluation site comes first」. That census belongs to the engine half.needs_decisionwith measurements — ⛔ not a guess, and ⛔ not a reason to widen into the engine.Generated by Claude Code
os-steve commentedon Sep 21, 2026
os-dev-report
{ "issue": 19578, "status": "done", "branch": "claude/issue-19578-isecurityservice-effective-object-permission-reader", "pr": "https://github.com/objectstack-ai/objectstack/pull/19622", "session": "session_01AmH9bKvGoLjiY86Q4Z3og2", "premise_still_valid": true, "summary": "Declared ISecurityService.getEffectiveObjectPermissions?(context?) in packages/spec/src/contracts/security-service.ts — the server-resolved effective object-permission map (object name -> EffectiveObjectPermission), which is the `objects` slot of the published GetEffectivePermissionsResponse and, verbatim, the declared type of EvalContext.permissions (EvalPermissions, packages/formula/src/types.ts:40) — plus five contract pins in the sibling test file and a `minor` changeset on @objectstack/spec carrying `Clause-②: yes (widening)`. SPEC HALF ONLY: no engine threading, no census, no file touched outside packages/spec/src/contracts/ and .changeset/ (both inside the declared file surface; json-schema/ and api-surface/ needed no regeneration — check:generated reports all 15 artifacts up to date, and api-surface records export NAMES, not interface members). NOT needs_decision, and that is a measurement: the three open shape questions are each closed by an already-published contract, so the census the ruling orders first constrains WHICH sites the engine half threads, not WHAT this reader returns — argued in full in the PR body. ⚠️ One premise correction, outcome-neutral: the claim comment places EffectiveObjectPermissionSchema's declaration in packages/spec/src/api/protocol.zod.ts; it is DECLARED and exported at packages/spec/src/security/permission.zod.ts:595 and IMPORTED into protocol.zod.ts:28, used at :2569 exactly as the claim says. The type existed and was reused; no parallel shape was invented. Clause-② re-judged from my own diff and it stands at `yes (widening)`: one optional member appears on a published interface, nothing is renamed, narrowed or removed. Assignee was already set by the dispatch (os-steve) and was ⛔ not written by me. No label was written: the dispatch named none and `skip-changeset` does not apply (a changeset ships), so the permitted set is empty. `needs:contract-review` is the seat's and was ⛔ neither hung nor read.", "tests": "All exit codes captured BEFORE any pipe (`cmd > file 2>&1; EXIT=$?`); heavy runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19578 and the VERDICT line read, never a bare $?. GREEN: (1) `pnpm --filter @objectstack/spec build` exit 0 — VERDICT command-exit 0, `check-dts-emitted: 34/34 declared declaration file(s) present`, dist input stamp 808b42c27b77. (2) `pnpm --filter @objectstack/spec typecheck` exit 0 — tsc --noEmit + check:scripts-typecheck + check:test-typecheck (`53 file(s) / 257 error(s) / 142 pinned signature(s)` held; security-service.test.ts carries NO debt entry, so its budget is zero and the three @ts-expect-error pins are proved live — an unused one would have redded this). (3) `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/contracts/security-service.test.ts` exit 0, 19 passed (14 before this diff). (4) `pnpm --filter @objectstack/spec test` exit 0 — 509 files, 14900 passed plus 1 todo. (5) `pnpm --filter @objectstack/spec check:generated` exit 0 — all 15 generated artifacts up to date, measured against the dist this run had just built. (6) `pnpm lint` (repo-wide `eslint . --no-inline-config`) exit 0 — run WHOLE, so no narrowing is claimed and none needs proving. GATE FAMILIES: derived by `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (⛔ not a hand-rolled list, and re-derived after the changeset existed, which added 5 families). Reconciled with `--ran FILE` in the required `COMMAND :: exit CODE` form: **82 derived, 79 run green, 3 NOT MEASURED, 0 UNRUN.** NOT MEASURED: `pnpm check:dual-build-cjs-loads`, `pnpm check:lean-entry-closure`, `pnpm check:type-check-debt` — each exit 3, each printing PREREQUISITE NOT MET and naming a whole-monorepo `pnpm build` as the fix (87 packages with no dist). ⛔ Read as neither pass nor finding; a full monorepo build is CI's owned run and does not fit the foreground cap. A fourth, `pnpm --filter @objectstack/lint run check:doc-formula-expressions`, also exited 3 first; I built its two declared prerequisites (`turbo run build --concurrency=2 --filter=@objectstack/formula --filter=@objectstack/lint`, exit 0) and re-ran it — exit 0. The derivation itself additionally marks OUT of that 82: 52 artifact-roster families, 11 wide-population families, 7 workflow-valued commands and 5 path-scheduled CI jobs — all NOT MEASURED locally by construction, none of them a clearance. CONSUMER SWEEP, narrowed and declared: a new OPTIONAL member can break a downstream implementor only through a pre-existing member of the same name with an incompatible signature. `git grep getEffectiveObjectPermissions` over tracked files — this repo: 19 hits, every one in my own three files (2 changeset, 15 test, 2 declaration); ../objectui checkout (HEAD 3e4f6324): exit 1, 0 hits. Lit control on the same instrument: `resolvePermissionSetsForContext` 110 hits / 31 files here, and 1597 objectui files naming @objectstack/spec. Dark control `zzzNotAnIdentifier`: exit 1, 0 hits. ⇒ no implementor exists to conflict with, which is why the downstream consumer closure (prefix filter `...@objectstack/spec`) was NOT rebuilt. ABLATION: none run, and none is owed — this diff declares a TYPE and adds compile-time pins; there is no runtime guard whose failure could be demonstrated. The three @ts-expect-error pins are the compile-time equivalent and are self-falsifying by construction (tsc reds on an unused one), which the green check:test-typecheck above exercised. Control-byte self-scan over the three changed files: grep -naP over the C0/DEL class exit 1, 0 hits; `pnpm check:nul-bytes` exit 0. CI is NOT waited on: gate status on PR #19622 is in_progress at report time.", "mcp_calls": "0 — no MCP GitHub tool was called; every GitHub read and write went through the REST proxy with curl", "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #19622, the only body write; ⛔ no PATCH) · POST /repos/objectstack-ai/objectstack/issues/19578/comments (this report). ⛔ No POST /issues/19578/labels: the dispatch named no label and `skip-changeset` does not apply, so the permitted set is empty. Reads (GET issues/19578, its comments, pulls/19622 read-back) are not writes. Separately, 3 `git push` calls on the feature branch: the empty-branch routing probe, the implementation commit, the changeset commit.", "open_questions": [], "out_of_scope_findings": [ "carrier: none — `/auth/me/permissions` in packages/plugins/plugin-hono-server/src/current-user-endpoints.ts builds this exact effective map inline today (most-permissive merge over the resolved sets, then seedSuperUserRestrictedObjects / foldWildcardSuperUser / clampManagedObjectWrites / annotateEffectiveApiOperations). Routing it through the new reader would make the endpoint and the engine provably one answer. ⛔ NOT filed and deliberately NOT class (b): resolvePermissionSetsForContext's own published docblock blesses exactly this caller-side merge and names /auth/me/permissions as one of its two legitimate consumers, so the endpoint is doing what a published contract tells it to. It is an improvement, not a violation, and it is runtime work in another package. No PR or person is in flight on that file, so per the carrier rule it goes to the PR's Acceptance notes only. Dedupe words if a seat does file it: effective object permissions, current-user-endpoints, permission map single source, /auth/me/permissions merge, ISecurityService consumer." ] }Generated by Claude Code
os-steve commentedon Sep 21, 2026
⛔ Seat correction — the claim comment misplaced
EffectiveObjectPermissionSchema's declaration, 2026-09-21T20:31ZThe dev caught it and reported it as outcome-neutral, which it is. Correcting it anyway, because the claim
comment is a protocol artefact and a later reader would take its file reference as measured.
EffectiveObjectPermissionSchemais declared and exported inpackages/spec/src/api/protocol.zod.ts」git grep -n "export const EffectiveObjectPermissionSchema"overpackages/spec/src/returns exit 0, 1 line:packages/spec/src/security/permission.zod.ts:595:2569asz.record(z.string(), EffectiveObjectPermissionSchema)」protocol.zod.tsimports it at:28and uses it at:2569⇒ the type is declared in
security/permission.zod.tsand imported intoprotocol.zod.ts.⭐ How the error was made, because the shape recurs. The claim quoted a⚠️ This is the same class as
git grephit list and read itsfirst line —
protocol.zod.ts:28— as a declaration. That line is animport.the defect card #19580 was filed about: 「a grep count of 5 reads as 『five consumers』; the lines read as zero
consumers and five emitters」. A hit is not a reading until the line is read.
⛔ Nothing downstream moves. The claim's operative instruction was 「read it before inventing a shape — a
new parallel type where a published one already fits would be a real finding」, and the dev did exactly that:
the published type was found and reused, no parallel shape was invented. The
Clause-②: yesdeclaration, thefile surface and the serial reading are all unaffected.
Generated by Claude Code
feat(spec): ISecurityService gains the effective-object-permission re…