feat: type-safe CRUD sensitive field definitions (AEG-V16-021)

Constrain sensitive fields to row keys and preserve generic resource-definition contracts.
This commit is contained in:
2026-08-08 15:00:57 +09:00
parent 6f47d71c4a
commit 3287ae399e
4 changed files with 24 additions and 4 deletions
@@ -0,0 +1,16 @@
# AEG-V16-021 — CRUD definition type
**WBS / Requirement / UI / Test:** AEG-V16-021 / REQ-V16-FEC-05 / UI-V16-FEC-05 / T-V16-FEC-05
## Source / Assumption / Unknown / Decision Required
- **Source:** CRUD resource v2 contract and existing resource definition/assertion.
- **Assumption:** each definition's row schema is the source for permitted sensitive field names.
- **Unknown:** no feature-level definition exists yet; screen integration evidence is unclaimed.
- **Decision Required:** field masking behavior remains a screen/provider policy; this type guards declaration consistency only.
## Evidence
- `sensitiveFields` is restricted to string keys of the row type.
- Generic assertion preserves query/row/form type relationships.
- CRUD resource tests 3/3 and frontend typecheck passed.
@@ -12,6 +12,7 @@ AEG-V16-015,S0,VS-00,Adapter rollback runbook,IN_PROGRESS,TBD,"docs/CURRENT/ui-p
AEG-V16-018,S6,Cross,DataContextHeader,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-018_DATA_CONTEXT_HEADER_SLICE_NOTE.md; frontend/src/shared/ui/components/KsDataContextHeader.vue; frontend/src/shared/ui/components/tests/KsDataContextHeader.spec.ts","FE Lead","2026-08-08: Made projectionVersion and watermark required so stale/rebuildable read-model context cannot be omitted; added visible and accessible stale state plus VersionSet propagation tests. Actual evidence: targeted Vitest 2/2 PASS, frontend typecheck PASS, production build PASS. Build emitted unrelated tracked .js drift, excluded from this Slice. COMPLETED is blocked pending predecessor AEG-V16-017 acceptance and UX/a11y evidence."
AEG-V16-019,S6,Cross,CommandBar,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-019_COMMAND_BAR_SLICE_NOTE.md; frontend/src/shared/ui/components/KsCommandBar.vue; frontend/src/shared/ui/components/tests/KsCommandBar.spec.ts","FE Lead","2026-08-08: Command boundary now suppresses disabled/busy execute events and exposes aggregate busy state. Actual evidence: targeted Vitest 1/1 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-018 acceptance and UX/a11y evidence."
AEG-V16-020,S6,Cross,CRUD Resource v2,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-020_CRUD_RESOURCE_V2_SLICE_NOTE.md; contracts/ui/crud-resource.v2.json; frontend/src/shared/crud/resourceDefinition.ts; frontend/src/shared/crud/tests/resourceDefinition.spec.ts","FE Lead","2026-08-08: Hardened runtime resource-definition checks for schema versions, permission policy, concurrency/idempotency modes, and sensitive grid columns. Actual evidence: resource definition Vitest 3/3 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-019 acceptance and UX/a11y evidence."
AEG-V16-021,S6,Cross,CRUD definition type,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-021_CRUD_DEFINITION_TYPE_SLICE_NOTE.md; frontend/src/shared/crud/resourceDefinition.ts; frontend/src/shared/crud/tests/resourceDefinition.spec.ts","FE Lead","2026-08-08: Sensitive field declarations are constrained to actual row keys and generic assertion preserves definition type relationships. Actual evidence: resource definition Vitest 3/3 PASS; frontend typecheck PASS. COMPLETED is blocked pending feature-level CRUD definition integration and predecessor evidence."
AEG-V16-022,S6,Cross,Optimistic command hook,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-022_OPTIMISTIC_COMMAND_SLICE_NOTE.md; frontend/src/shared/crud/useOptimisticCommand.ts; frontend/src/shared/crud/tests/useOptimisticCommand.spec.ts","FE Lead","2026-08-08: Request creation now freezes one Idempotency-Key per user intent and retries reuse it; 409/412 conflict state remains explicit. Actual evidence: targeted Vitest 2/2 PASS; frontend typecheck PASS. COMPLETED is blocked pending actual CRUD-screen integration and predecessor evidence."
AEG-V16-023,S6,Cross,T01~T10 계약 회귀,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md; frontend/src/shared/ui/screen-types/catalogue.ts; frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts","FE Lead","2026-08-08: State contract is typed and all 13 standard states are now covered across T01~T10, including READY and FORBIDDEN. Actual evidence: catalogue Vitest 4/4 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor integration and FE accessibility gate evidence."
AEG-V16-024,S6,Cross,FE accessibility Gate,IN_PROGRESS,TBD,"docs/CURRENT/AEG-V16-024_A11Y_GATE_SLICE_NOTE.md; frontend/src/shared/ui/components/tests/accessibility.contract.spec.ts","FE Lead","2026-08-08: Shared accessibility contract verifies required invalid field label/error/ARIA relationships and busy command action suppression. Actual evidence: accessibility Vitest 2/2 PASS; frontend typecheck PASS. COMPLETED is blocked pending UX/QA keyboard/focus, assistive-technology, and visual evidence."
1 WBS_ID Sprint Slice_ID Task Status Completion_Date Evidence_Link Owner Notes
12 AEG-V16-018 S6 Cross DataContextHeader IN_PROGRESS TBD docs/CURRENT/AEG-V16-018_DATA_CONTEXT_HEADER_SLICE_NOTE.md; frontend/src/shared/ui/components/KsDataContextHeader.vue; frontend/src/shared/ui/components/tests/KsDataContextHeader.spec.ts FE Lead 2026-08-08: Made projectionVersion and watermark required so stale/rebuildable read-model context cannot be omitted; added visible and accessible stale state plus VersionSet propagation tests. Actual evidence: targeted Vitest 2/2 PASS, frontend typecheck PASS, production build PASS. Build emitted unrelated tracked .js drift, excluded from this Slice. COMPLETED is blocked pending predecessor AEG-V16-017 acceptance and UX/a11y evidence.
13 AEG-V16-019 S6 Cross CommandBar IN_PROGRESS TBD docs/CURRENT/AEG-V16-019_COMMAND_BAR_SLICE_NOTE.md; frontend/src/shared/ui/components/KsCommandBar.vue; frontend/src/shared/ui/components/tests/KsCommandBar.spec.ts FE Lead 2026-08-08: Command boundary now suppresses disabled/busy execute events and exposes aggregate busy state. Actual evidence: targeted Vitest 1/1 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-018 acceptance and UX/a11y evidence.
14 AEG-V16-020 S6 Cross CRUD Resource v2 IN_PROGRESS TBD docs/CURRENT/AEG-V16-020_CRUD_RESOURCE_V2_SLICE_NOTE.md; contracts/ui/crud-resource.v2.json; frontend/src/shared/crud/resourceDefinition.ts; frontend/src/shared/crud/tests/resourceDefinition.spec.ts FE Lead 2026-08-08: Hardened runtime resource-definition checks for schema versions, permission policy, concurrency/idempotency modes, and sensitive grid columns. Actual evidence: resource definition Vitest 3/3 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor AEG-V16-019 acceptance and UX/a11y evidence.
15 AEG-V16-021 S6 Cross CRUD definition type IN_PROGRESS TBD docs/CURRENT/AEG-V16-021_CRUD_DEFINITION_TYPE_SLICE_NOTE.md; frontend/src/shared/crud/resourceDefinition.ts; frontend/src/shared/crud/tests/resourceDefinition.spec.ts FE Lead 2026-08-08: Sensitive field declarations are constrained to actual row keys and generic assertion preserves definition type relationships. Actual evidence: resource definition Vitest 3/3 PASS; frontend typecheck PASS. COMPLETED is blocked pending feature-level CRUD definition integration and predecessor evidence.
16 AEG-V16-022 S6 Cross Optimistic command hook IN_PROGRESS TBD docs/CURRENT/AEG-V16-022_OPTIMISTIC_COMMAND_SLICE_NOTE.md; frontend/src/shared/crud/useOptimisticCommand.ts; frontend/src/shared/crud/tests/useOptimisticCommand.spec.ts FE Lead 2026-08-08: Request creation now freezes one Idempotency-Key per user intent and retries reuse it; 409/412 conflict state remains explicit. Actual evidence: targeted Vitest 2/2 PASS; frontend typecheck PASS. COMPLETED is blocked pending actual CRUD-screen integration and predecessor evidence.
17 AEG-V16-023 S6 Cross T01~T10 계약 회귀 IN_PROGRESS TBD docs/CURRENT/AEG-V16-023_SCREEN_STATE_MATRIX_SLICE_NOTE.md; frontend/src/shared/ui/screen-types/catalogue.ts; frontend/src/shared/ui/screen-types/tests/catalogue.spec.ts FE Lead 2026-08-08: State contract is typed and all 13 standard states are now covered across T01~T10, including READY and FORBIDDEN. Actual evidence: catalogue Vitest 4/4 PASS; frontend typecheck PASS. COMPLETED is blocked pending predecessor integration and FE accessibility gate evidence.
18 AEG-V16-024 S6 Cross FE accessibility Gate IN_PROGRESS TBD docs/CURRENT/AEG-V16-024_A11Y_GATE_SLICE_NOTE.md; frontend/src/shared/ui/components/tests/accessibility.contract.spec.ts FE Lead 2026-08-08: Shared accessibility contract verifies required invalid field label/error/ARIA relationships and busy command action suppression. Actual evidence: accessibility Vitest 2/2 PASS; frontend typecheck PASS. COMPLETED is blocked pending UX/QA keyboard/focus, assistive-technology, and visual evidence.
@@ -2,6 +2,7 @@ import type { ZodType } from 'zod'
import type { UiGridColumn } from '../ui/adapter/contracts'
export type CrudConcurrencyMode = 'NONE' | 'ETAG_IF_MATCH'
export type CrudIdempotencyMode = 'NONE' | 'IDEMPOTENCY_KEY'
export type CrudRowField<TRow> = Extract<keyof TRow, string>
export interface CrudResourceDefinition<TQuery, TRow, TForm> {
resourceId: string
querySchemaVersion: string
@@ -10,12 +11,12 @@ export interface CrudResourceDefinition<TQuery, TRow, TForm> {
rowSchema: ZodType<TRow>
formSchema: ZodType<TForm>
columns: readonly UiGridColumn[]
sensitiveFields: readonly string[]
sensitiveFields: readonly CrudRowField<TRow>[]
permissionPolicy: string
concurrencyMode: CrudConcurrencyMode
idempotencyMode: CrudIdempotencyMode
}
export function assertCrudResourceDefinition(definition: CrudResourceDefinition<unknown, unknown, unknown>): void {
export function assertCrudResourceDefinition<TQuery, TRow, TForm>(definition: CrudResourceDefinition<TQuery, TRow, TForm>): void {
if (!definition.resourceId.trim()) throw new Error('resourceId is required')
if (!definition.querySchemaVersion.trim() || !definition.responseSchemaVersion.trim()) throw new Error('schema versions are required')
if (!definition.permissionPolicy.trim()) throw new Error('permissionPolicy is required')
@@ -2,9 +2,11 @@ import { z } from 'zod'
import { describe, expect, it } from 'vitest'
import { assertCrudResourceDefinition, type CrudResourceDefinition } from '../resourceDefinition'
const definition: CrudResourceDefinition<unknown, unknown, unknown> = {
interface ClientRow { email: string }
const definition: CrudResourceDefinition<unknown, ClientRow, unknown> = {
resourceId: 'client-advisory', querySchemaVersion: '1', responseSchemaVersion: '1',
querySchema: z.unknown(), rowSchema: z.unknown(), formSchema: z.unknown(),
querySchema: z.unknown(), rowSchema: z.object({ email: z.string() }), formSchema: z.unknown(),
columns: [{ field: 'email', header: 'Email', sensitive: true }], sensitiveFields: ['email'],
permissionPolicy: 'ClientAdvisory.Read', concurrencyMode: 'ETAG_IF_MATCH', idempotencyMode: 'IDEMPOTENCY_KEY'
}