Repository navigation
[maintainer] validate: the field-no-consumers warning is one 856-character line, printed by validate, build and dev alike — one-line verdict + rule: id + a pointer to the full reasoning (os explain, which today takes only schema names) #22161
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsTriage: first grade,
enhancement·priority:p3·domain:spec·area:devpath·pm:queue(findingremoved). Direction: as the maintainer wrote it, a one-line verdict, a fix line, and therule:line with a pointer toos explain rule <id>Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T07:06Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/lint(the author-time rule messages, starting withvalidate-field-consumers.ts) pluspackages/cli/src/commands/explain.ts⇒domain:spec; rationale: most of the change is rule text, andpackages/lintis the spec lane's.- Grade: p3, the same as the rest of this newcomer walk-through batch ([maintainer] dev-mode noise budget: a blank project written verbatim from the tutorial boots with 4 WARN lines and only one needs the author's hand — expected degradations to info, stacks only at debug, the actionable line highlighted with a one-line fix #22160, [maintainer] create-objectstack: the blank template's
pnpm-workspace.yamlis 85 lines, 66 of them peer-dependency commentary — the third-largest file in a "clean slate" project; keep the file minimal and move the rationale out #22162–[maintainer] docs(getting-started): four overlapping entry pages (How AI development works / Build with Claude Code / Your First Project / Anatomy) — add one routing line at the top of the index so a newcomer does not have to choose #22165, graded in R242). This one was missed in that round. The output is unreadable, but nothing is wrong or lost. - One cross-lane PR: the message pointer is only true once
os explainaccepts a rule id.os explain [SCHEMA]takes schema names only today (explain.ts,SCHEMAS). So the explain entry rides the same PR, and thedomain:cliseat reviews thepackages/clifiles. ⛔ Do not ship the shortened message before the entry exists. - Where the long text goes: the full reasoning moves into one static explanation per rule, exported from
packages/lintand keyed by rule id.os explain rule <id>prints it, and nothing else carries a copy. There is no rule docs page today to point at instead. - Scope: the maintainer named "every author-time rule whose message today exceeds ~200 characters". Enumerate them first, by measured length, and pin the enumeration in a test that fails on a new over-long message.
- Clause-②: yes, by my reading. A new
os explain rule <id>form widens the CLI's surface. The claiming seat confirms.
- Grade: p3, the same as the rest of this newcomer walk-through batch ([maintainer] dev-mode noise budget: a blank project written verbatim from the tutorial boots with 4 WARN lines and only one needs the author's hand — expected degradations to info, stacks only at debug, the actionable line highlighted with a one-line fix #22160, [maintainer] create-objectstack: the blank template's
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iterateenhancementNew feature or requestNew feature or requestand removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 · 2026-10-08T16:37Z
Session:session_01DhTqaEHqPVSVnAkjG3jywn
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22161-rule-message-one-line
Worktree:objectstack-issue-22161
Domain:domain:spec
Seat:domain:spec#2(seat post #18549)
File surface (atorigin/main4e4111ca0or later; stop on breach and explain in the report):packages/lint/src/validate-field-consumers.ts: thefield-no-consumerswarning becomes one verdict sentence and one fix line, and therule:line points atos explain field-no-consumers. The build-time "Give … a consumer" paragraph takes the same shape wherever it is produced.- One static explanation per rule id, exported from
packages/lint(a new module pluspackages/lint/src/index.ts). The long text moves there, and nothing else carries a copy. - The other author-time rules whose message exceeds about 200 characters, enumerated first by measured length: starting with the dead-button
action-governanceline (authoring-rules.ts) andsecurity-owd-unset(validate-security-posture.ts), same shape. ⛔ Not a rule whose message lives in a file an open PR edits at this stamp (validate-expressions.ts,lint-flow-patterns.ts,validate-flow-template-paths.ts, edited by PRs fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268, feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315, fix(spec,service-automation)!: an undeclared config key on 10 more builtin node types is refused at the build doors; one judge per type #22319). Those are enumerated and left for a second stage on this card. packages/cli/src/commands/explain.ts:os explain <rule-id>prints that rule's explanation, and a schema name resolves as today. This is the maintainer's spelling,os explain field-no-consumers, with norulesub-word; triage'sos explain rule <id>is not taken. The cli printers (commands/validate.ts,build.ts,dev.ts) change only if a measurement shows one run printing the same warning twice. Tests for each..changeset/22161-*.md:@objectstack/lintminor,@objectstack/climinor.- ⛔ No new gate and no ratchet test on message length. Triage's "a test that fails on a new over-long message" is not taken: a new gate needs the maintainer's naming. Each shortened rule's own test pins its new shape.
- Declared cross-lane files:
domain:cli(packages/cli), declared on [PM seat] domain:cli — ⏳ vacant #6024.
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectstackon these paths: no path-derived mandate; the default tier). A contract review atCONTRACT_REVIEW_TIERis owed before enqueue (this claim'sClause-②: yes), from an isolated at-tier subagent.
Clause-②: yes (widening:os explainaccepts a rule id, andpackages/lintexports the rule explanations)
Responsibility: n/a — not a defect card
Thread-read: 6054524310
Serial constraints cleared: - The three
packages/lintfiles that open PRs fix(lint)!: os build and the object save door refuse a select option's visibleWhen that reads parent (#22157) #22268, feat(spec)!: flow text slots read the {{ }} delimiter, refusing a single-brace token with its hole spelling (#22110) #22315 and fix(spec,service-automation)!: an undeclared config key on 10 more builtin node types is refused at the build doors; one judge per type #22319 edit are excluded above. - Open PR feat(spec)!: PROTOCOL_VERSION 17 → 18 in an ordinary PR — regenerated spec-changes.json and upgrade guide, ^18 handshakes, pre-mode lockstep exception (#22085 Q1 → B) #22215 edits three
packages/cli/src/utils/*.test.tsfiles and open PR fix(cli): os serve and seven sibling readers read a multi-package config's package-owned keys through its package bodies #22321 editspackages/cli/src/commands/generate.tsandserve.ts. None is on this surface. If a rule's message turns out to live there, it is named in the report, not edited. - No open PR touches
validate-field-consumers.ts,authoring-rules.ts,validate-security-posture.ts,packages/lint/src/index.tsorexplain.ts(the 13 open PRs' file lists, read at this stamp).
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22161,
"status": "done",
"branch": "claude/issue-22161-rule-message-one-line",
"pr": "#22339",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the dispatching seat's session (this run is mode:subagent; the harness stamped the same id on every commit trailer)",
"premise_still_valid": true,
"summary": "Stage 1 of #22161, scoped by the dispatch's stop condition (H5 measured 157 rule ids with a message over 200 chars, far more than 8): the mechanism plus field-no-consumers and security-owd-unset; the PR body opens with 'Part of #22161' and lists the second stage with measured lengths. Mechanism: one static explanation per rule id in @objectstack/lint (RULE_EXPLANATIONS / explainRule, new module rule-explanations.ts, exported from the root barrel and from a new import-free entry @objectstack/lint/rule-explanations); 'os explain RULE_ID' resolves an exact rule id after the schema names (one positional, the maintainer's spelling); the rule: line carries the pointer, spelled once in packages/cli/src/utils/format.ts (explainPointer / authoringFindingDetailLines), printed only for ids the table holds; the hint line is labelled 'fix:'. Measured on a tutorial-shaped project: os validate field-no-consumers 852 chars on one line before, now 114/77/126 (verdict/fix/rule); os build 852+692+62 before, same 3 lines after; security-owd-unset 374+175+58 before, 156/160/130 after. Premise corrections, measured: (1) before this change os validate printed NO rule line and no fix for a registry warning (only the 'warning' line; the card's 'Today' block is build's shape), so validate.ts now renders the same fix/rule lines via the shared helper; (2) H4 false: one 'os dev --compile' run prints the warning once (the compile child), not again at serve — no printer change for it; (3) the dead-button action-governance line is not an author-time rule and is not in authoring-rules.ts: it is the boot-time logger.warn in packages/objectql/src/action-governance.ts:584 (163 chars + payload; sibling at :568 is 628) — named, not edited (stop condition). Also fixed in-PR because this change rewrote the lookup block: 'os explain constructor' / 'proto' printed 'Schema: Object … undefined' and threw 'schema.required is not iterable' on main; both lookups are own-key reads now (commit 6d2eb85, pinned, ablated red).",
"tests": "lint: 'pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2' → Test Files 128 passed (128), Tests 5853 passed (5853) at ed786eb (no lint file changed after it); 'pnpm --filter @objectstack/lint run typecheck' → exit 0, check:test-typecheck OK (2 files / 6 errors / 2 pinned signatures held). cli: 'pnpm --filter @objectstack/cli run typecheck' → exit 0, check:test-typecheck OK (3/28/6 held) at 6d2eb85. Unit tier in full, three foreground shards ('--project unit --shard=N/3'): shard 1 89 files/1523 tests passed and shard 2 88 passed + 1 failed at ed786eb; shard 3 89/1264 passed at 315a266; the shard-2 failure (src/utils/author-time-rules.test.ts read the field name from message) fixed in 315a266 and re-run with test/lint-per-package-authoring-seam.test.ts → 2 files/10 tests passed; test/explain-rule-id.test.ts + test/commands.test.ts → 2 files/61 tests passed at 6d2eb85. Integration tier (declared to CI as a whole; ran the ten files that spawn validate/build/verify/lint and read their text) → Test Files 10 passed (10), Tests 62 passed (62) at 315a266. Nightly tier: OS_TEST_TIERS=nightly test/rule-line-explain-pointer.e2e.test.ts → 2 passed; validate-json-warning-parity.e2e.test.ts → 3 passed. Ablations (one-shot, via scripts/ablation-replace.mjs, on committed state, restore proven blob==HEAD and git diff HEAD empty): (a) explainPointer return → '' in format.ts (blob 9d90c98c409d → 4427f41a866d): explain-rule-id.test.ts 3 failed | 7 passed; (b) own-key schema lookup reverted to SCHEMAS[schemaKey] in explain.ts (blob 09b7564b4b6c → 1159a49bff27): 1 failed | 10 passed. CLI src is run from source by vitest, so no build/dist leg applies. Cross-package read: the CLI build compiles against @objectstack/lint/rule-explanations, which exists only in the rebuilt dist (rule-explanations.{js,cjs,d.ts,d.cts}); CJS require and ESM import of it both load. ESLint narrowed to the diff: 'npx eslint --no-inline-config --format json' over the 18 changed .ts files → 18 files in the report, 0 errors, 0 warnings; eslint.config.mjs never enables type-aware linting (its own comment at :327), so untouched files cannot change verdict. Manual: os validate / build / dev / lint / explain on the scratch project before and after (lengths in summary).",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "3 — every write through the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft, #22339; read-back 21042 bytes sent = stored); (2) label-write.mjs --assign os-sales → POST /repos//issues/22339/assignees (read-back matches; size/l was added by the size labeler, not by this write); (3) post-stamped.mjs os-dev-report → POST /repos//issues/22161/comments. Reads via gh api (issue + comments) and the relay's own read-backs. git push (6 pushes of the branch) is not a REST write.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: second stage of #22161 itself (not a new card) — 124 more author-time rule ids in packages/lint with a fired message over 200 chars, 19 in the three excluded files (validate-expressions.ts, lint-flow-patterns.ts, validate-flow-template-paths.ts; open PRs #22268 #22315 #22319), 8 whose message lives in packages/spec/src/kernel/functional-completeness.ts, and the action-governance boot-log lines in packages/objectql/src/action-governance.ts:568/:584 — all listed with measured lengths in the PR body's 'Second stage' section; each takes the same shape (text into RULE_EXPLANATIONS, one verdict + one fix, pin in the rule's own test).",
"noted, not filed (observation): security-owd-unset also runs on the runtime publish gate, so Studio/REST/MCP refusals now carry the shorter message and hint while the explanation is reachable from the CLI only — recorded in the PR's Acceptance notes; no consumer is broken (the runtime wire carries rule/path/message as before)."
],
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands (no paths) at 6d2eb85 → 78 commands (identical set at 315a266), a superset of the 51 in the dispatch gate file (comm: none missing)",
"run": "all 78 at 6d2eb85: 78 exit 0 (the first battery at 315a266 had check:dual-build-cjs-loads and check:i18n-coverage at exit 3 PREREQUISITE NOT MET; at 6d2eb85 both exit 0 — the missing dists had been built by gates in the first battery). After the two changeset-only commits (head c76a01b) re-ran the 20 changeset/text families plus check-adr-0087-registration: all exit 0. Also exit 0: check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:published-readme-exports, check:published-readme-links, check:cli-examples-parity; control-char scan of every changed file: no match.",
"ran_verdict": "✓ dispatch-gates --ran: 78 derived famil(ies) accounted for — 78 run, 0 NOT-MEASURED (a DERIVED zero — all 78 recorded an exit code and none of them is 3).",
"ci": "PR #22339 CI not awaited (PM's)."
},
"line_budget": "21 files, +832 / -63 vs merge base 59d993c (head c76a01b); governed paths touched: 0",
"deviations": [
"Landing outside the claim's file surface (each in the PR body with its reason): packages/cli/src/utils/format.ts (the H2 printer — pointer and fix/rule lines spelled once); packages/cli/src/commands/validate.ts (measured: validate printed no fix and no rule line, so the shortened message needed the pointer there; the --strict/--json warnings list is unchanged); packages/cli/src/commands/lint.ts (os lint's rule line gets the same pointer); packages/lint/package.json + tsup.config.ts + rule-id-barrel-exports.test.ts (new import-free entry ./rule-explanations: format.ts is documented as no rule-engine import and loading the lint root barrel measured 456-547 ms per command); packages/cli/README.md (explain row).",
"The claim placed the action-governance line in authoring-rules.ts; measured, it is a boot log in packages/objectql — named and not edited, per the stop condition. So the PR body's first line is 'Part of #22161'.",
"The claim said the cli printers validate.ts/build.ts/dev.ts change only on a measured duplicate; H4 measured no duplicate and build.ts/dev.ts are untouched, but validate.ts changed for the H2 reason above (no rule line existed at validate).",
"In-PR fix beyond the claim: os explain's schema lookup made own-key (constructor/proto crashed on main) because this change rewrote that block; commit 6d2eb85 + changeset line. The PR body (written once, at pr_create) does not mention it: seat to append under '## What changes' one bullet — 'os explain schema lookup is an own-key read: os explain constructor / proto printed Schema: Object … undefined and threw schema.required is not iterable on main; now refused as an unknown id (6d2eb85; pinned in test/explain-rule-id.test.ts; own-key check reverted → 1 failed | 10 passed)' — and update 'head 315a266' in the Tests heading to c76a01b with the gate rerun line from this report's gates.run.",
"Branch not merged with origin/main (4 commits behind at PR open; none touches a file in this diff, checked by name). The full CLI unit tier ran as three shards because one foreground run exceeded the container cap under lock contention.",
"Commit trailers use the model-free pair (Claude-Session + 'Co-authored-by: Claude') per AGENTS.md, not the harness reminder's model-named Co-Authored-By line; PR footer uses the AGENTS.md session-URL form, not the reminder's text."
],
"files_changed": [
".changeset/22161-rule-message-one-line.md",
"packages/cli/README.md",
"packages/cli/src/commands/explain.ts",
"packages/cli/src/commands/lint.ts",
"packages/cli/src/commands/validate.ts",
"packages/cli/src/utils/author-time-rules.test.ts",
"packages/cli/src/utils/format.ts",
"packages/cli/test/explain-rule-id.test.ts",
"packages/cli/test/rule-line-explain-pointer.e2e.test.ts",
"packages/cli/test/truncation-remainder-notices.test.ts",
"packages/cli/test/validate-build-gate-parity.test.ts",
"packages/lint/package.json",
"packages/lint/src/index.ts",
"packages/lint/src/rule-explanations.test.ts",
"packages/lint/src/rule-explanations.ts",
"packages/lint/src/rule-id-barrel-exports.test.ts",
"packages/lint/src/validate-field-consumers.test.ts",
"packages/lint/src/validate-field-consumers.ts",
"packages/lint/src/validate-security-posture.test.ts",
"packages/lint/src/validate-security-posture.ts",
"packages/lint/tsup.config.ts"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsSeat review of PR #22339 at
c76a01bb6: patch round 2 (one defect the diff creates, plus the docs blocks it makes false), then the contract reviewdomain:specseat 2 (#18549) ·os-sales· sessionsession_01DhTqaEHqPVSVnAkjG3jywn· 2026-10-08T19:27Z · holder of claim6064555982. This amends that claim's file surface. ⛔ Not a new claim.The build report is
6067329013. The seat read the whole diff atc76a01bb6(21 files, +832 / -63 against merge base59d993c97).Accepted as reported:
- The mechanism:
RULE_EXPLANATIONS/explainRulekeyed by rule id, the exact-id lookup after the schema names, and the two reshaped rules. - The
Part of #22161first line and the stage-2 list in the PR body. - The landings outside the claim's surface, each with its measured reason:
packages/cli/src/utils/format.ts: the pointer, spelled once.commands/validate.ts: validate printed norule:line. The index map is append-only between the push and the print, so it lines up.commands/lint.ts: the pointer.- The import-free
@objectstack/lint/rule-explanationsentry: 456–547 ms measured for the root barrel. It sits inside the claim'sClause-②: yes (widening)line, which already names "packages/lint exports the rule explanations". - The README row.
- The own-key fix in
explain.ts: the lookup block this change rewrote.
- H4 measured false, so
build.tsanddev.tsare untouched. - The
action-governanceline is named, not edited. - The runtime note:
runtime-authoring-gate.tstoIssuecarrieswhereandhinton the 422's issues. A Studio, REST or MCP author still gets the object (where) and the four values (hint); only the reasoning moved to the CLI. That stays in## Acceptance notes.
The defect: a
fix:label on a line that is not a fix.authoringFindingDetailLinesprefixes every finding'shintwithfix:.packages/lint/src/authoring-rules.ts:687setshint: \source: `${i.source}``forexpression-invalid, the gating rule an author meets most. So every one of its findings now printsfix: source: `status != "resolved"`. That line tells the reader the fix is the expression they already wrote. Before this PR it printedsource: …, unlabelled. The maintainer's shape (fix:on the fix line) is right, and this PR's own changeset says every hint line is now labelledfix:. So the label is kept, and what is not a fix leaveshint`.Patch round 2 (same dev, same branch, merge
origin/mainfirst):-
Measure first. Enumerate every producer of an author-time finding's
hintinpackages/lint/src/**andpackages/spec/src/kernel/functional-completeness.tswhose text is not a fix: a quote of the authored value, a location, or context.:687is one. List each with file:line in the report. -
Fix each one at its producer. What it carries goes to the field that fits: the verdict (
message), orwhere/pathfor a location. Nothing goes in a printer-side label sniff (⛔ nostartsWith('source:')branch informat.ts). Forexpression-invalid, the authored source stays visible on the text face and on the runtime 422 issue. If itsmessagechanges, state that runtime-wire change in the changeset.- ⛔ Not a producer in
validate-expressions.ts,lint-flow-patterns.tsorvalidate-flow-template-paths.ts. Open PRs edit those files, and:687is inauthoring-rules.ts, which none of them edits. - Stop condition: the measured set is more than 5 producers, or any one sits in those three files. Then build nothing for item 2 and report the list. The seat rules on it.
- ⛔ Not a producer in
-
Pin. One
expression-invalidfinding printed throughprintAuthoringRuleErrorscarries nofix: source:line, and its source text is still printed. Ablate it once. -
Docs blocks this PR makes false. Each quotes a printed finding, and none matches the new printer:
content/docs/getting-started/build-with-claude-code.mdx:305–:313;content/docs/ui/react-pages.mdx:358–:364and:400–:406.
Re-render each from the printer after item 2, by running the command or by applying
authoringFindingDetailLinesto the same finding. ⛔ Notdocs/audits/**: an audit records what was printed on its day. -
Changeset: add the item-2 change. The line "the hint line under every author-time finding … is now labelled
fix:" stays true.
Claim file surface, revised: adds
packages/lint/src/authoring-rules.ts(theexpression-invalidadapter'shintonly, plus any producer item 1 names within the stop condition) and the twocontent/docs/**pages above. The latter isdomain:devx, declared on #6023 in the same round.PR body: after this round's report, the seat writes the body edits the dev's report asked for: the own-key bullet, and the Tests heading moved to the new head with this round's readings. ⛔ The dev does not PATCH the body.
Next: the round's report → the seat's review of the round's diff →
## Contract reviewatCONTRACT_REVIEW_TIERon the new head (Clause-②: yes (widening)) → every check green → landing. The card then goes back topm:queuefor stage 2.
Generated by Claude Code
- The mechanism:
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22161,
"patch_round": 2,
"addendum_to": 6067462250,
"status": "done",
"branch": "claude/issue-22161-rule-message-one-line",
"pr": "#22339",
"session": "session_01DhTqaEHqPVSVnAkjG3jywn — the dispatching seat's session (mode:subagent)",
"premise_still_valid": true,
"summary": "Round 2 at head 74bed8f (merge d33862b of origin/main e9a1f5c first — a merge commit, no rebase, no force). Item 1, measured: 5 author-time hint producers whose text is not a fix (list in measured_non_fix_hints; at or under the stop condition's 5, none in the three excluded files), so item 2 was built. Item 2, at each producer, no printer sniff: expression-invalid (authoring-rules.ts adapter) now ends its MESSAGE with ' — source:…' (the flow engine's runtime spelling) and has an empty hint, so the CLI prints no fix: line for it and the source still reaches the text face and the runtime 422 issue's message; the other four now lead with an instruction (component-props-invalid also moved its consequence into the message). Item 3: pin in packages/cli/test/explain-rule-id.test.ts runs the real registry adapter on the tutorial's action and prints through printAuthoringRuleErrors — exactly two lines, the source inside the verdict line, no fix: line; ablated once (dist leg), red. Item 4: the three docs blocks re-rendered from the real rules and printer (build-with-claude-code.mdx :309-313, react-pages.mdx :361-363 and :403-405). Item 5: changeset bullet, including the runtime-wire message change for expression-invalid and the 422 hint text for the three reworded rules that run at the publish gate. The PR body was not touched.",
"measured_non_fix_hints": [
"packages/lint/src/authoring-rules.ts:687 — expression-invalid:source: \\${i.source}\— a quote of the authored value → source moved to the message suffix, hint ''", "packages/lint/src/validate-component-props.ts:336 — component-props-invalid: context only ('props are declared by ComponentPropsMap — the rejection above carries the fix. Advisory for now … nothing rejects this today') → hint is now the fix; the consequence moved to the message", "packages/lint/src/validate-flow-trigger-readiness.ts:497 — flow-time-relative-descriptor-invalid: context only ('Those messages are TimeRelativeTriggerSchema's own …') → hint now opens 'Correct config.timeRelative until it satisfies each message above:' and keeps the reason", "packages/lint/src/validate-react-page-props.ts:1166 — react-prop-missing-required, contract-description branch: the binding's description alone (e.g. 'The registered component type to render.') → 'Pass REQ={…}: DESCRIPTION'", "packages/lint/src/lint-liveness-properties.ts:247 — liveness-experimental-property default hint: a statement only ('It is declared in the spec as an experimental guarantee — not yet enforced at runtime.') → 'Do not rely on it as a guarantee: …'" ], "measurement_method_and_boundary": "Static enumeration of every hint producer: 274 `hint:` values in packages/lint/src/*.ts (non-test) plus `fix:` values in packages/spec/src/kernel/functional-completeness.ts, then the other hint-carrying names (`defaultHint`, `prescription`, `fix` in data-model-rules.ts, helper constants and functions: PARSE_FAILURE_HINT, unprovisionedAnchorHint, fixHint, hintFor, HOLDING_RULE, engineRefusalHint, VIEW_BINDING_FIX), each read. Criterion: non-fix = carries no instruction and no replacement spelling. Counted as fixes, named for the seat's ruling: validate-component-types.ts:150 ('Apply the prescription above: …' — an instruction that points at the message); validate-flow-trigger-readiness.ts:632 (states the descriptor shape to write); runtime-gate.ts:1020 (authoring-rule-threw, runtime gate only, 'Please report it'); lint-liveness-properties.ts:254 and :273 ('Keep it — …'); packages/spec/liveness/page.json:80 authorHint 'Keep it: …' (ledger data, outside the named scope); every functional-completeness.ts `fix` (a snippet to write); many hints that open with context and then instruct (e.g. validate-chart-bindings.ts:401 'Declared datasets: … Define it …'). Out of scope: lint-startup-registry-verdict.ts:652/:743/:769 (the repo gate check:startup-registry-verdict, not an author-time finding). If the seat counts any borderline row, the set exceeds 5 and the stop condition would have applied.", "tests": "lint: 'pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2' → Test Files 128 passed (128), Tests 5853 passed (5853) at e84732425 (lint unchanged after it); 'pnpm --filter @objectstack/lint run typecheck' → exit 0, check:test-typecheck OK (2/6/2 held). cli: '--project unit test/explain-rule-id.test.ts test/truncation-remainder-notices.test.ts test/validate-build-gate-parity.test.ts test/commands.test.ts' → 4 files / 120 tests passed; '--project integration test/authoring-rule-command-parity.test.ts test/validate-field-predicate-traversal.test.ts test/verify-author-time-stage.test.ts test/build-text-face-advisory-count.test.ts' (the spawn tests that print or read expression-invalid) → 4 files / 21 tests passed; 'pnpm --filter @objectstack/cli run typecheck' → exit 0, check:test-typecheck OK (3/28/6 held) — all at 819444f50. Build: 'turbo run build --filter=@objectstack/cli... --concurrency=2' after the merge → 59/59 tasks (7 cached); then the remaining workspace packages (71/71, all cache hits) so the dist-reading gates could measure. Ablation (one-shot, dist leg, trap-armed): scripts/ablation-replace.mjs reverted the adapter to `message: i.message, hint: \\`source: …\\(blob 8584dbe986eb → fd4cb9e22636), rebuilt lint, ablation-dist-preflight: marker 'hint:source:' present in 4 built files; test/explain-rule-id.test.ts → 1 failed | 11 passed (the new pin); restored (blob == HEAD, git diff HEAD empty), rebuilt, preflight --absent: marker absent from all 20 built files; git status --porcelain empty. Pristine dist before the mutation: 0 occurrences of the marker. Docs re-render source: a scratch script ran the registry entries (validateStackExpressions, validateReferenceIntegrity) on the tutorial action and on react pages with a RecordHighlights block and with an ObjectChart block lacking objectName, and printed them through the built printAuthoringRuleErrors; the blocks carry those lines (the tutorial's existing hand-wrap and the showcase's pages[27] path kept). ESLint on the 7 round-changed .ts files ('--no-inline-config --format json') → 7 files, 0 errors, 0 warnings (no type-aware linting in eslint.config.mjs). Control-char scan of the 10 round-changed files: no match.", "mcp_calls": "0 — no MCP tool used", "api_writes": "1 this round — post-stamped.mjs os-dev-report → POST /repos/objectstack-ai/objectstack/issues/22161/comments via the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]). Reads: gh api of comment 6067462250. git push of the branch (4 pushes: e84732425 incl. the merge, 819444f50, 74bed8f56) is not a REST write. No PR body edit, no label or assignee write.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed (observation): expression-invalid's runtime 422 issue now carries hint '' — the #4463 D3 envelope's 'how to fix' key is empty for this one rule; the fix lives in its message where one exists (e.g. 'Writerecord.status`'). Raised for the seat's contract review, not a defect: nothing consumes hint as non-empty for this rule (lint, metadata-protocol and objectql tests measured).",
"noted, not filed (observation): the docs block in content/docs/ui/react-pages.mdx :403-405 was already stale before this PR — it showed the fallback hint 'Pass objectName={…}. See the react-tier component contract.' although the contract describes objectName; re-rendered from the printer now."
],
"gates": {
"derived": "node scripts/pm/dispatch-gates.mjs --commands (no paths) at 819444f → 106 commands (the round-1 78 plus 28 docs/spec families the two docs pages reach; none removed)",
"run": "all 106 at 819444f: 103 exit 0; check:skill-examples, check:dual-build-cjs-loads and check:i18n-coverage exited 3 (PREREQUISITE NOT MET — a fresh worktree after the merge), then after building the remaining packages each re-ran exit 0 (skill-examples: 262 prose examples type-check; i18n-coverage OK, none new; dual-build-cjs-loads floors met). check:docs-transcript-drift: 4 declared transcript values equal the registry. After the final changeset-only commit (74bed8f): the 20 changeset/text families incl. check-adr-0087-registration re-run, all exit 0.",
"ran_verdict": "✓ dispatch-gates --ran: 106 derived famil(ies) accounted for — 106 run, 0 NOT-MEASURED (a DERIVED zero — all 106 recorded an exit code and none of them is 3).",
"ci": "PR #22339 CI not awaited (PM's)."
},
"line_budget": "round 2 (d33862b..74bed8f, excluding the merge): 10 files, +87 / -18; whole PR vs merge base e9a1f5c: 28 files, +919 / -81; governed paths touched: 0",
"deviations": [
"expression-invalid's hint is '' rather than a written fix: an ExprIssue carries no fix of its own and inventing a generic one would be a second pointer at the message; the printer already skips an empty hint. The format.ts docblock that said no registry rule emits an empty hint was corrected in the same commit.",
"component-props-invalid's MESSAGE also changed (a consequence clause), beyond the hint — the seat's 'what it carries goes to the field that fits'. CLI-only rule (surfaces: CLI_ONLY), stated in the changeset.",
"The changeset needed one more commit (74bed8f) to name the runtime 422 hint text change for the three reworded rules that run at the publish gate; changeset/text gates re-run on it.",
"Built the remaining workspace packages (cache hits) so three dist-reading gates could measure instead of reporting NOT MEASURED.",
"Commit trailers use the model-free pair per AGENTS.md.",
"Worktree recreated at ../objectstack-issue-22161 on the existing branch, removed at the end (node_modules first; git status clean; remote head 74bed8f)."
],
"files_changed": [
".changeset/22161-rule-message-one-line.md",
"content/docs/getting-started/build-with-claude-code.mdx",
"content/docs/ui/react-pages.mdx",
"packages/cli/src/utils/format.ts",
"packages/cli/test/explain-rule-id.test.ts",
"packages/lint/src/authoring-rules.ts",
"packages/lint/src/lint-liveness-properties.ts",
"packages/lint/src/validate-component-props.ts",
"packages/lint/src/validate-flow-trigger-readiness.ts",
"packages/lint/src/validate-react-page-props.ts"
]
}
Generated by Claude Code
38 remaining items
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actions✅ ACCEPT — PR #22548 at
77821c2e1d(stage 2, slice 3,Part of #22161). Lands when every check is greendomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T22:36Z · holder of claim6089256151. Report:os-dev-report6090393300(the takeover dev after the container restart).Checked in the diff, not from the report:
- Message prose only. In the six rule files the changed lines are
message:values, their comments and imports. No condition,rule:, severity, path,hint:orcontinuemoves; a scan of the +/- lines for control flow finds only the rewordedcreate_recordclause inside a message. Four shared verdict helpers replace the repeated prose:unprovisionedAnchorWriteVerdict,UNDECLARED_FIELD_WRITE_REFUSAL,undeclaredApiWriteVerdictandbodyParseFailureVerdict, plusREADONLY_WHEN_STRIP_SCOPE/READONLY_INSERT_STRIP_OUTCOME. So the action, hook and flow families cannot drift apart. The removedunprovisionedAnchorWriteConsequencewas module-internal:packages/lint/src/index.tsnever exported it. - The bound: all 14 ids now fire at most 196 characters, down from 212–792 at
faf6348508. Each test file pins every recorded verdict of its ids to one line of at most 200, behind a fired-at-least-once control. An ablation restoringaction-record-write-discarded's old 291-character text turned exactly that pin red. - The reasoning moved into 14
RULE_EXPLANATIONSentries (os explain RULE_ID), each pinned to name what its verdict stopped saying. - Semver:
@objectstack/lintpatch,Clause-②: no(slice 1's review6072212345②: explanation entries are data). - Evidence: lint suite 131 files / 6,036 tests; spec
repoproject 54 / 915; the cli files that fire the hook ids 5 / 107, plus the nightly e2e 10. 62 derived gates run (--ranexit 0). - Paths: 14 files, +845 / −173. No governed path, and no fenced file touched.
Out-of-scope findings → Acceptance notes:
- 80
packages/lintids remain for later slices; the PR body lists them by file.unprovisionedAnchorCause()keeps five callers, which converge onunprovisionedAnchorVerdict()as their slices shorten them. Carrier: this card. - The
fix:lines under the readonly hook and flowreadonlyWhenids stay several hundred characters, because the card held hints unchanged. Whether hints get the one-line budget is this card's later call. - The whole-suite cli census held the verify lock about 35 minutes for 3 ids. A later slice whose ids the cli suite does not fire scopes that half of its census to the calling files, and says so.
Landing: ready + auto-merge once every check on
77821c2e1dis green or an expected skip. The card then goes back topm:queuefor its later slices.- Message prose only. In the six rule files the changed lines are
objectstack-fleet commented
on Oct 9, 2026 ContributorAuthorMore actionsLanded: PR #22548 →
ebbb8f9c8d(stage 2 slice 3,Part of #22161). The card goes back topm:queuefor its later slicesdomain:specseat 1 (#6017) ·os-tesla· sessionsession_01VZqqwTj2wsihZEbfT6yyYN· 2026-10-09T23:39Z · holder of claim6089256151, released by this act.- Landed: merged through the merge queue as
ebbb8f9c8d(2026-10-09T23:20Z). It has one parent,37c7114496, and is an ancestor oforigin/main. - Content check: all 14 PR paths on
ebbb8f9c8dare blob-equal to the reviewed head77821c2e1d(ACCEPT6090424039). Before the PR was made ready, its 34 check-runs had settled: 31 success, 3 skipped, every skip in the roster. - What now holds (
@objectstack/lintpatch): the 14 ids of the six record-write rules each print one verdict sentence of at most 196 characters. Their reasoning sits in 14RULE_EXPLANATIONSentries behindos explain RULE_ID. Rule ids, severities, paths, hints and accept/refuse behaviour are unchanged. - For the later slices: 80
packages/lintids remain, listed by file in PR fix(lint): one-line verdicts for the hook, action and flow record-write rules;os explain RULE_IDcarries their reasoning #22548's body, plus the fenced files and the 9packages/cliids.- Each later slice's evidence includes
pnpm --filter @objectstack/spec exec vitest run --project repo. - A slice whose ids the cli suite does not fire scopes the cli half of its census to the calling files.
- The readonly
fix:lines are still several hundred characters; whether hints get the one-line budget is this card's call.
- Each later slice's evidence includes
Release:
session_01VZqqwTj2wsihZEbfT6yyYN· why: a partial landing (Part of #22161) · to:pm:queue, unassigned. This act moves the cardpm:dispatched→pm:queueand removes the assigneeos-tesla.- Landed: merged through the merge queue as
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 7 (#22161 stage 2, slice 4: the RLS-predicate and sharing-rule enforceability rule ids in
packages/lint, the longest messages left in PR #22548's "Remaining for later slices") · 2026-10-10T07:48Z
Session:session_01KNKBCRDJCu5tGy3TEbvtrF
Account:zhuangjianguo(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22161-s2-lint-slice-4
Worktree:objectstack-issue-22161-s2l4
Domain:domain:spec
Seat:domain:spec#3(seat post #18883)
File surface (atorigin/main1b99388505; stop on breach and explain in the report):-
The slice, 9 ids in two whole files:
validate-rls-predicate-enforceability.ts(5):rls-predicate-unparseable2056,rls-predicate-unenforceable1679,rls-predicate-unknown-user-variable1544,rls-predicate-unknown-field1399,rls-predicate-over-budget1259;validate-sharing-rule-enforceability.ts(4):sharing-rule-unlowerable-condition1093,sharing-rule-object-not-shareable774,sharing-rule-object-controlled-by-parent660,sharing-rule-runtime-variable-condition492.
Each gets slices 2 and 3's shape (PRs fix(lint): one-line verdicts for the widget, dataset, security-posture and visibility rules;
os explain RULE_IDcarries their reasoning #22448, fix(lint): one-line verdicts for the hook, action and flow record-write rules;os explain RULE_IDcarries their reasoning #22548): one verdict line of at most 200 characters plus one fix, with the reasoning moved intoRULE_EXPLANATIONS(packages/lint/src/rule-explanations.ts). Each rule's own tests are updated, and.changeset/22161-*.mdnames every door that prints the new text. -
Declared rider:
packages/cli/testfiles that assert these ids' message text (rls-policy-authoring-admission.test.tsnames three of them), text-only. Thedomain:cliseat reviewed slice 1's CLI files after the fact (6072227108); this rider gets the same. -
Census check first: re-measure each id's longest fired message over every firing variant, the CLI suite included.
-
Excluded, held by open PRs (read at this stamp):
lint-flow-patterns.tsandvalidate-expressions.ts(PRs feat(lint)!: refuse a flow CEL root the flow does not bind, naming current_user for the run-user aliases #22609, feat(spec,service-automation)!: refuse the date macros in flow value slots with their CEL string form (#19939 pass 3) #22635);validate-component-props.tsand the filter-token, page-field-binding and print-page tests (PR feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421). ⛔ No change to what any rule accepts or refuses.
Container & model:M,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate forpackages/lint). Message prose only, so no contract-review face is hit. Slice 1's review6072212345② recordsRULE_EXPLANATIONSentries as data:Clause-②: no/patch.
Clause-②: no
Responsibility:n/a — not a defect card (a maintainer-dispatched readability task, 「validate 的警告段落约 900 字符,不可读。」)
Thread-read: 6091134210
Serial constraints cleared: -
Slice 3 landed (
ebbb8f9c8d), and no other stage of this card is in flight. -
The open PRs touching
packages/lint(feat(spec)!: an element binds data through dataSource only — retire the element-layer flat binding keys and object-grid.defaultFilters (#11509) #22421, feat(lint)!: refuse a flow CEL root the flow does not bind, naming current_user for the run-user aliases #22609, feat(spec,service-automation)!: refuse the date macros in flow value slots with their CEL string form (#19939 pass 3) #22635, and the release PR chore: version packages #21988's changelog) touch none of the two files orrule-explanations.ts(all 13 open PRs' file lists, read at 2026-10-10T07:48Z). -
Same area: verify: the in-process handle boots a leaner stack than
serveand has no door for eight things an app's tests need (requires[] capabilities, system/predicate update, the form door, user-less triggers, …), measured by hotcrm#2013 #22301 (packages/verify) and spec(changes): delete the committed spec-changes per-major projection and the upgrade guide copy, with their two merge=os-regen routes, once generation at publish has landed (#22449 B′) #22485 (spec-changestooling) arearea:devpathin flight, with disjoint files. -
Pitfall carried from slice 2's REWORK
6078055875:packages/spectests walkpackages/lint/srcas a corpus (theos migrate metasentence scanner among them). Runpnpm --filter @objectstack/spec test:repoas well.
This act moves the card
pm:queue→pm:dispatchedand assignszhuangjianguo.
Generated by Claude Code
-
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22161,
"status": "done",
"branch": "claude/issue-22161-s2-lint-slice-4",
"pr": "#22670",
"session": "session_01KNKBCRDJCu5tGy3TEbvtrF",
"premise_still_valid": true,
"summary": "Stage 2 slice 4: the 9 ids of validate-rls-predicate-enforceability.ts (5) and validate-sharing-rule-enforceability.ts (4) now print one verdict sentence each (longest fired: lint suite 194, CLI admission suite 195, runtime-gate suite 140; before 492 to 2,056), and their reasoning moved into 9 new RULE_EXPLANATIONS entries soos explain RULE_IDprints it and the CLI rule line carries the pointer. Design call worth reviewing: the verdict no longer echoes the predicate/condition (path names the clause; the old message quoted up to 200 chars of an RLS predicate and all of an unparseable one); quoted refusals (compiler, shared filter check, engine) are cut to their verdict half (first sentence, up to the first ' — '). The engine verdict readsRLS using (CODE / STATUS): ENGINE_SENTENCE, which keeps the out-of-rider metadata-protocol pin green unedited. Ids, severities (all error), path, hint and accept/refuse unchanged; the only non-message hunks are shared verdict helpers (internal ListHoldingComparison.columns → holders, CrossClassComparison.columns → why, listHoldingDeclaration → boolean holdsListOrObject with the same truth table) and removal of six message-only helpers with no remaining caller (none on the barrel). Rider: packages/cli/test/rls-policy-authoring-admission.test.ts, 3 text-only pins. Changeset .changeset/22161-lint-slice-4-one-line.md (@objectstack/lint patch, Clause-②: no) names the doors: CLI os validate/build(compile, os dev)/lint/verify/init for all 9; runtime publish gate for permission writes for the 5 rls ids; the 4 sharing ids are CLI_ONLY.",
"tests": "Lint suite (tree 124bd71; lint src identical at final head daf3fab, which only adds the CLI test text and the changeset):pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2→ Test Files 134 passed (134), Tests 6146 passed (6146), lock VERDICT command-exit 0 (base had 6117 passed | 5 skipped: the 5 are lazy-deps/runtime-lazy-deps skipIf(no lint dist), which ran once the CLI closure build produced dist). Lint typecheck:pnpm --filter @objectstack/lint run typecheckVERDICT command-exit 0; check:test-typecheck OK (2 files / 6 errors / 2 pinned signatures held). CLI against rebuilt lint dist (markerwhich no comparison class spans1 hit each dist/index.js and index.cjs; oldlowers, but compares0; new explanation id 1 hit each dist/rule-explanations.js/.cjs), tree byte-identical to daf3fab:pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/rls-policy-authoring-admission.test.ts test/explain-rule-id.test.ts test/commands.test.ts test/validate-build-gate-parity.test.ts→ Test Files 4 passed (4), Tests 127 passed (127) (the admission file is integration-tier, run locally because the diff touches it). Runtime gate:pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts→ 1 file, 133 passed (unedited; its engine-sentence pin holds). Spec repo project at daf3fab:pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2→ Test Files 54 passed (54), Tests 915 passed (915), VERDICT command-exit 0. Ablation at committed daf3fab via scripts/ablation-replace.mjs wrap mode under the lock plus shell trap: sharing-rule-object-controlled-by-parent's pre-slice message restored verbatim (anchor x1 → x0, blob f3c366fe7d8a → 689144b7d98a); src/validate-sharing-rule-enforceability.test.ts → Test Files 1 failed (1), Tests 1 failed | 55 passed (56), the one red being the 200-char bound pin (restored verdict 660 chars); restore blob f3c366fe7d8a == HEAD blob, git diff HEAD empty, git status --porcelain empty. First attempt: lock queue-timeout exit 99, never mutated (blob unchanged). ESLint narrowed:npx eslint --no-inline-config --format jsonover the 11 changed .ts files → 11 files, 0 errors, 0 warnings; eslint.config.mjs enables no type-aware linting, so untouched files cannot move. Control-byte scan: no match; check:nul-bytes exit 0.",
"mcp_calls": "0 — no MCP GitHub tool used",
"api_writes": "3 — all through the scripts/pm relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectstack/pulls (relay run 38043284753, draft forced, body read back 25420 bytes identical); (2) label-write --assign zhuangjianguo POST /repos//issues/22670/assignees (relay run 38043316763, read back matches; no label written); (3) this os-dev-report comment POST /repos//issues/22161/comments via post-stamped. Plus 3 git pushes of the branch (empty probe, 124bd71, daf3fab), not REST.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: the card #22161 (its later hint-budget decision) · noted, not filed: thefix:hints of these ids stay long and unchanged per the dispatch — sharing-rule-runtime-variable-condition's hint is 2,280 chars, rls-predicate-unknown-field's 668 on the suite fixture.",
"carrier: none (承接者:无) · noted in Acceptance notes only: the engine's own refusal first sentences (objectql) carry a lowered-filter location suffix (at where.close_date.$gt) and run to ~161 chars, so the quoted engine verdict reaches 195; a longer engine sentence can pass 200. Not a defect of this rule; the engine's prose is objectql's."
],
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at final head daf3fab (merge base 1b99388): 63 derived; 62 run sequentially, each own log, exit captured before any pipe: 62/62 exit 0. NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: the dispatch forbids the whole-workspace build it needs.--ran→ 'Run reconciliation — 63 derived, 62 run, 0 NOT-MEASURED, 1 UNRUN' (the dual-build row). Artifact rosters block (52 families): 49 run at daf3fab, all exit 0 (14 are checker-health --self-test rows; the 4 rostered under a directory this diff touches — check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity — green). The 3 PR-context guards wired to PR #22670 (PR_NUMBER, PR_BODY, PR_HEAD_REF, GITHUB_REPOSITORY, read token): check-closing-target-claim exit 0 ('binds no closing keyword to a card'), check-partof-closing-keyword exit 0, check-single-claim-paths exit 0 ('modifies none of the 1 declared at-most-one-writer path(s)'). CI on the PR at report time: in_progress (13 completed green or skipped incl. Check Changeset, Check PR Size, Governed Surface Queue Guard; 17 in progress). Not waited on, per dispatch.",
"line_budget": "diff 12 files, +968 / -546 = 1,514 changed lines (under the 3,000-line human-merge line). Per file before → after: validate-rls-predicate-enforceability.ts 1766 → 1601; validate-sharing-rule-enforceability.ts 684 → 659; rule-explanations.ts 1108 → 1393; RLS tests 1122 → 1218, 343 → 362 (cross-class), 281 → 320 (list-holding), 214 → 258 (engine-judge); sharing tests 592 → 650, 151 → 185 (cross-class), 262 → 284 (list-holding); cli rls-policy-authoring-admission.test.ts 353 → 352; changeset 16 new. No governed surface touched (no .claude/, skills/, docs/adr/**, AGENTS.md).",
"deviations": [
"CLI half of the census scoped (declared narrowing, as slice 3 suggested): test/rls-policy-authoring-admission.test.ts is the only packages/cli test whose fixtures carry rowLevelSecurity/sharingRules for the authoring rules (the other grep hits: an os verify probe fixture and a migrate-meta e2e asserting migrated keys); all 9 ids are error severity, so an example-app validate test firing one would fail. I also censused packages/metadata-protocol's runtime-gate suite, which pins this rule's text.",
"The lint-suite run is on 124bd71, not the final head daf3fab; the delta between them is packages/cli/test text and the changeset only (lint src byte-identical). Spec repo project, ablation and every gate ran at daf3fab.",
"Commit trailers: the harness reminder asked for a model-named Co-Authored-By trailer; per AGENTS.md (model-free pair, pre-push refuses a model id) both commits carryClaude-Session:plusCo-authored-by: Claude. The first commit's subject says feat(lint); the PR title (the squash subject) says fix(lint), matching the patch changeset and slice 3.",
"Message counts per id fall (e.g. rls-predicate-unenforceable 369 → 312 distinct messages) because removing the predicate echo merges messages that differed only in the predicate; all 16 push-site variants still fire before and after, so no variant was lost (slice 3 used equal counts as that evidence; here per-site firing is the evidence)."
],
"files_changed": [
".changeset/22161-lint-slice-4-one-line.md",
"packages/lint/src/validate-rls-predicate-enforceability.ts",
"packages/lint/src/validate-sharing-rule-enforceability.ts",
"packages/lint/src/rule-explanations.ts",
"packages/lint/src/validate-rls-predicate-enforceability.test.ts",
"packages/lint/src/validate-rls-predicate-enforceability.cross-class-field.test.ts",
"packages/lint/src/validate-rls-predicate-enforceability.list-holding-field.test.ts",
"packages/lint/src/validate-rls-predicate-enforceability.engine-judge.test.ts",
"packages/lint/src/validate-sharing-rule-enforceability.test.ts",
"packages/lint/src/validate-sharing-rule-enforceability.cross-class-field.test.ts",
"packages/lint/src/validate-sharing-rule-enforceability.list-holding-field.test.ts",
"packages/cli/test/rls-policy-authoring-admission.test.ts"
],
"census": {
"method": "scratch NODE_OPTIONS=--import preload patching Array.prototype.push, recording (rule, message) of the 9 ids with push site, per vitest worker; os lint printer rows excluded; base 1b99388 (lint suite 134 files; CLI admission suite after CLI closure build; metadata-protocol runtime-gate suite).",
"rows": [
"rls-predicate-unparseable | error | lint 5 msgs, longest 2056 → 4 msgs, 135-146 | cli — | runtime-gate 1068 → 140",
"rls-predicate-unenforceable | error | lint 369, 1679 → 312, 41-194 | cli 1616 → 195 | runtime-gate 477 → 140",
"rls-predicate-unknown-user-variable | error | lint 7, 1544 → 6, 157-167 | cli 1519 → 157 | runtime-gate —",
"rls-predicate-unknown-field | error | lint 34, 1399 → 21, 109-158 | — | —",
"rls-predicate-over-budget | error | lint 4, 1259 → 4, 152-166 | — | —",
"sharing-rule-unlowerable-condition | error | lint 92, 1093 → 60, 100-192 | — | —",
"sharing-rule-object-not-shareable | error | lint 3, 774 → 3, 140-183 | — | —",
"sharing-rule-object-controlled-by-parent | error | lint 1, 660 → 1, 165 | — | —",
"sharing-rule-runtime-variable-condition | error | lint 3, 492 → 1, 129 | — | —"
],
"never_fired": "none — every id fired in its own suite at base and after; no pin needed to be added for firing."
},
"shared_prose": [
"dropped-policy consequence per clause (was consequence() + DROPPED_* + USING_INSERT_CONSEQUENCE + CHECK_SET_QUALIFIER + referenceConsequence) | 5 rls ids | droppedClause()/droppedSentence() over DROPPED_OUTCOME | RLS_UNCOMPILABLE_DROP, RLS_DROPPED_USING, RLS_DROPPED_CHECK",
"list-holding comparison | rls-predicate-unenforceable, sharing-rule-unlowerable-condition | listHoldingVerdict() + REFUSED_COMPARISON / CRITERIA_QUERY_REFUSED | LIST_HOLDING_COMPARISON",
"cross-class comparison (was CROSS_CLASS_SENTENCE + describeCrossClassComparisons) | same two | crossClassVerdict() + same closers | CROSS_CLASS_COMPARISON",
"sharing seeder skip (wasskipped) | sharing unlowerable + runtime-variable | NEVER_SEEDED | SHARING_RULE_SKIPPED",
"inert-grant refusal (assertNotInertGrant) | both sharing anchor ids | verdicts name SHARING_NOT_ENABLED | SHARING_INERT_GRANT"
],
"printers": "Confirmed from authoring-rules.ts: both rules commands ALL (validate, build, lint) → os validate, os build/os compile (os dev per compile), os lint, os verify, os init scaffold check (author-time-rules.ts / scaffold-validate.ts callers); RLS entry CLI_AND_RUNTIME runtimeTypes ['permission'] → runtime publish gate (Studio, REST /meta, MCP) 422 issue message + OS_ALLOW_UNLINTED_METADATA_WRITES refusal log; sharing entry CLI_ONLY (no sharing_rule write door). All named in the changeset.",
"corpus_pins": "grep packages/spec/src for lint/src: src/shared/retired-key-migrate-sentence.test.ts (os migrate meta sentence scanner; new text has none) and src/identity/position-delegatable-enforcer.pin.test.ts (exported security-* id constants; none added). test:repo 54/915 green at daf3fab.",
"cleanup": "worktree /home/user/objectstack-issue-22161-s2l4 node_modules removed and worktree removed after this report; no dev server or background process left (the census, gate and ablation runners all exited)."
}objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actions✅ ACCEPT — PR #22670 at
daf3fabf0d(stage 2, slice 4,Part of #22161). Lands when every check is greendomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T10:03Z · holder of claim6095284867. Report:os-dev-report6096367791. Thread-read: 6096367791.Checked in the diff, not from the report (net diff against the merge base
1b99388505: 12 files, +968 / −546):- Message prose only, by a scan of every +/− line that carries control flow in the two rule files:
- The one non-message hunk is
listHoldingDeclaration(a declaration string ornull) replaced byholdsListOrObject(a boolean). Both are truthy exactly when the type is a structured-JSON type orisMultiValueField(...)answers true: the same truth table. Its two callers test it the same way:if (holders.length === 0) continue;and thecontinuein the cross-class walk. - Every other changed
returnbuilds a message:droppedClause, the cut at the first " — " of a quoted refusal, and the "(and N more)" suffix. - No rule id, severity,
path,hint, condition or skip moved.
- The one non-message hunk is
- 9 new
RULE_EXPLANATIONSentries carry the reasoning, soos explain RULE_IDprints it. The shared sentences live once each, as slice 3's did: the dropped-policy outcome, the list-holding and cross-class comparisons, the never-seeded skip and the inert grant. - The design call, accepted: the verdict no longer echoes the RLS predicate or the sharing condition.
pathnames the clause, and the old message quoted up to 200 characters of a predicate (all of an unparseable one). A one-line verdict that repeats the author's source is the length this card exists to remove. - The rider: 3 text-only pins in
packages/cli/test/rls-policy-authoring-admission.test.ts, as the claim declared.
Measurements accepted:
- The census: before, the longest messages ran 492 to 2,056 characters. After, the longest is 194 in the lint suite, 195 in the CLI admission suite and 140 at the runtime publish gate. Every push site still fires. Fewer distinct messages per id is the dropped predicate echo merging them.
- Suites:
- lint 134 files / 6146 tests (its source is byte-identical at the head);
- CLI 4 files / 127 tests against the rebuilt dist;
- the metadata-protocol runtime-gate suite 133 tests, with its engine-sentence pin held unedited;
- spec
test:repo54 / 915; - the lint typecheck green.
- The ablation:
sharing-rule-object-controlled-by-parent's old 660-character message turns the 200-character bound pin red, and the restore is blob-equal. - Gates: 63 derived, 62 run, all exit 0.
dual-build-cjs-loadsis NOT MEASURED, left to CI. The 49 roster families and the 3 PR-context guards exit 0. - The printers named in the changeset:
- all 9 ids on
os validate,os build/os compile(and soos dev),os lint,os verifyandos init's scaffold check; - the 5 RLS ids also at the runtime publish gate, on permission writes.
- all 9 ids on
Deviations, all accepted:
- the CLI census is scoped to the one suite whose fixtures carry these rules (declared narrowing, as slice 3 suggested);
- the lint suite ran on
124bd71204, whose lint source is byte-identical to the head; - the commit trailers follow AGENTS.md's model-free pair;
- the squash subject
fix(lint)matches thepatchchangeset.
Out-of-scope findings:
- The
fix:hints stay long:sharing-rule-runtime-variable-condition's is 2,280 characters. Whether hints get the one-line budget is this card's own later call, as slice 3's ACCEPT already noted; this card carries it. - objectql's engine sentences carry a location suffix, so a quoted engine verdict reaches 195 characters, and a longer engine sentence could pass 200. Acceptance note: the prose is objectql's.
For the next slice: PR #22670's body carries the "Remaining for later slices" list (71 ids).
Generated by Claude Code
- Message prose only, by a scan of every +/− line that carries control flow in the two rule files:
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsLanded: PR #22670 →
07ee6f6e58(stage 2 slice 4,Part of #22161). The card goes back topm:queuefor its later slicesdomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-10T10:57Z · holder of claim6095284867, released by this act.- Landed: through the merge queue at 2026-10-10T10:56Z as
07ee6f6e58, a squash with one parent,e194ab4f7a. The queue did not eject it. - The review chain: the ACCEPT
6096383240and the pre-queue record6096538004, both ondaf3fabf0d. - Content check: all 12 PR paths on
07ee6f6e58are blob-equal to the reviewed headdaf3fabf0d. - What now holds (
@objectstack/lintpatch):- the 9 ids of the RLS-predicate and sharing-rule enforceability rules print one verdict sentence each, the longest 195 characters where they were 492 to 2,056;
- their reasoning sits in 9
RULE_EXPLANATIONSentries behindos explain RULE_ID; - rule ids, severities, paths, hints and accept/refuse behaviour are unchanged.
- For the later slices: 71
packages/lintids remain, listed by file under "Remaining for later slices" in PR fix(lint): one-line verdicts for the RLS-predicate and sharing-rule enforceability rules;os explain RULE_IDcarries their reasoning #22670's body.- Every later slice's evidence includes
pnpm --filter @objectstack/spec exec vitest run --project repo. - ⛔ A slice's gate run must not include
check:type-check-debt --re-measure, which builds every package (spec (17.7.0): a list view or dashboard cannot be limited to an audience, andrequiredPermissionshas no any-of form — an app cannot show each audience only its own views and boards #22611's ACCEPT6096250858, deviation 2). - The long
fix:hints are still this card's own call (sharing-rule-runtime-variable-condition's is 2,280 characters).
- Every later slice's evidence includes
- Mis-close scan: the PR body, its commits and the squash message carry no closing keyword, so the merge closed nothing.
Release:
session_01KNKBCRDJCu5tGy3TEbvtrF· why: a partial landing (Part of #22161), and this seat stands down by the maintainer's word (seat post #18883) · to:pm:queue, unassigned. This act moves the cardpm:dispatched→pm:queueand removes the assigneezhuangjianguo.
Generated by Claude Code
- Landed: through the merge queue at 2026-10-10T10:56Z as
Filing gate: ③ task dispatched by the maintainer, quoted verbatim below; measurements class (a), this session.
reach:
pnpm run validateon the tutorial project prints the warning as a single 856-character line (the whole validate output is 1,734 characters, so the one warning is half of it);pnpm run buildprints it plus a second ~700-character "Give … a consumer" paragraph;npx os devprints both again on every compile.Reader:
domain:specseat for the rule text (packages/lint, author-time rulefield-no-consumers);domain:cliseat for the printer and for anos explainentry point that can take a rule id (packages/cli/src/commands/explain.tsaccepts schema names only:os explain [SCHEMA]).Dedup:
search_issues"validate warning message too long field-no-consumers paragraph unreadable terminal shorten one line rule id os explain" → 3 hits, all closed and none about message length: #17135 (what the rule counts as a consumer), #11529 (the 50-warning cap), #11947 (line-length gate for repo files).Filed on the maintainer's instruction in this session (category ③, quoted verbatim): 「validate 的警告段落约 900 字符,不可读。
field-no-consumers在 validate、build、dev 各打一次整段。建议一行裁决 +rule:id + "os explain field-no-consumers看完整推理",长文搬进 explain 或文档」Today
One line, 856 characters, no wrap.
buildadds the second paragraph ("Give "description" a consumer — a view column, a form section, … Roots scanned: objects, views, pages, apps, flows, dashboards, reports, datasets, actions, hooks, jobs, emailTemplates, agents, tools, skills, apis, webhooks, sharingRules, analyticsCubes (consumers) · translations, data, mappings, permissions (carriers); test fixtures are never scanned."). The reasoning is correct and complete; it is also unreadable at 80 columns and arrives three times per edit-run loop.Asked for
rule:line as it is today.os explain rule <id>(new:explaincurrently takes only a schema name) and/or the rule's docs page; the message carries the pointer.action-governanceline and thesecurity-owd-unsetrefusal are the next two a newcomer meets.validateprints a warning once per run;devprints it once per compile, not again at serve.Generated by Claude Code