Skip to content

test: retire the local assertions objectstack lint already reds on at error severity - #1629

Merged
os-steve merged 4 commits into
mainfrom
claude/issue-1625-retire-proven-duplicates
Sep 6, 2026
Merged

test: retire the local assertions objectstack lint already reds on at error severity#1629
os-steve merged 4 commits into
mainfrom
claude/issue-1625-retire-proven-duplicates

Conversation

@os-steve

@os-steve os-steve commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Fixes #1625

Retires 11 rule ids across 9 local assertions in 6 files — 169 lines removed, 0 added. Test-only: no metadata change, no suppression, no local re-severity, no new test. pnpm verify green (160 files, 3386 passed, 1 skipped, exit 0).

The soundness check, before anything was deleted

Both gates hold, so the surveys' measurements carry.

  1. Pins unmoved. @objectstack/spec is 17.3.0 in package.json; @objectstack/lint is transitive through @objectstack/cli and pinned 17.3.0 in pnpm-lock.yaml. Read back from node_modules after install: lint 17.3.0 · spec 17.3.0 · cli 17.3.0 · formula 17.3.0.
  2. src/ drift. git diff 1670557..HEAD -- src/ package.json (the F2–F6 base) is empty — every F2/F3/F5/F6 measurement carries verbatim. git diff a0362a37..HEAD -- src/ package.json (the F1 base) is one comment-only hunk: src/objects/_case-assignment.ts, +24/-4, all // lines, from docs(case-assignment): guard 4's comment names the triage rule, not the flag #1615 (a docs commit). No effective metadata moved.
  3. Third, independent confirmation. The clean-tree baseline at this branch's base reproduces both reports byte-for-byte: passed:true · errors:0 · warnings:17 (16 component-props-invalid + 1 component-props-unknown-key) · suggestions:12 · hiddenPlatform:2319, exit 0.

Rows re-run anyway: every row below, all 11 — each deletion carries its own fresh injection at this base, so the "re-run if src/ moved" clause is satisfied by construction rather than by argument.

Base cut from origin/main at ff4332161745a34c5eb2a45a95ccbfc8bf902ca4 (ff43321).

What was deleted — one row per rule id, with its three-gate citation and its observed exit 1

Every "observed" cell is a fresh injection into this branch's base, restored by blob hash and verified by observation (git status clean AND git hash-object PATH equal to git rev-parse HEAD:PATH), never by exit code.

