assertledger 1.3.1 → 1.5.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 (107) hide show
  1. package/README.fr.md +6 -3
  2. package/README.md +6 -3
  3. package/conformance/schema-extensions.json +15 -0
  4. package/dist/build-info.json +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +218 -8
  7. package/dist/cli.js.map +1 -1
  8. package/dist/contracts/index.d.ts +467 -4
  9. package/dist/contracts/index.d.ts.map +1 -1
  10. package/dist/contracts/index.js +256 -3
  11. package/dist/contracts/index.js.map +1 -1
  12. package/dist/contracts/qualification.d.ts +528 -0
  13. package/dist/contracts/qualification.d.ts.map +1 -0
  14. package/dist/contracts/qualification.js +357 -0
  15. package/dist/contracts/qualification.js.map +1 -0
  16. package/dist/core/index.d.ts +9 -0
  17. package/dist/core/index.d.ts.map +1 -1
  18. package/dist/core/index.js +46 -2
  19. package/dist/core/index.js.map +1 -1
  20. package/dist/core/qualification.d.ts +17 -0
  21. package/dist/core/qualification.d.ts.map +1 -0
  22. package/dist/core/qualification.js +571 -0
  23. package/dist/core/qualification.js.map +1 -0
  24. package/dist/diagnostics.d.ts.map +1 -1
  25. package/dist/diagnostics.js +81 -4
  26. package/dist/diagnostics.js.map +1 -1
  27. package/dist/engine/adapters/bun-native.d.ts +35 -0
  28. package/dist/engine/adapters/bun-native.d.ts.map +1 -0
  29. package/dist/engine/adapters/bun-native.js +179 -0
  30. package/dist/engine/adapters/bun-native.js.map +1 -0
  31. package/dist/engine/adapters/bun-test-designated-profile.d.ts +23 -0
  32. package/dist/engine/adapters/bun-test-designated-profile.d.ts.map +1 -0
  33. package/dist/engine/adapters/bun-test-designated-profile.js +24 -0
  34. package/dist/engine/adapters/bun-test-designated-profile.js.map +1 -0
  35. package/dist/engine/container.d.ts +7 -0
  36. package/dist/engine/container.d.ts.map +1 -1
  37. package/dist/engine/container.js +22 -7
  38. package/dist/engine/container.js.map +1 -1
  39. package/dist/engine/git-regression.d.ts.map +1 -1
  40. package/dist/engine/git-regression.js +9 -2
  41. package/dist/engine/git-regression.js.map +1 -1
  42. package/dist/engine/index.d.ts +14 -0
  43. package/dist/engine/index.d.ts.map +1 -1
  44. package/dist/engine/index.js +828 -97
  45. package/dist/engine/index.js.map +1 -1
  46. package/dist/engine/qualification.d.ts +154 -0
  47. package/dist/engine/qualification.d.ts.map +1 -0
  48. package/dist/engine/qualification.js +725 -0
  49. package/dist/engine/qualification.js.map +1 -0
  50. package/dist/engine/test-closure.d.ts +39 -0
  51. package/dist/engine/test-closure.d.ts.map +1 -0
  52. package/dist/engine/test-closure.js +513 -0
  53. package/dist/engine/test-closure.js.map +1 -0
  54. package/dist/engine/witness-import.d.ts +31 -0
  55. package/dist/engine/witness-import.d.ts.map +1 -0
  56. package/dist/engine/witness-import.js +224 -0
  57. package/dist/engine/witness-import.js.map +1 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +3 -0
  61. package/dist/index.js.map +1 -1
  62. package/dist/mcp/index.d.ts.map +1 -1
  63. package/dist/mcp/index.js +65 -0
  64. package/dist/mcp/index.js.map +1 -1
  65. package/dist/sdk/index.d.ts +292 -3
  66. package/dist/sdk/index.d.ts.map +1 -1
  67. package/dist/sdk/index.js +39 -1
  68. package/dist/sdk/index.js.map +1 -1
  69. package/docs/container-isolation.md +6 -0
  70. package/docs/migration-orchestration-qualification-v1.md +109 -0
  71. package/docs/migration-verification-v4.md +94 -0
  72. package/docs/orchestration-qualification.md +167 -0
  73. package/docs/reference.md +13 -0
  74. package/docs/witness-import.md +67 -0
  75. package/examples/orchestration/README.md +68 -0
  76. package/examples/orchestration/consumer-obligations.json +101 -0
  77. package/examples/orchestration/workspace/bitbucket-pipelines.yml +10 -0
  78. package/examples/orchestration/workspace/command-shape.test.mjs +25 -0
  79. package/examples/orchestration/workspace/commands.mjs +14 -0
  80. package/examples/orchestration/workspace/completion.mjs +39 -0
  81. package/examples/orchestration/workspace/faults.json +1 -0
  82. package/examples/orchestration/workspace/native.test.ts +4 -0
  83. package/examples/orchestration/workspace/package.json +6 -0
  84. package/examples/orchestration/workspace/packages/app/package.json +12 -0
  85. package/examples/orchestration/workspace/packages/app/verdict.txt +1 -0
  86. package/examples/orchestration/workspace/packages/leaf/package.json +9 -0
  87. package/examples/orchestration/workspace/packages/leaf/verdict.txt +1 -0
  88. package/examples/orchestration/workspace/pnpm-lock.yaml +170 -0
  89. package/examples/orchestration/workspace/pnpm-workspace.yaml +2 -0
  90. package/examples/orchestration/workspace/task.mjs +23 -0
  91. package/examples/orchestration/workspace/turbo.json +38 -0
  92. package/examples/orchestration/workspace/wrapper.mjs +45 -0
  93. package/integrations/bun/driver.d.mts +30 -1
  94. package/integrations/bun/driver.mjs +215 -6
  95. package/integrations/bun/preload.mjs +186 -5
  96. package/integrations/bun-native/README.md +86 -0
  97. package/integrations/bun-native/driver.mjs +396 -0
  98. package/integrations/bun-native/preload.mjs +375 -0
  99. package/integrations/skill/SKILL.md +25 -5
  100. package/package.json +1 -1
  101. package/schemas/evidence-manifest.v4.json +995 -0
  102. package/schemas/qualification-execution-request.v1.json +571 -0
  103. package/schemas/qualification-plan.v1.json +528 -0
  104. package/schemas/qualification-receipt.v1.json +821 -0
  105. package/schemas/qualification-replay-result.v1.json +37 -0
  106. package/schemas/verification-request.v4.json +590 -0
  107. package/schemas/witness-import-request.v1.json +231 -0
@@ -83,6 +83,14 @@ export const TrustedLocalIsolationSchema = z.strictObject({
83
83
  });
84
84
  export const VERIFICATION_SCHEMA_VERSION_V2 = "2.0.0";
85
85
  export const VERIFICATION_SCHEMA_VERSION_V3 = "3.0.0";
86
+ export const VERIFICATION_SCHEMA_VERSION_V4 = "4.0.0";
87
+ // Windows-native execution is a distinct, operator-declared level: it is never inferred from a
88
+ // trusted-local request and requires its own authorization on a Windows host.
89
+ export const WindowsNativeIsolationSchema = z.strictObject({
90
+ kind: z.literal("windows-native"),
91
+ acknowledgedUnsafeExecution: z.boolean(),
92
+ environmentAllowlist: z.array(EnvironmentVariableSchema).max(100),
93
+ });
86
94
  // Only digest-pinned references are accepted; the leading alphanumeric also keeps a reference
87
95
  // from being parsed as a container runtime option.
88
96
  export const ContainerImageReferenceSchema = z
@@ -171,6 +179,94 @@ export const VerificationRequestV3Schema = z
171
179
  title: "AssertLedger verification request v3",
172
180
  description: "A versioned campaign with an official Bun test adapter and explicit execution backend.",
173
181
  });
182
+ /**
183
+ * "excluded" keeps the snapshot without Git metadata. "synthesized" commits the snapshot once into
184
+ * a fresh repository, never the source's own .git, for tests that ask Git for the tracked files.
185
+ */
186
+ export const RepositoryGitModeSchema = z.enum(["excluded", "synthesized"]);
187
+ /** One existing repository test, named by its file and its describe path ending with the test. */
188
+ export const DesignatedTestSchema = z.strictObject({
189
+ file: z.string().min(1).max(512),
190
+ path: z.array(z.string().min(1).max(1_024)).min(1).max(32),
191
+ });
192
+ export const BunDesignatedTestAdapterSchema = z.strictObject({
193
+ kind: z.literal("bun-test-designated"),
194
+ executable: z.string().min(1),
195
+ /** Bun's per-test timeout (`bun test --timeout`); null keeps Bun's default. */
196
+ testTimeoutMs: z.number().int().min(1).max(3_600_000).nullable(),
197
+ });
198
+ export const AdapterV4Schema = z.discriminatedUnion("kind", [
199
+ NodeTestAdapterSchema,
200
+ StructuredCommandAdapterSchema,
201
+ BunTestAdapterSchema,
202
+ BunDesignatedTestAdapterSchema,
203
+ ]);
204
+ export const DesignatedCandidateSchema = z.strictObject({
205
+ id: IdentifierSchema,
206
+ test: DesignatedTestSchema,
207
+ expectedFailure: z.string().min(1).max(1_024).nullable(),
208
+ });
209
+ export const CandidateV4Schema = z.union([CandidateSchema, DesignatedCandidateSchema]);
210
+ export const VerificationRequestV4Schema = z
211
+ .strictObject({
212
+ ...VerificationRequestV3Schema.shape,
213
+ schemaVersion: z.literal(VERIFICATION_SCHEMA_VERSION_V4),
214
+ repository: z.strictObject({
215
+ root: z.string().min(1),
216
+ exclude: z.array(z.string().min(1).max(512)).max(1_000),
217
+ includeDependencies: z.boolean(),
218
+ git: RepositoryGitModeSchema,
219
+ }),
220
+ adapter: AdapterV4Schema,
221
+ isolation: z.discriminatedUnion("kind", [
222
+ TrustedLocalIsolationSchema,
223
+ ContainerIsolationSchema,
224
+ WindowsNativeIsolationSchema,
225
+ ]),
226
+ candidates: z.array(CandidateV4Schema).min(1).max(1_000),
227
+ })
228
+ .meta({
229
+ id: "https://testforge.dev/schemas/verification-request.v4.json",
230
+ title: "AssertLedger verification request v4",
231
+ description: "A versioned campaign that can designate existing Bun tests, snapshot dependencies, and declare Windows-native execution.",
232
+ });
233
+ // A recorded red/green witness: the mutated targets, the one test expected to detect them and the
234
+ // first line of its expected failure. The isolation backend is an operator flag, never request data.
235
+ export const WitnessImportRequestSchema = z
236
+ .strictObject({
237
+ schemaVersion: z.literal("1.0.0"),
238
+ provenance: z.string().min(1).max(512),
239
+ repository: z.strictObject({
240
+ root: z.string().min(1),
241
+ exclude: z.array(z.string().min(1).max(512)).max(1_000),
242
+ includeDependencies: z.boolean(),
243
+ git: RepositoryGitModeSchema,
244
+ }),
245
+ adapter: BunDesignatedTestAdapterSchema,
246
+ test: DesignatedTestSchema,
247
+ expectedFailure: z.string().min(1).max(1_024),
248
+ targets: z
249
+ .array(z.strictObject({
250
+ path: z.string().min(1).max(512),
251
+ beforeDigest: Sha256DigestSchema,
252
+ content: z.string().max(1_048_576),
253
+ }))
254
+ .min(1)
255
+ .max(100),
256
+ neutral: z
257
+ .strictObject({
258
+ reason: z.string().min(1).max(1_024),
259
+ files: z.array(FileOverlaySchema).min(1).max(100),
260
+ })
261
+ .nullable(),
262
+ requiredAttempts: z.int().min(2).max(10),
263
+ timeoutMsPerExecution: z.int().min(1_000).max(3_600_000),
264
+ })
265
+ .meta({
266
+ id: "https://testforge.dev/schemas/witness-import-request.v1.json",
267
+ title: "AssertLedger witness import request",
268
+ description: "A recorded red/green witness to replay: mutated targets, one designated test and its expected failure line.",
269
+ });
174
270
  export const RepositoryAnalysisSchema = z
175
271
  .strictObject({
176
272
  root: z.string().min(1),
@@ -575,6 +671,64 @@ export const EvidenceManifestV3Schema = z
575
671
  title: "AssertLedger evidence manifest v3",
576
672
  description: "A replayable campaign artifact recording the official Bun test adapter and execution backend.",
577
673
  });
674
+ const FinalAdapterSummaryV4Schema = z.discriminatedUnion("kind", [
675
+ z.strictObject({ kind: z.literal("node-test") }),
676
+ z.strictObject({ kind: z.literal("bun-test") }),
677
+ z.strictObject({ kind: z.literal("bun-test-designated") }),
678
+ z.strictObject({
679
+ kind: z.literal("testforge-command"),
680
+ protocolVersion: z.literal("1.0.0"),
681
+ }),
682
+ ]);
683
+ const ExecutionBackendRecordV4Schema = z.discriminatedUnion("kind", [
684
+ ...ExecutionBackendRecordSchema.options,
685
+ z.strictObject({
686
+ kind: z.literal("windows-native"),
687
+ level: z.literal("WINDOWS_NATIVE_UNSANDBOXED"),
688
+ platform: z.literal("win32"),
689
+ }),
690
+ ]);
691
+ // The snapshot record qualifies the repository digest: which dependency directories it covers and
692
+ // which repository links outside every executed test's closure were omitted from the snapshot.
693
+ const RepositorySnapshotRecordSchema = z.strictObject({
694
+ includeDependencies: z.boolean(),
695
+ omittedLinks: z.array(z.string().min(1).max(1_024)).max(10_000),
696
+ git: z
697
+ .strictObject({
698
+ mode: z.literal("synthesized"),
699
+ treeId: z.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/),
700
+ gitVersion: z.string().min(1).max(256),
701
+ })
702
+ .nullable(),
703
+ });
704
+ const EvidenceContextV4Schema = z.strictObject({
705
+ ...EvidenceContextSchema.shape,
706
+ execution: z.strictObject({
707
+ ...EvidenceContextSchema.shape.execution.shape,
708
+ backend: ExecutionBackendRecordV4Schema,
709
+ }),
710
+ repository: RepositorySnapshotRecordSchema,
711
+ });
712
+ export const EvidenceManifestV4Schema = z
713
+ .strictObject({
714
+ ...EvidenceManifestV3Schema.shape,
715
+ schemaVersion: z.literal(VERIFICATION_SCHEMA_VERSION_V4),
716
+ evidenceContext: EvidenceContextV4Schema,
717
+ adapter: FinalAdapterSummaryV4Schema,
718
+ isolation: z.discriminatedUnion("kind", [
719
+ ...EvidenceManifestV2Schema.shape.isolation.options,
720
+ z.strictObject({
721
+ kind: z.literal("windows-native"),
722
+ level: z.literal("WINDOWS_NATIVE_UNSANDBOXED"),
723
+ acknowledgedUnsafeExecution: z.literal(true),
724
+ }),
725
+ ]),
726
+ })
727
+ .meta({
728
+ id: "https://testforge.dev/schemas/evidence-manifest.v4.json",
729
+ title: "AssertLedger evidence manifest v4",
730
+ description: "A replayable campaign artifact that records designated tests, the repository snapshot scope and the declared isolation level.",
731
+ });
578
732
  export const ReplayResultSchema = z
579
733
  .strictObject({
580
734
  valid: z.boolean(),
@@ -1836,7 +1990,8 @@ function assertBudgets(request) {
1836
1990
  }
1837
1991
  let totalCandidateBytes = 0;
1838
1992
  for (const candidate of request.candidates) {
1839
- const bytes = candidate.files.reduce((total, file) => total + Buffer.byteLength(file.content, "utf8"), 0);
1993
+ // A designated candidate overlays nothing; its test bytes belong to the repository snapshot.
1994
+ const bytes = (candidate.files ?? []).reduce((total, file) => total + Buffer.byteLength(file.content, "utf8"), 0);
1840
1995
  totalCandidateBytes += bytes;
1841
1996
  if (bytes > request.budgets.maximumCandidateBytes) {
1842
1997
  throw new ContractError("CANDIDATE_BYTES_EXCEEDED", `${candidate.id}: ${bytes} > ${request.budgets.maximumCandidateBytes}`);
@@ -1908,8 +2063,14 @@ export function parseVerificationRequest(value) {
1908
2063
  assertBudgets(request);
1909
2064
  return request;
1910
2065
  }
1911
- /** Parses a frozen v1/v2 request or a v3 request that adds the Bun adapter. */
2066
+ /** Parses a frozen v1/v2/v3 request or a v4 request that adds designated Bun tests. */
1912
2067
  export function parseVersionedVerificationRequest(value) {
2068
+ if (typeof value === "object" &&
2069
+ value !== null &&
2070
+ "schemaVersion" in value &&
2071
+ value.schemaVersion === VERIFICATION_SCHEMA_VERSION_V4) {
2072
+ return parseVerificationRequestV4(value);
2073
+ }
1913
2074
  if (typeof value === "object" &&
1914
2075
  value !== null &&
1915
2076
  "schemaVersion" in value &&
@@ -1952,6 +2113,70 @@ export function parseVerificationRequestV3(value) {
1952
2113
  }
1953
2114
  return request;
1954
2115
  }
2116
+ function isDesignatedCandidate(candidate) {
2117
+ return "test" in candidate;
2118
+ }
2119
+ /**
2120
+ * Parses a v4 request. Designated candidates belong to the designated Bun adapter only, and that
2121
+ * adapter accepts nothing else, so native expect attribution never reaches a v3-style campaign.
2122
+ */
2123
+ export function parseVerificationRequestV4(value) {
2124
+ if (typeof value !== "object" ||
2125
+ value === null ||
2126
+ !("schemaVersion" in value) ||
2127
+ value.schemaVersion !== VERIFICATION_SCHEMA_VERSION_V4) {
2128
+ throw new ContractError("SCHEMA_VERSION_UNSUPPORTED");
2129
+ }
2130
+ assertVerificationRequestPreconditions(value);
2131
+ const parsed = VerificationRequestV4Schema.safeParse(value);
2132
+ if (!parsed.success) {
2133
+ throw new ContractError("REQUEST_SCHEMA_INVALID", z.prettifyError(parsed.error));
2134
+ }
2135
+ const request = parsed.data;
2136
+ assertUniqueIdentifiers(request.worlds, "DUPLICATE_WORLD_ID");
2137
+ assertUniqueIdentifiers(request.candidates, "DUPLICATE_CANDIDATE_ID");
2138
+ assertWorldKinds(request);
2139
+ assertBudgets(request);
2140
+ const designated = request.candidates.filter(isDesignatedCandidate);
2141
+ if (request.adapter.kind === "bun-test-designated") {
2142
+ if (designated.length !== request.candidates.length) {
2143
+ throw new ContractError("DESIGNATED_CANDIDATE_REQUIRED");
2144
+ }
2145
+ assertDesignatedTestTimeout(request.adapter.testTimeoutMs, request.budgets.timeoutMsPerExecution);
2146
+ }
2147
+ else if (designated.length > 0) {
2148
+ throw new ContractError("DESIGNATED_CANDIDATE_ADAPTER_MISMATCH", request.adapter.kind);
2149
+ }
2150
+ if (request.isolation.kind === "container") {
2151
+ // Only the designated adapter qualifies Bun inside a container; candidate files stay local.
2152
+ if (request.adapter.kind === "bun-test") {
2153
+ throw new ContractError("BUN_TEST_CONTAINER_UNSUPPORTED");
2154
+ }
2155
+ const names = request.isolation.environment.map((variable) => variable.name);
2156
+ if (new Set(names).size !== names.length) {
2157
+ throw new ContractError("DUPLICATE_CONTAINER_ENVIRONMENT_VARIABLE");
2158
+ }
2159
+ }
2160
+ return request;
2161
+ }
2162
+ /** A per-test timeout must end before the execution timeout, or it could never be observed. */
2163
+ function assertDesignatedTestTimeout(testTimeoutMs, executionTimeoutMs) {
2164
+ if (testTimeoutMs !== null && testTimeoutMs >= executionTimeoutMs) {
2165
+ throw new ContractError("DESIGNATED_TEST_TIMEOUT_EXCEEDS_EXECUTION");
2166
+ }
2167
+ }
2168
+ /** Parses a witness import request; its targets and test are checked against the repository later. */
2169
+ export function parseWitnessImportRequest(value) {
2170
+ if (!isJsonObject(value) || value.schemaVersion !== "1.0.0") {
2171
+ throw new ContractError("SCHEMA_VERSION_UNSUPPORTED");
2172
+ }
2173
+ const parsed = WitnessImportRequestSchema.safeParse(value);
2174
+ if (!parsed.success) {
2175
+ throw new ContractError("WITNESS_IMPORT_REQUEST_INVALID", z.prettifyError(parsed.error));
2176
+ }
2177
+ assertDesignatedTestTimeout(parsed.data.adapter.testTimeoutMs, parsed.data.timeoutMsPerExecution);
2178
+ return parsed.data;
2179
+ }
1955
2180
  /** Parses only the v2 request, which adds container isolation to the frozen v1 request. */
1956
2181
  export function parseVerificationRequestV2(value) {
1957
2182
  if (typeof value !== "object" ||
@@ -2537,8 +2762,27 @@ export function parseEvidenceManifestV3(value) {
2537
2762
  }
2538
2763
  return manifest;
2539
2764
  }
2540
- /** Parses a frozen v1/v2 manifest or a v3 manifest with the Bun adapter. */
2765
+ export function parseEvidenceManifestV4(value) {
2766
+ const parsed = EvidenceManifestV4Schema.safeParse(value);
2767
+ if (!parsed.success) {
2768
+ throw new ContractError("EVIDENCE_MANIFEST_INVALID", z.prettifyError(parsed.error));
2769
+ }
2770
+ const manifest = parsed.data;
2771
+ const omittedLinks = manifest.evidenceContext.repository.omittedLinks;
2772
+ if (!manifestSummariesAreConsistent(manifest) ||
2773
+ !manifestReferencesAreConsistent(manifest) ||
2774
+ !executionBackendIsConsistent(manifest) ||
2775
+ new Set(omittedLinks).size !== omittedLinks.length ||
2776
+ omittedLinks.some((link, index) => index > 0 && (omittedLinks[index - 1] ?? "") >= link)) {
2777
+ throw new ContractError("EVIDENCE_MANIFEST_INCONSISTENT");
2778
+ }
2779
+ return manifest;
2780
+ }
2781
+ /** Parses a frozen v1/v2/v3 manifest or a v4 manifest with designated tests. */
2541
2782
  export function parseVersionedEvidenceManifest(value) {
2783
+ if (isJsonObject(value) && value.schemaVersion === VERIFICATION_SCHEMA_VERSION_V4) {
2784
+ return parseEvidenceManifestV4(value);
2785
+ }
2542
2786
  if (isJsonObject(value) && value.schemaVersion === VERIFICATION_SCHEMA_VERSION_V3) {
2543
2787
  return parseEvidenceManifestV3(value);
2544
2788
  }
@@ -3227,6 +3471,15 @@ export function verificationRequestV3JsonSchema() {
3227
3471
  export function evidenceManifestV3JsonSchema() {
3228
3472
  return jsonSchemaFor(EvidenceManifestV3Schema, "https://testforge.dev/schemas/evidence-manifest.v3.json");
3229
3473
  }
3474
+ export function verificationRequestV4JsonSchema() {
3475
+ return jsonSchemaFor(VerificationRequestV4Schema, "https://testforge.dev/schemas/verification-request.v4.json");
3476
+ }
3477
+ export function evidenceManifestV4JsonSchema() {
3478
+ return jsonSchemaFor(EvidenceManifestV4Schema, "https://testforge.dev/schemas/evidence-manifest.v4.json");
3479
+ }
3480
+ export function witnessImportRequestJsonSchema() {
3481
+ return jsonSchemaFor(WitnessImportRequestSchema, "https://testforge.dev/schemas/witness-import-request.v1.json");
3482
+ }
3230
3483
  export function replayResultJsonSchema() {
3231
3484
  return jsonSchemaFor(ReplayResultSchema, "https://testforge.dev/schemas/replay-result.v1.json");
3232
3485
  }