@sprig-and-prose/sprig-edge 0.1.1 → 0.2.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.
- package/CHANGELOG.md +17 -0
- package/dist/attestation/generate.d.ts.map +1 -1
- package/dist/attestation/generate.js +44 -9
- package/dist/attestation/generate.js.map +1 -1
- package/dist/attestation/validate-record.d.ts +24 -3
- package/dist/attestation/validate-record.d.ts.map +1 -1
- package/dist/attestation/validate-record.js +62 -12
- package/dist/attestation/validate-record.js.map +1 -1
- package/dist/check/render.d.ts.map +1 -1
- package/dist/check/render.js +8 -7
- package/dist/check/render.js.map +1 -1
- package/package.json +1 -1
- package/src/attestation/generate.ts +50 -12
- package/src/attestation/validate-record.ts +99 -15
- package/src/check/render.ts +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# @sprig-and-prose/sprig-edge
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- **sprig-scenes (minor):** Export `PRIMITIVE_KINDS`, `ARTIFACT_TYPES`, and `ARTIFACT_TYPES_SET` from manifest as single source of truth. Add compile-time validation for primitive constraints (min/max/of) and array minItems/maxItems in `validateSceneManifest`. JSDoc: include `dataset` in artifactType.
|
|
8
|
+
|
|
9
|
+
**sprig-edge (minor):** Attestation now enforces optional fields, primitive constraints (min, max, of), and nested actor-shaped fields when manifest is available. Legend printed once at end for multiple scenes. Export `ValidationKind` and `ValidateRecordOptions`.
|
|
10
|
+
|
|
11
|
+
**sprig (patch):** Friendlier scene compile error prefix: "Scene compile failed" instead of "Scene compilation error".
|
|
12
|
+
|
|
13
|
+
**create-scene-tutorial (patch):** Generated scene file uses kebab-case filename: `demo-scene.scene.prose` instead of `DemoScene.scene.prose`.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @sprig-and-prose/sprig-scenes@0.2.0
|
|
19
|
+
|
|
3
20
|
## 0.1.1
|
|
4
21
|
|
|
5
22
|
### Patch Changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/attestation/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/attestation/generate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,qBAAqB,CAAC;AAiDnE,wBAAsB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiB1F"}
|
|
@@ -3,6 +3,28 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { loadManifest } from "../manifest/reader.js";
|
|
4
4
|
import { loadDatasetFiles } from "./file-loader.js";
|
|
5
5
|
import { validateRecord } from "./validate-record.js";
|
|
6
|
+
/**
|
|
7
|
+
* Get the Kind used for record validation. Supports both legacy manifest (actorDef.kind)
|
|
8
|
+
* and new manifest (actorDef.type === "shape" with actorDef.fields).
|
|
9
|
+
* Passes through optional flags and field types (including primitive constraints).
|
|
10
|
+
*/
|
|
11
|
+
function getActorKind(actorDef) {
|
|
12
|
+
if (actorDef.kind != null && typeof actorDef.kind === "object") {
|
|
13
|
+
return actorDef.kind;
|
|
14
|
+
}
|
|
15
|
+
const def = actorDef;
|
|
16
|
+
if (def.type === "shape" && def.fields && typeof def.fields === "object") {
|
|
17
|
+
const object = {};
|
|
18
|
+
for (const [name, field] of Object.entries(def.fields)) {
|
|
19
|
+
if (field?.type == null)
|
|
20
|
+
continue;
|
|
21
|
+
object[name] =
|
|
22
|
+
field.optional === true ? { kind: field.type, optional: true } : field.type;
|
|
23
|
+
}
|
|
24
|
+
return { object };
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
6
28
|
export async function handleAttest(config, sceneFilter) {
|
|
7
29
|
const scenesToProcess = sceneFilter
|
|
8
30
|
? config.scenes.filter((s) => s.scene === sceneFilter)
|
|
@@ -41,7 +63,8 @@ async function attestScene(sceneConfig) {
|
|
|
41
63
|
// Only process dataset expectations
|
|
42
64
|
if (expectation.artifactType !== "dataset")
|
|
43
65
|
continue;
|
|
44
|
-
const
|
|
66
|
+
const actorKind = getActorKind(actorDef);
|
|
67
|
+
const attestation = await attestDataset(sceneConfig, expectation, actorName, actorKind, manifest);
|
|
45
68
|
attestations.push(attestation);
|
|
46
69
|
}
|
|
47
70
|
if (attestations.length > 0) {
|
|
@@ -53,7 +76,13 @@ async function attestScene(sceneConfig) {
|
|
|
53
76
|
// Print summary
|
|
54
77
|
printAttestationSummary(manifest.sceneName, attestationFile);
|
|
55
78
|
}
|
|
56
|
-
async function attestDataset(sceneConfig, expectation,
|
|
79
|
+
async function attestDataset(sceneConfig, expectation, _actorName, actorKind, manifest) {
|
|
80
|
+
const actorsContext = {};
|
|
81
|
+
for (const [name, def] of Object.entries(manifest.actors)) {
|
|
82
|
+
const k = getActorKind(def);
|
|
83
|
+
if (k != null)
|
|
84
|
+
actorsContext[name] = k;
|
|
85
|
+
}
|
|
57
86
|
const datasetName = expectation.name;
|
|
58
87
|
const locationName = expectation.location;
|
|
59
88
|
// Look up dataset config
|
|
@@ -77,16 +106,22 @@ async function attestDataset(sceneConfig, expectation, actorName, actorKind) {
|
|
|
77
106
|
const resolvedFiles = datasetConfig.files.map((f) => join(baseDir, f));
|
|
78
107
|
// Load files
|
|
79
108
|
const { records, errors: loadErrors } = await loadDatasetFiles(resolvedFiles, datasetConfig.format, datasetConfig.mode);
|
|
80
|
-
// Validate records
|
|
109
|
+
// Validate records (skip if kind not available, e.g. new manifest shape without conversion)
|
|
81
110
|
const validationErrors = [...loadErrors];
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
111
|
+
if (actorKind != null) {
|
|
112
|
+
const opts = { actors: actorsContext };
|
|
113
|
+
for (let i = 0; i < records.length; i++) {
|
|
114
|
+
const record = records[i];
|
|
115
|
+
const recordErrors = validateRecord(record, actorKind, `record[${i}]`, opts);
|
|
116
|
+
for (const err of recordErrors) {
|
|
117
|
+
const fieldPart = err.field ? ` (${err.field})` : "";
|
|
118
|
+
validationErrors.push(`${err.message}${fieldPart}`);
|
|
119
|
+
}
|
|
88
120
|
}
|
|
89
121
|
}
|
|
122
|
+
else if (records.length > 0) {
|
|
123
|
+
validationErrors.push("Actor kind not available for validation (manifest may use new format)");
|
|
124
|
+
}
|
|
90
125
|
const status = validationErrors.length === 0 ? "present" : "failed";
|
|
91
126
|
const sampleErrors = validationErrors.slice(0, 10); // Keep first 10
|
|
92
127
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/attestation/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/attestation/generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,YAAY,EAA6C,MAAM,uBAAuB,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAE3E;;;;GAIG;AACH,SAAS,YAAY,CAAC,QAAkB;IACtC,IAAI,QAAQ,CAAC,IAAI,IAAI,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC/D,OAAO,QAAQ,CAAC,IAAsB,CAAC;IACzC,CAAC;IACD,MAAM,GAAG,GAAG,QAGX,CAAC;IACF,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO,IAAI,GAAG,CAAC,MAAM,IAAI,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QACzE,MAAM,MAAM,GAA4B,EAAE,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACvD,IAAI,KAAK,EAAE,IAAI,IAAI,IAAI;gBAAE,SAAS;YAClC,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAa,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;QACzF,CAAC;QACD,OAAO,EAAE,MAAM,EAAoB,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAqBD,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,MAAkB,EAAE,WAAoB;IACzE,MAAM,eAAe,GAAG,WAAW;QACjC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,WAAW,CAAC;QACtD,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;IAElB,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACjC,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,6BAA6B,WAAW,EAAE,CAAC,CAAC;YAC1D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;QACpC,OAAO;IACT,CAAC;IAED,KAAK,MAAM,WAAW,IAAI,eAAe,EAAE,CAAC;QAC1C,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,WAAwB;IACjD,gBAAgB;IAChB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IAE9D,qBAAqB;IACrB,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,UAAU,IAAI,IAAI,CAAC;IAErD,yBAAyB;IACzB,MAAM,eAAe,GAAoB;QACvC,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,QAAQ,CAAC,SAAS;QACzB,UAAU;QACV,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,oCAAoC;IACpC,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACpE,IAAI,CAAC,QAAQ,CAAC,YAAY;YAAE,SAAS;QAErC,MAAM,YAAY,GAAkB,EAAE,CAAC;QAEvC,KAAK,MAAM,WAAW,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;YAChD,oCAAoC;YACpC,IAAI,WAAW,CAAC,YAAY,KAAK,SAAS;gBAAE,SAAS;YAErD,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;YACzC,MAAM,WAAW,GAAG,MAAM,aAAa,CACrC,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,EACT,QAAQ,CACT,CAAC;YACF,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC5B,eAAe,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,YAAY,CAAC;QACnD,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,MAAM,oBAAoB,CAAC,WAAW,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IAE1E,gBAAgB;IAChB,uBAAuB,CAAC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAC/D,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,WAAwB,EACxB,WAAkC,EAClC,UAAkB,EAClB,SAAqC,EACrC,QAA8C;IAE9C,MAAM,aAAa,GAAmC,EAAE,CAAC;IACzD,KAAK,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,MAAM,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI;YAAE,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzC,CAAC;IACD,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC;IACrC,MAAM,YAAY,GAAG,WAAW,CAAC,QAAQ,CAAC;IAE1C,yBAAyB;IACzB,MAAM,aAAa,GAAG,WAAW,CAAC,QAAQ,EAAE,CAAC,WAAW,CAAC,CAAC;IAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,OAAO;YACL,QAAQ,EAAE,YAAY;YACtB,YAAY,EAAE,SAAS;YACvB,IAAI,EAAE,WAAW;YACjB,MAAM,EAAE,QAAQ;YAChB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;YAC3C,MAAM,EAAE,EAAE;YACV,YAAY,EAAE,CAAC,WAAW,WAAW,sBAAsB,CAAC;SAC7D,CAAC;IACJ,CAAC;IAED,0BAA0B;IAC1B,MAAM,cAAc,GAAG,WAAW,CAAC,SAAS,EAAE,CAAC,YAAY,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,cAAc,EAAE,OAAO,IAAI,GAAG,CAAC;IAE/C,qBAAqB;IACrB,MAAM,aAAa,GAAG,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAEvE,aAAa;IACb,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,gBAAgB,CAC5D,aAAa,EACb,aAAa,CAAC,MAAM,EACpB,aAAa,CAAC,IAAI,CACnB,CAAC;IAEF,4FAA4F;IAC5F,MAAM,gBAAgB,GAAa,CAAC,GAAG,UAAU,CAAC,CAAC;IACnD,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;QACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;YAC1B,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC7E,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAC/B,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,gBAAgB,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,SAAS,EAAE,CAAC,CAAC;YACtD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,gBAAgB,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC;IACjG,CAAC;IAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,YAAY,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,gBAAgB;IAEpE,OAAO;QACL,QAAQ,EAAE,YAAY;QACtB,YAAY,EAAE,SAAS;QACvB,IAAI,EAAE,WAAW;QACjB,MAAM;QACN,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACpC,MAAM,EAAE;YACN,KAAK,EAAE,aAAa,CAAC,MAAM;YAC3B,OAAO,EAAE,OAAO,CAAC,MAAM;YACvB,MAAM,EAAE,gBAAgB,CAAC,MAAM;SAChC;QACD,MAAM,EAAE,aAAa;QACrB,YAAY;KACb,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,oBAAoB,CACjC,IAAY,EACZ,IAAqB;IAErB,0BAA0B;IAC1B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEhD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,uBAAuB,CAAC,SAAiB,EAAE,eAAgC;IAClF,OAAO,CAAC,GAAG,CAAC,gBAAgB,SAAS,GAAG,CAAC,CAAC;IAE1C,KAAK,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/E,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;YAC/B,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7B,OAAO,CAAC,GAAG,CACT,QAAQ,SAAS,KAAK,GAAG,CAAC,IAAI,aAAa,GAAG,CAAC,MAAM,CAAC,OAAO,aAAa,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS,CACpG,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,QAAQ,SAAS,KAAK,GAAG,CAAC,IAAI,YAAY,GAAG,CAAC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAM,CAAC,OAAO,WAAW,CACrG,CAAC;gBACF,IAAI,GAAG,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAChC,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;wBAC/C,OAAO,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC;oBAC/B,CAAC;oBACD,IAAI,GAAG,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAChC,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,cAAc,CAAC,CAAC;oBACzE,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate a record against an actor shape (Kind)
|
|
2
|
+
* Validate a record against an actor shape (Kind).
|
|
3
|
+
* When options.actors is provided, actor-typed fields are validated recursively
|
|
4
|
+
* against the referenced actor's shape.
|
|
3
5
|
*/
|
|
6
|
+
/** Field in an object shape; may be optional. */
|
|
7
|
+
type FieldOrOptional = KindRef | {
|
|
8
|
+
kind: KindRef;
|
|
9
|
+
optional: true;
|
|
10
|
+
};
|
|
11
|
+
/** Primitive constraints from manifest (min, max, enum). */
|
|
12
|
+
interface TypeConstraints {
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
of?: (string | number)[];
|
|
16
|
+
}
|
|
4
17
|
type Kind = {
|
|
5
18
|
primitive: "string" | "integer" | "boolean" | "float" | "date" | "route";
|
|
19
|
+
constraints?: TypeConstraints;
|
|
6
20
|
} | {
|
|
7
|
-
object: Record<string,
|
|
21
|
+
object: Record<string, FieldOrOptional>;
|
|
8
22
|
} | {
|
|
9
23
|
array: KindRef;
|
|
10
24
|
};
|
|
11
25
|
type KindRef = Kind | {
|
|
12
26
|
actor: string;
|
|
13
27
|
};
|
|
28
|
+
/** Exported for use by callers (e.g. getActorKind return type). */
|
|
29
|
+
export type ValidationKind = Kind;
|
|
14
30
|
export interface ValidationError {
|
|
15
31
|
field?: string;
|
|
16
32
|
message: string;
|
|
17
33
|
}
|
|
18
|
-
|
|
34
|
+
/** Optional context for recursive validation of actor-typed fields. */
|
|
35
|
+
export interface ValidateRecordOptions {
|
|
36
|
+
/** Map actor name -> Kind (from getActorKind) for nested shape validation. */
|
|
37
|
+
actors?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
export declare function validateRecord(record: unknown, kind: ValidationKind, fieldPath?: string, options?: ValidateRecordOptions): ValidationError[];
|
|
19
40
|
export {};
|
|
20
41
|
//# sourceMappingURL=validate-record.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-record.d.ts","sourceRoot":"","sources":["../../src/attestation/validate-record.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validate-record.d.ts","sourceRoot":"","sources":["../../src/attestation/validate-record.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,iDAAiD;AACjD,KAAK,eAAe,GAAG,OAAO,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAEnE,4DAA4D;AAC5D,UAAU,eAAe;IACvB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;CAC1B;AAED,KAAK,IAAI,GACL;IAAE,SAAS,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,eAAe,CAAA;CAAE,GAC3G;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;CAAE,GAC3C;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEvB,KAAK,OAAO,GAAG,IAAI,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG,IAAI,CAAC;AAUlC,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,cAAc,EACpB,SAAS,SAAK,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,eAAe,EAAE,CAyJnB"}
|
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate a record against an actor shape (Kind)
|
|
2
|
+
* Validate a record against an actor shape (Kind).
|
|
3
|
+
* When options.actors is provided, actor-typed fields are validated recursively
|
|
4
|
+
* against the referenced actor's shape.
|
|
3
5
|
*/
|
|
4
|
-
|
|
6
|
+
function isOptionalField(f) {
|
|
7
|
+
return typeof f === "object" && f !== null && "optional" in f && f.optional === true;
|
|
8
|
+
}
|
|
9
|
+
function resolveFieldKind(f) {
|
|
10
|
+
return isOptionalField(f) ? f.kind : f;
|
|
11
|
+
}
|
|
12
|
+
export function validateRecord(record, kind, fieldPath = "", options) {
|
|
5
13
|
const errors = [];
|
|
14
|
+
if (kind == null || typeof kind !== "object") {
|
|
15
|
+
errors.push({ field: fieldPath, message: "Missing or invalid kind for validation" });
|
|
16
|
+
return errors;
|
|
17
|
+
}
|
|
6
18
|
// Primitive validation
|
|
7
19
|
if ("primitive" in kind) {
|
|
8
20
|
const expectedType = kind.primitive;
|
|
@@ -35,6 +47,25 @@ export function validateRecord(record, kind, fieldPath = "") {
|
|
|
35
47
|
}
|
|
36
48
|
break;
|
|
37
49
|
}
|
|
50
|
+
// Enforce primitive constraints if present
|
|
51
|
+
const constraints = kind.constraints;
|
|
52
|
+
if (constraints && actualType !== "undefined" && record !== null) {
|
|
53
|
+
if (typeof constraints.min === "number" && typeof record === "number" && record < constraints.min) {
|
|
54
|
+
errors.push({ field: fieldPath, message: `Value ${record} is below min (${constraints.min})` });
|
|
55
|
+
}
|
|
56
|
+
if (typeof constraints.max === "number" && typeof record === "number" && record > constraints.max) {
|
|
57
|
+
errors.push({ field: fieldPath, message: `Value ${record} is above max (${constraints.max})` });
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(constraints.of) && constraints.of.length > 0) {
|
|
60
|
+
const val = record;
|
|
61
|
+
if (!constraints.of.includes(val)) {
|
|
62
|
+
errors.push({
|
|
63
|
+
field: fieldPath,
|
|
64
|
+
message: `Value ${JSON.stringify(val)} is not in allowed set [${constraints.of.map((x) => JSON.stringify(x)).join(", ")}]`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
38
69
|
return errors;
|
|
39
70
|
}
|
|
40
71
|
// Array validation
|
|
@@ -48,7 +79,7 @@ export function validateRecord(record, kind, fieldPath = "") {
|
|
|
48
79
|
return errors;
|
|
49
80
|
}
|
|
50
81
|
for (let i = 0; i < record.length; i++) {
|
|
51
|
-
const itemErrors = validateRecord(record[i], kind.array, `${fieldPath}[${i}]
|
|
82
|
+
const itemErrors = validateRecord(record[i], kind.array, `${fieldPath}[${i}]`, options);
|
|
52
83
|
errors.push(...itemErrors);
|
|
53
84
|
}
|
|
54
85
|
return errors;
|
|
@@ -61,20 +92,39 @@ export function validateRecord(record, kind, fieldPath = "") {
|
|
|
61
92
|
}
|
|
62
93
|
const recordObj = record;
|
|
63
94
|
const shapeFields = kind.object;
|
|
64
|
-
// Check required fields
|
|
65
|
-
for (const [fieldName,
|
|
95
|
+
// Check required fields (optional fields may be missing)
|
|
96
|
+
for (const [fieldName, fieldOrOpt] of Object.entries(shapeFields)) {
|
|
66
97
|
const fieldValue = recordObj[fieldName];
|
|
67
98
|
const fullPath = fieldPath ? `${fieldPath}.${fieldName}` : fieldName;
|
|
99
|
+
const fieldKind = resolveFieldKind(fieldOrOpt);
|
|
100
|
+
const isOptional = isOptionalField(fieldOrOpt);
|
|
68
101
|
if (fieldValue === undefined || fieldValue === null) {
|
|
69
|
-
|
|
102
|
+
if (!isOptional) {
|
|
103
|
+
errors.push({ field: fullPath, message: "Required field missing" });
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
70
106
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if ("
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
if ("actor" in fieldKind) {
|
|
108
|
+
const actorName = fieldKind.actor;
|
|
109
|
+
if (typeof fieldValue !== "object" || fieldValue === null || Array.isArray(fieldValue)) {
|
|
110
|
+
errors.push({
|
|
111
|
+
field: fullPath,
|
|
112
|
+
message: `Expected object (actor ${actorName}), got ${typeof fieldValue}`,
|
|
113
|
+
});
|
|
76
114
|
}
|
|
77
|
-
|
|
115
|
+
else {
|
|
116
|
+
const nestedKind = options?.actors?.[actorName];
|
|
117
|
+
if (nestedKind != null &&
|
|
118
|
+
typeof nestedKind === "object" &&
|
|
119
|
+
"object" in nestedKind &&
|
|
120
|
+
nestedKind.object != null) {
|
|
121
|
+
const nestedErrors = validateRecord(fieldValue, nestedKind, fullPath, options);
|
|
122
|
+
errors.push(...nestedErrors);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
const fieldErrors = validateRecord(fieldValue, fieldKind, fullPath, options);
|
|
78
128
|
errors.push(...fieldErrors);
|
|
79
129
|
}
|
|
80
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-record.js","sourceRoot":"","sources":["../../src/attestation/validate-record.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"validate-record.js","sourceRoot":"","sources":["../../src/attestation/validate-record.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/check/render.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,qBAAqB,CAAC;AAWnE,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/check/render.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,qBAAqB,CAAC;AAWnE,wBAAsB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzF"}
|
package/dist/check/render.js
CHANGED
|
@@ -18,6 +18,13 @@ export async function handleCheck(config, sceneFilter) {
|
|
|
18
18
|
for (const sceneConfig of scenesToProcess) {
|
|
19
19
|
await checkScene(sceneConfig);
|
|
20
20
|
}
|
|
21
|
+
if (scenesToProcess.length > 0) {
|
|
22
|
+
console.log("\n Legend:");
|
|
23
|
+
console.log(" ⚪ expected - Projection declared but not yet attested");
|
|
24
|
+
console.log(" 🟢 attested - Projection exists and is attested");
|
|
25
|
+
console.log(" 🟠 drifted - Shape has changed (not yet implemented)");
|
|
26
|
+
console.log(" 🔴 failed - Attestation exists but status is not 'present'");
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
29
|
async function checkScene(sceneConfig) {
|
|
23
30
|
// Load manifest
|
|
@@ -64,16 +71,10 @@ function renderStatusReport(sceneName, status) {
|
|
|
64
71
|
console.log(` ${actorName}:`);
|
|
65
72
|
for (const exp of expectations) {
|
|
66
73
|
const icon = getStatusIcon(exp.status);
|
|
67
|
-
const statusText = exp.status.
|
|
74
|
+
const statusText = exp.status.padEnd(10);
|
|
68
75
|
console.log(` ${icon} ${statusText} → ${exp.location} / ${exp.artifactType} / ${exp.name}`);
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
|
-
// Print legend
|
|
72
|
-
console.log("\n Legend:");
|
|
73
|
-
console.log(" ⚪ EXPECTED - Projection declared but not yet attested");
|
|
74
|
-
console.log(" 🟢 ATTESTED - Projection exists and is attested");
|
|
75
|
-
console.log(" 🟠 DRIFTED - Shape has changed (not yet implemented)");
|
|
76
|
-
console.log(" 🔴 FAILED - Attestation exists but status is not 'present'");
|
|
77
78
|
}
|
|
78
79
|
function getStatusIcon(status) {
|
|
79
80
|
switch (status) {
|
package/dist/check/render.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/check/render.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/check/render.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import type { EdgeConfig, SceneConfig } from "../config/schema.js";
|
|
4
|
-
import { loadManifest, type ProjectionExpectation } from "../manifest/reader.js";
|
|
4
|
+
import { loadManifest, type ActorDef, type ProjectionExpectation } from "../manifest/reader.js";
|
|
5
5
|
import { loadDatasetFiles } from "./file-loader.js";
|
|
6
|
-
import { validateRecord } from "./validate-record.js";
|
|
6
|
+
import { validateRecord, type ValidationKind } from "./validate-record.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get the Kind used for record validation. Supports both legacy manifest (actorDef.kind)
|
|
10
|
+
* and new manifest (actorDef.type === "shape" with actorDef.fields).
|
|
11
|
+
* Passes through optional flags and field types (including primitive constraints).
|
|
12
|
+
*/
|
|
13
|
+
function getActorKind(actorDef: ActorDef): ValidationKind | undefined {
|
|
14
|
+
if (actorDef.kind != null && typeof actorDef.kind === "object") {
|
|
15
|
+
return actorDef.kind as ValidationKind;
|
|
16
|
+
}
|
|
17
|
+
const def = actorDef as {
|
|
18
|
+
type?: string;
|
|
19
|
+
fields?: Record<string, { type?: unknown; optional?: boolean }>;
|
|
20
|
+
};
|
|
21
|
+
if (def.type === "shape" && def.fields && typeof def.fields === "object") {
|
|
22
|
+
const object: Record<string, unknown> = {};
|
|
23
|
+
for (const [name, field] of Object.entries(def.fields)) {
|
|
24
|
+
if (field?.type == null) continue;
|
|
25
|
+
object[name] =
|
|
26
|
+
field.optional === true ? { kind: field.type, optional: true as const } : field.type;
|
|
27
|
+
}
|
|
28
|
+
return { object } as ValidationKind;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
7
32
|
|
|
8
33
|
interface Attestation {
|
|
9
34
|
location: string;
|
|
@@ -69,11 +94,13 @@ async function attestScene(sceneConfig: SceneConfig): Promise<void> {
|
|
|
69
94
|
// Only process dataset expectations
|
|
70
95
|
if (expectation.artifactType !== "dataset") continue;
|
|
71
96
|
|
|
97
|
+
const actorKind = getActorKind(actorDef);
|
|
72
98
|
const attestation = await attestDataset(
|
|
73
99
|
sceneConfig,
|
|
74
100
|
expectation,
|
|
75
101
|
actorName,
|
|
76
|
-
|
|
102
|
+
actorKind,
|
|
103
|
+
manifest,
|
|
77
104
|
);
|
|
78
105
|
attestations.push(attestation);
|
|
79
106
|
}
|
|
@@ -93,9 +120,15 @@ async function attestScene(sceneConfig: SceneConfig): Promise<void> {
|
|
|
93
120
|
async function attestDataset(
|
|
94
121
|
sceneConfig: SceneConfig,
|
|
95
122
|
expectation: ProjectionExpectation,
|
|
96
|
-
|
|
97
|
-
actorKind:
|
|
123
|
+
_actorName: string,
|
|
124
|
+
actorKind: ValidationKind | undefined,
|
|
125
|
+
manifest: { actors: Record<string, ActorDef> },
|
|
98
126
|
): Promise<Attestation> {
|
|
127
|
+
const actorsContext: Record<string, ValidationKind> = {};
|
|
128
|
+
for (const [name, def] of Object.entries(manifest.actors)) {
|
|
129
|
+
const k = getActorKind(def);
|
|
130
|
+
if (k != null) actorsContext[name] = k;
|
|
131
|
+
}
|
|
99
132
|
const datasetName = expectation.name;
|
|
100
133
|
const locationName = expectation.location;
|
|
101
134
|
|
|
@@ -128,15 +161,20 @@ async function attestDataset(
|
|
|
128
161
|
datasetConfig.mode,
|
|
129
162
|
);
|
|
130
163
|
|
|
131
|
-
// Validate records
|
|
164
|
+
// Validate records (skip if kind not available, e.g. new manifest shape without conversion)
|
|
132
165
|
const validationErrors: string[] = [...loadErrors];
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
166
|
+
if (actorKind != null) {
|
|
167
|
+
const opts = { actors: actorsContext };
|
|
168
|
+
for (let i = 0; i < records.length; i++) {
|
|
169
|
+
const record = records[i];
|
|
170
|
+
const recordErrors = validateRecord(record, actorKind, `record[${i}]`, opts);
|
|
171
|
+
for (const err of recordErrors) {
|
|
172
|
+
const fieldPart = err.field ? ` (${err.field})` : "";
|
|
173
|
+
validationErrors.push(`${err.message}${fieldPart}`);
|
|
174
|
+
}
|
|
139
175
|
}
|
|
176
|
+
} else if (records.length > 0) {
|
|
177
|
+
validationErrors.push("Actor kind not available for validation (manifest may use new format)");
|
|
140
178
|
}
|
|
141
179
|
|
|
142
180
|
const status = validationErrors.length === 0 ? "present" : "failed";
|
|
@@ -1,26 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Validate a record against an actor shape (Kind)
|
|
2
|
+
* Validate a record against an actor shape (Kind).
|
|
3
|
+
* When options.actors is provided, actor-typed fields are validated recursively
|
|
4
|
+
* against the referenced actor's shape.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
/** Field in an object shape; may be optional. */
|
|
8
|
+
type FieldOrOptional = KindRef | { kind: KindRef; optional: true };
|
|
9
|
+
|
|
10
|
+
/** Primitive constraints from manifest (min, max, enum). */
|
|
11
|
+
interface TypeConstraints {
|
|
12
|
+
min?: number;
|
|
13
|
+
max?: number;
|
|
14
|
+
of?: (string | number)[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type Kind =
|
|
18
|
+
| { primitive: "string" | "integer" | "boolean" | "float" | "date" | "route"; constraints?: TypeConstraints }
|
|
19
|
+
| { object: Record<string, FieldOrOptional> }
|
|
8
20
|
| { array: KindRef };
|
|
9
21
|
|
|
10
22
|
type KindRef = Kind | { actor: string };
|
|
11
23
|
|
|
24
|
+
/** Exported for use by callers (e.g. getActorKind return type). */
|
|
25
|
+
export type ValidationKind = Kind;
|
|
26
|
+
|
|
27
|
+
function isOptionalField(f: FieldOrOptional): f is { kind: KindRef; optional: true } {
|
|
28
|
+
return typeof f === "object" && f !== null && "optional" in f && (f as { optional?: boolean }).optional === true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveFieldKind(f: FieldOrOptional): KindRef {
|
|
32
|
+
return isOptionalField(f) ? f.kind : f;
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
export interface ValidationError {
|
|
13
36
|
field?: string;
|
|
14
37
|
message: string;
|
|
15
38
|
}
|
|
16
39
|
|
|
40
|
+
/** Optional context for recursive validation of actor-typed fields. */
|
|
41
|
+
export interface ValidateRecordOptions {
|
|
42
|
+
/** Map actor name -> Kind (from getActorKind) for nested shape validation. */
|
|
43
|
+
actors?: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
17
46
|
export function validateRecord(
|
|
18
47
|
record: unknown,
|
|
19
|
-
kind:
|
|
48
|
+
kind: ValidationKind,
|
|
20
49
|
fieldPath = "",
|
|
50
|
+
options?: ValidateRecordOptions,
|
|
21
51
|
): ValidationError[] {
|
|
22
52
|
const errors: ValidationError[] = [];
|
|
23
53
|
|
|
54
|
+
if (kind == null || typeof kind !== "object") {
|
|
55
|
+
errors.push({ field: fieldPath, message: "Missing or invalid kind for validation" });
|
|
56
|
+
return errors;
|
|
57
|
+
}
|
|
58
|
+
|
|
24
59
|
// Primitive validation
|
|
25
60
|
if ("primitive" in kind) {
|
|
26
61
|
const expectedType = kind.primitive;
|
|
@@ -54,6 +89,25 @@ export function validateRecord(
|
|
|
54
89
|
}
|
|
55
90
|
break;
|
|
56
91
|
}
|
|
92
|
+
// Enforce primitive constraints if present
|
|
93
|
+
const constraints = kind.constraints;
|
|
94
|
+
if (constraints && actualType !== "undefined" && record !== null) {
|
|
95
|
+
if (typeof constraints.min === "number" && typeof record === "number" && record < constraints.min) {
|
|
96
|
+
errors.push({ field: fieldPath, message: `Value ${record} is below min (${constraints.min})` });
|
|
97
|
+
}
|
|
98
|
+
if (typeof constraints.max === "number" && typeof record === "number" && record > constraints.max) {
|
|
99
|
+
errors.push({ field: fieldPath, message: `Value ${record} is above max (${constraints.max})` });
|
|
100
|
+
}
|
|
101
|
+
if (Array.isArray(constraints.of) && constraints.of.length > 0) {
|
|
102
|
+
const val = record as string | number;
|
|
103
|
+
if (!constraints.of.includes(val)) {
|
|
104
|
+
errors.push({
|
|
105
|
+
field: fieldPath,
|
|
106
|
+
message: `Value ${JSON.stringify(val)} is not in allowed set [${constraints.of.map((x) => JSON.stringify(x)).join(", ")}]`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
57
111
|
return errors;
|
|
58
112
|
}
|
|
59
113
|
|
|
@@ -70,7 +124,12 @@ export function validateRecord(
|
|
|
70
124
|
}
|
|
71
125
|
|
|
72
126
|
for (let i = 0; i < record.length; i++) {
|
|
73
|
-
const itemErrors = validateRecord(
|
|
127
|
+
const itemErrors = validateRecord(
|
|
128
|
+
record[i],
|
|
129
|
+
kind.array as Kind,
|
|
130
|
+
`${fieldPath}[${i}]`,
|
|
131
|
+
options,
|
|
132
|
+
);
|
|
74
133
|
errors.push(...itemErrors);
|
|
75
134
|
}
|
|
76
135
|
return errors;
|
|
@@ -86,20 +145,45 @@ export function validateRecord(
|
|
|
86
145
|
const recordObj = record as Record<string, unknown>;
|
|
87
146
|
const shapeFields = kind.object;
|
|
88
147
|
|
|
89
|
-
// Check required fields
|
|
90
|
-
for (const [fieldName,
|
|
148
|
+
// Check required fields (optional fields may be missing)
|
|
149
|
+
for (const [fieldName, fieldOrOpt] of Object.entries(shapeFields)) {
|
|
91
150
|
const fieldValue = recordObj[fieldName];
|
|
92
151
|
const fullPath = fieldPath ? `${fieldPath}.${fieldName}` : fieldName;
|
|
152
|
+
const fieldKind = resolveFieldKind(fieldOrOpt);
|
|
153
|
+
const isOptional = isOptionalField(fieldOrOpt);
|
|
93
154
|
|
|
94
155
|
if (fieldValue === undefined || fieldValue === null) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
// Recurse for nested validation (skip actor references)
|
|
98
|
-
if ("actor" in fieldKind) {
|
|
99
|
-
// Skip actor reference validation for now (would need manifest context)
|
|
100
|
-
continue;
|
|
156
|
+
if (!isOptional) {
|
|
157
|
+
errors.push({ field: fullPath, message: "Required field missing" });
|
|
101
158
|
}
|
|
102
|
-
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if ("actor" in fieldKind) {
|
|
162
|
+
const actorName = (fieldKind as { actor: string }).actor;
|
|
163
|
+
if (typeof fieldValue !== "object" || fieldValue === null || Array.isArray(fieldValue)) {
|
|
164
|
+
errors.push({
|
|
165
|
+
field: fullPath,
|
|
166
|
+
message: `Expected object (actor ${actorName}), got ${typeof fieldValue}`,
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
const nestedKind = options?.actors?.[actorName];
|
|
170
|
+
if (
|
|
171
|
+
nestedKind != null &&
|
|
172
|
+
typeof nestedKind === "object" &&
|
|
173
|
+
"object" in nestedKind &&
|
|
174
|
+
(nestedKind as { object?: unknown }).object != null
|
|
175
|
+
) {
|
|
176
|
+
const nestedErrors = validateRecord(
|
|
177
|
+
fieldValue,
|
|
178
|
+
nestedKind as Kind,
|
|
179
|
+
fullPath,
|
|
180
|
+
options,
|
|
181
|
+
);
|
|
182
|
+
errors.push(...nestedErrors);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
const fieldErrors = validateRecord(fieldValue, fieldKind as Kind, fullPath, options);
|
|
103
187
|
errors.push(...fieldErrors);
|
|
104
188
|
}
|
|
105
189
|
}
|
package/src/check/render.ts
CHANGED
|
@@ -30,6 +30,14 @@ export async function handleCheck(config: EdgeConfig, sceneFilter?: string): Pro
|
|
|
30
30
|
for (const sceneConfig of scenesToProcess) {
|
|
31
31
|
await checkScene(sceneConfig);
|
|
32
32
|
}
|
|
33
|
+
|
|
34
|
+
if (scenesToProcess.length > 0) {
|
|
35
|
+
console.log("\n Legend:");
|
|
36
|
+
console.log(" ⚪ expected - Projection declared but not yet attested");
|
|
37
|
+
console.log(" 🟢 attested - Projection exists and is attested");
|
|
38
|
+
console.log(" 🟠 drifted - Shape has changed (not yet implemented)");
|
|
39
|
+
console.log(" 🔴 failed - Attestation exists but status is not 'present'");
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
async function checkScene(sceneConfig: SceneConfig): Promise<void> {
|
|
@@ -88,19 +96,12 @@ function renderStatusReport(
|
|
|
88
96
|
console.log(` ${actorName}:`);
|
|
89
97
|
for (const exp of expectations) {
|
|
90
98
|
const icon = getStatusIcon(exp.status);
|
|
91
|
-
const statusText = exp.status.
|
|
99
|
+
const statusText = exp.status.padEnd(10);
|
|
92
100
|
console.log(
|
|
93
101
|
` ${icon} ${statusText} → ${exp.location} / ${exp.artifactType} / ${exp.name}`,
|
|
94
102
|
);
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
|
|
98
|
-
// Print legend
|
|
99
|
-
console.log("\n Legend:");
|
|
100
|
-
console.log(" ⚪ EXPECTED - Projection declared but not yet attested");
|
|
101
|
-
console.log(" 🟢 ATTESTED - Projection exists and is attested");
|
|
102
|
-
console.log(" 🟠 DRIFTED - Shape has changed (not yet implemented)");
|
|
103
|
-
console.log(" 🔴 FAILED - Attestation exists but status is not 'present'");
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
function getStatusIcon(status: string): string {
|