Citation legend (the # form is kept out of the table below so no closing keyword can end up adjacent to a card number): F1 = survey #1613, report comment 5555835078; F2-6 = survey #1621, report comment 5556100629.

# file · assertion rule id gate 1 (non-vacuous) gate 2 (reachable) gate 3 (subsuming) observed here
1 analytics-integrity · every chart yAxis is a measure of the report dataset chart-measure-unknown F2-6 §1a A2 — 13 report blocks, 5 chart yAxis; RED on its own defect F2-6 §1a A2 YES F2-6 §1a A2 YES non-measure yAxis ⇒ chart-measure-unknown error, exit 1; only that case reds
2 analytics-integrity · every widget dataset / values / dimensions entry resolves widget-dataset-unknown F2-6 §1a A5 — 62 widgets, all dataset-bound; RED F2-6 §1a A5 YES F2-6 §1a A5 YES unknown dataset ⇒ widget-dataset-unknown error, exit 1
3 (same assertion) widget-measure-unknown F2-6 §1a A5 — 78 widget value entries; RED F2-6 §1a A5 YES F2-6 §1a A5 YES unknown values[0]widget-measure-unknown error, exit 1
4 (same assertion, third sub-check) widget-dimension-unknown (unclaimed, fires) F2-6 §1a A5 — 31 widget dimension entries; RED F2-6 §1a A5 YES F2-6 §1a A5 YES unknown dimensions[0]widget-dimension-unknown error, exit 1
5 analytics-integrity · every placeholder in a temporal filter position is a recognised date macro filter-token-unknown F2-6 §1a A9 — 79 filter leaves across 66 sources; RED F2-6 §1a A9 YES F2-6 §1a A9 YES {fortnight_ago}error, exit 1 on BOTH surfaces this assertion walks: report runtimeFilter and dashboard widget filter
6 view-references · every list sort targets a real field sort-field-unknown F2-6 §3b — 54 listView entries, 47 sort entries; RED F2-6 §3b YES F2-6 §3b RED on the same defect dangling sort field ⇒ sort-field-unknown error, exit 1
7 view-references · list view and page filters only use user tokens or date macros filter-token-unknown F2-6 §3b — 14 views, 13 listView blocks, 8 pages; RED F2-6 §3b YES F2-6 §3b RED on the same defect error, exit 1 at BOTH walked positions (view list filter; page component filter, 11 authored) and on all three near-miss spellings the sibling assertion pins: {this_quarter_start}, {current_user}, {not_a_token}
8 action-references · every rowAction / bulkAction names a defined action action-name-undefined F2-6 §3b — 31 actions; RED. Population re-measured here: 0 rowActions but 2 bulkActions authored, so non-vacuous F2-6 §3b YES F2-6 §3b RED on the same defect undefined name in the live bulkActions population ⇒ action-name-undefined error, exit 1
9 authorization-coverage · controlled_by_parent objects expose a parent the engine can resolve security-controlled-by-parent-no-relation F2-6 §2 — 18 objects, 75 grants; every planted defect went red F2-6 §2 YES F2-6 §2 YES demoting crm_quote_line_item's master-detail and its required lookup ⇒ error, exit 1
10 sharing-seeding · seeds all of them — seeded + 0 skipped sharing-rule-unlowerable-condition F2-6 §2 — 10 sharing rules; RED F2-6 §2 YES F2-6 §2 YES (via sharing-seeding) nested path ⇒ error, exit 1; has() guard ⇒ error, exit 1
11 (same assertion) sharing-rule-runtime-variable-condition F2-6 §2 — 10 sharing rules; RED F2-6 §2 YES F2-6 §2 YES (via sharing-seeding) record.owner_id == current_user.iderror, exit 1
12 readonly-write-semantics · escalate_case writes only user-writable columns (readonly half only) flow-update-readonly-field F1 §3 — 22 cases on a real engine; RED on escalate_case writes only user-writable columns F1 §4 YES F1 §4 RED on the same defect is_escalated: true on the user-context node ⇒ error, exit 1, naming flow, node and field; exactly one case reds

(12 rows, 11 distinct rule ids — rows 2–4 are three sub-checks of one assertion, and filter-token-unknown is retired once in F3 and once in F2, which is how the surveys count it.)

Line counts removed, per file

file lines removed
test/analytics-integrity.test.ts 50
test/view-references.test.ts 41
test/action-references.test.ts 32
test/sharing-seeding.test.ts 20
test/authorization-coverage.test.ts 18
test/readonly-write-semantics.test.ts 8
total 169

Two helpers were left used solely by a retired assertion and go with it: MACRO_RE plus the isDateMacroToken import in analytics-integrity, and actionNames in action-references. Nothing else in any of these files changed — group B is byte-identical in all six.

The 10 rows I did NOT take, each on a measurement rather than a hunch

This is the substance of the run. The card named 21 rows; I took 11. Every decline below is a gate-3 failure I measured at this base, with a control that fired in the same run. Retiring any of them would have removed coverage the platform does not have — silently, with nothing going red to say so.

rule id why it stays
visibility-bare-identifier New finding, not in either report. status == "qualified" ⇒ error, exit 1 (control). But has(status) && status == "qualified"exit 0, nothing, while the local sweep reds — once the identifier also appears inside a has(), the rule goes silent for it entirely. That is the guarded-but-unprefixed spelling an author is most likely to write. Filed as objectstack#16118.
visibility-predicate-syntax No standalone local assertion. Its only local counterpart is the parse guard inside the sweep above, which stays for the row above.
visibility-predicate-unknown-function Reds only the live-engine TOTALITY cases (answers on a brand-new record with no keys at all and siblings). Those catch predicates that parse, resolve and are record-bound yet still abort at runtime — the #688 class — which no static rule sees.
expression-invalid Same shape: reds only the null-guard sweeps and the live-engine totality cases. An unguarded-but-VALID predicate is the defect those assertions exist for, and expression-invalid does not see it.
rls-predicate-unenforceable New finding. Nested path ⇒ error, exit 1 (control). But an RLS predicate naming a field the object does not have ⇒ exit 0, and one reading current_user.nopeexit 0, while both local sweeps red. Filed as objectstack#16119.
rls-predicate-unparseable Fires (error, exit 1) on a shape the local sweeps ACCEPT — their clause regex reads is_private == = false as field/op/rhs, so the suite stays green. Nothing to retire; and see the row above for the converse.
sharing-rule-object-not-shareable public_read_write ⇒ error, exit 1 (control). But the third case the local assertion covers — a read-level share on a public_read object — ⇒ exit 0, local RED.
sharing-rule-object-controlled-by-parent Fires (error, exit 1), but it shares the assertion above, which is strictly broader by that same public_read case.
security-fls-unqualified-key F2-6 §2 flagged it conditional ("only if objectstack#16108 lands"). Re-measured here: unqualified key ⇒ error, exit 1; crm_account.description_nope ⇒ exit 0, local RED. #16108 has not landed.
list-view-field-unknown (kanban.groupByField) Missing field ⇒ error, exit 1 (control). But a groupByField naming a real field with no optionsexit 0, local RED. The rule covers half of what the assertion covers.

Declared no-ops — not silently retried

  • has(status) && status == "qualified" as a visibility-bare-identifier injection. It reached disk (blob hash moved, marker counted) but produced no verdict. That first attempt was out of the rule's class, so it proved nothing about reachability; re-run with the unwrapped spelling, which fired. The negative it produced is reported above on its own merits, with its own control — it is not being banked as the failed measurement.
  • Two anchor refusals before the tree was touched, both because the anchor occurred 3 times rather than 1: a sort field in case.view.ts and object: 'crm_account' in account.sharing.ts. Both re-run against unique anchors.

Out of scope, as the card requires

Untouched, and none of them measured as a candidate: the 21 "nothing to retire" rows (no new test was written to match any rule), the 18 waiting on #1581, the 18 blocked upstream, the 22 unmeasured, test/metadata-references.test.ts against component-props-* (the clean tree still carries those 16+1 warnings and that test is still green on them), rls-predicate-over-budget (dormant by design until the GA limits flip), security-private-no-readscope (ships at info), and the mis-credited rows.

chart-dimension-unknown deserves a note: F2-6 §1a lists A3 (every chart xAxis is a dimension) as retirable "once the id is corrected", but the card names chart-field-unknown among the mis-credited rows and scopes this PR to 21. Taking A3 would have been a 22nd row, so it is left in place and reported rather than deleted.

Verification

  • pnpm verifyexit 0: validate passed, typecheck, lint (errors:0), lint:i18n-gate, hygiene, hygiene:tokens, build, then Test Files 160 passed (160) · Tests 3386 passed | 1 skipped (3387).
  • Every injection in this run reached disk before any conclusion was drawn (on-disk blob hash compared against the HEAD blob, plus a marker count) and was restored by observation. Every restore verified: git status empty for the path AND git hash-object equal to git rev-parse HEAD:PATH.
  • objectstack lint --json was parsed with JSONDecoder().raw_decode, so pnpm's ELIFECYCLE tail after the payload on exit 1 cannot turn a real exit-1 verdict into "no verdict" — the trap Survey part 2 — run F2–F6 through the three-gate test that #1613 proved necessary on F1 (epic #1579, step 3; needs no release) #1621 hit in its own harness.
  • Heavy runs went through the shared verify lock; verdicts read from its VERDICT command-exit line.
  • Changeset: .changeset/retire-assertions-the-platform-already-reds-on.md, empty frontmatter (the sanctioned "releases nothing" declaration changeset-check.yml documents).

Refs #1579 · #1613 · #1621 · new upstream cards objectstack#16118 and objectstack#16119.


🤖 Generated with Claude Code

https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj


Generated by Claude Code

…ds on

Retires `chart-measure-unknown`, `widget-dataset-unknown`,
`widget-measure-unknown` and `filter-token-unknown` from
`test/analytics-integrity.test.ts` (50 lines).

All four gate at error severity on the pinned @objectstack/lint@17.3.0 with
no `--strict`: each defect below was injected into this branch's base and
`objectstack lint` exited 1 naming the rule.

- `every chart yAxis is a measure of the report dataset` — a non-measure
  yAxis gives `chart-measure-unknown` at error, exit 1.
- `every widget dataset / values / dimensions entry resolves` — all three of
  its sub-checks are covered: `widget-dataset-unknown`,
  `widget-measure-unknown` and `widget-dimension-unknown`, each error/exit 1.
- `every {placeholder} in a temporal filter position is a recognised date
  macro` — `filter-token-unknown` fires at error/exit 1 on BOTH surfaces this
  assertion walks, report `runtimeFilter` and dashboard widget `filter`.

`MACRO_RE` and the `isDateMacroToken` import were used solely by the third
assertion and go with it.

⛔ Deliberately NOT retired from this file: A1 (a chartless report block's
dataset is never resolved — objectstack#16105), A4 (`rows`/`columns` are
matched by no rule at all) and A8 (`filter-preset-comparand` never fires on
this authoring path — objectstack#16106).

Refs #1613 #1621

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
…eds on

Retires `sort-field-unknown`, `filter-token-unknown` and
`action-name-undefined` (73 lines: 41 from view-references, 32 from
action-references).

Each defect was injected into this branch's base and `objectstack lint`
exited 1 naming the rule, on the pinned @objectstack/lint@17.3.0:

- `every list sort targets a real field` (47 sort entries inspected) — a
  dangling sort field gives `sort-field-unknown` at error, exit 1.
- `list view and page filters only use user tokens or date macros` —
  `filter-token-unknown` fires at error/exit 1 at BOTH positions this
  assertion walks (a view list filter and a page component filter), and on
  all three near-miss spellings its sibling assertion pins as unresolvable:
  `{this_quarter_start}`, `{current_user}` and `{not_a_token}`. That sibling,
  `the widened list-view rule still rejects what neither path resolves`,
  stays — it pins the local allow-list's own boundary, which no rule reports.
- `every rowAction / bulkAction names a defined action` — measured
  non-vacuous first (0 rowActions but 2 bulkActions authored), then
  `action-name-undefined` at error/exit 1 on that live population.

`actionNames` was left used solely by the retired assertion and goes with it.

⛔ Deliberately NOT retired: `kanban groupByField is a select field with
options`. `list-view-field-unknown` covers only its missing-field half — a
groupByField naming a real field with NO options gives exit 0 and nothing,
while the local assertion reds. Gate 3 fails; the assertion stays.

Refs #1613 #1621

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
…s on

Retires `security-controlled-by-parent-no-relation`,
`sharing-rule-unlowerable-condition` and
`sharing-rule-runtime-variable-condition` (38 lines: 18 from
authorization-coverage, 20 from sharing-seeding).

Each defect was injected into this branch's base and `objectstack lint`
exited 1 naming the rule, on the pinned @objectstack/lint@17.3.0:

- `controlled_by_parent objects expose a parent the engine can resolve` —
  demoting `crm_quote_line_item`'s master-detail and its required lookup
  gives `security-controlled-by-parent-no-relation` at error, exit 1.
- `seeds all of them — seeded + 0 skipped` — every refusal class the
  compiler has is reported: a nested path and a `has()` guard both give
  `sharing-rule-unlowerable-condition`, a `current_user.id` read gives
  `sharing-rule-runtime-variable-condition`, all at error/exit 1. The
  file's own live-driver guards keep the local half independently: a rule
  that stops seeding drops out of `seeded`, which reds
  `every seeded rule has a witness, and no witness is stale`, and
  `%s materialises exactly the records it declares` asserts `compiled.ok`
  per rule.

⛔ Deliberately NOT retired, each on a MEASURED gate-3 failure at this base:

- `every FLS key is object-qualified and names a real field` — an unqualified
  key gives `security-fls-unqualified-key` at error/exit 1, but a
  qualified-but-dangling key (`crm_account.description_nope`) gives exit 0
  and nothing, while the local assertion reds on both. objectstack#16108 has
  not landed.
- `every predicate is pushdownable and names real fields` and `the first
  field of a using predicate exists` — the platform covers only the
  non-pushdownable half (`rls-predicate-unenforceable`, error/exit 1). An
  RLS predicate naming a field the object does not have, and one reading an
  un-pre-resolved `current_user.nope`, BOTH give exit 0 while the local
  assertions red. Conversely `rls-predicate-unparseable` fires on a shape
  the local regex accepts. Neither side subsumes the other.
- `a sharing rule only targets an object whose OWD lets shares apply` —
  `sharing-rule-object-not-shareable` and
  `sharing-rule-object-controlled-by-parent` both fire at error/exit 1, but
  the third case the local assertion covers, a read-level share on a
  `public_read` object, gives exit 0 while the assertion reds.

Refs #1613 #1621

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
…dy reds on

Retires `flow-update-readonly-field` (8 lines), and adds the changeset.

Group A only. Adding `is_escalated: true` to `escalate_case`'s user-context
`update_record` node gives `flow-update-readonly-field` at error, exit 1 on
the pinned @objectstack/lint@17.3.0, naming the flow, the node and the
field. Exactly one local case reds on it, and the readonly-specific half of
that case is what comes out.

⛔ Its first line stays byte-identical:
`expect(written.sort()).toEqual(['escalation_reason', 'priority', 'status'])`
is an EXACT-SET pin, and no platform rule reports a write set that lost
`status` or gained an ordinary editable column. That half is not a
duplicate, so it is not group A.

⛔ Deliberately NOT retired from F1, each on a MEASURED gate-3 failure at
this base:

- `references only namespaces the evaluator binds`
  (`test/view-predicate-dialect.test.ts`). A bare `status` gives
  `visibility-bare-identifier` at error/exit 1 — but the moment the same
  identifier also appears inside a `has()`, the rule goes silent for it
  entirely: `has(status) && status == "qualified"` gives exit 0 and nothing,
  while the local sweep reds. Control, same file, same site, same run: the
  unwrapped spelling exits 1. The local assertion is strictly broader.
- `visibility-predicate-syntax` has no standalone local assertion — its only
  local counterpart is the parse guard inside the sweep above, which stays.
- `visibility-predicate-unknown-function` and `expression-invalid` red only
  live-engine TOTALITY assertions (`answers on a brand-new record with no
  keys at all` and its siblings) and the null-guard sweeps. Those catch
  predicates that parse, resolve and are record-bound yet still abort at
  runtime — the #688 and #630 regressions — which no static rule sees.
  Retiring them would lose that coverage silently.

Refs #1613 #1621

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 6, 2026 2:13am UTC

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Retire the 21 local assertions proven duplicate through all three gates — the part of step 3 that was never release-blocked (epic #1579)

2 participants