@declarion/react 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist-lib/{DraggableChildrenTable-CzdhUQ-B.js → DraggableChildrenTable-B4JD8tAe.js} +30 -30
  2. package/dist-lib/{DraggableChildrenTable-CzdhUQ-B.js.map → DraggableChildrenTable-B4JD8tAe.js.map} +1 -1
  3. package/dist-lib/{Grid-pE2-dvug.js → Grid-BEEwkvX8.js} +2 -2
  4. package/dist-lib/{Grid-pE2-dvug.js.map → Grid-BEEwkvX8.js.map} +1 -1
  5. package/dist-lib/{JsonEditor-CexL5HXN.js → JsonEditor-KelPV-OQ.js} +1 -1
  6. package/dist-lib/{JsonEditor-CexL5HXN.js.map → JsonEditor-KelPV-OQ.js.map} +1 -1
  7. package/dist-lib/{MarkdownRenderer-Dl3GDuY9.js → MarkdownRenderer-Dvo_0Zcx.js} +16 -13
  8. package/dist-lib/MarkdownRenderer-Dvo_0Zcx.js.map +1 -0
  9. package/dist-lib/components/primitives/ClassifiedFieldLocked.d.ts +10 -0
  10. package/dist-lib/components/primitives/Icons.d.ts +1 -0
  11. package/dist-lib/components/primitives/index.d.ts +1 -0
  12. package/dist-lib/{dataTableStyles-DVOINYzY.js → dataTableStyles-DQdH4Ilw.js} +622 -604
  13. package/dist-lib/dataTableStyles-DQdH4Ilw.js.map +1 -0
  14. package/dist-lib/declarion-react.css +1 -1
  15. package/dist-lib/hooks/useAllEntityData.d.ts +1 -1
  16. package/dist-lib/hooks/useChildrenData.d.ts +1 -1
  17. package/dist-lib/hooks/useEntityData.d.ts +2 -2
  18. package/dist-lib/hooks/useEntityItem.d.ts +1 -1
  19. package/dist-lib/hooks/useParams.d.ts +2 -2
  20. package/dist-lib/hooks/useSchema.d.ts +1 -1
  21. package/dist-lib/index.d.ts +3 -0
  22. package/dist-lib/index.js +5196 -4881
  23. package/dist-lib/index.js.map +1 -1
  24. package/dist-lib/lib/field-classification.d.ts +52 -0
  25. package/dist-lib/lib/sort.d.ts +72 -0
  26. package/dist-lib/{rich-text-CaQ6-8Gx.js → rich-text-CJpzFOG5.js} +1 -1
  27. package/dist-lib/{rich-text-CaQ6-8Gx.js.map → rich-text-CJpzFOG5.js.map} +1 -1
  28. package/dist-lib/types/schema.d.ts +65 -0
  29. package/dist-lib/{value-qao1rdkR.js → value-DaEUgsnK.js} +2 -1
  30. package/dist-lib/{value-qao1rdkR.js.map → value-DaEUgsnK.js.map} +1 -1
  31. package/package.json +8 -8
  32. package/dist-lib/MarkdownRenderer-Dl3GDuY9.js.map +0 -1
  33. package/dist-lib/dataTableStyles-DVOINYzY.js.map +0 -1
