Skip to content

[AAASM-5245] 🚨 (dashboard): Add no-restricted-syntax rule banning Record<> lookup tables - #1775

Merged
Chisanan232 merged 2 commits into
mainfrom
v0.0.1/AAASM-5245/feat/no_restricted_syntax_lookup_rule
Jul 28, 2026
Merged

[AAASM-5245] 🚨 (dashboard): Add no-restricted-syntax rule banning Record<> lookup tables#1775
Chisanan232 merged 2 commits into
mainfrom
v0.0.1/AAASM-5245/feat/no_restricted_syntax_lookup_rule

Conversation

@Chisanan232

Copy link
Copy Markdown
Contributor

Description

Adds the no-restricted-syntax ESLint rule from AAASM-5245 to dashboard/.eslintrc.cjs, scoped to the existing lint script's targets (src tests .storybook, .ts/.tsx). The rule flags any VariableDeclarator typed Record<...> whose initializer is a non-empty ObjectExpression — i.e. a new object-literal lookup table keyed by a Record<> type. This is the prevention half of Epic AAASM-5208: all 18 wire-data-keyed object-literal lookups identified by that Epic have already been converted to Map/Object.create(null) across prior merged PRs (AAASM-5211 through 5215); this rule stops a new one from being reintroduced.

Pure AST rule — no @typescript-eslint type-aware parser project required.

The three documented gaps (AAASM-5245's title)

Documented in a comment directly above the rule in dashboard/.eslintrc.cjs:

  1. Misses write-side = {} accumulators — deliberately, via the rule's own properties.length>0 predicate, so legitimate patterns like const headers: Record<string, string> = {} aren't flagged.
  2. Flags legitimate narrow-union Record<SomeUnion, T> tables that could be narrowed further or converted to Map — treated as a feature, not a false positive, since the rule can't distinguish a real union key from a raw wire key from AST alone.
  3. Trusts the key type annotation even where nothing enforces it at runtime — e.g. a bare as T cast at a fetch boundary. AAASM-5217 (a sibling ticket) audits exactly those cast sites separately; this rule can't see across that boundary.

Running the rule against the current codebase

Running the rule flagged 46 pre-existing sites. Of those:

  • 1 genuine miss: RoleCapabilityCards.tsx's ROLE_BADGE_TONE was still an object literal indexed by card.role, which is a widened string on the live-grants path (from GET /api/v1/iam/roles), not the closed member Role union — the exact defect class AAASM-5208 targeted. Fixed to Map, mirroring the identical fix already applied to the sibling ROLE_BADGE_TONE in MemberList.tsx (AAASM-5109/5190). Added a regression test covering constructor/toString/__proto__/hasOwnProperty/valueOf role ids, mirroring the existing MemberList.test.tsx coverage.
  • 45 legitimate gap-2/gap-1 sites: narrow-union Record<> display/config tables keyed by closed app unions (already validated/allow-listed before the table is ever indexed), plus a couple of write-side header/tally accumulators. Each gets a one-line // eslint-disable-next-line no-restricted-syntax with a short justification of why its key is closed or non-wire, so a genuinely new violation still fails loudly.

Type of Change

  • 🐛 Bug fix (the RoleCapabilityCards miss)
  • 🔧 Configuration / CI change (the new lint rule)
  • ✅ Tests added

Breaking Changes

  • No

Related Issues

  • Related Jira ticket: AAASM-5245 (subtask of AAASM-5216, closing out the prevention half of Epic AAASM-5208)

Testing

  • Unit tests added / updated (RoleCapabilityCards.test.tsx — inherited-key regression test)
  • Manual testing performed (pnpm run lint, pnpm exec tsc --noEmit, pnpm exec vitest run all clean: 272 test files / 3016 tests passing)
  • No integration tests required — this is a lint-config + one bugfix change

Checklist

  • Code follows project style guidelines
  • Self-review of the diff completed
  • Documentation updated (three-gaps comment above the rule)
  • All CI checks passing — pending CI run
  • Commits are small and follow the Gitmoji convention
  • Commits are signed off — DCO sign-off not enforced on this repo

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…tables

Prevents regression on Epic AAASM-5208 (all 18 wire-keyed object-literal
lookups converted to Map/Object.create(null)): a pure AST no-restricted-syntax
rule flags any new `Record<...>` type-annotated variable initialised with a
non-empty object literal. Documents its three deliberate gaps in a comment
above the rule per AAASM-5245: (1) misses write-side `= {}` accumulators, (2)
flags legitimate narrow-union Record tables by design, forcing narrowing or a
Map, and (3) trusts the key type annotation even where nothing enforces it at
a fetch-boundary cast (AAASM-5217 audits those sites separately).

Refs AAASM-5216, AAASM-5245, AAASM-5217.
…ables

The new AAASM-5245 rule's gap #2 is deliberate: it cannot distinguish a real
narrow-union Record<Role, T> lookup from a wire-keyed Record<string, T> one,
so it flags every Record<> literal keyed by a closed app union too. Every one
of the 37 sites the rule surfaced across the existing codebase is one of
these legitimate cases (or, in a couple of spots, a write-side header/tally
accumulator under gap #1) — each gets a one-line eslint-disable-next-line
explaining why its key type is closed/validated before this table is ever
indexed, so a future violation still fails loudly instead of silently
reusing an existing disable.
@Chisanan232
Chisanan232 force-pushed the v0.0.1/AAASM-5245/feat/no_restricted_syntax_lookup_rule branch from 61b94c0 to c25bfa5 Compare July 28, 2026 04:21
@sonarqubecloud

Copy link
Copy Markdown

@Chisanan232 Chisanan232 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — reviewed and approved by Claude Code

(Recorded as a review comment because GitHub blocks self-approval on an own-authored PR; this is the sign-off of record.)

Requirement correctness

The no-restricted-syntax rule exactly matches the ticket's spec (selector, message, properties.length>0 guard preserved as-is). All three documented gaps are captured in a comment directly above the rule in dashboard/.eslintrc.cjs, correctly referencing AAASM-5216/5245/5217.

Running the rule against the existing codebase surfaced 46 sites, not zero — this PR correctly treated that as a real signal to investigate rather than blanket-suppressing:

  • 1 genuine missed site: RoleCapabilityCards.tsx's ROLE_BADGE_TONE was still a plain object literal. This was independently found and fixed by two parallel efforts in this session — PR #1771 (AAASM-5211) landed first; this PR's branch was rebased onto that merge and its now-duplicate hunk (fix + test) was dropped during rebase rather than reapplied, confirmed via git diff origin/main...HEAD showing zero remaining touches to either RoleCapabilityCards.tsx file, and a clean post-rebase local run (272 files / 3022 tests, same count as post-#1771-merge — no orphaned duplicate test).
  • 45 legitimate suppressions: spot-checked a representative sample (StatusChip.tsx, trace/decision.ts's VERDICT_META/STATUS_META, several closed-union display tables) — every one has a specific, individualized, checkable justification (closed app union vs. raw wire string, or naming the exact validator function that guards the wire value beforehand) rather than a blanket disable. None just assert "safe" without a reason.

CI — fully green

17/17 checks pass post-rebase, including Dashboard (type-check + lint), Dashboard tests + coverage, Dashboard e2e (Playwright), aa-cli build compat, SonarCloud ×2, CodeQL, and CI Success.

Security

This closes the prevention half of Epic AAASM-5208 — no new risk, and it's the mechanism that stops the whole defect class from being reintroduced.

Merging with a merge commit as org admin.

🤖 Reviewed with Claude Code

@Chisanan232
Chisanan232 merged commit efd11af into main Jul 28, 2026
40 checks passed
@Chisanan232
Chisanan232 deleted the v0.0.1/AAASM-5245/feat/no_restricted_syntax_lookup_rule branch July 28, 2026 04:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant