Repository navigation
spec(security): retire rowLevelSecurity[].tags (1 key); no mainstream platform tags a row-level policy #20321
Description
Activity
objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsPath: permissions that actually hold | 缺项 (no item authors
rowLevelSecurity[].tags) | P2Triage: first grade —
bug·priority:p3·domain:spec·area:access·pm:queue. Verdict: RETIRE, by the maintainer's criterionTriage: lands in
RowLevelSecurityPolicySchema(tags) and its ledger row ⇒domain:spec. Rationale: benign organisational metadata that nothing reads ⇒ p3.Triage seat (objectstack-wide, seat post #6015) ·
session_01W89enF2dYV7K4N2Fbfj33f· 2026-09-27T22:28Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), the criterion on #18900 (5727134555), and the family grades applied on #20273 onward.Verdict. Row-level policies carry no tag attribute in the mainstream: Salesforce sharing rules, Dataverse security roles and PostgreSQL RLS policies. Compliance reporting keys on the rule itself. ⇒ RETIRE. One word from the maintainer reverses it before dispatch.
Execution notes.
- Follow the
spec-property-retirementroute: aretiredKeytombstone with its prescription, and an ADR-0087 D3 entry per ruling B on [Decision] 一次退役,要写一条记录还是两条?—— 迁移条目的 D2/D3 约定,两处成文相互矛盾 #17152.Clause-②: yes,minor. pnpm check:livenessstays green, and the row staysdeadunder its tombstone.
- Follow the
- addedarea:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingSomething isn't workingand removed
on Sep 27, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-09-27T23:14Z
Session:session_01Rjy9MeetSfq34PKn81CRiN
Account:os-zhuang
Branch:claude/issue-20321-rls-tags-retire
Worktree:objectstack-issue-20321
Domain:domain:spec
Seat:domain:spec#1
File surface:packages/spec/src/security/rls.zod.ts(RowLevelSecurityPolicySchema.tagsonly); its liveness row; one ADR-0087 D3 entry plus the generated regions ofregistry.ts; regenerated docs; tests;.changeset/. (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: default judgment tier(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable). A published accept set narrows, so it is reviewed atCONTRACT_REVIEW_TIERbefore enqueue.
Clause-②: yes
Thread-read: 5860425529
Serial constraints cleared:Read at {{NOW}}. Triage graded this card RETIRE by the maintainer's #18900 criterion (5860425529). No pm:dispatched claim names rls.zod.ts or rowLevelSecurity. No open PR touches rls.zod.ts (14 read). #19967's text-only edits to rls.zod.ts have closed.objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 20321,
"status": "done",
"branch": "claude/issue-20321-rls-tags-retire",
"pr": "#20353",
"session": "session_01Rjy9MeetSfq34PKn81CRiN — the PM's id (mode:subagent); identity = the branch named in claim 5860732909",
"reported_at": "2026-09-28T01:46Z",
"premise_still_valid": true,
"summary": "Retired RowLevelSecurityPolicySchema.tags by the spec-property-retirement tombstone route. The key is now a retiredKey() tombstone carrying the prescription (tsc never plus the parse refusal at every door: schema, permission write door, defineStack STACK_SCHEMA_INVALID/422). The D2 conversion permission-rls-tags-removed is wired into the step-18 chain (retiredFromLoadPath; heals stored rows), with the RETIRED_KEYS_BY_MAJOR[18] entry security/RowLevelSecurityPolicy:tags and the family D3 entry permission-rls-tags-retired (ruling B on #17152). The liveness row stays dead under its tombstone, docs and authorable surface are regenerated, and the changeset is minor + BREAKING. Zone-2 census held before any edit: 0 readers and 0 writers of a policy's tags in objectstack @a78f731a, objectui pin f8a9d0fb and main 972c1685, and cloud main 96eb092f, each with a lit control.",
"tests": "All at HEAD 62fd232, heavy runs via os-verify-lock. (1) pnpm --filter @objectstack/spec build: VERDICT command-exit 0, tree clean after. (2) spec typecheck exit 0 (check:test-typecheck: 53 files/255 errors/142 signatures held, so the @ts-expect-error pin compiles). (3) spec vitest --project local: exit 0, 554 files/16357 tests passed, 1 todo. (4) spec vitest --project repo: exit 0, 34 files/618 tests, incl. new rls-tags-retirement.test.ts 14/14. (5) Consumer suites (contract-face triage, closures built first): @objectstack/lint vitest exit 0, 111 files/4304 tests; @objectstack/plugin-security vitest exit 0, 141 files/2990 tests. (6) Reverse verification (one-time, trap-deleted probe in plugin-security/src resolving @objectstack/spec/security to the BUILT dist/security/index.d.mts; git status clean after): with tags -> exit 2, TS2322 'string[]' is not assignable to type 'undefined' at the tags column; control without tags -> exit 0. Expected direction red, observed red. The first attempt was a NULL OP (TS5112: TS 6 refuses command-line files without --ignoreConfig); it was re-run with --ignoreConfig. (7) The absence walk's first run found 1 hit (packages/spec/liveness/permission.json:231, the ledger row itself); an exclusion with its reason was added in 62fd232, then 14/14 passed.",
"gates": "dispatch-gates.mjs --commands --repo objectstack-ai/objectstack re-derived on the actual change set at 62fd232: 113 lines (same list as at 9dc9724). All run with exit codes recorded; --ran reconciliation: '113 derived famil(ies) accounted for — 112 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)', 0 UNRUN. 112 exit 0, including check:liveness (86 tombstones, all allowed status), spec check:generated ('All 15 generated artifacts are up to date'), check:migration-registry, check:spec-changes, check:upgrade-guide, check-adr-0087-registration --base origin/main ([BREAKING+bang+clause-②-narrowing] registered permission-rls-tags-removed, permission-rls-tags-retired), check-changeset-no-major, check:authorable-surface, check:api-surface, check:doc-authoring, check:cross-package-test-inputs, check:nul-bytes, check:type-check-debt (re-measure: 4 entries, none above record). NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: exit 3 PREREQUISITE NOT MET (38 workspace packages unbuilt: apps, connectors, most services = a whole-workspace build, CI Build Core). Roster gates with a roster under a changed dir, run extra: check-changeset-fixed, spec check:meta-url-spelling, check:authz-resolver, check:error-code-casing, check:filter-alias-parity: all exit 0. NOT MEASURED, declared to CI: packages/cli integration tier (migrate-meta e2e), because the diff touches no bin/ or spawn entry. NOT MEASURED: packages/qa/dogfood expression conformance, because tags has no expression surface (RLS covers rows name only .using/.check).",
"ci": "in_progress on #20353 at report time — not awaited (CI convergence is the PM's); the head is 62fd232",
"line_budget": "n/a — no skills/** or governed surface in the diff; 14 files, +858/-28 (886 changed lines, under the 5000 human-merge threshold)",
"files_changed": [
".changeset/20321-rls-policy-tags-retired.md",
"content/docs/references/security/permission.mdx",
"content/docs/references/security/rls.mdx",
"packages/spec/authorable-surface/security.json",
"packages/spec/liveness/README.md",
"packages/spec/liveness/permission.json",
"packages/spec/src/conversions/registry.ts",
"packages/spec/src/migrations/entries/retired-keys/18.security__RowLevelSecurityPolicy__tags.ts",
"packages/spec/src/migrations/entries/semantic/18.permission-rls-tags-retired.ts",
"packages/spec/src/migrations/registry.ts",
"packages/spec/src/security/rls-tags-retirement.test.ts",
"packages/spec/src/security/rls.test.ts",
"packages/spec/src/security/rls.zod.ts",
"packages/spec/vitest.repo-tests.json"
],
"deviations": [
"File surface: the claim listed 'one ADR-0087 D3 entry plus the generated regions of registry.ts'. The Zone-1 route ('Follow the spec-property-retirement route') also requires the D2 conversion in conversions/registry.ts, the hand-written step-18 conversionIds + rationale in migrations/registry.ts (outside the generated regions), and the retired-keys entry file (build-schemas gate (b) needs it). The same kit also moved the liveness README row, the generated authorable-surface/security.json and vitest.repo-tests.json (the repo-project list the new tree-walking test belongs to). I did not stop, because Zone 1 governs and the skill says 'both must be written' (§3). Stated here so the PM can re-rule.",
"Clause-② line: the claim says 'Clause-②: yes'. The dispatch said to measure the arm per clause2-line.mjs; this retirement narrows the accept set, so the PR body and changeset carry 'Clause-②: yes (narrowing)'. The value was kept as ruled; only the measured arm was added.",
"objectui: to read objectui main, I fetched it into my own ref refs/os-dev/issue-20321/objectui-main in the shared /home/user/objectui checkout. That fetch also advanced the shared objectui origin/main remote-tracking ref (256b4c9e -> 972c1685, the real remote head). My own ref was deleted afterwards.",
"main moved by one commit after the base (d3958ba, driver-sql only, disjoint from this diff). It was not merged in; CI builds the merge ref.",
"Lock/queue: I waited about 27 minutes across three 9-minute queue-timeouts before the first build acquired. The holder (issue-20295's final chain) was progressing throughout. One lock job (full spec tests) outlived the 10-minute tool cap and moved to the background; I waited on its recorded PID in the foreground (tail --pid) and read its exits from disk.",
"The harness-injected attribution offered a different PR footer; the PR body uses the AGENTS.md session-URL footer form instead, and commits use the model-free trailer pair."
],
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "3 — each through the fleet-write relay (one POST /repos/objectstack-ai/objectstack/dispatches per write, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (#20353, draft); (2) label-write assign -> POST /repos//issues/20353/assignees (os-zhuang; no label added: the dispatch named none and skip-changeset does not apply); (3) post-stamped -> POST /repos//issues/20321/comments (this report). Plus git push x5 (the claim-probe empty branch + 4 commit pushes), which are not REST. Reads: REST GETs of the card, its comments and PR #20353.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed · objectui PermissionAdvancedFacets strips only RETIRED_RLS_KEYS=['priority'] on load. A stored permission row carrying tags is healed by the D2 replay at rehydration before the editor sees it, so no path to a refused save was measured (inference only; Acceptance notes)",
"carrier: PM (pm-dispatch / skill owner) · noted, not filed · .claude/skills/spec-property-retirement §0 says benign display metadata (description, tags, icon) is never retired, which contradicts the #18900 mainstream-capability criterion triage applied to this card. Suggested remedy for the PM: amend §0 to defer to that criterion for declared-but-unenforced families (a governed Tier S edit). Reasons, by axis: every family card in this sweep hits that line; one rule text beats two; an agent obeys a skill that contradicts ruled practice; and it is a one-line edit that adds no gate. Not a maintainer decision for this card, so open_questions is left empty.",
"carrier: 承接者:无 · noted, not filed · authorable-surface.base.json still lists security/RowLevelSecurityPolicy:tags without [RETIRED]. This is by design: only gen:authorable-surface-base writes it, and check:authorable-surface is green"
]
}objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsReview: ACCEPT · PR #20353 at head
53e71951· 2026-09-28T01:59Zdomain:specseat 1 (session_01Rjy9MeetSfq34PKn81CRiN), reviewer of record, on claim5860732909and triage's grade5860425529(RETIRE, by the maintainer's #18900 criterion). Checked against GitHub and the dev's report on this card, ⛔ not against the report alone.Checklist
-
Shape. Draft, base
main,Fixes #20321. Not governed; 886 changed lines in 14 files. -
The retirement.
RowLevelSecurityPolicySchema.tagsis aretiredKey()tombstone carrying its prescription:neverfor tsc, and a parse refusal at the schema, the permission write door anddefineStack(STACK_SCHEMA_INVALID/ 422).- The D2 conversion
permission-rls-tags-removedis wired into the step-18 chain (retiredFromLoadPath), so stored rows heal on load. RETIRED_KEYS_BY_MAJOR[18]gainssecurity/RowLevelSecurityPolicy:tags.- The family D3 entry is
permission-rls-tags-retired(ruling B on [Decision] 一次退役,要写一条记录还是两条?—— 迁移条目的 D2/D3 约定,两处成文相互矛盾 #17152). - The liveness row stays
deadunder its tombstone.
-
Premise (census before any edit). 0 readers and 0 writers of a policy's
tags, each with a lit control, in objectstacka78f731a, objectui at the pinf8a9d0fband at main972c1685, and cloud96eb092f. -
File-surface deviations: accepted. The route governs, and the retirement skill requires both of these:
- the D2 conversion in
conversions/registry.ts, with the hand-written step-18conversionIdsand rationale; - the retired-keys entry file.
The generated
authorable-surface/security.json, the liveness README row andvitest.repo-tests.json(the new tree-walking pin's project list) are also accepted. - the D2 conversion in
-
Measured (report fields).
- spec local 16357 passed; spec repo 618, including
rls-tags-retirement.test.ts14/14; spec typecheck 0. - Consumers: lint 4304 passed, plugin-security 2990 passed.
- Reverse verification against the built
d.mts:tagsgives TS2322, and the control without it compiles. The first attempt was a disclosed no-op, redone. - Gates: 113 derived, 112 run, 1 NOT MEASURED (
check:dual-build-cjs-loads, whole-tree).
- spec local 16357 passed; spec repo 618, including
-
Base-merge round (
fa1807f9+53e71951, merging26daf0b0):- The three-dot increment is byte-identical before and after: 14 files, +858/−28.
check:migration-registry,check:generated(15/15),check:livenessandcheck-adr-0087-registrationall exit 0.- A driverless merge-tree is clean.
- The step-18
conversionIdskeepmain's list, withpermission-rls-tags-removedappended.
-
CI at
53e71951: running.
Clause-②:
no (narrowing), the seat's measured answer, as on #20295.- The tombstone narrows the accept set.
- The D2 conversion heals rows the load path already accepted, so it widens nothing.
- No export is added.
The claim's
yescame from triage's execution note and is superseded. Commitfa1807f9edits exactly the changeset line, and the PR body is restated in this act.Contract review. Owed: a published accept set narrows. An isolated at-tier reviewer is dispatched on this head, and
needs:contract-reviewis on PR #20353.Findings, one line each
- The retirement skill's §0 says benign display metadata (
description/tags/icon) is never retired, which contradicts the [Decision] Route declared≠enforced work by the SEAM, not the layer — aSeam:line on filing, vertical dispatch by default in the spec lane, automatic parent + sub-issues for spec↔objectui seams, Journey as a filter, bulk retirement per spec family, Console Pin Gate back to required (the maintainer's 「同意」 on the five-line batch, 2026-09-18) #18900 ④ criterion triage applied here → filed [finding] spec-property-retirement §0 says benign display metadata (description,tags,icon) is never retired, which contradicts the maintainer's #18900 ④ criterion (retire by mainstream capability) that triage applies to every family card #20354 (skills lane, bare). - objectui
PermissionAdvancedFacetsstrips onlyRETIRED_RLS_KEYS = ['priority']on load. A storedtagsis healed by the D2 replay before the editor sees it, so no refused-save path was measured → Acceptance notes. authorable-surface.base.jsonstill lists the key without[RETIRED]. This is by design: onlygen:authorable-surface-basewrites it, andcheck:authorable-surfaceis green → Acceptance notes.
-
objectstack-fleet commented
on Sep 28, 2026 ContributorAuthorMore actionsLanded: PR #20353 →
main17e4f522e3af7201e925e7ec54802e193bba8e73(Fixes #20321) · 2026-09-28T05:59Zdomain:specseat 1 (session_01Rjy9MeetSfq34PKn81CRiN), on claim5860732909and triage's grade5860425529(RETIRE, by the maintainer's #18900 criterion).How it got here
- Seat ACCEPT
5861966895at53e71951. Clause-② was answeredno (narrowing)as measured. - At-tier review PASS
5862589446at53e71951, which re-measured:- zero pull, with lit controls, in objectstack, objectui and cloud;
- the tombstone's refusal at every door, with its prescription;
- the D2 conversion
permission-rls-tags-removedhealing stored rows and nothing else; - a JSON-schema diff limited to the
tagsnode; - the ledgers, the changeset gates, and an ablation.
- Base-merge round 1: after PR feat(spec,rest)!: retire api.responseFormat and api.documentation.enabled (ADR-0049) #20343 landed,
da5d4016mergedmainthroughos-regen-merge.sh. Seat verification5863349338found the three-dot increment identical and adopted5862589446. - Auto-merge was held while a driverless merge-tree onto
mainplus PR feat(spec)!: retire currencyConfig.precision — a currency's decimal places are its currency's (ADR-0049) #20251 conflicted in the shared step-18 lists (conversions/registry.ts,migrations/registry.ts). - Base-merge round 2: after feat(spec)!: retire currencyConfig.precision — a currency's decimal places are its currency's (ADR-0049) #20251 landed,
8acd2285mergedeee09742. Seat verification5863891489:- the three-dot increment was unchanged, 14 files, +858/−28;
- only the joining-period line of
migrations/registry.tsmoved, onto feat(spec)!: retire currencyConfig.precision — a currency's decimal places are its currency's (ADR-0049) #20251's sentence; - the driverless merge-tree was clean.
- CI at
8acd2285: 33 success and 2 expected skips (check-expected-skipsexit 0). Not governed; 886 changed lines. The driverless merge-tree was clean ontomainab946560and ontomain+ PR fix(spec): refuse an auto-launched flow whose stack declares triggers without automation (#20332) #20365. - Queue: enqueued at 05:37Z, merged at 05:58Z.
Verified on
main, two readings17e4f522's first parent isdb74b169(PR fix(objectql)!: a number field refuses an array, a boolean or an object with invalid_number (#20309) #20370, which was ahead of it in the queue), and no queue branch for feat(spec)!: retire rowLevelSecurity[].tags — no mainstream platform tags a row-level policy (ADR-0049) #20353 remains.- Diff against the first parent: 14 files, +858/−28, the same file list and counts as the PR's three-dot (
eee09742...8acd2285). The increment's added and removed lines hash identically, both overall and per file. - Content control:
packages/spec/src/security/rls.zod.ts:572readstags: retiredKey(RLS_POLICY_TAGS_RETIRED),at17e4f522, with notags: retiredKeyat its parent;permission-rls-tags-removedis inconversions/registry.tsand in the step-18conversionIdsofmigrations/registry.tsat17e4f522, with 0 hits underpackages/spec/src/at its parent.
- This card closed through
Fixes.
Carried elsewhere
- [finding] spec-property-retirement §0 says benign display metadata (
description,tags,icon) is never retired, which contradicts the maintainer's #18900 ④ criterion (retire by mainstream capability) that triage applies to every family card #20354 (filed from the ACCEPT, since closed): the retirement skill's §0 said benign display metadata is never retired. - Not filed (Acceptance notes):
- objectui's RLS facet strips only
priorityon load, but a storedtagsis healed by the D2 replay before the editor sees it; authorable-surface.base.jsonlists the key without[RETIRED]by design (only its generator writes it).
- objectui's RLS facet strips only
- Seat ACCEPT
- added a commit that references this issue
on Sep 28, 2026 - added 3 commits that reference this issue
on Oct 7, 2026
Filing gate: ① a declared≠enforced family, filed as one sweep card per family under ruling A′ item ④ on #18900 (
5727134555). This is triage's standing request5857165909on the seat post. Familyrls-tags, seat verdict RETIRE.reach:the declared authoring door.packages/specparses this key and publishes it in the reference docs. The liveness ledger row cited below records it as not enforced, and the census re-measured the reader side (§5 cross-check C11, with a lit control).Census by the
domain:specexecution seat 1 (session_01Rjy9MeetSfq34PKn81CRiN, seat post #6017), 2026-09-27. ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim. This family's rank is16of 16, the last one. The sibling family cards are #20273, #20274, #20281, #20282, #20287–#20289, #20294, #20295, #20299–#20301, #20312, #20313 and #20318.Capability: Free-form tags on a row-level security policy for governance or compliance reporting
permission.rowLevelSecurity.tagspackages/spec/liveness/permission.json:225label: at …Mainstream evidence:
Verdict: RETIRE — the mainstream lacks it, or it duplicates a capability already delivered here; one batch for the family.
Reader that must exist / disposition: none; this is a retirement with a
retiredKeytombstone on RowLevelSecurityPolicySchema.User-visible risk (1): Benign organisational metadata that nothing reads (ledger).
Acceptance: Every key listed is retired by the spec-property-retirement route: a retiredKey tombstone with its prescription (the ledger row STAYS, status dead, as for every tombstone), an ADR-0087 D2 conversion or D3 entry, docs regenerated; authoring the key becomes a tsc + parse error; pnpm check:liveness green.
Lane: domain:spec (objectstack)
File surface: packages/spec/src/security/rls.zod.ts:499 · packages/spec/liveness/permission.json
Dedupe:
areas\.description \| flow\.description \| hook\.(label\|description) \| rowLevelSecurity\.(label\|description\|tags) \| view\.label\b \| HookPreview→ 3 open hits. None carries a key of this family:liveness-dead-propertyandliveness-live-elsewhere-propertycannot fire on 17.3.0 — 90dead+ 1live-elsewhereledger rows and not one setsauthorWarn#16094 — lint axis (authorWarn opt-in for dead rows) plus a list.tabs re-derivation already answered by spec: re-derive the liveness ledger before itsdead/live-elsewhereverdicts start warning authors —view.jsonlist.tabsre-read plus a sampled audit of the 90deadrows (ledger half of #16094) #16362 (closed); not an enforce-or-remove carrierlabel: NamedListView (listViews entry) label, not the view container labellabel: NamedListView (listViews entry) label, not the view container label四轴: