@xemahq/kernel-contracts 0.29.0 → 0.30.0

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 (131) hide show
  1. package/dist/document-patch/index.d.ts +4 -0
  2. package/dist/document-patch/index.d.ts.map +1 -0
  3. package/dist/document-patch/index.js +20 -0
  4. package/dist/document-patch/index.js.map +1 -0
  5. package/dist/document-patch/lib/patch-anchor.d.ts +7 -0
  6. package/dist/document-patch/lib/patch-anchor.d.ts.map +1 -0
  7. package/dist/document-patch/lib/patch-anchor.js +14 -0
  8. package/dist/document-patch/lib/patch-anchor.js.map +1 -0
  9. package/dist/document-patch/lib/patch-enums.d.ts +11 -0
  10. package/dist/document-patch/lib/patch-enums.d.ts.map +1 -0
  11. package/dist/document-patch/lib/patch-enums.js +16 -0
  12. package/dist/document-patch/lib/patch-enums.js.map +1 -0
  13. package/dist/document-patch/lib/pending-patch.d.ts +22 -0
  14. package/dist/document-patch/lib/pending-patch.d.ts.map +1 -0
  15. package/dist/document-patch/lib/pending-patch.js +3 -0
  16. package/dist/document-patch/lib/pending-patch.js.map +1 -0
  17. package/dist/output/index.d.ts +7 -0
  18. package/dist/output/index.d.ts.map +1 -0
  19. package/dist/output/index.js +23 -0
  20. package/dist/output/index.js.map +1 -0
  21. package/dist/output/lib/output-deliverable-expectation.d.ts +9 -0
  22. package/dist/output/lib/output-deliverable-expectation.d.ts.map +1 -0
  23. package/dist/output/lib/output-deliverable-expectation.js +3 -0
  24. package/dist/output/lib/output-deliverable-expectation.js.map +1 -0
  25. package/dist/output/lib/output-item-dto.d.ts +25 -0
  26. package/dist/output/lib/output-item-dto.d.ts.map +1 -0
  27. package/dist/output/lib/output-item-dto.js +3 -0
  28. package/dist/output/lib/output-item-dto.js.map +1 -0
  29. package/dist/output/lib/output-kind.d.ts +14 -0
  30. package/dist/output/lib/output-kind.d.ts.map +1 -0
  31. package/dist/output/lib/output-kind.js +18 -0
  32. package/dist/output/lib/output-kind.js.map +1 -0
  33. package/dist/output/lib/output-render-hint.d.ts +11 -0
  34. package/dist/output/lib/output-render-hint.d.ts.map +1 -0
  35. package/dist/output/lib/output-render-hint.js +10 -0
  36. package/dist/output/lib/output-render-hint.js.map +1 -0
  37. package/dist/output/lib/output-version-dto.d.ts +10 -0
  38. package/dist/output/lib/output-version-dto.d.ts.map +1 -0
  39. package/dist/output/lib/output-version-dto.js +3 -0
  40. package/dist/output/lib/output-version-dto.js.map +1 -0
  41. package/dist/output/lib/payload-schemas/code-diff.d.ts +7 -0
  42. package/dist/output/lib/payload-schemas/code-diff.d.ts.map +1 -0
  43. package/dist/output/lib/payload-schemas/code-diff.js +11 -0
  44. package/dist/output/lib/payload-schemas/code-diff.js.map +1 -0
  45. package/dist/output/lib/payload-schemas/decision-record.d.ts +19 -0
  46. package/dist/output/lib/payload-schemas/decision-record.d.ts.map +1 -0
  47. package/dist/output/lib/payload-schemas/decision-record.js +39 -0
  48. package/dist/output/lib/payload-schemas/decision-record.js.map +1 -0
  49. package/dist/output/lib/payload-schemas/diagram.d.ts +12 -0
  50. package/dist/output/lib/payload-schemas/diagram.d.ts.map +1 -0
  51. package/dist/output/lib/payload-schemas/diagram.js +34 -0
  52. package/dist/output/lib/payload-schemas/diagram.js.map +1 -0
  53. package/dist/output/lib/payload-schemas/external-blob.d.ts +9 -0
  54. package/dist/output/lib/payload-schemas/external-blob.d.ts.map +1 -0
  55. package/dist/output/lib/payload-schemas/external-blob.js +15 -0
  56. package/dist/output/lib/payload-schemas/external-blob.js.map +1 -0
  57. package/dist/output/lib/payload-schemas/index.d.ts +17 -0
  58. package/dist/output/lib/payload-schemas/index.d.ts.map +1 -0
  59. package/dist/output/lib/payload-schemas/index.js +94 -0
  60. package/dist/output/lib/payload-schemas/index.js.map +1 -0
  61. package/dist/output/lib/payload-schemas/json-payload.d.ts +4 -0
  62. package/dist/output/lib/payload-schemas/json-payload.d.ts.map +1 -0
  63. package/dist/output/lib/payload-schemas/json-payload.js +9 -0
  64. package/dist/output/lib/payload-schemas/json-payload.js.map +1 -0
  65. package/dist/output/lib/payload-schemas/manifest.d.ts +37 -0
  66. package/dist/output/lib/payload-schemas/manifest.d.ts.map +1 -0
  67. package/dist/output/lib/payload-schemas/manifest.js +43 -0
  68. package/dist/output/lib/payload-schemas/manifest.js.map +1 -0
  69. package/dist/output/lib/payload-schemas/markdown-doc.d.ts +8 -0
  70. package/dist/output/lib/payload-schemas/markdown-doc.d.ts.map +1 -0
  71. package/dist/output/lib/payload-schemas/markdown-doc.js +12 -0
  72. package/dist/output/lib/payload-schemas/markdown-doc.js.map +1 -0
  73. package/dist/output/lib/payload-schemas/review-findings.d.ts +30 -0
  74. package/dist/output/lib/payload-schemas/review-findings.d.ts.map +1 -0
  75. package/dist/output/lib/payload-schemas/review-findings.js +39 -0
  76. package/dist/output/lib/payload-schemas/review-findings.js.map +1 -0
  77. package/dist/output/lib/payload-schemas/schema.d.ts +4 -0
  78. package/dist/output/lib/payload-schemas/schema.d.ts.map +1 -0
  79. package/dist/output/lib/payload-schemas/schema.js +10 -0
  80. package/dist/output/lib/payload-schemas/schema.js.map +1 -0
  81. package/dist/output/lib/payload-schemas/test-report.d.ts +33 -0
  82. package/dist/output/lib/payload-schemas/test-report.d.ts.map +1 -0
  83. package/dist/output/lib/payload-schemas/test-report.js +46 -0
  84. package/dist/output/lib/payload-schemas/test-report.js.map +1 -0
  85. package/dist/workspace-image/index.d.ts +5 -0
  86. package/dist/workspace-image/index.d.ts.map +1 -0
  87. package/dist/workspace-image/index.js +21 -0
  88. package/dist/workspace-image/index.js.map +1 -0
  89. package/dist/workspace-image/lib/enums.d.ts +17 -0
  90. package/dist/workspace-image/lib/enums.d.ts.map +1 -0
  91. package/dist/workspace-image/lib/enums.js +22 -0
  92. package/dist/workspace-image/lib/enums.js.map +1 -0
  93. package/dist/workspace-image/lib/hash.d.ts +3 -0
  94. package/dist/workspace-image/lib/hash.d.ts.map +1 -0
  95. package/dist/workspace-image/lib/hash.js +50 -0
  96. package/dist/workspace-image/lib/hash.js.map +1 -0
  97. package/dist/workspace-image/lib/schemas.d.ts +103 -0
  98. package/dist/workspace-image/lib/schemas.d.ts.map +1 -0
  99. package/dist/workspace-image/lib/schemas.js +53 -0
  100. package/dist/workspace-image/lib/schemas.js.map +1 -0
  101. package/dist/workspace-image/lib/types.d.ts +50 -0
  102. package/dist/workspace-image/lib/types.d.ts.map +1 -0
  103. package/dist/workspace-image/lib/types.js +3 -0
  104. package/dist/workspace-image/lib/types.js.map +1 -0
  105. package/package.json +1 -1
  106. package/src/document-patch/index.ts +25 -0
  107. package/src/document-patch/lib/patch-anchor.ts +22 -0
  108. package/src/document-patch/lib/patch-enums.ts +36 -0
  109. package/src/document-patch/lib/pending-patch.ts +31 -0
  110. package/src/output/index.ts +20 -0
  111. package/src/output/lib/output-deliverable-expectation.ts +25 -0
  112. package/src/output/lib/output-item-dto.ts +54 -0
  113. package/src/output/lib/output-kind.ts +44 -0
  114. package/src/output/lib/output-render-hint.ts +28 -0
  115. package/src/output/lib/output-version-dto.ts +21 -0
  116. package/src/output/lib/payload-schemas/code-diff.ts +20 -0
  117. package/src/output/lib/payload-schemas/decision-record.ts +71 -0
  118. package/src/output/lib/payload-schemas/diagram.ts +54 -0
  119. package/src/output/lib/payload-schemas/external-blob.ts +36 -0
  120. package/src/output/lib/payload-schemas/index.ts +133 -0
  121. package/src/output/lib/payload-schemas/json-payload.ts +19 -0
  122. package/src/output/lib/payload-schemas/manifest.ts +81 -0
  123. package/src/output/lib/payload-schemas/markdown-doc.ts +33 -0
  124. package/src/output/lib/payload-schemas/review-findings.ts +75 -0
  125. package/src/output/lib/payload-schemas/schema.ts +22 -0
  126. package/src/output/lib/payload-schemas/test-report.ts +69 -0
  127. package/src/workspace-image/index.ts +13 -0
  128. package/src/workspace-image/lib/enums.ts +38 -0
  129. package/src/workspace-image/lib/hash.ts +95 -0
  130. package/src/workspace-image/lib/schemas.ts +63 -0
  131. package/src/workspace-image/lib/types.ts +79 -0
