Skip to content

fix(lint): one-line fix lines for the slice-19 rule ids (two flow wrong-syntax ids, capability, liveness ledger, two page-field ids, list-view mode); os explain carries what they explained - #23918

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s19-five-file-hints
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s19-five-file-hints

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22161
Clause-②: no

Slice 19 of the card: the over-long fix: lines, continued in slices 11–17's shape, for the ids slice 16's report (6140250582) lists in validate-capability-references.ts, lint-liveness-properties.ts, validate-page-field-bindings.ts and validate-list-view-mode.ts, plus lint-flow-patterns.ts's two wrong-syntax ids now that #19939 has finished. Each finding's hint (the CLI's fix: line, the runtime advisory's hint) of these 7 rule ids is now one remedy line of at most 200 characters, and what the lines used to explain is each id's RULE_EXPLANATIONS entry behind os explain RULE_ID. The card stays open: the last section lists what is left.

Claim: 6213957180 (the newest Claim: naming this branch; the only later-or-concurrent claim, 6213942673, names slice 18's branch; verified before the first edit). No file of slice 18 (data-model-rules.ts) is touched.

What changes

One remedy line per finding. Every hint the lint suite fires, before → after (count of distinct hints · length range). Measured with the push-preload census (an Array.prototype.push preload over the whole lint suite, plus an Array.prototype.map leg for findings built with .map()), at the base 6db53acd6 and at this branch:

rule id file lint suite, before → after runtime door
flow-double-brace-interpolation lint-flow-patterns.ts 18 · 297–1413 → 12 · 87–197 flow write, 2xx advisory
flow-bare-dollar-reference lint-flow-patterns.ts 23 · 114–1234 → 11 · 83–198 flow write, 2xx advisory
liveness-ledger-unreadable lint-liveness-properties.ts 1 · 270 → 1 · 82 (map leg) only on an install that lost a ledger file
capability-reference-unknown validate-capability-references.ts 1 · 241 → 1 · 131 CLI only
page-section-count-not-multi-value validate-page-field-bindings.ts 1 · 228 → 1 · 167 does not fire on a page write
list-view-filters-in-views-mode validate-list-view-mode.ts 2 · 99–219 → 2 · 99–135 CLI only
page-field-unknown validate-page-field-bindings.ts 24 · 129–206 → 23 · 47–94 does not fire on a page write

Measured and not changed: liveness-planned-property (slice 16 listed it at 230) is 3 · 1–88 today. No shipped ledger row carries an authorHint, so its line is the verdict's default. The file's over-200 id is liveness-ledger-unreadable, which the push census cannot see (its findings come from a .map()); the map leg measured it, so it is converted instead. Stated as a deviation from the claim's per-file count.

Counts that fall are lists and judge sentences that no longer make each hint distinct. The flow counts also include cases this slice added (a reference list too long for one line, in each slot kind).

The flow ids keep the judge's spelling, and only where it evaluates. Before this change, both flow hints spliced the spec value-slot or text-slot judge's whole remedy into the line: guards, the run user's null, callee and client sentences, literal-position and legacy-shape paragraphs, the list of text slots. After:

  • Value slot: the line is the judge's own direct spelling, read out of the judge's remedy and never re-spelled here (directSpellingAt, matching the judge's two direct openings, Write `{x}` as ENVELOPE and the text-with-holes concatenation). FROM (681) A value slot reads a CEL value envelope, … $source.id names $source, which is not one of the flow engine's own $ variables, … Write {source.id} as { dialect: 'cel', source: 'source.id' }. CEL refuses an absent variable or key … (the guarded form writes null). TO (116) A value slot stores a bare `$` reference as written: write `{source.id}` as { dialect: 'cel', source: 'source.id' }.
  • Where an envelope at the string's place would not evaluate (a string inside an object or list literal, a legacy assignments: [{ variable, value }] shape, a $User path that never resolved), the judge's remedy opens differently. The line names no envelope and points at os explain, so it never prescribes an envelope that would be stored as data. For a double brace it deliberately does not point at the build's own value-slot refusal. That refusal reads the outer braces as literal text, so its spelling is '{' + record.title + '}', not the hole the author meant; a test pins this as the control.
  • Text slot: the {{ }} hole for each reference the engine binds. For any other $ root, the remedy every refusal of one ends with: compute the value into a variable with an assignment node and write {{ v }}.
  • Literals and elsewhere: the literal lines keep "neither {var} nor {{ }} resolves there. Write the value itself.". Where they are refused, and the list of text slots, moved to the entries.
  • Bound: a list of references that would not fit in one line takes a fixed bounded line (withinHintBudget, HINT_BUDGET = 200).

Lists are no longer echoed (slices 11, 12, 14, 16 and 17's rule). page-field-unknown no longer appends Object fields: …; it names the nearest declared field with the package's one "Did you mean" helper (suggestName).

os explain carries the rest. Six ids gain an entry, so their rule: line now carries the pointer: flow-double-brace-interpolation, flow-bare-dollar-reference, liveness-ledger-unreadable, page-field-unknown, page-section-count-not-multi-value and list-view-filters-in-views-mode. capability-reference-unknown's entry gains one sentence: a capability another installed package declares needs no change. The two flow entries share five paragraphs (the text slots, the literals, the CEL caveats, the filter operand, the no-envelope positions), written once. Every id already had a rule-id constant on the root barrel, so no published name is added and Clause-②: no holds.

Nothing else moves. Every message, rule id, severity and path is unchanged, and so is what each rule accepts or refuses. The rule-file hunks are hint expressions, comments, the new hint helpers and constants in lint-flow-patterns.ts, and the suggestName import.

File surface: the claim's

The five rule files, their tests, rule-explanations.ts (entries for these ids only, inserted beside the capability entry and away from slice 18's data-model entries), and .changeset/22161-s19-five-file-fix-lines.md. Pin sweep, repo-wide (git grep of each id, its constant and every distinctive old phrase, docs and examples included): only packages/lint/src tests pin these hints, so no packages/cli or packages/metadata-protocol rider was needed. Every flipped flow pin keeps its structural half: each envelope the line prescribes, put back in the slot, still passes the value-slot judge, the build door, the flow contract and this rule with 0 refusals. What left the line is asserted absent there and present in the entry.

The runtime wire

lintFlowPatterns is CLI_AND_RUNTIME with runtimeTypes: ['flow']. Measured at the door with runRuntimeAuthoringRules (a scratch probe of 12 writes, base rule sources vs branch), all 2xx advisories[].hint, FROM → TO:

  • flow-bare-dollar-reference: a value-slot $source.id 681 → 116, $User.Id 681 → 121, $User.Email 742 → 198, a nested literal 1231 → 198, a legacy assignment 1030 → 198; a text-slot $User.Id 767 → 144, $error.message 114 → 83;
  • flow-double-brace-interpolation: a value-slot {{ }} 746 → 116, a notify literal 297 → 137, a value no slot reads 362 → 158.

validateCapabilityReferences and validateListViewMode are CLI_ONLY. A page write runs 4 rules and neither page id fires there (0 findings, base and branch). lintLivenessProperties runs on email_template, mapping and datasource writes, but liveness-ledger-unreadable fires only when @objectstack/spec ships without a ledger file; not probed. No 422 changes.

Verification (all at the merge head bb6c6032d unless noted; heavy runs via os-verify-lock.sh, verdict lines read)

  • Lint suite: base 6db53acd6 140 files / 6776 passed | 5 skipped; branch b8c9c4ac1 140 / 6788 passed | 5 skipped; merge head 140 / 6794 passed. VERDICT 0 each.
  • Census: base 231 ids fire, 33 over 200 (32 on the push leg alone); branch and merge head 231 / 26, the difference exactly these 7.
  • Lint build (check-dts-emitted) + typecheck (tsc + check:test-typecheck OK, 2 files / 6 errors / 2 pinned): VERDICT 0 at b8c9c4ac1 and at the merge head.
  • CLI unit tier (--project unit), branch b8c9c4ac1: 302 files / 4498 passed, VERDICT 0.
  • @objectstack/metadata-protocol whole suite, branch b8c9c4ac1: 242 passed | 3 skipped files, 20598 passed | 19 skipped, VERDICT 0.
  • @objectstack/spec repo project (--project repo, which reads packages/lint/src as a text corpus), branch b8c9c4ac1: 63 files / 1070 passed, VERDICT 0.
  • objectstack validate (built CLI), branch b8c9c4ac1, on app-crm / app-todo / app-showcase / app-multi-package: exit 0 each, rule: lines 9 / 7 / 68 / 3; none of the 7 ids fires on the apps.
  • Ablation (one shot from the committed b8c9c4ac1, scripts/ablation-replace.mjs under the lock, a shell trap restoring HEAD by absolute path; the lint suite imports source, so there is no dist leg): HINT_BUDGET = 200 → 2000 (anchor x1 → x0, replacement x0 → x1, blob a2a5a375ea3f → 8e143fdfd2cd). Predicted before the run: exactly the two bound cases red. Observed: lint-flow-patterns.test.ts 2 failed | 227 passed, exactly those two. Restored blob == HEAD, git diff HEAD empty, status clean.
  • Derived gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths), 62 families, run one by one with each exit code captured before any pipe; --ran reconciliation in the report.
  • Control-character scan of every changed file and of this body: no match.

Acceptance notes

  • The count question in the dispatch, measured. The census on main (6db53acd6) says 33 ids over 200 = 3 repo-gate (lint-startup-registry-verdict.ts) + 24 in the files slice 2 fenced (PR fix(lint): one-line verdicts for the widget, dataset, security-posture and visibility rules; os explain RULE_ID carries their reasoning #22448) + 6 author-time outside the fence. Slice 16's ledger counted only the last group (13 then, 6 after slice 17), so the two counts were each right about a different population.
  • The flow lines carry less than they did, on purpose and bounded. At a position where no envelope at the string's place evaluates, the line now names no spelling. The os explain entry describes each such position, but cannot carry a per-string spelling. For a double brace there, the build's own refusal is no substitute (it keeps the braces as text, pinned above).

Remaining for this card (census at the merge head, 26 over 200, by file)

  • data-model-rules.ts (3 measured): unique/legacy-organization-composite 578, unique/unscoped-declared-index 380, unique/double-declaration 279. This is slice 18's file; slice 17 recorded 3 more of its ids as NOT MEASURED by the census.
  • lint-startup-registry-verdict.ts (3, repo gate, not author-time): startup-open-vocabulary-verdict 972, startup-verdict-assertive-wording 972, startup-source-unparseable 254.
  • In slice 2's fenced files (20), outside the card's close condition as triage worded it:
    • lint-flow-patterns.ts (12): flow-loop-body-uncontained 800, flow-multi-write-unfiltered 695, flow-try-catch-without-catch 594, flow-decision-inclusive-overlap 437, flow-approval-revise-target-not-service-owned 412, flow-time-relative-antipattern 397, flow-date-equality-filter 379, flow-runas-unscoped 372, flow-decision-mode-invalid 295, flow-decision-unconditional-branch 286, flow-error-label-not-fault 239, flow-branch-label-unmatched 224;
    • validate-flow-template-paths.ts (2): flow-template-field-unprovisioned 362, flow-template-lookup-traversal 316;
    • validate-page-field-bindings.ts (2): page-field-unprovisioned 362, page-section-group-unknown 279;
    • validate-component-props.ts (1): component-props-invalid 258;
    • validate-filter-tokens.ts (1): filter-token-unknown 259;
    • validate-form-layout.ts (1): form-section-group-unknown 275;
    • validate-print-page-blocks.ts (1): print-page-block-unprintable 362.
  • The close-out slice still owes the content/docs/ui/react-pages.mdx transcript carry from slice 15's landing record 6136999237.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XK9otrEUnkWkwJQAnVeTk6


Generated by Claude Code

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 31 documentable anchor(s).

3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/permissions/authorization.mdx (via validateCapabilityReferences (symbol, a top-level function))
  • content/docs/permissions/capabilities.mdx (via validateCapabilityReferences (symbol, a top-level function))
  • content/docs/permissions/permission-sets.mdx (via validateCapabilityReferences (symbol, a top-level function))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v13.mdx (via validateCapabilityReferences (symbol, a top-level function))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d5666be8769a18c5c8d7695b3ac9100af2394620 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 16438192e6e6fdd5e710c462013c382bb2da3897 — the merge of head bb6c6032d6073e645b9247c0625f2951c4e6ce6b into base d5666be8769a18c5c8d7695b3ac9100af2394620, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 16438192e6e6fdd5e710c462013c382bb2da3897 && git checkout 16438192e6e6fdd5e710c462013c382bb2da3897
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d5666be8769a18c5c8d7695b3ac9100af2394620 bb6c6032d6073e645b9247c0625f2951c4e6ce6b && git checkout -B drift-repro d5666be8769a18c5c8d7695b3ac9100af2394620 && git merge --no-ff bb6c6032d6073e645b9247c0625f2951c4e6ce6b

node scripts/docs-audit/affected-docs.mjs --json d5666be8769a18c5c8d7695b3ac9100af2394620

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d5666be8769a18c5c8d7695b3ac9100af2394620 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Review: ACCEPT · #22161 slice 19 · PR #23918 @ bb6c6032d6073e645b9247c0625f2951c4e6ce6b · domain:spec seat 2 (#18549) · 2026-10-16T13:27Z

Session session_01LFqdRsuM5nkqoJdHd5ztVk · os-bill. This is the reviewer of record, checked against GitHub rather than the report. The chain: claim 6213957180, then report 6216051129.

  • Shape.
    • Draft against main. Line 1 is Part of #22161, with no closing keyword; line 2 is Clause-②: no.
    • 12 files, +624/−91, all in packages/lint plus one changeset. No governed path is touched, and no file of slice 18 (data-model-rules.ts) is touched.
    • No packages/spec/src/** file and no Clause-②: yes, so no contract-tier review is owed. This is the seat's own read.
    • git merge-tree is clean against main @ 11fea6b000, and on top of slice 18's head 3993be17dd.
  • The slice.
    • One line each. The hint of 7 ids is one remedy line of at most 200 characters on every variant the suite fires. Old → new:
      • flow-double-brace-interpolation 1413→197
      • flow-bare-dollar-reference 1234→198
      • liveness-ledger-unreadable 270→82
      • capability-reference-unknown 241→131
      • page-section-count-not-multi-value 228→167
      • list-view-filters-in-views-mode 219→135
      • page-field-unknown 206→94
    • The two flow ids. The fix: line takes the spec value-slot judge's own direct spelling, read out of the judge's remedy and never re-spelled. That happens only where the judge's remedy opens with one (DIRECT_SPELLING).
      • Every other position falls back to a fixed line that points at os explain. So do the text-slot and value-slot lines that would run past 200 (withinHintBudget).
      • The ablation HINT_BUDGET 200 → 2000 turned exactly the two bound cases red.
    • page-field-unknown. It no longer echoes the object's field list. It names the nearest field through the shared suggestName instead, which is the slices' rule: a list goes only where a "Did you mean" covers it.
    • What moved. Six ids gain RULE_EXPLANATIONS entries, and capability-reference-unknown's entry gains a sentence. All 7 constants were already root-barrel exports, so Clause-② stays no.
      • The seat checked the CEL facts in the flow entries against spec source on this head: current_user carries id, positions, organizationId and isPlatformAdmin (flow-template-token.ts, flow-value-slot-template.ts); CEL_RUN_USER_ID_GUARDED is current_user != null ? current_user.id : null; coalesce(…, '') is the value-slot judge's own advice.
    • Unchanged. Messages, ids, severities, paths and accept/refuse results do not change. The seat read the five source diffs hunk by hunk; only hint composition moved.
  • Changeset. @objectstack/lint patch. The flow ids reach the runtime gate as 2xx advisories[].hint on a flow write, and the changeset states FROM → TO as measured at the door. There is no 422 change.
  • Gates.
  • Open questions, the seat's reading.
    • Q1, nested-literal / legacy-assignment / unresolved-$User positions: A, keep as shipped. A third judge opening ties the lint to one more sentence of spec prose. And the nested-literal envelope is the whole value, so pasting it at the string's place would bring back the trap the line exists to prevent.
    • Q2, slices for the 20 ids slice 2 fenced: the close condition is triage 6109231286 (c). It closes once no author-time id is over 200 outside slice 2's 26 fenced ids and item 3 lands. The seat does not re-rule a triage close condition. The question goes to triage on the card with this landing's Release.
  • Accepted deviations.
    • liveness-planned-property measures 1–88 today and is unchanged; the file's over-200 id is liveness-ledger-unreadable, built by .map() and measured by the census's new map leg.
    • The census was extended with a map leg.
    • Model-free commit trailers, which the repo's pre-push check:commit-card-trailers requires.
    • The shared checkout was detached and restored, not edited.
  • Out of scope, dispositions.
    • The 20 fenced ids still over 200: they stay with this card, listed by file in the Release, with the triage question.
    • The value-slot judge's refusal of a {{ }} string keeps the outer braces as literal text: Acceptance notes. It is pre-existing. main's hint already said "the value-slot refusal reads its outer braces as literal text". The lint line spells the hole the author meant, and the test pins the refusal as a control.
  • Landing terms. Ready, then auto-merge. The card stays open: this is a Part of landing.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 13:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit cbbd96a Oct 16, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-22161-s19-five-file-hints branch October 16, 2026 13:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants