|
|
# Audit: year-grid
|
|
|
audit-version: 1
|
|
|
audited-at: 2026-06-26
|
|
|
scope: soma, sema (SCOPE-DRIFT → SYS-1)
|
|
|
method: adversarially-verified workflow (analyze → refute); HIGH/CRITICAL personally re-verified by the lead. Batch-3 ground-truth: each picker fires trigger(close) (close NOT inert), but open/commit-reset ARE inert; calendar/range-calendar are MID-REFACTOR (uncommitted view-switch work).
|
|
|
provider: src/uix/soma/components/year-grid/year-grid-provider.svelte.ts
|
|
|
|
|
|
## Summary
|
|
|
Counts (post-verification): CRITICAL 0 · HIGH 0 · MEDIUM 1 · LOW 1.
|
|
|
systemic hits: SYS-1.
|
|
|
composition (A27): N/A (year-grid is not a picker; it is a standalone grid component, not a composition of primitives)"
|
|
|
|
|
|
## Findings
|
|
|
### MEDIUM: Dimension A: Scope Drift (SYS-1) — morfo declares scope:['soma','sema'] but eidos director — year-grid-003 <!-- id: year-grid-003 -->
|
|
|
- dimension: A-Contract-scope
|
|
|
- rule: Dimension A: Scope Drift (SYS-1) — morfo declares scope:['soma','sema'] but eidos directory exists
|
|
|
- location: src/uix/morfo/components/year-grid.ts:7 vs. src/uix/eidos/components/year-grid/
|
|
|
- evidence: Morfo line 7: `scope: ['soma', 'sema'],`. Directory listing shows eidos/components/year-grid/ exists with CSS and Svelte components (year-grid.css, year-grid.svelte, etc.).
|
|
|
- impact: The morfo contract claims eidos is out-of-scope, but the eidos layer clearly exists and is active. This is the documented SYS-1 systemic issue.
|
|
|
- proposed-fix: Update morfo scope to `['soma', 'sema', 'eidos']` to reflect reality.
|
|
|
- verify: [confirmed] Confirmed at MEDIUM (documented systemic SYS-1 scope-drift). Morfo line 7: `scope: ['soma', 'sema'],` — eidos omitted. Yet src/uix/eidos/components/year-grid/year-grid.css exists (284 lines, mtime Jun 23, real recipe selecting [data-year-grid], [data-year-grid-cell], [data-year-grid-grid], variant/color/size cascades) plus a full eidos component dir (year-grid.svelte, year-grid-cell.svelte, year-grid-grid.svelte, index.ts, types.ts, README.md). The eidos layer is active and shipping, so the morfo scope under-declares reality. Matches the known SYS-1 baseline; MEDIUM is appropriate.
|
|
|
- fix-status: open
|
|
|
|
|
|
### LOW: Dimension C: DOM-selector — querySelector/querySelectorAll must interpolate user/consumer — year-grid-001 <!-- id: year-grid-001 -->
|
|
|
- dimension: C-DOM-selector
|
|
|
- rule: Dimension C: DOM-selector — querySelector/querySelectorAll must interpolate user/consumer values via CSS.escape
|
|
|
- location: src/uix/soma/components/year-grid/year-grid-provider.svelte.ts:218-220
|
|
|
- evidence: ```
|
|
|
const el = root.querySelector<HTMLElement>(
|
|
|
`[data-year-grid-cell][data-value="${nextYear!}"]`
|
|
|
);
|
|
|
```
|
|
|
The selector interpolates `nextYear!` (a number cast to string) without CSS.escape(). While years are numeric and safe in this case, the pattern violates the rule: any consumer-derived value must be escaped.
|
|
|
- impact: If a malicious or accidental value contains CSS selector characters (though unlikely for year numbers), the query could fail or match unintended elements.
|
|
|
- proposed-fix: Import CSS.escape and wrap: ``const el = root.querySelector<HTMLElement>(`[data-year-grid-cell][data-value="${CSS.escape(String(nextYear!))}"]`);``
|
|
|
- verify: [downgraded] Downgraded CRITICAL -> LOW. At provider line 218-220 the selector is `root.querySelector(`[data-year-grid-cell][data-value="${nextYear!}"]`)`. `nextYear` is provably a `number` in every branch of handleCellKeydown (lines 190-197): `year + 1`, `year - 1`, `year + 3`, `year - 3`, `this.pageStart`, `this.pageEnd`, `year - this.decadeSize`, `year + this.decadeSize` — all arithmetic on numbers. No consumer/user-supplied string ever reaches the selector, so it cannot contain CSS metacharacters; the query can never break. CRITICAL ('wrong behavior users hit') is unsupported — the auditor's own evidence concedes 'years are numeric and safe in this case'. The CSS.escape callers elsewhere (listbox-provider:196, radio-group-provider:93, tabs-provider:109, tree-view-provider:110, grid-list-provider:184, command-provider:394) all interpolate an arbitrary consumer `value: string` — a genuinely different risk class. The numeric-grid sibling month-grid-provider.svelte.ts:247-248 follows the identical no-escape pattern (`data-value="${this.placeholderYear}-${nextMonth!}"`), confirming the grid family treats numeric interpolation as safe-by-construction. Real but cosmetic consistency nit at most.
|
|
|
- fix-status: open
|
|
|
|
|
|
## No-findings dimensions
|
|
|
A-Contract-parts, A-Contract-keyboard, A-Morfo-signature, B-Roving-focus, B-A30-heading-registration, D-Soma-eidos-imports
|
|
|
|
|
|
## Theming facts (E-bis)
|
|
|
- magic z-index: none
|
|
|
- magic literals: none
|
|
|
- undeclared parts: none
|
|
|
- roles clean: true · variants clean: true
|
|
|
- conformance: All roles (gridcell, grid, button) are declared in morfo. All parts (provider, header, heading, prev-button, next-button, grid, cell) are registered via runtime.part(). Variants (size, variant, color) are eidos-owned visual attributes, not morfo-declared.
|
|
|
|
|
|
## Tests (F)
|
|
|
- exists: false · env: N/A
|
|
|
- covers:
|
|
|
- untested: Year-grid keyboard routing (all 9 keys: Arrow×4, Home, End, PageUp, PageDown); Focus trap during page navigation; Roving focus tabindex switching; Selection on Enter/Space; Boundary conditions (pageStart/pageEnd edge years); min/max value constraints; readonly state prevents selection; Two-moment sequence: placeholder change + trigger event
|
|
|
|
|
|
## Style observations (non-blocking)
|
|
|
- Keyboard routing follows APG grid pattern correctly (Arrow×4 + Home/End + PageUp/PageDown)
|
|
|
- Roving focus implementation clean with tabindex toggle
|
|
|
- All morfo keyboard actions properly handled in provider
|