Extend SINA
SINA grows in three directions: new primitives in core, new governed domains
beside fintech, and new tests that prove the rules hold. Each recipe below
mirrors a repo skill (/new-primitive, /new-schema, /adversarial-test).
Add a primitive to core
A core primitive is headless, accessible, and strictly domain-agnostic. The
boundary is hard: no zod, no domain package, no domain vocabulary (no
"$50,000", no "limit", no "approval"), no Tailwind.
- Confirm design sign-off. It gates implementation. Build to the signed-off anatomy; don't invent UX.
- Build on Radix in
packages/core/src/<Name>/<Name>.tsx, styled by a co-located<Name>.module.css: flat class names, real pseudo-classes for state, a per-component--sina-<name>-*token layer aliasing the semantic tokens. Never write a raw color. - Meet the a11y bar. All four, non-negotiable: focus-trap where a surface
traps focus, correct ARIA, full keyboard operability, and an automated
axe(jest-axe) pass. Error and warning states render the matching bold status glyph beside the text, never color alone. - Test and demo:
<Name>.test.tsxwithexpect(await axe(container)).toHaveNoViolations()plus keyboard/focus assertions, then a playground story.
Add a governed domain
SINA ships one domain today, fintech, and no second domain is currently
planned. The architecture keeps one addable, though: the shared
@sina-design-system/governance seam owns the
contract, the audit emit point, and the
intercept engine, and knows nothing about wire transfers. A new domain is two
packages mirroring fintech and fintech-react:
- A pure Zod constitution (
packages/<domain>, depends only ongovernance): format primitives, cited thresholds (// 31 CFR …, never an invented number), rules returning{ valid, violations, requiredComponent }, and a verb → rule registry with one evaluate entry point. No React, nocore, notheme, no UI. To author the rules themselves, follow Writing a rule. - A
*-reactlayer (packages/<domain>-react) that composes acoreprimitive with a domain schema and renders the decision the gate already made. It never authors schemas and never validates a payload; the gate stays server-side (see How it works).
Because the contract is uniform, the new domain's rules drop into the same gate, emit the same audit events, and are exercised by the same adversarial harness. Governance doesn't fork per domain.
Write an adversarial test
Don't only test that a valid request passes; test that an invalid one can't sneak through (Writing a rule covers fixtures). Cover the classics:
- A smuggled field: a fabricated
confirmButtonor acvv; the strict parse must reject it. - An over-limit value: just past a threshold; it must escalate to the right component.
- A bypass attempt: approve small, execute big, or self-approve; the server-side binding must reject it.
- A flood: an oversized array; the bound must reject it.
Assert the decision, not just that something threw:
const decision = evaluateFintechIntent(hostileEnvelope, ctx);
expect(decision.result.valid).toBe(false);
expect(decision.result.requiredComponent).toBe("SecureWireDialog"); // or null for a reject
expect(decision.result.violations.map((v) => v.code)).toContain("AMOUNT_REQUIRES_APPROVAL");Make it a required check
Wire these tests into CI as a required check, so a change that quietly weakens a rule fails the build instead of shipping.
Related: Escalation · Governance.