@@ -0,0 +1,52 @@
1
+ import type { Entity, FieldAccessInfo, ClassificationTierDef, Schema } from "../types/schema";
2
+ /**
3
+ * isFieldReadable returns true when the caller can see the field's
4
+ * value. Non-classified fields are always readable (no entry in
5
+ * field_access). Classified fields are readable when field_access
6
+ * marks read="granted". Plan §B9.
7
+ */
8
+ export declare function isFieldReadable(entity: Entity | undefined, fieldName: string): boolean;
9
+ /**
10
+ * isFieldWritable returns true when the caller can submit a new value
11
+ * for the field. Form components disable input + show a lock badge
12
+ * when this returns false. Non-classified fields are always writable
13
+ * (gating happens via entity-level RBAC at the HTTP layer). Plan §B9.
14
+ */
15
+ export declare function isFieldWritable(entity: Entity | undefined, fieldName: string): boolean;
16
+ /**
17
+ * fieldClassification returns the classification tier code gating
18
+ * the field, or undefined when the field is non-classified. Lets
19
+ * UI code render a tier badge without re-implementing the field_access
20
+ * lookup.
21
+ */
22
+ export declare function fieldClassification(entity: Entity | undefined, fieldName: string): string | undefined;
23
+ /**
24
+ * fieldRequiredPermission returns the exact grant string an
25
+ * administrator must add to a role to unlock the field for the
26
+ * current caller. Returns undefined when the field is readable +
27
+ * writable (no hint needed). Plan §B9 — operator hand-off surface.
28
+ */
29
+ export declare function fieldRequiredPermission(entity: Entity | undefined, fieldName: string): string | undefined;
30
+ /**
31
+ * classificationTier returns the localized tier metadata for a field
32
+ * (display name, description, audit_on_read flag, icon). Resolves
33
+ * via the top-level schema.classifications map. Returns undefined when
34
+ * the field is non-classified or the tier code is unknown in the
35
+ * catalog (defense — points at a misconfigured registry).
36
+ */
37
+ export declare function classificationTier(schema: Schema | undefined, entity: Entity | undefined, fieldName: string): ClassificationTierDef | undefined;
38
+ /**
39
+ * isFieldSoDProtected reports whether the field's tier carries
40
+ * sod_protected:true in the catalog. Used by UI to surface a stronger
41
+ * visual treatment (e.g. red lock vs. grey lock) for tiers that
42
+ * trigger the stricter SoD controls (superadmin-bypass-disabled
43
+ * tenants, role self-assignment refusal, etc).
44
+ */
45
+ export declare function isFieldSoDProtected(schema: Schema | undefined, entity: Entity | undefined, fieldName: string): boolean;
46
+ /**
47
+ * fieldAccessInfo returns the raw FieldAccessInfo entry for a field,
48
+ * or undefined when the field is non-classified. Convenience for
49
+ * components that need the full structure (e.g. a tooltip rendering
50
+ * both classification name + required_permission hint).
51
+ */
52
+ export declare function fieldAccessInfo(entity: Entity | undefined, fieldName: string): FieldAccessInfo | undefined;
@@ -0,0 +1,72 @@
1
+ export type SortDir = "asc" | "desc";
2
+ export type SortNulls = "first" | "last";
3
+ export interface SortCol {
4
+ field: string;
5
+ dir: SortDir;
6
+ nulls?: SortNulls;
7
+ }
8
+ /**
9
+ * Parse a sort expression string into a list of SortCol entries.
10
+ *
11
+ * Tolerant per plan §5.2:
12
+ * - empty/null/undefined → []
13
+ * - missing direction defaults to "asc"
14
+ * - missing nulls segment is left undefined (system default applies server-side)
15
+ * - unknown nulls literal is dropped (server is the strict authority)
16
+ * - empty fields between commas are skipped
17
+ */
18
+ export declare function parseSort(s: string | null | undefined): SortCol[];
19
+ /**
20
+ * Filter a raw sort URL string so $property.* segments (deferred per plan §2)
21
+ * don't reach the backend's strict parser. EVERY OTHER token passes through
22
+ * unchanged - including malformed grammar like `:nulls_middle` or extra
23
+ * colons - so the backend can return a typed ValidationError and the SDK
24
+ * surfaces the error to the user. Plan §14 strict-parse contract requires
25
+ * that grammar violations are NOT silently rewritten on the client.
26
+ */
27
+ export declare function stripUnsupportedSortSegments(s: string | null | undefined): string | undefined;
28
+ /**
29
+ * Serialize a list of SortCol entries back to the canonical string form.
30
+ * Round-trips losslessly with parseSort for any valid input.
31
+ */
32
+ export declare function serializeSort(cols: SortCol[]): string;
33
+ /**
34
+ * Server-enforced cap (plan §3 Decision 4, §4.2). Mirrored on the client so
35
+ * we never POST a sort the server will reject with HTTP 400.
36
+ */
37
+ export declare const MAX_SORT_COLS = 5;
38
+ /**
39
+ * Shift-click on a column header (plan §7.1).
40
+ *
41
+ * - field already in `prev` → remove it (toggle off).
42
+ * - field not in `prev` → append { field, dir: "asc" } to the tail.
43
+ *
44
+ * When the list is already at the {@link MAX_SORT_COLS} cap AND `field` is
45
+ * not already in it, the helper refuses and returns `prev` unchanged. The
46
+ * UI surfaces this implicitly: the "Add to multi-sort" menu item is hidden
47
+ * once the cap is reached, and a shift-click on a brand-new column at the
48
+ * cap is a no-op (consistent UX, matches the brief's "refuse" call).
49
+ *
50
+ * Nulls placement of every retained column is preserved verbatim - shift-
51
+ * click never silently rewrites another column's NULL setting.
52
+ *
53
+ * Returned arrays are always new references; never mutates `prev`.
54
+ */
55
+ export declare function applyShiftClickSort(prev: SortCol[], field: string): SortCol[];
56
+ /**
57
+ * "Add to multi-sort" menu action (plan §7.2). Adds `col` to the sort tail
58
+ * with the caller-chosen `dir` + `nulls`. Dedupes: if `col.field` is already
59
+ * present in `prev`, the earlier occurrence is removed and the new one
60
+ * appended at the tail (server rejects duplicate fields, plan §4.2).
61
+ *
62
+ * Refuses to grow past {@link MAX_SORT_COLS}: when appending would exceed
63
+ * the cap AND the field is not already present, returns `prev` unchanged.
64
+ * The menu item that drives this is hidden at the cap, so this guard exists
65
+ * to keep the helper safe under any caller.
66
+ */
67
+ export declare function applyAppendSort(prev: SortCol[], col: SortCol): SortCol[];
68
+ /**
69
+ * "Clear sort on this column" menu action (plan §7.2). Removes the one
70
+ * entry matching `field` from `prev`; returns `prev` unchanged if absent.
71
+ */
72
+ export declare function removeSortField(prev: SortCol[], field: string): SortCol[];
@@ -45,4 +45,4 @@ function l(e) {
45
45
  //#endregion
46
46
  export { c as a, t as i, s as n, r as o, o as r, i as s, n as t };
47
47
 
48
- //# sourceMappingURL=rich-text-CaQ6-8Gx.js.map
48
+ //# sourceMappingURL=rich-text-CJpzFOG5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rich-text-CaQ6-8Gx.js","names":[],"sources":["../src/lib/rich-text.ts"],"sourcesContent":["import type { HydratedFile } from \"@/types/files\";\n\nexport interface RichTextValue {\n markdown: string;\n files: Record<string, HydratedFile>;\n}\n\nconst declarionFilePattern = /declarion:\\/\\/file\\/([0-9a-fA-F-]{36})/g;\n\nexport function richTextMarkdown(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (isRecord(value) && typeof value.markdown === \"string\") return value.markdown;\n return \"\";\n}\n\nexport function richTextFiles(value: unknown): Record<string, HydratedFile> {\n if (!isRecord(value) || !isRecord(value.files)) return {};\n const files: Record<string, HydratedFile> = {};\n for (const [fileId, file] of Object.entries(value.files)) {\n if (isRecord(file) && typeof file.id === \"string\") {\n files[fileId] = file as unknown as HydratedFile;\n }\n }\n return files;\n}\n\nexport function createRichTextValue(markdownValue: unknown, inlineFilesValue: unknown): RichTextValue {\n return {\n markdown: richTextMarkdown(markdownValue),\n files: inlineFilesById(inlineFilesValue),\n };\n}\n\nexport function withRichTextFile(value: unknown, file: HydratedFile): RichTextValue {\n const next = createRichTextValue(value, null);\n next.files[file.id] = file;\n return next;\n}\n\nexport function withoutRichTextFiles(value: unknown, removedIds: Iterable<string>): RichTextValue {\n const next = createRichTextValue(value, null);\n for (const fileId of removedIds) delete next.files[fileId];\n return next;\n}\n\nexport function inlineFilesById(value: unknown): Record<string, HydratedFile> {\n if (!Array.isArray(value)) return {};\n const files: Record<string, HydratedFile> = {};\n for (const item of value) {\n if (isRecord(item) && typeof item.id === \"string\") {\n files[item.id] = item as unknown as HydratedFile;\n }\n }\n return files;\n}\n\nexport function extractDeclarionFileIds(markdown: string): string[] {\n const seen = new Set<string>();\n const ids: string[] = [];\n let match: RegExpExecArray | null;\n declarionFilePattern.lastIndex = 0;\n while ((match = declarionFilePattern.exec(markdown)) !== null) {\n const fileId = match[1];\n if (seen.has(fileId)) continue;\n seen.add(fileId);\n ids.push(fileId);\n }\n return ids;\n}\n\nexport function extractDeclarionFileId(url: string | undefined): string | null {\n if (!url) return null;\n const match = /^declarion:\\/\\/file\\/([0-9a-fA-F-]{36})$/.exec(url);\n return match?.[1] ?? null;\n}\n\nexport function richTextTokenForFile(file: Pick<HydratedFile, \"id\" | \"content_type\" | \"filename\">): string {\n const ref = `declarion://file/${file.id}`;\n if (file.content_type.startsWith(\"image/\")) return `![](${ref})`;\n const label = file.filename || file.id;\n return `[${label}](${ref})`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":";AAOA,IAAM,IAAuB;AAE7B,SAAgB,EAAiB,GAAwB;CAGvD,OAFI,OAAO,KAAU,WAAiB,IAClC,EAAS,CAAK,KAAK,OAAO,EAAM,YAAa,WAAiB,EAAM,WACjE;AACT;AAaA,SAAgB,EAAoB,GAAwB,GAA0C;CACpG,OAAO;EACL,UAAU,EAAiB,CAAa;EACxC,OAAO,EAAgB,CAAgB;CACzC;AACF;AAEA,SAAgB,EAAiB,GAAgB,GAAmC;CAClF,IAAM,IAAO,EAAoB,GAAO,IAAI;CAE5C,OADA,EAAK,MAAM,EAAK,MAAM,GACf;AACT;AAEA,SAAgB,EAAqB,GAAgB,GAA6C;CAChG,IAAM,IAAO,EAAoB,GAAO,IAAI;CAC5C,KAAK,IAAM,KAAU,GAAY,OAAO,EAAK,MAAM;CACnD,OAAO;AACT;AAEA,SAAgB,EAAgB,GAA8C;CAC5E,IAAI,CAAC,MAAM,QAAQ,CAAK,GAAG,OAAO,CAAC;CACnC,IAAM,IAAsC,CAAC;CAC7C,KAAK,IAAM,KAAQ,GACjB,AAAI,EAAS,CAAI,KAAK,OAAO,EAAK,MAAO,aACvC,EAAM,EAAK,MAAM;CAGrB,OAAO;AACT;AAEA,SAAgB,EAAwB,GAA4B;CAClE,IAAM,oBAAO,IAAI,IAAY,GACvB,IAAgB,CAAC,GACnB;CAEJ,KADA,EAAqB,YAAY,IACzB,IAAQ,EAAqB,KAAK,CAAQ,OAAO,OAAM;EAC7D,IAAM,IAAS,EAAM;EACjB,EAAK,IAAI,CAAM,MACnB,EAAK,IAAI,CAAM,GACf,EAAI,KAAK,CAAM;CACjB;CACA,OAAO;AACT;AAEA,SAAgB,EAAuB,GAAwC;CAG7E,OAFK,IACS,2CAA2C,KAAK,CACvD,IAAQ,MAAM,OAFJ;AAGnB;AAEA,SAAgB,EAAqB,GAAsE;CACzG,IAAM,IAAM,oBAAoB,EAAK;CAGrC,OAFI,EAAK,aAAa,WAAW,QAAQ,IAAU,OAAO,EAAI,KAEvD,IADO,EAAK,YAAY,EAAK,GACnB,IAAI,EAAI;AAC3B;AAEA,SAAS,EAAS,GAAkD;CAClE,OAAO,OAAO,KAAU,cAAY;AACtC"}
1
+ {"version":3,"file":"rich-text-CJpzFOG5.js","names":[],"sources":["../src/lib/rich-text.ts"],"sourcesContent":["import type { HydratedFile } from \"@/types/files\";\n\nexport interface RichTextValue {\n markdown: string;\n files: Record<string, HydratedFile>;\n}\n\nconst declarionFilePattern = /declarion:\\/\\/file\\/([0-9a-fA-F-]{36})/g;\n\nexport function richTextMarkdown(value: unknown): string {\n if (typeof value === \"string\") return value;\n if (isRecord(value) && typeof value.markdown === \"string\") return value.markdown;\n return \"\";\n}\n\nexport function richTextFiles(value: unknown): Record<string, HydratedFile> {\n if (!isRecord(value) || !isRecord(value.files)) return {};\n const files: Record<string, HydratedFile> = {};\n for (const [fileId, file] of Object.entries(value.files)) {\n if (isRecord(file) && typeof file.id === \"string\") {\n files[fileId] = file as unknown as HydratedFile;\n }\n }\n return files;\n}\n\nexport function createRichTextValue(markdownValue: unknown, inlineFilesValue: unknown): RichTextValue {\n return {\n markdown: richTextMarkdown(markdownValue),\n files: inlineFilesById(inlineFilesValue),\n };\n}\n\nexport function withRichTextFile(value: unknown, file: HydratedFile): RichTextValue {\n const next = createRichTextValue(value, null);\n next.files[file.id] = file;\n return next;\n}\n\nexport function withoutRichTextFiles(value: unknown, removedIds: Iterable<string>): RichTextValue {\n const next = createRichTextValue(value, null);\n for (const fileId of removedIds) delete next.files[fileId];\n return next;\n}\n\nexport function inlineFilesById(value: unknown): Record<string, HydratedFile> {\n if (!Array.isArray(value)) return {};\n const files: Record<string, HydratedFile> = {};\n for (const item of value) {\n if (isRecord(item) && typeof item.id === \"string\") {\n files[item.id] = item as unknown as HydratedFile;\n }\n }\n return files;\n}\n\nexport function extractDeclarionFileIds(markdown: string): string[] {\n const seen = new Set<string>();\n const ids: string[] = [];\n let match: RegExpExecArray | null;\n declarionFilePattern.lastIndex = 0;\n while ((match = declarionFilePattern.exec(markdown)) !== null) {\n const fileId = match[1];\n if (seen.has(fileId)) continue;\n seen.add(fileId);\n ids.push(fileId);\n }\n return ids;\n}\n\nexport function extractDeclarionFileId(url: string | undefined): string | null {\n if (!url) return null;\n const match = /^declarion:\\/\\/file\\/([0-9a-fA-F-]{36})$/.exec(url);\n return match?.[1] ?? null;\n}\n\nexport function richTextTokenForFile(file: Pick<HydratedFile, \"id\" | \"content_type\" | \"filename\">): string {\n const ref = `declarion://file/${file.id}`;\n if (file.content_type.startsWith(\"image/\")) return `![](${ref})`;\n const label = file.filename || file.id;\n return `[${label}](${ref})`;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n"],"mappings":";AAOA,IAAM,IAAuB;AAE7B,SAAgB,EAAiB,GAAwB;CAGvD,OAFI,OAAO,KAAU,WAAiB,IAClC,EAAS,CAAK,KAAK,OAAO,EAAM,YAAa,WAAiB,EAAM,WACjE;AACT;AAaA,SAAgB,EAAoB,GAAwB,GAA0C;CACpG,OAAO;EACL,UAAU,EAAiB,CAAa;EACxC,OAAO,EAAgB,CAAgB;CACzC;AACF;AAEA,SAAgB,EAAiB,GAAgB,GAAmC;CAClF,IAAM,IAAO,EAAoB,GAAO,IAAI;CAE5C,OADA,EAAK,MAAM,EAAK,MAAM,GACf;AACT;AAEA,SAAgB,EAAqB,GAAgB,GAA6C;CAChG,IAAM,IAAO,EAAoB,GAAO,IAAI;CAC5C,KAAK,IAAM,KAAU,GAAY,OAAO,EAAK,MAAM;CACnD,OAAO;AACT;AAEA,SAAgB,EAAgB,GAA8C;CAC5E,IAAI,CAAC,MAAM,QAAQ,CAAK,GAAG,OAAO,CAAC;CACnC,IAAM,IAAsC,CAAC;CAC7C,KAAK,IAAM,KAAQ,GACjB,AAAI,EAAS,CAAI,KAAK,OAAO,EAAK,MAAO,aACvC,EAAM,EAAK,MAAM;CAGrB,OAAO;AACT;AAEA,SAAgB,EAAwB,GAA4B;CAClE,IAAM,oBAAO,IAAI,IAAY,GACvB,IAAgB,CAAC,GACnB;CAEJ,KADA,EAAqB,YAAY,IACzB,IAAQ,EAAqB,KAAK,CAAQ,OAAO,OAAM;EAC7D,IAAM,IAAS,EAAM;EACjB,EAAK,IAAI,CAAM,MACnB,EAAK,IAAI,CAAM,GACf,EAAI,KAAK,CAAM;CACjB;CACA,OAAO;AACT;AAEA,SAAgB,EAAuB,GAAwC;CAG7E,OAFK,IACS,2CAA2C,KAAK,CACvD,IAAQ,MAAM,OAFJ;AAGnB;AAEA,SAAgB,EAAqB,GAAsE;CACzG,IAAM,IAAM,oBAAoB,EAAK;CAGrC,OAFI,EAAK,aAAa,WAAW,QAAQ,IAAU,OAAO,EAAI,KAEvD,IADO,EAAK,YAAY,EAAK,GACnB,IAAI,EAAI;AAC3B;AAEA,SAAS,EAAS,GAAkD;CAClE,OAAO,OAAO,KAAU,cAAY;AACtC"}
@@ -98,6 +98,34 @@ export interface EntityField {
98
98
  structure?: string;
99
99
  array?: boolean;
100
100
  copyable?: boolean;
101
+ /**
102
+ * Plan 2026-05-26 §4 / B13. Names the data_classifications tier
103
+ * gating reads + writes on this field. Read-side: stripped from the
104
+ * row when the caller lacks `classification:<tier>:read`. Write-side:
105
+ * 422 FIELDS_FORBIDDEN when the caller writes the key without
106
+ * `classification:<tier>:write`. Frontend reads the tier's localized
107
+ * display name from the top-level `classifications:` map and
108
+ * cross-references `schema.entities.<code>.field_access.<field>` for
109
+ * the per-caller decision.
110
+ */
111
+ classification?: string;
112
+ }
113
+ /**
114
+ * Per-caller classification decision for one classified field on one
115
+ * entity. Plan 2026-05-26 §8 / B9. Populated by the schema endpoint
116
+ * from the caller's grants + authority tier; the frontend reads this
117
+ * to render the locked-widget badge ("🔒 Compensation"), disable write
118
+ * inputs, and surface the operator-facing `required_permission` hint
119
+ * to the user (e.g. "Ask your admin to grant you
120
+ * classification:compensation:read").
121
+ */
122
+ export interface FieldAccessInfo {
123
+ read: "granted" | "denied";
124
+ write: "granted" | "denied";
125
+ classification: string;
126
+ /** Only set when read OR write is denied — the exact grant string an
127
+ * administrator needs to add to a role to unlock this field. */
128
+ required_permission?: string;
101
129
  }
102
130
  export interface StructureDef {
103
131
  name: LocalizedString;
@@ -283,6 +311,16 @@ export interface Entity {
283
311
  * Server delivers RBAC-pruned nodes in tagged-union wire format (plan §8).
284
312
  */
285
313
  action_layout?: ActionLayoutNode[];
314
+ /**
315
+ * Plan 2026-05-26 §B9 / §B13. Per-caller classification access
316
+ * decision keyed by field name. Only present for entities that
317
+ * declare at least one classified field; absent on non-classification
318
+ * entities so the wire stays byte-equivalent for non-classification
319
+ * deployments. Frontend reads this map to render the locked-widget
320
+ * badge, disable write inputs, and surface the required_permission
321
+ * hint to the user.
322
+ */
323
+ field_access?: Record<string, FieldAccessInfo>;
286
324
  /**
287
325
  * Per-entity realtime opt-in. When true the entity is in the CDC
288
326
  * publication, its list views auto-update from the SSE stream, and the
@@ -763,4 +801,31 @@ export interface Schema {
763
801
  branding?: Branding;
764
802
  areas?: AreaDef[];
765
803
  accents?: Record<string, AccentDef>;
804
+ /**
805
+ * Plan 2026-05-26 §B9 / §B13. data_classifications catalog keyed by
806
+ * tier code — display name + description + audit_on_read flag + icon
807
+ * + sod_protected flag. Absent when the consumer declares no tiers
808
+ * (preserves byte-equivalent wire shape for non-classification
809
+ * deployments). Frontend resolves the localized tier name from this
810
+ * map when rendering locked-widget badges.
811
+ */
812
+ classifications?: Record<string, ClassificationTierDef>;
813
+ }
814
+ /**
815
+ * Plan 2026-05-26 §B9. One entry in the top-level classifications map.
816
+ * Mirrors engine.DataClassification.
817
+ */
818
+ export interface ClassificationTierDef {
819
+ name: LocalizedString;
820
+ description?: LocalizedString;
821
+ audit_on_read?: boolean;
822
+ icon?: string;
823
+ /**
824
+ * When true, the tier triggers stricter SoD controls: superadmin
825
+ * reveals emit a distinct audit code, tenant param
826
+ * `auth.superadmin_classification_bypass=false` disables the
827
+ * implicit bypass, role self-assignment is refused, transitive role
828
+ * inheritance is hard-errored at load. Plan §11 Decisions Log #20-22.
829
+ */
830
+ sod_protected?: boolean;
766
831
  }
@@ -410,6 +410,7 @@ async function K(e, t, n) {
410
410
  url: i.url,
411
411
  kind: "direct"
412
412
  };
413
+ if (!i.url.startsWith("/api/files/")) throw Error(`embed file proxy: refusing non-files-route URL ${JSON.stringify(i.url)}`);
413
414
  let a = await G(t === "download" ? Ee(i.url) : i.url, n);
414
415
  if (!a.ok) throw Error(`embed file proxy fetch failed: ${a.status} ${a.statusText}`);
415
416
  let o = await a.blob();
@@ -765,4 +766,4 @@ function it(e) {
765
766
  //#endregion
766
767
  export { ve as A, g as B, B as C, F as D, ge as E, x as F, m as H, re as I, b as L, fe as M, C as N, L as O, S as P, oe as R, A as S, V as T, p as U, _ as V, c as W, q as _, Xe as a, De as b, Be as c, Ue as d, We as f, Je as g, Re as h, tt as i, E as j, N as k, He as l, Ke as m, rt as n, Qe as o, Ve as p, nt as r, Ze as s, it as t, qe as u, Me as v, Se as w, Ne as x, Q as y, h as z };
767
768
 
768
- //# sourceMappingURL=value-qao1rdkR.js.map
769
+ //# sourceMappingURL=value-DaEUgsnK.js.map