proof-harness 0.1.0-next.1

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 (53) hide show
  1. package/COMPATIBILITY.md +26 -0
  2. package/LICENSE +201 -0
  3. package/PROOF_SDK_CONTRACT.md +1255 -0
  4. package/README.md +60 -0
  5. package/cli/config.mjs +116 -0
  6. package/cli/engines/generate_proof_action_registry.mjs +271 -0
  7. package/cli/engines/parse_proof_schema.mjs +457 -0
  8. package/cli/engines/proof_coverage.mjs +871 -0
  9. package/cli/engines/proof_drift.mjs +980 -0
  10. package/cli/engines/proof_module_check.mjs +498 -0
  11. package/cli/engines/proof_mutation_check.mjs +1084 -0
  12. package/cli/engines/proof_mutation_inventory.mjs +508 -0
  13. package/cli/engines/proof_source_mutation.mjs +35 -0
  14. package/cli/engines/proof_verify.mjs +620 -0
  15. package/cli/engines/scan_module_meta.mjs +352 -0
  16. package/cli/engines/scan_proof_capabilities.mjs +359 -0
  17. package/cli/proof-harness.mjs +107 -0
  18. package/dist/action-types-C3OmHRyH.d.ts +26 -0
  19. package/dist/fixture-B-dmnG5G.d.ts +105 -0
  20. package/dist/mission-types-Dr5rBHYO.d.ts +299 -0
  21. package/dist/node.d.ts +79 -0
  22. package/dist/node.js +553 -0
  23. package/dist/node.js.map +1 -0
  24. package/dist/playwright.d.ts +433 -0
  25. package/dist/playwright.js +1824 -0
  26. package/dist/playwright.js.map +1 -0
  27. package/dist/portable-vocabulary.d.ts +86 -0
  28. package/dist/portable-vocabulary.js +43 -0
  29. package/dist/portable-vocabulary.js.map +1 -0
  30. package/dist/server.d.ts +55 -0
  31. package/dist/server.js +376 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/shared.d.ts +169 -0
  34. package/dist/shared.js +121 -0
  35. package/dist/shared.js.map +1 -0
  36. package/dist/trace-types-DkIzq-l9.d.ts +333 -0
  37. package/package.json +109 -0
  38. package/proof-consumer-fixtures/README.md +60 -0
  39. package/proof-consumer-fixtures/aggregates/M-CONFORMANCE-FAIL.json +112 -0
  40. package/proof-consumer-fixtures/aggregates/M-CONFORMANCE-PASS.json +101 -0
  41. package/proof-consumer-fixtures/baseline/failing/widgets-tenant-isolation.json +46 -0
  42. package/proof-consumer-fixtures/baseline/passing/widgets-tenant-isolation.json +45 -0
  43. package/proof-consumer-fixtures/corpus.json +102 -0
  44. package/proof-consumer-fixtures/duplicates/first/widgets-tenant-isolation.json +12 -0
  45. package/proof-consumer-fixtures/duplicates/second/widgets-tenant-isolation.json +12 -0
  46. package/proof-consumer-fixtures/inputs/capabilities.json +4 -0
  47. package/proof-consumer-fixtures/inputs/mission.json +20 -0
  48. package/proof-consumer-fixtures/inputs/schema.json +4 -0
  49. package/proof-consumer-fixtures/malformed/invalid-trace.json.invalid +4 -0
  50. package/proof-consumer-fixtures/malformed/wrong-manifest-version.json +11 -0
  51. package/proof-consumer-fixtures/malformed/wrong-trace-shape.json +8 -0
  52. package/proof-consumer-fixtures/mutations/MUT-WIDGETS-LEAK/mutation.json +17 -0
  53. package/proof-consumer-fixtures/mutations/MUT-WIDGETS-LEAK/widgets-tenant-isolation.json +50 -0
package/dist/node.js ADDED
@@ -0,0 +1,553 @@
1
+ // src/shared/portable-vocabulary.mjs
2
+ var ACTION_CHANGE_KINDS = (
3
+ /** @type {const} */
4
+ [
5
+ "added",
6
+ "removed",
7
+ "middleware_changed",
8
+ "invariants_changed",
9
+ "service_role_mutations_changed",
10
+ "metadata_changed"
11
+ ]
12
+ );
13
+ var TABLE_CHANGE_KINDS = (
14
+ /** @type {const} */
15
+ [
16
+ "added",
17
+ "removed",
18
+ "columns_added",
19
+ "columns_removed",
20
+ "rls_classification_changed",
21
+ "policies_changed"
22
+ ]
23
+ );
24
+ var TENANT_ISOLATION_TABLE_HELPERS = (
25
+ /** @type {const} */
26
+ [
27
+ "assert.tenantIsolation"
28
+ ]
29
+ );
30
+ var TENANT_ISOLATION_ACTION_HELPERS = (
31
+ /** @type {const} */
32
+ [
33
+ "assert.tenantIsolation",
34
+ "assert.authorization"
35
+ ]
36
+ );
37
+
38
+ // src/shared/mission-types.ts
39
+ var MISSION_MANIFEST_SCHEMA_VERSION = 1;
40
+ function requiredEvidenceOrigin(kind, evidence) {
41
+ if (kind === "tenant_isolation") return "helper";
42
+ return evidence ?? "any";
43
+ }
44
+
45
+ // src/shared/vocabulary.ts
46
+ var PROOF_VERBS = [
47
+ "create",
48
+ "read",
49
+ "update",
50
+ "delete",
51
+ "invoke",
52
+ "transfer"
53
+ ];
54
+ var PROOF_KINDS = [
55
+ "happy_path",
56
+ "tenant_isolation",
57
+ "authorization",
58
+ "validation",
59
+ "error_handling",
60
+ "idempotency"
61
+ ];
62
+ var RLS_CLASSIFICATIONS = [
63
+ "workspace_scoped",
64
+ "user_scoped",
65
+ "admin_only",
66
+ "public_read",
67
+ "service_only",
68
+ "unclassified"
69
+ ];
70
+ var POLICY_COMMANDS = [
71
+ "SELECT",
72
+ "INSERT",
73
+ "UPDATE",
74
+ "DELETE",
75
+ "ALL"
76
+ ];
77
+ function isProofVerb(value) {
78
+ return typeof value === "string" && PROOF_VERBS.includes(value);
79
+ }
80
+ function isProofKind(value) {
81
+ return typeof value === "string" && PROOF_KINDS.includes(value);
82
+ }
83
+ function isRlsClassification(value) {
84
+ return typeof value === "string" && RLS_CLASSIFICATIONS.includes(value);
85
+ }
86
+ function isPolicyCommand(value) {
87
+ return typeof value === "string" && POLICY_COMMANDS.includes(value);
88
+ }
89
+
90
+ // src/shared/trace-types.ts
91
+ var ASSERTION_ROLE_ALIASES = {
92
+ negative: "primary",
93
+ positive_control: "control"
94
+ };
95
+ function normalizeAssertionRole(role) {
96
+ if (role === void 0 || role === null) return "primary";
97
+ if (role === "primary" || role === "control") return role;
98
+ if (typeof role === "string" && role in ASSERTION_ROLE_ALIASES) {
99
+ return ASSERTION_ROLE_ALIASES[role];
100
+ }
101
+ return null;
102
+ }
103
+
104
+ // src/node/validate-mission.ts
105
+ function isNonEmptyString(value) {
106
+ return typeof value === "string" && value.length > 0;
107
+ }
108
+ function expectedChangesShapeIssues(block) {
109
+ const issues = [];
110
+ if (block === void 0) return issues;
111
+ if (!block || typeof block !== "object" || Array.isArray(block)) {
112
+ issues.push({
113
+ category: "manifest_shape",
114
+ message: "expectedChanges must be an object when present"
115
+ });
116
+ return issues;
117
+ }
118
+ const changes = block;
119
+ for (const key of ["modules", "dependencies"]) {
120
+ const list = changes[key];
121
+ if (list === void 0) continue;
122
+ if (!Array.isArray(list) || !list.every(isNonEmptyString)) {
123
+ issues.push({
124
+ category: "manifest_shape",
125
+ message: `expectedChanges.${key} must be an array of non-empty strings when present`
126
+ });
127
+ }
128
+ }
129
+ const validateEntryList = (key, nameField, kinds) => {
130
+ const list = changes[key];
131
+ if (list === void 0) return;
132
+ if (!Array.isArray(list)) {
133
+ issues.push({
134
+ category: "manifest_shape",
135
+ message: `expectedChanges.${key} must be an array when present`
136
+ });
137
+ return;
138
+ }
139
+ for (const entry of list) {
140
+ if (isNonEmptyString(entry)) continue;
141
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
142
+ issues.push({
143
+ category: "manifest_shape",
144
+ message: `expectedChanges.${key} entries must be non-empty strings or { ${nameField}, changes? } objects`
145
+ });
146
+ continue;
147
+ }
148
+ const record = entry;
149
+ if (!isNonEmptyString(record[nameField])) {
150
+ issues.push({
151
+ category: "manifest_shape",
152
+ message: `expectedChanges.${key}[].${nameField} is required (non-empty string)`
153
+ });
154
+ }
155
+ if (record.changes !== void 0) {
156
+ if (!Array.isArray(record.changes) || record.changes.length === 0 || !record.changes.every((c) => kinds.includes(c))) {
157
+ issues.push({
158
+ category: "manifest_shape",
159
+ message: `expectedChanges.${key}[].changes must be a non-empty array drawn from: ${kinds.join(", ")}`
160
+ });
161
+ }
162
+ }
163
+ }
164
+ };
165
+ validateEntryList("actions", "ref", ACTION_CHANGE_KINDS);
166
+ validateEntryList("tables", "name", TABLE_CHANGE_KINDS);
167
+ if (changes.lockfile !== void 0 && typeof changes.lockfile !== "boolean") {
168
+ issues.push({
169
+ category: "manifest_shape",
170
+ message: "expectedChanges.lockfile must be a boolean when present"
171
+ });
172
+ }
173
+ return issues;
174
+ }
175
+ function shapeIssues(manifest) {
176
+ const issues = [];
177
+ if (!manifest || typeof manifest !== "object") {
178
+ issues.push({
179
+ category: "manifest_shape",
180
+ message: "manifest is not a JSON object"
181
+ });
182
+ return issues;
183
+ }
184
+ const m = manifest;
185
+ if (m.schemaVersion !== MISSION_MANIFEST_SCHEMA_VERSION) {
186
+ issues.push({
187
+ category: "manifest_shape",
188
+ message: `expected schemaVersion=${MISSION_MANIFEST_SCHEMA_VERSION}, found ${String(m.schemaVersion)}`
189
+ });
190
+ }
191
+ if (typeof m.missionId !== "string" || m.missionId.length === 0) {
192
+ issues.push({
193
+ category: "manifest_shape",
194
+ message: "missionId is required (non-empty string)"
195
+ });
196
+ }
197
+ if (typeof m.missionTitle !== "string" || m.missionTitle.length === 0) {
198
+ issues.push({
199
+ category: "manifest_shape",
200
+ message: "missionTitle is required (non-empty string)"
201
+ });
202
+ }
203
+ if (typeof m.createdAt !== "string" || m.createdAt.length === 0) {
204
+ issues.push({
205
+ category: "manifest_shape",
206
+ message: "createdAt is required (ISO-8601 string)"
207
+ });
208
+ }
209
+ issues.push(...expectedChangesShapeIssues(m.expectedChanges));
210
+ const req = m.requirements;
211
+ if (!req || typeof req !== "object") {
212
+ issues.push({
213
+ category: "manifest_shape",
214
+ message: "requirements block is required"
215
+ });
216
+ return issues;
217
+ }
218
+ if (!Array.isArray(req.capabilities_must_exist)) {
219
+ issues.push({
220
+ category: "manifest_shape",
221
+ message: "requirements.capabilities_must_exist must be an array"
222
+ });
223
+ }
224
+ if (!Array.isArray(req.schema_must_contain)) {
225
+ issues.push({
226
+ category: "manifest_shape",
227
+ message: "requirements.schema_must_contain must be an array"
228
+ });
229
+ }
230
+ if (!Array.isArray(req.trace_must_prove)) {
231
+ issues.push({
232
+ category: "manifest_shape",
233
+ message: "requirements.trace_must_prove must be an array"
234
+ });
235
+ }
236
+ if (req.policies_must_not_allow !== void 0 && !Array.isArray(req.policies_must_not_allow)) {
237
+ issues.push({
238
+ category: "manifest_shape",
239
+ message: "requirements.policies_must_not_allow must be an array when present"
240
+ });
241
+ }
242
+ for (const cap of req.capabilities_must_exist ?? []) {
243
+ if (!isProofVerb(cap.verb)) {
244
+ issues.push({
245
+ category: "manifest_shape",
246
+ message: `capabilities_must_exist[].verb "${String(cap.verb)}" is not in PROOF_VERBS`
247
+ });
248
+ }
249
+ }
250
+ for (const s of req.schema_must_contain ?? []) {
251
+ if (!isRlsClassification(
252
+ s.rls_classification
253
+ )) {
254
+ issues.push({
255
+ category: "manifest_shape",
256
+ message: `schema_must_contain[].rls_classification "${String(s.rls_classification)}" is not in RLS_CLASSIFICATIONS`
257
+ });
258
+ }
259
+ }
260
+ for (const t of req.trace_must_prove ?? []) {
261
+ if (!isProofKind(t.kind)) {
262
+ issues.push({
263
+ category: "manifest_shape",
264
+ message: `trace_must_prove[].kind "${String(t.kind)}" is not in PROOF_KINDS`
265
+ });
266
+ }
267
+ const evidence = t.evidence;
268
+ if (evidence !== void 0 && evidence !== "helper" && evidence !== "any") {
269
+ issues.push({
270
+ category: "manifest_shape",
271
+ message: `trace_must_prove[].evidence "${String(evidence)}" is not a valid evidence origin; expected "helper" or "any"`
272
+ });
273
+ }
274
+ if (evidence === "any" && t.kind === "tenant_isolation") {
275
+ issues.push({
276
+ category: "manifest_shape",
277
+ message: 'trace_must_prove[].evidence "any" is not allowed for kind "tenant_isolation"; isolation evidence must come from an SDK helper (assert.tenantIsolation, or assert.authorization for action targets)'
278
+ });
279
+ }
280
+ }
281
+ for (const p of Array.isArray(req.policies_must_not_allow) ? req.policies_must_not_allow : []) {
282
+ const prohibition = p;
283
+ if (typeof prohibition.role !== "string" || prohibition.role.length === 0) {
284
+ issues.push({
285
+ category: "manifest_shape",
286
+ message: "policies_must_not_allow[].role is required (non-empty database role name)"
287
+ });
288
+ }
289
+ if (prohibition.commands !== void 0) {
290
+ if (!Array.isArray(prohibition.commands)) {
291
+ issues.push({
292
+ category: "manifest_shape",
293
+ message: "policies_must_not_allow[].commands must be an array when present"
294
+ });
295
+ } else {
296
+ for (const command of prohibition.commands) {
297
+ if (!isPolicyCommand(command)) {
298
+ issues.push({
299
+ category: "manifest_shape",
300
+ message: `policies_must_not_allow[].commands entry "${String(command)}" is not in POLICY_COMMANDS`
301
+ });
302
+ }
303
+ }
304
+ }
305
+ }
306
+ }
307
+ return issues;
308
+ }
309
+ function checkCapabilities(manifest, capabilities) {
310
+ const issues = [];
311
+ for (const req of manifest.requirements.capabilities_must_exist) {
312
+ const match = capabilities.capabilities.find(
313
+ (c) => c.name === req.name && c.module === req.module
314
+ );
315
+ if (!match) {
316
+ issues.push({
317
+ category: "capability_missing",
318
+ message: `expected capability ${req.module}:${req.name} (verb=${req.verb}, object=${req.object}); not found in capabilities.json`,
319
+ context: { requirement: req }
320
+ });
321
+ continue;
322
+ }
323
+ if (match.verb !== req.verb) {
324
+ issues.push({
325
+ category: "capability_mismatch",
326
+ message: `capability ${req.module}:${req.name} has verb="${match.verb ?? "<missing>"}" but manifest requires "${req.verb}"; add or update withProof({ verb }) on the action`,
327
+ context: { requirement: req, found: match }
328
+ });
329
+ }
330
+ if (req.object && match.object !== req.object) {
331
+ issues.push({
332
+ category: "capability_mismatch",
333
+ message: `capability ${req.module}:${req.name} has object="${match.object ?? "<missing>"}" but manifest requires "${req.object}"; add or update withProof({ object }) on the action`,
334
+ context: { requirement: req, found: match }
335
+ });
336
+ }
337
+ if (req.invariants && req.invariants.length > 0) {
338
+ const missing = req.invariants.filter(
339
+ (inv) => !match.invariants.includes(inv)
340
+ );
341
+ if (missing.length > 0) {
342
+ issues.push({
343
+ category: "capability_mismatch",
344
+ message: `capability ${req.module}:${req.name} is missing invariants: ${missing.join(", ")}; add them to withProof({ invariants })`,
345
+ context: { requirement: req, found: match, missing }
346
+ });
347
+ }
348
+ }
349
+ }
350
+ return issues;
351
+ }
352
+ function checkSchema(manifest, schema) {
353
+ const issues = [];
354
+ for (const req of manifest.requirements.schema_must_contain) {
355
+ const table = schema.tables.find((t) => t.name === req.table);
356
+ if (!table) {
357
+ issues.push({
358
+ category: "schema_missing",
359
+ message: `expected table "${req.table}" not found in schema.json; add a CREATE TABLE migration`,
360
+ context: { requirement: req }
361
+ });
362
+ continue;
363
+ }
364
+ const missingCols = req.required_columns.filter(
365
+ (c) => !table.columns.includes(c)
366
+ );
367
+ if (missingCols.length > 0) {
368
+ issues.push({
369
+ category: "schema_column_missing",
370
+ message: `table "${req.table}" is missing columns: ${missingCols.join(", ")}; found columns: ${table.columns.join(", ")}`,
371
+ context: {
372
+ requirement: req,
373
+ missing: missingCols,
374
+ found: table.columns
375
+ }
376
+ });
377
+ }
378
+ if (table.rls_classification !== req.rls_classification) {
379
+ issues.push({
380
+ category: "schema_rls_mismatch",
381
+ message: `table "${req.table}" has rls_classification="${table.rls_classification}" but manifest requires "${req.rls_classification}"; review RLS policies on the table`,
382
+ context: { requirement: req, found: table.rls_classification }
383
+ });
384
+ }
385
+ }
386
+ return issues;
387
+ }
388
+ function policyGrantsRole(policy, role) {
389
+ if (policy.roles.length === 0) return true;
390
+ return policy.roles.some(
391
+ (r) => r === role || r === "public" || r === "PUBLIC"
392
+ );
393
+ }
394
+ function policyMatchesCommands(policy, commands) {
395
+ if (!commands || commands.length === 0) return true;
396
+ if (policy.command === "ALL") return true;
397
+ return commands.includes(policy.command);
398
+ }
399
+ function checkPolicies(manifest, schema) {
400
+ const issues = [];
401
+ for (const req of manifest.requirements.policies_must_not_allow ?? []) {
402
+ const table = schema.tables.find((t) => t.name === req.table);
403
+ if (!table) {
404
+ issues.push({
405
+ category: "schema_missing",
406
+ message: `expected table "${req.table}" not found in schema.json; add a CREATE TABLE migration`,
407
+ context: { requirement: req }
408
+ });
409
+ continue;
410
+ }
411
+ const offending = table.policies.filter(
412
+ (p) => policyGrantsRole(p, req.role) && policyMatchesCommands(p, req.commands)
413
+ );
414
+ if (offending.length > 0) {
415
+ const scope = req.commands?.length ? req.commands.join(", ") : "any command";
416
+ issues.push({
417
+ category: "schema_policy_permits",
418
+ message: `table "${req.table}" must not allow role "${req.role}" (${scope}), but ${offending.length} policy/policies do: ` + offending.map(
419
+ (p) => `"${p.name}" FOR ${p.command} TO ${p.roles.length > 0 ? p.roles.join("/") : "PUBLIC (no TO clause)"}`
420
+ ).join(", ") + `; requirement: "${req.description}"`,
421
+ context: { requirement: req, offending }
422
+ });
423
+ }
424
+ }
425
+ return issues;
426
+ }
427
+ function collectAssertions(bundle) {
428
+ const out = [];
429
+ for (const trace of bundle) {
430
+ for (const step of trace.steps ?? []) {
431
+ for (const a of step.assertions ?? []) {
432
+ out.push({
433
+ ...a,
434
+ proofId: trace.proofId,
435
+ ...trace.specFile ? { specFile: trace.specFile } : {}
436
+ });
437
+ }
438
+ }
439
+ }
440
+ return out;
441
+ }
442
+ function allowedHelpersFor(kind, target, tableNames) {
443
+ if (kind !== "tenant_isolation") return null;
444
+ const head = String(target).split(".")[0];
445
+ return tableNames.has(head) ? TENANT_ISOLATION_TABLE_HELPERS : TENANT_ISOLATION_ACTION_HELPERS;
446
+ }
447
+ function checkTraces(manifest, bundle, schema) {
448
+ const issues = [];
449
+ const evidence = [];
450
+ const assertions = collectAssertions(bundle);
451
+ const tableNames = new Set((schema.tables ?? []).map((t) => t.name));
452
+ for (const req of manifest.requirements.trace_must_prove) {
453
+ const requiredRole = normalizeAssertionRole(req.role);
454
+ if (requiredRole === null) {
455
+ issues.push({
456
+ category: "manifest_shape",
457
+ message: `trace_must_prove[].role "${String(req.role)}" is not a valid assertion role; expected "primary" or "control"`,
458
+ context: { requirement: req }
459
+ });
460
+ continue;
461
+ }
462
+ const candidates = assertions.filter(
463
+ (a) => a.kind === req.kind && a.target === req.target && (req.proofId === void 0 || a.proofId === req.proofId)
464
+ );
465
+ const requiredOrigin = requiredEvidenceOrigin(req.kind, req.evidence);
466
+ const allowedHelpers = allowedHelpersFor(req.kind, req.target, tableNames);
467
+ const originSatisfied = (a) => requiredOrigin === "any" || typeof a.emittedBy === "string" && (allowedHelpers === null || allowedHelpers.includes(a.emittedBy));
468
+ const match = candidates.find(
469
+ (a) => a.passed === true && normalizeAssertionRole(a.role) === requiredRole && originSatisfied(a)
470
+ );
471
+ if (match) {
472
+ evidence.push({
473
+ kind: req.kind,
474
+ target: req.target,
475
+ role: requiredRole,
476
+ satisfied: true,
477
+ proofId: match.proofId,
478
+ ...match.specFile ? { specFile: match.specFile } : {},
479
+ ...match.detail ? { detail: match.detail } : {},
480
+ ...match.emittedBy ? { emittedBy: match.emittedBy } : {}
481
+ });
482
+ continue;
483
+ }
484
+ evidence.push({
485
+ kind: req.kind,
486
+ target: req.target,
487
+ role: requiredRole,
488
+ satisfied: false
489
+ });
490
+ const failed = candidates.filter((a) => a.passed === false);
491
+ const wrongRole = candidates.filter(
492
+ (a) => a.passed === true && normalizeAssertionRole(a.role) !== requiredRole
493
+ );
494
+ const unverified = candidates.filter(
495
+ (a) => a.passed === true && normalizeAssertionRole(a.role) === requiredRole && !originSatisfied(a)
496
+ );
497
+ if (unverified.length > 0) {
498
+ const found = unverified[0].emittedBy ? `was emitted by ${unverified[0].emittedBy}, which cannot vouch for this claim` : "was recorded directly by spec code (no emittedBy)";
499
+ const wanted = allowedHelpers ? allowedHelpers.join(" or ") : "an SDK assert.* helper";
500
+ issues.push({
501
+ category: "trace_unverified",
502
+ message: `a passing role="${requiredRole}" assertion with kind="${req.kind}" and target="${req.target}" exists but ${found}, and this requirement demands evidence="helper"${req.evidence === void 0 ? ` (mandatory for ${req.kind})` : ""}. Emit the claim through ${wanted}${allowedHelpers === null ? `, or set evidence: "any" on the requirement if a bespoke probe is intended` : ""}; description: "${req.description}"`,
503
+ context: { requirement: req, unverifiedCount: unverified.length }
504
+ });
505
+ continue;
506
+ }
507
+ let reason;
508
+ if (failed.length > 0) {
509
+ reason = `found ${failed.length} matching assertion(s) but they FAILED: ` + failed.map((a) => `[${a.proofId}] ${a.detail ?? "(no detail)"}`).join(" | ");
510
+ } else if (wrongRole.length > 0) {
511
+ reason = `found ${wrongRole.length} passing assertion(s) with role="${normalizeAssertionRole(wrongRole[0].role)}" but this requirement needs role="${requiredRole}". A "control" assertion passes by design and only shows the probe could have failed \u2014 it cannot stand in for the claim itself. Add the missing ${requiredRole} probe`;
512
+ } else if (req.proofId !== void 0) {
513
+ reason = `no assertion with this kind+target was emitted by proof "${req.proofId}" (other proofs may have emitted one; this requirement is pinned)`;
514
+ } else {
515
+ reason = "no assertion with this kind+target was emitted by any proof";
516
+ }
517
+ issues.push({
518
+ category: "trace_missing",
519
+ message: `no passing role="${requiredRole}" assertion found with kind="${req.kind}" and target="${req.target}"; ${reason}; description: "${req.description}"`,
520
+ context: { requirement: req, candidateCount: candidates.length }
521
+ });
522
+ }
523
+ return { issues, evidence };
524
+ }
525
+ function validateMission(input) {
526
+ const shape = shapeIssues(input.manifest);
527
+ if (shape.length > 0) {
528
+ return {
529
+ missionId: input.manifest?.missionId ?? "<invalid>",
530
+ ok: false,
531
+ issues: shape
532
+ };
533
+ }
534
+ const manifest = input.manifest;
535
+ const traceResult = checkTraces(manifest, input.traces, input.schema);
536
+ const issues = [
537
+ ...checkCapabilities(manifest, input.capabilities),
538
+ ...checkSchema(manifest, input.schema),
539
+ ...checkPolicies(manifest, input.schema),
540
+ ...traceResult.issues
541
+ ];
542
+ return {
543
+ missionId: manifest.missionId,
544
+ ok: issues.length === 0,
545
+ issues,
546
+ evidence: traceResult.evidence
547
+ };
548
+ }
549
+ export {
550
+ collectAssertions,
551
+ validateMission
552
+ };
553
+ //# sourceMappingURL=node.js.map