@@ -0,0 +1,54 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Diagram-dialect enum. Closed today on `mermaid` only — the FE
5
+ * renderer pipeline can ingest mermaid via the existing
6
+ * `MarkdownView` mermaid block. Add new values here when a new
7
+ * dialect (drawio, plantuml, …) has a renderer wired.
8
+ */
9
+ export enum DiagramDialect {
10
+ MERMAID = 'mermaid',
11
+ }
12
+
13
+ export const DiagramDialectSchema = z.nativeEnum(DiagramDialect);
14
+
15
+ /**
16
+ * Payload shape for `OutputKind.DIAGRAM` artifacts — standalone
17
+ * diagrams emitted by architecture / requirements agents.
18
+ *
19
+ * Two valid shapes (mirroring how `xema/emit-artifact` producers
20
+ * actually emit today):
21
+ *
22
+ * 1. Inline source: `{ type: 'mermaid', source: '<mermaid src>' }`.
23
+ * 2. Markdown fallback: `{ body: '...mermaid block...' }` — when
24
+ * the producer wrapped a mermaid fence in a markdown narrative.
25
+ *
26
+ * The FE `DiagramRenderer` reads `source` when present, else falls
27
+ * through to markdown rendering which itself supports mermaid.
28
+ */
29
+ export const DiagramPayloadSchema = z
30
+ .object({
31
+ type: DiagramDialectSchema.optional(),
32
+ source: z.string().optional(),
33
+ body: z.string().optional(),
34
+ })
35
+ .passthrough()
36
+ .superRefine((value, ctx) => {
37
+ const hasSource = typeof value.source === 'string' && value.source.trim().length > 0;
38
+ const hasBody = typeof value.body === 'string' && value.body.trim().length > 0;
39
+ if (!hasSource && !hasBody) {
40
+ ctx.addIssue({
41
+ code: 'custom',
42
+ message: 'diagram payload must include either `source` or `body`.',
43
+ });
44
+ }
45
+ if (hasSource && value.type === undefined) {
46
+ ctx.addIssue({
47
+ code: 'custom',
48
+ path: ['type'],
49
+ message: 'diagram payload with `source` must declare `type` (e.g. `mermaid`).',
50
+ });
51
+ }
52
+ });
53
+
54
+ export type DiagramPayload = z.infer<typeof DiagramPayloadSchema>;
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Payload shape for `OutputKind.EXTERNAL_BLOB` artifacts — images,
5
+ * PDFs, archives, and other binary content stored out-of-band in
6
+ * MinIO / S3.
7
+ *
8
+ * The `payload` itself is metadata about the external bytes. The
9
+ * actual bytes live behind `ArtifactVersion.pointer` (durable
10
+ * storage URI) and are streamed via the artifact-store's
11
+ * version-content endpoint, NOT carried inline.
12
+ *
13
+ * `contentType` (IANA media type) is required so the FE renderer
14
+ * can branch on preview mode (image / pdf / video / download).
15
+ * `size` is informational. `originalFilename` flows into the
16
+ * download affordance when present.
17
+ */
18
+ export const ExternalBlobPayloadSchema = z
19
+ .object({
20
+ contentType: z
21
+ .string()
22
+ .min(1, 'external_blob.contentType must be a non-empty IANA media type'),
23
+ /** Byte count, when known. Producers without it may omit. */
24
+ size: z.number().nonnegative().int().optional(),
25
+ /** Original filename, used as the download attachment name. */
26
+ originalFilename: z.string().optional(),
27
+ /**
28
+ * Producer-supplied alt text for image-kind blobs (a11y) and
29
+ * for any future LLM-briefing path that needs a non-visual
30
+ * description.
31
+ */
32
+ altText: z.string().optional(),
33
+ })
34
+ .passthrough();
35
+
36
+ export type ExternalBlobPayload = z.infer<typeof ExternalBlobPayloadSchema>;
@@ -0,0 +1,133 @@
1
+ import type { ZodTypeAny } from 'zod';
2
+
3
+ import { CodeDiffPayloadSchema } from './code-diff';
4
+ import { DecisionRecordPayloadSchema } from './decision-record';
5
+ import { DiagramPayloadSchema } from './diagram';
6
+ import { ExternalBlobPayloadSchema } from './external-blob';
7
+ import { JsonPayloadPayloadSchema } from './json-payload';
8
+ import { ManifestPayloadSchema } from './manifest';
9
+ import { MarkdownDocPayloadSchema } from './markdown-doc';
10
+ import { ReviewFindingsPayloadSchema } from './review-findings';
11
+ import { JsonSchemaPayloadSchema } from './schema';
12
+ import { TestReportPayloadSchema } from './test-report';
13
+
14
+ import { OutputKind } from '../output-kind';
15
+
16
+ export * from './code-diff';
17
+ export * from './decision-record';
18
+ export * from './diagram';
19
+ export * from './external-blob';
20
+ export * from './json-payload';
21
+ export * from './manifest';
22
+ export * from './markdown-doc';
23
+ export * from './review-findings';
24
+ export * from './schema';
25
+ export * from './test-report';
26
+
27
+ /**
28
+ * Registry mapping closed `OutputKind` → Zod schema for the matching
29
+ * artifact payload. Every kind that has a renderer also has a schema;
30
+ * `OutputKind.UNKNOWN` is intentionally absent — biome-supplied
31
+ * payloads we can't classify are NOT validated (they pass through to
32
+ * the `unknown` renderer's "Show raw" surface).
33
+ *
34
+ * Keeping the registry keyed on `OutputKind` rather than free-form
35
+ * `type` strings means: the `(type → OutputKind)` mapping is the single
36
+ * place biomes add new aliases; once a producer's `type` resolves to
37
+ * a known kind, validation kicks in automatically.
38
+ */
39
+ export const OUTPUT_KIND_PAYLOAD_SCHEMAS: Readonly<
40
+ Partial<Record<OutputKind, ZodTypeAny>>
41
+ > = {
42
+ [OutputKind.MARKDOWN_DOC]: MarkdownDocPayloadSchema,
43
+ [OutputKind.CODE_DIFF]: CodeDiffPayloadSchema,
44
+ [OutputKind.DECISION_RECORD]: DecisionRecordPayloadSchema,
45
+ [OutputKind.REVIEW_FINDINGS]: ReviewFindingsPayloadSchema,
46
+ [OutputKind.SCHEMA]: JsonSchemaPayloadSchema,
47
+ [OutputKind.MANIFEST]: ManifestPayloadSchema,
48
+ [OutputKind.TEST_REPORT]: TestReportPayloadSchema,
49
+ [OutputKind.DIAGRAM]: DiagramPayloadSchema,
50
+ [OutputKind.JSON_PAYLOAD]: JsonPayloadPayloadSchema,
51
+ [OutputKind.EXTERNAL_BLOB]: ExternalBlobPayloadSchema,
52
+ };
53
+
54
+ /**
55
+ * Closed mapping from producer-supplied `type` string (the
56
+ * `EmitArtifactDto.type` value) → `OutputKind` enum. This is the
57
+ * platform's authoritative `type → kind` table; the FE and BE both
58
+ * read from here. Biomes that ship new aliases for an existing kind
59
+ * extend this map via a biome-host extension point (future) or
60
+ * register a new OutputKind (kernel-level).
61
+ *
62
+ * Order is irrelevant — duplicate keys would be a build-time error
63
+ * in TS, and the map is frozen at module load.
64
+ */
65
+ export const ARTIFACT_TYPE_TO_OUTPUT_KIND: Readonly<Record<string, OutputKind>> = {
66
+ // markdown_doc
67
+ document_page: OutputKind.MARKDOWN_DOC,
68
+ 'markdown.doc': OutputKind.MARKDOWN_DOC,
69
+ 'doc.markdown': OutputKind.MARKDOWN_DOC,
70
+ 'requirements.doc': OutputKind.MARKDOWN_DOC,
71
+ 'design.doc': OutputKind.MARKDOWN_DOC,
72
+ 'spec.doc': OutputKind.MARKDOWN_DOC,
73
+ // code_diff
74
+ 'code.diff': OutputKind.CODE_DIFF,
75
+ 'src.diff': OutputKind.CODE_DIFF,
76
+ 'patch.diff': OutputKind.CODE_DIFF,
77
+ // decision_record
78
+ 'decision.record': OutputKind.DECISION_RECORD,
79
+ adr: OutputKind.DECISION_RECORD,
80
+ // review_findings
81
+ 'review.findings': OutputKind.REVIEW_FINDINGS,
82
+ 'review.report': OutputKind.REVIEW_FINDINGS,
83
+ 'review.verdict': OutputKind.REVIEW_FINDINGS,
84
+ // schema
85
+ schema: OutputKind.SCHEMA,
86
+ 'json.schema': OutputKind.SCHEMA,
87
+ // manifest
88
+ manifest: OutputKind.MANIFEST,
89
+ 'workflow.manifest': OutputKind.MANIFEST,
90
+ 'biome.manifest': OutputKind.MANIFEST,
91
+ // test_report
92
+ 'test.report': OutputKind.TEST_REPORT,
93
+ 'qa.report': OutputKind.TEST_REPORT,
94
+ // diagram
95
+ diagram: OutputKind.DIAGRAM,
96
+ mermaid: OutputKind.DIAGRAM,
97
+ // json_payload
98
+ endpoint_fetch_response: OutputKind.JSON_PAYLOAD,
99
+ structured_json: OutputKind.JSON_PAYLOAD,
100
+ 'json.payload': OutputKind.JSON_PAYLOAD,
101
+ // external_blob
102
+ 'image.png': OutputKind.EXTERNAL_BLOB,
103
+ 'image.jpeg': OutputKind.EXTERNAL_BLOB,
104
+ 'binary.blob': OutputKind.EXTERNAL_BLOB,
105
+ };
106
+
107
+ /**
108
+ * Resolve a producer-supplied `type` to the matching `OutputKind`.
109
+ * Returns `undefined` when no mapping exists — callers treat that as
110
+ * "biome-defined custom type" and skip schema validation, falling
111
+ * through to the `unknown` renderer downstream.
112
+ */
113
+ export function outputKindForType(type: string): OutputKind | undefined {
114
+ return ARTIFACT_TYPE_TO_OUTPUT_KIND[type];
115
+ }
116
+
117
+ /**
118
+ * Resolve the Zod payload schema for a given `type` string, going
119
+ * through the `type → OutputKind → schema` chain. Returns
120
+ * `undefined` when the `type` is unknown OR when the kind has no
121
+ * registered schema (today only `OutputKind.UNKNOWN`).
122
+ *
123
+ * Callers (artifact-store-api's `EmitArtifactValidator`,
124
+ * future FE preview helpers) treat `undefined` as "pass through —
125
+ * no schema-level validation for this type". Biome extensibility is
126
+ * preserved: a custom `my-org.special-output` type that's not in the
127
+ * table simply isn't validated, exactly as today.
128
+ */
129
+ export function payloadSchemaForType(type: string): ZodTypeAny | undefined {
130
+ const kind = outputKindForType(type);
131
+ if (kind === undefined) {return undefined;}
132
+ return OUTPUT_KIND_PAYLOAD_SCHEMAS[kind];
133
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Payload shape for `OutputKind.JSON_PAYLOAD` artifacts — the
5
+ * deliberate "structured JSON without a more specific kind" bucket.
6
+ * Used today by `xema/http`, `xema/endpoint-fetch`, and any producer
7
+ * emitting an opaque JSON envelope.
8
+ *
9
+ * Permissive by design — the WHOLE payload IS the deliverable, no
10
+ * particular field is required. Validation just ensures it's an
11
+ * object (not a bare primitive); arrays masquerading as the root are
12
+ * also accepted via the union below.
13
+ */
14
+ export const JsonPayloadPayloadSchema = z.union([
15
+ z.record(z.string(), z.unknown()),
16
+ z.array(z.unknown()),
17
+ ]);
18
+
19
+ export type JsonPayloadPayload = z.infer<typeof JsonPayloadPayloadSchema>;
@@ -0,0 +1,81 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * One reference inside a manifest's `refs[]` — a pointer to a KB page,
5
+ * a backlog item, an emitted file, or any other tracked artifact the
6
+ * manifest summarises.
7
+ */
8
+ export const ManifestRefSchema = z
9
+ .object({
10
+ /** Closed-ish set of ref kinds; producers may extend via .passthrough(). */
11
+ type: z.string().min(1),
12
+ id: z.string().min(1),
13
+ title: z.string().optional(),
14
+ /** Producer-defined verb describing what happened to the ref (created / modified / referenced / …). */
15
+ action: z.string().optional(),
16
+ /** File-system path for `file`-kind refs. */
17
+ path: z.string().optional(),
18
+ })
19
+ .passthrough();
20
+
21
+ export type ManifestRef = z.infer<typeof ManifestRefSchema>;
22
+
23
+ /**
24
+ * An inline diagram embedded in the manifest. Today only `mermaid`
25
+ * is supported; the FE renders via the existing mermaid pipeline in
26
+ * `MarkdownView`.
27
+ */
28
+ export const ManifestDiagramSchema = z
29
+ .object({
30
+ key: z.string().min(1),
31
+ title: z.string().min(1),
32
+ type: z.literal('mermaid'),
33
+ source: z.string().min(1),
34
+ })
35
+ .passthrough();
36
+
37
+ export type ManifestDiagram = z.infer<typeof ManifestDiagramSchema>;
38
+
39
+ /**
40
+ * Payload shape for `OutputKind.MANIFEST` artifacts — phase summaries,
41
+ * workflow manifests, biome manifests, and any structured "summary
42
+ * with cross-references" document.
43
+ *
44
+ * Two render shapes the FE recognises:
45
+ *
46
+ * 1. Rich manifest: `{ schemaVersion, summary, refs[]?, diagrams[]? }`.
47
+ * The FE renderer (`ManifestRenderer`, plus the legacy
48
+ * `ArtifactContentViewer`) renders the summary as markdown with
49
+ * phase + ref + diagram sidebars.
50
+ * 2. Plain YAML/JSON: `{ body: string }`. The renderer falls back
51
+ * to monospaced text preserving indentation.
52
+ *
53
+ * `passthrough()` so the `schemaVersion` field — which is a producer-
54
+ * declared manifest version, distinct from the artifact-store's
55
+ * `EmitArtifactDto.schemaVersion` — flows through.
56
+ */
57
+ export const ManifestPayloadSchema = z
58
+ .object({
59
+ schemaVersion: z.string().optional(),
60
+ summary: z.string().optional(),
61
+ refs: z.array(ManifestRefSchema).optional(),
62
+ diagrams: z.array(ManifestDiagramSchema).optional(),
63
+ body: z.string().optional(),
64
+ stageKey: z.string().optional(),
65
+ })
66
+ .passthrough()
67
+ .superRefine((value, ctx) => {
68
+ const hasSummary =
69
+ typeof value.summary === 'string' && value.summary.trim().length > 0;
70
+ const hasBody = typeof value.body === 'string' && value.body.trim().length > 0;
71
+ const hasRefs = Array.isArray(value.refs) && value.refs.length > 0;
72
+ if (!hasSummary && !hasBody && !hasRefs) {
73
+ ctx.addIssue({
74
+ code: 'custom',
75
+ message:
76
+ 'manifest payload must include at least one of `summary`, `body`, or non-empty `refs[]`.',
77
+ });
78
+ }
79
+ });
80
+
81
+ export type ManifestPayload = z.infer<typeof ManifestPayloadSchema>;
@@ -0,0 +1,33 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Payload shape for `OutputKind.MARKDOWN_DOC` artifacts.
5
+ *
6
+ * The canonical document-page shape across the platform. Used by the
7
+ * deliverable harvester (one artifact per page), `xema/emit-artifact`
8
+ * with markdown content, requirements/design/spec agents, and KB-bound
9
+ * publications.
10
+ *
11
+ * `body` is the rendered narrative — markdown source. The FE renderer
12
+ * (`MarkdownDocRenderer`) reads `body` directly via
13
+ * `useArtifactContent.textViewOfPayload` and renders it through
14
+ * `MarkdownView` (react-markdown + remark-gfm + mermaid).
15
+ *
16
+ * `slug` and `parentSlug` are KB-publish hints; the publish-kb activity
17
+ * reads them when present, otherwise it derives slugs from the
18
+ * artifact's title + phase. They are NOT required at emit-time — a
19
+ * deliverable that's reviewed but never published has no slug.
20
+ *
21
+ * `.passthrough()` is intentional: producers may attach arbitrary
22
+ * extras (e.g. front-matter metadata, agent commentary) without the
23
+ * schema rejecting the emit.
24
+ */
25
+ export const MarkdownDocPayloadSchema = z
26
+ .object({
27
+ body: z.string().min(1, 'markdown_doc.body must be non-empty'),
28
+ slug: z.string().optional(),
29
+ parentSlug: z.string().optional(),
30
+ })
31
+ .passthrough();
32
+
33
+ export type MarkdownDocPayload = z.infer<typeof MarkdownDocPayloadSchema>;
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Closed severity enum for `ReviewFinding.severity`. Drives FE
5
+ * grouping + colouring in `ReviewFindingsRenderer`. `info` is
6
+ * commentary that doesn't gate a verdict; `minor` / `major` /
7
+ * `blocking` escalate.
8
+ */
9
+ export enum ReviewFindingSeverity {
10
+ INFO = 'info',
11
+ MINOR = 'minor',
12
+ MAJOR = 'major',
13
+ BLOCKING = 'blocking',
14
+ }
15
+
16
+ export const ReviewFindingSeveritySchema = z.nativeEnum(ReviewFindingSeverity);
17
+
18
+ /**
19
+ * A single finding inside a review-findings payload.
20
+ *
21
+ * `anchor` is a free-form string today (pre-W3C-anchor migration on
22
+ * REVIEW inquiries). Producers conventionally use shapes like
23
+ * `file:path:line` or `heading:slug`. A future tightening could
24
+ * promote this to a `WebAnnotationTarget` once the producer fleet
25
+ * is migrated, matching the inquiry-side anchor work.
26
+ */
27
+ export const ReviewFindingSchema = z
28
+ .object({
29
+ severity: ReviewFindingSeveritySchema,
30
+ message: z.string().min(1, 'review_findings.findings[].message must be non-empty'),
31
+ anchor: z.string().optional(),
32
+ })
33
+ .passthrough();
34
+
35
+ export type ReviewFinding = z.infer<typeof ReviewFindingSchema>;
36
+
37
+ /**
38
+ * Payload shape for `OutputKind.REVIEW_FINDINGS` artifacts — emitted
39
+ * by reviewer agents inside `xema/review` iterations.
40
+ *
41
+ * Two valid shapes (the FE renderer detects both):
42
+ *
43
+ * 1. Structured: `findings: ReviewFinding[]` — preferred. Renders
44
+ * as a severity-grouped table with anchors.
45
+ * 2. Legacy: `body: string` — markdown narrative of the review.
46
+ * The renderer falls back to markdown rendering when `findings`
47
+ * is absent.
48
+ *
49
+ * `verdict` + `summary` are optional rollups — the reviewer's
50
+ * top-line answer and a short paragraph framing the detailed
51
+ * findings.
52
+ */
53
+ export const ReviewFindingsPayloadSchema = z
54
+ .object({
55
+ findings: z.array(ReviewFindingSchema).optional(),
56
+ verdict: z.enum(['approve', 'reject', 'abstain']).optional(),
57
+ summary: z.string().optional(),
58
+ body: z.string().optional(),
59
+ })
60
+ .passthrough()
61
+ .superRefine((value, ctx) => {
62
+ const hasFindings = Array.isArray(value.findings) && value.findings.length > 0;
63
+ const hasBody = typeof value.body === 'string' && value.body.trim().length > 0;
64
+ const hasSummary =
65
+ typeof value.summary === 'string' && value.summary.trim().length > 0;
66
+ if (!hasFindings && !hasBody && !hasSummary) {
67
+ ctx.addIssue({
68
+ code: 'custom',
69
+ message:
70
+ 'review_findings payload must include at least one of `findings[]`, `summary`, or `body`.',
71
+ });
72
+ }
73
+ });
74
+
75
+ export type ReviewFindingsPayload = z.infer<typeof ReviewFindingsPayloadSchema>;
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Payload shape for `OutputKind.SCHEMA` artifacts — JSON Schema
5
+ * documents declared by agents.
6
+ *
7
+ * The payload IS the JSON Schema document. We don't dictate which
8
+ * dialect (Draft 07, 2020-12, OpenAPI) — the validator we use
9
+ * downstream picks based on the document's own `$schema` field.
10
+ * Minimum check: it must be a non-empty object.
11
+ *
12
+ * The FE `SchemaRenderer` reads the payload directly via
13
+ * `useArtifactContent.payload` (NOT `content`) and renders the
14
+ * schema as a typed tree.
15
+ */
16
+ export const JsonSchemaPayloadSchema = z
17
+ .record(z.string(), z.unknown())
18
+ .refine((v) => Object.keys(v).length > 0, {
19
+ message: 'schema payload must be a non-empty JSON Schema document.',
20
+ });
21
+
22
+ export type JsonSchemaPayload = z.infer<typeof JsonSchemaPayloadSchema>;
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Outcome of a single test in a test-report payload. Closed enum
5
+ * because the FE renders each state with distinctive iconography.
6
+ */
7
+ export enum TestOutcome {
8
+ PASSED = 'passed',
9
+ FAILED = 'failed',
10
+ SKIPPED = 'skipped',
11
+ ERRORED = 'errored',
12
+ }
13
+
14
+ export const TestOutcomeSchema = z.nativeEnum(TestOutcome);
15
+
16
+ export const TestCaseSchema = z
17
+ .object({
18
+ name: z.string().min(1),
19
+ outcome: TestOutcomeSchema,
20
+ /** Duration in milliseconds; producers without timing data omit. */
21
+ durationMs: z.number().nonnegative().optional(),
22
+ /** Failure / error message for FAILED + ERRORED outcomes. */
23
+ message: z.string().optional(),
24
+ /** Optional path to the file under test (e.g. `src/auth.test.ts`). */
25
+ file: z.string().optional(),
26
+ })
27
+ .passthrough();
28
+
29
+ export type TestCase = z.infer<typeof TestCaseSchema>;
30
+
31
+ /**
32
+ * Payload shape for `OutputKind.TEST_REPORT` artifacts.
33
+ *
34
+ * `tests[]` is the per-case detail; the rollup counters
35
+ * (`passed`, `failed`, `skipped`, `errored`) are derived from
36
+ * `tests[]` when present but may be supplied directly when the
37
+ * producer only has aggregate numbers.
38
+ *
39
+ * `body` is the legacy markdown fallback (same pattern as other
40
+ * markdown-bearing kinds).
41
+ */
42
+ export const TestReportPayloadSchema = z
43
+ .object({
44
+ tests: z.array(TestCaseSchema).optional(),
45
+ passed: z.number().nonnegative().int().optional(),
46
+ failed: z.number().nonnegative().int().optional(),
47
+ skipped: z.number().nonnegative().int().optional(),
48
+ errored: z.number().nonnegative().int().optional(),
49
+ summary: z.string().optional(),
50
+ body: z.string().optional(),
51
+ })
52
+ .passthrough()
53
+ .superRefine((value, ctx) => {
54
+ const hasTests = Array.isArray(value.tests) && value.tests.length > 0;
55
+ const hasCounters =
56
+ typeof value.passed === 'number' ||
57
+ typeof value.failed === 'number' ||
58
+ typeof value.skipped === 'number';
59
+ const hasBody = typeof value.body === 'string' && value.body.trim().length > 0;
60
+ if (!hasTests && !hasCounters && !hasBody) {
61
+ ctx.addIssue({
62
+ code: 'custom',
63
+ message:
64
+ 'test_report payload must include at least one of `tests[]`, the rollup counters, or `body`.',
65
+ });
66
+ }
67
+ });
68
+
69
+ export type TestReportPayload = z.infer<typeof TestReportPayloadSchema>;
@@ -0,0 +1,13 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Workspace Image Contracts — Barrel Export ──
3
+ //
4
+ // Kernel (Layer 1) contracts for per-org workspace image variants. Pure
5
+ // types, closed enums, zod schemas, plus the deterministic content-hash
6
+ // helper used to identify a variant from its declarative manifest +
7
+ // base-image digest. Zero NestJS, zero Prisma.
8
+ // ═══════════════════════════════════════════════════════════════════════════
9
+
10
+ export * from './lib/enums';
11
+ export * from './lib/types';
12
+ export * from './lib/hash';
13
+ export * from './lib/schemas';
@@ -0,0 +1,38 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Workspace image — closed-domain enums ──
3
+ //
4
+ // Closed sets backing the per-org workspace image variants described in
5
+ // the ImageLayerKind enumerates every layer recipe the kernel
6
+ // understands; VariantStatus enumerates the bake lifecycle.
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+
9
+ /**
10
+ * Closed set of layer-recipe kinds. Each kind has a strict TypeScript
11
+ * payload shape (see `ImageLayer` in `types.ts`).
12
+ *
13
+ * IMPORTANT: there is no `entrypoint` / `cmd` kind — the entrypoint is
14
+ * owned by the worker-runtime, never by an org-supplied recipe.
15
+ */
16
+ export enum ImageLayerKind {
17
+ Apt = 'apt',
18
+ Dnf = 'dnf',
19
+ Apk = 'apk',
20
+ Pip = 'pip',
21
+ NpmGlobal = 'npm-global',
22
+ Env = 'env',
23
+ File = 'file',
24
+ }
25
+
26
+ /**
27
+ * Lifecycle of a single image variant. `draft` is editable in the UI;
28
+ * `building` is the active bake; `ready` is mountable by workers;
29
+ * `failed` retains the build log for debugging; `archived` keeps the row
30
+ * for history but disallows new use.
31
+ */
32
+ export enum VariantStatus {
33
+ Draft = 'draft',
34
+ Building = 'building',
35
+ Ready = 'ready',
36
+ Failed = 'failed',
37
+ Archived = 'archived',
38
+ }
@@ -0,0 +1,95 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // ── Workspace image — deterministic content hash ──
3
+ //
4
+ // Given a `WorkspaceImageManifest` plus the resolved base-image digest,
5
+ // produce a stable sha256 used as the variant's content identity. Two
6
+ // inputs that differ only in JSON formatting MUST hash to the same value;
7
+ // two inputs that differ semantically MUST NOT collide.
8
+ //
9
+ // Strategy:
10
+ // 1. Canonicalize: recursively sort object keys; arrays keep their
11
+ // authored order (order is semantically meaningful — `apt install`
12
+ // ordering can change layer caching and image size).
13
+ // 2. JSON.stringify the canonical form (no whitespace, stable key order).
14
+ // 3. sha256 over the concatenation `canonicalManifest + '\n' + digest`.
15
+ // ═══════════════════════════════════════════════════════════════════════════
16
+
17
+ import { createHash } from 'node:crypto';
18
+
19
+ import type { WorkspaceImageManifest } from './types';
20
+
21
+ type CanonicalJson =
22
+ | null
23
+ | boolean
24
+ | number
25
+ | string
26
+ | readonly CanonicalJson[]
27
+ | { readonly [key: string]: CanonicalJson };
28
+
29
+ /**
30
+ * Recursively produce a canonical, order-stable JSON-compatible value:
31
+ *
32
+ * - Objects: keys sorted lexicographically. `undefined` values dropped
33
+ * (mirrors `JSON.stringify`'s default and avoids two inputs differing
34
+ * only by an explicit `undefined` hashing differently).
35
+ * - Arrays: order is preserved (semantically meaningful in image layers).
36
+ * - Primitives & null: pass through.
37
+ *
38
+ * Throws on values JSON cannot represent (functions, symbols, bigint,
39
+ * circular refs) — fail-fast per the project's constitution.
40
+ */
41
+ function canonicalize(value: unknown, seen: WeakSet<object> = new WeakSet()): CanonicalJson {
42
+ if (value === null) return null;
43
+ const t = typeof value;
44
+ if (t === 'string' || t === 'boolean') {
45
+ return value as CanonicalJson;
46
+ }
47
+ if (t === 'number') {
48
+ if (!Number.isFinite(value as number)) {
49
+ throw new Error('canonicalize: non-finite number is not JSON-representable');
50
+ }
51
+ return value as number;
52
+ }
53
+ if (t === 'undefined') {
54
+ // Will be dropped by the object pass; encountering it at the root or
55
+ // inside an array indicates a programmer error.
56
+ throw new Error('canonicalize: undefined is not JSON-representable');
57
+ }
58
+ if (t === 'bigint' || t === 'function' || t === 'symbol') {
59
+ throw new Error(`canonicalize: ${t} is not JSON-representable`);
60
+ }
61
+ if (Array.isArray(value)) {
62
+ if (seen.has(value)) throw new Error('canonicalize: circular reference');
63
+ seen.add(value);
64
+ return value.map((entry) => canonicalize(entry, seen));
65
+ }
66
+ // Plain object.
67
+ if (seen.has(value as object)) throw new Error('canonicalize: circular reference');
68
+ seen.add(value as object);
69
+ const entries = Object.entries(value as Record<string, unknown>)
70
+ .filter(([, v]) => v !== undefined)
71
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
72
+ const out: Record<string, CanonicalJson> = {};
73
+ for (const [k, v] of entries) {
74
+ out[k] = canonicalize(v, seen);
75
+ }
76
+ return out;
77
+ }
78
+
79
+ /**
80
+ * Compute the canonical content hash of a workspace image variant. The
81
+ * `baseImageDigest` MUST be the resolved digest of the base image at
82
+ * bake-time (not a mutable tag) — otherwise a tag drift would silently
83
+ * invalidate the hash.
84
+ */
85
+ export function computeContentHash(
86
+ manifest: WorkspaceImageManifest,
87
+ baseImageDigest: string,
88
+ ): string {
89
+ if (!baseImageDigest || typeof baseImageDigest !== 'string') {
90
+ throw new Error('computeContentHash: baseImageDigest is required');
91
+ }
92
+ const canonical = canonicalize(manifest);
93
+ const serialized = JSON.stringify(canonical);
94
+ return createHash('sha256').update(`${serialized}\n${baseImageDigest}`).digest('hex');
95
+ }