assertledger 1.0.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 (203) hide show
  1. package/CONTRIBUTING.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.fr.md +236 -0
  4. package/README.md +224 -0
  5. package/SECURITY.md +51 -0
  6. package/benchmarks/agentic-profile/README.md +15 -0
  7. package/benchmarks/agentic-profile/public/README.md +5 -0
  8. package/benchmarks/self-hosted-core/README.md +113 -0
  9. package/benchmarks/self-hosted-core/adapter.mjs +293 -0
  10. package/benchmarks/self-hosted-core/builder.ts +193 -0
  11. package/benchmarks/self-hosted-core/campaign.ts +233 -0
  12. package/benchmarks/self-hosted-core/existing-tests-builder.ts +217 -0
  13. package/benchmarks/self-hosted-core/existing-tests.ts +146 -0
  14. package/benchmarks/self-hosted-core/liveness.test.mjs +8 -0
  15. package/conformance/v1/bundle.json +104 -0
  16. package/conformance/v1/expected/canonical-order-a.json +4 -0
  17. package/conformance/v1/expected/canonical-order-b.json +4 -0
  18. package/conformance/v1/expected/create-benchmark-v1-measured.json +575 -0
  19. package/conformance/v1/expected/create-profile-v1-qualified.json +280 -0
  20. package/conformance/v1/expected/decide-collection-failure-non-kill.json +192 -0
  21. package/conformance/v1/expected/decide-compile-failure-non-kill.json +192 -0
  22. package/conformance/v1/expected/decide-infra-error-non-kill.json +192 -0
  23. package/conformance/v1/expected/decide-no-test-discovered-non-kill.json +192 -0
  24. package/conformance/v1/expected/decide-process-crash-non-kill.json +192 -0
  25. package/conformance/v1/expected/decide-timeout-non-kill.json +192 -0
  26. package/conformance/v1/expected/decide-verified.json +192 -0
  27. package/conformance/v1/expected/replay-benchmark-v1-resealed-summary-forgery.json +12 -0
  28. package/conformance/v1/expected/replay-evidence-raw-tamper.json +6 -0
  29. package/conformance/v1/expected/replay-evidence-resealed-semantic-forgery.json +6 -0
  30. package/conformance/v1/inputs/canonical-order-a.json +8 -0
  31. package/conformance/v1/inputs/canonical-order-b.json +8 -0
  32. package/conformance/v1/inputs/create-benchmark-v1-measured.json +459 -0
  33. package/conformance/v1/inputs/create-profile-v1-qualified.json +228 -0
  34. package/conformance/v1/inputs/decide-collection-failure-non-kill.json +143 -0
  35. package/conformance/v1/inputs/decide-compile-failure-non-kill.json +143 -0
  36. package/conformance/v1/inputs/decide-infra-error-non-kill.json +143 -0
  37. package/conformance/v1/inputs/decide-no-test-discovered-non-kill.json +143 -0
  38. package/conformance/v1/inputs/decide-process-crash-non-kill.json +143 -0
  39. package/conformance/v1/inputs/decide-timeout-non-kill.json +143 -0
  40. package/conformance/v1/inputs/decide-verified.json +143 -0
  41. package/conformance/v1/inputs/replay-benchmark-v1-resealed-summary-forgery.json +575 -0
  42. package/conformance/v1/inputs/replay-evidence-raw-tamper.json +201 -0
  43. package/conformance/v1/inputs/replay-evidence-resealed-semantic-forgery.json +192 -0
  44. package/conformance/v1/schemas/expected-digests.json +175 -0
  45. package/dist/cli.d.ts +9 -0
  46. package/dist/cli.d.ts.map +1 -0
  47. package/dist/cli.js +951 -0
  48. package/dist/cli.js.map +1 -0
  49. package/dist/contracts/diagnostics.d.ts +18 -0
  50. package/dist/contracts/diagnostics.d.ts.map +1 -0
  51. package/dist/contracts/diagnostics.js +13 -0
  52. package/dist/contracts/diagnostics.js.map +1 -0
  53. package/dist/contracts/index.d.ts +3908 -0
  54. package/dist/contracts/index.d.ts.map +1 -0
  55. package/dist/contracts/index.js +2569 -0
  56. package/dist/contracts/index.js.map +1 -0
  57. package/dist/contracts/runtime-doctor.d.ts +107 -0
  58. package/dist/contracts/runtime-doctor.d.ts.map +1 -0
  59. package/dist/contracts/runtime-doctor.js +91 -0
  60. package/dist/contracts/runtime-doctor.js.map +1 -0
  61. package/dist/core/index.d.ts +200 -0
  62. package/dist/core/index.d.ts.map +1 -0
  63. package/dist/core/index.js +2587 -0
  64. package/dist/core/index.js.map +1 -0
  65. package/dist/diagnostics.d.ts +7 -0
  66. package/dist/diagnostics.d.ts.map +1 -0
  67. package/dist/diagnostics.js +252 -0
  68. package/dist/diagnostics.js.map +1 -0
  69. package/dist/engine/adapters/node-test-profile.d.ts +14 -0
  70. package/dist/engine/adapters/node-test-profile.d.ts.map +1 -0
  71. package/dist/engine/adapters/node-test-profile.js +14 -0
  72. package/dist/engine/adapters/node-test-profile.js.map +1 -0
  73. package/dist/engine/adapters/node-test-runtime.d.ts +39 -0
  74. package/dist/engine/adapters/node-test-runtime.d.ts.map +1 -0
  75. package/dist/engine/adapters/node-test-runtime.js +173 -0
  76. package/dist/engine/adapters/node-test-runtime.js.map +1 -0
  77. package/dist/engine/adapters/runtime-facts.d.ts +26 -0
  78. package/dist/engine/adapters/runtime-facts.d.ts.map +1 -0
  79. package/dist/engine/adapters/runtime-facts.js +73 -0
  80. package/dist/engine/adapters/runtime-facts.js.map +1 -0
  81. package/dist/engine/connection.d.ts +22 -0
  82. package/dist/engine/connection.d.ts.map +1 -0
  83. package/dist/engine/connection.js +343 -0
  84. package/dist/engine/connection.js.map +1 -0
  85. package/dist/engine/git-regression.d.ts +25 -0
  86. package/dist/engine/git-regression.d.ts.map +1 -0
  87. package/dist/engine/git-regression.js +803 -0
  88. package/dist/engine/git-regression.js.map +1 -0
  89. package/dist/engine/index.d.ts +55 -0
  90. package/dist/engine/index.d.ts.map +1 -0
  91. package/dist/engine/index.js +2782 -0
  92. package/dist/engine/index.js.map +1 -0
  93. package/dist/engine/node-test-reporter.d.ts +2 -0
  94. package/dist/engine/node-test-reporter.d.ts.map +1 -0
  95. package/dist/engine/node-test-reporter.js +70 -0
  96. package/dist/engine/node-test-reporter.js.map +1 -0
  97. package/dist/engine/runtime-doctor.d.ts +16 -0
  98. package/dist/engine/runtime-doctor.d.ts.map +1 -0
  99. package/dist/engine/runtime-doctor.js +100 -0
  100. package/dist/engine/runtime-doctor.js.map +1 -0
  101. package/dist/evaluation/agentic-corpus.d.ts +161 -0
  102. package/dist/evaluation/agentic-corpus.d.ts.map +1 -0
  103. package/dist/evaluation/agentic-corpus.js +710 -0
  104. package/dist/evaluation/agentic-corpus.js.map +1 -0
  105. package/dist/index.d.ts +8 -0
  106. package/dist/index.d.ts.map +1 -0
  107. package/dist/index.js +8 -0
  108. package/dist/index.js.map +1 -0
  109. package/dist/mcp/index.d.ts +13 -0
  110. package/dist/mcp/index.d.ts.map +1 -0
  111. package/dist/mcp/index.js +391 -0
  112. package/dist/mcp/index.js.map +1 -0
  113. package/dist/mcp/stdio.d.ts +3 -0
  114. package/dist/mcp/stdio.d.ts.map +1 -0
  115. package/dist/mcp/stdio.js +13 -0
  116. package/dist/mcp/stdio.js.map +1 -0
  117. package/dist/sdk/index.d.ts +52 -0
  118. package/dist/sdk/index.d.ts.map +1 -0
  119. package/dist/sdk/index.js +224 -0
  120. package/dist/sdk/index.js.map +1 -0
  121. package/dist/version.d.ts +2 -0
  122. package/dist/version.d.ts.map +1 -0
  123. package/dist/version.js +10 -0
  124. package/dist/version.js.map +1 -0
  125. package/docs/adapter-protocol.md +196 -0
  126. package/docs/agentic-benchmark.md +118 -0
  127. package/docs/agentic-corpus-experiment-h3.md +89 -0
  128. package/docs/agentic-corpus-plan.md +105 -0
  129. package/docs/agentic-corpus-provenance.md +59 -0
  130. package/docs/agentic-test-profile-pilot.md +57 -0
  131. package/docs/agentic-test-profile-v2.md +116 -0
  132. package/docs/agentic-test-profile.md +274 -0
  133. package/docs/architecture.md +157 -0
  134. package/docs/ci.md +37 -0
  135. package/docs/client-connections.md +61 -0
  136. package/docs/conformance-v1.md +72 -0
  137. package/docs/decisions/0001-typescript-runtime.md +24 -0
  138. package/docs/developer-experience.md +55 -0
  139. package/docs/diagnostics.md +35 -0
  140. package/docs/distribution.md +40 -0
  141. package/docs/git-regression.md +39 -0
  142. package/docs/migration-repository-validation-order.md +35 -0
  143. package/docs/migration-testforge-to-assertledger.md +64 -0
  144. package/docs/project-intent.md +173 -0
  145. package/docs/proof-model.md +116 -0
  146. package/docs/reference.md +336 -0
  147. package/docs/release-1.0.md +63 -0
  148. package/docs/repository-audit.md +52 -0
  149. package/docs/repository-init.md +60 -0
  150. package/docs/research-basis.md +27 -0
  151. package/docs/roadmap.md +74 -0
  152. package/docs/runtime-doctor.md +65 -0
  153. package/docs/testexplora-calibration.md +71 -0
  154. package/examples/agentic-benchmark/benchmark-request.mjs +19 -0
  155. package/examples/agentic-benchmark/structured-phase-adapter-fixture.mjs +35 -0
  156. package/examples/agentic-profile/profile-benchmark.mjs +34 -0
  157. package/examples/agentic-profile/profile-manifest.mjs +28 -0
  158. package/examples/git-history/README.md +44 -0
  159. package/examples/git-history/create-demo.mjs +128 -0
  160. package/examples/git-history/escape-string-regexp/LICENSE +9 -0
  161. package/examples/git-history/escape-string-regexp/before.cjs.txt +11 -0
  162. package/examples/git-history/escape-string-regexp/fixed.cjs.txt +13 -0
  163. package/examples/git-history/escape-string-regexp/provenance.json +28 -0
  164. package/examples/node-test/repository/package.json +5 -0
  165. package/examples/node-test/repository/src/is-even.js +3 -0
  166. package/examples/node-test/repository/tests/base.test.js +6 -0
  167. package/examples/node-test/request.json +93 -0
  168. package/integrations/skill/SKILL.md +51 -0
  169. package/package.json +88 -0
  170. package/schemas/agentic-benchmark-acquisition-replay-result.v1.json +70 -0
  171. package/schemas/agentic-benchmark-acquisition-request.v1.json +564 -0
  172. package/schemas/agentic-benchmark-acquisition-result.v1.json +1409 -0
  173. package/schemas/agentic-benchmark-artifact.v1.json +1251 -0
  174. package/schemas/agentic-benchmark-replay-result.v1.json +84 -0
  175. package/schemas/agentic-benchmark-request.v1.json +1034 -0
  176. package/schemas/agentic-corpus-allocation-commitment-replay-result.v1.json +58 -0
  177. package/schemas/agentic-corpus-allocation-commitment.v1.json +141 -0
  178. package/schemas/agentic-corpus-allocation-replay-result.v1.json +34 -0
  179. package/schemas/agentic-corpus-allocation-request.v1.json +65 -0
  180. package/schemas/agentic-corpus-allocation-reveal.v1.json +66 -0
  181. package/schemas/agentic-corpus-allocation.v1.json +167 -0
  182. package/schemas/agentic-corpus-experiment-artifact.v1.json +329 -0
  183. package/schemas/agentic-corpus-experiment-plan-replay-result.v1.json +50 -0
  184. package/schemas/agentic-corpus-experiment-plan.v1.json +424 -0
  185. package/schemas/agentic-corpus-experiment-replay-request.v1.json +336 -0
  186. package/schemas/agentic-corpus-experiment-replay-result.v1.json +106 -0
  187. package/schemas/agentic-corpus-experiment-request.v1.json +204 -0
  188. package/schemas/agentic-corpus-provenance.v1.json +143 -0
  189. package/schemas/agentic-corpus-trust-policy.v1.json +133 -0
  190. package/schemas/agentic-profile-replay-result.v1.json +56 -0
  191. package/schemas/agentic-profile-replay-result.v2.json +63 -0
  192. package/schemas/agentic-profile-report.v1.json +961 -0
  193. package/schemas/agentic-profile-report.v2.json +1674 -0
  194. package/schemas/agentic-profile-request.v1.json +671 -0
  195. package/schemas/agentic-profile-request.v2.json +1338 -0
  196. package/schemas/evidence-manifest.v1.json +636 -0
  197. package/schemas/replay-result.v1.json +49 -0
  198. package/schemas/repository-analysis.v1.json +119 -0
  199. package/schemas/repository-audit.v1.json +811 -0
  200. package/schemas/repository-init-config.v1.json +183 -0
  201. package/schemas/repository-init-lock.v1.json +162 -0
  202. package/schemas/repository-init-result.v1.json +212 -0
  203. package/schemas/verification-request.v1.json +389 -0
@@ -0,0 +1,233 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const CAMPAIGN_SCHEMA_VERSION = "1.0.0" as const;
4
+ export const CAMPAIGN_ID = "assertledger-self-hosted-core-v1" as const;
5
+ export const CANDIDATE_ROOT = "benchmarks/self-hosted-core/generated-candidates" as const;
6
+
7
+ export interface MutationDefinition {
8
+ id: string;
9
+ description: string;
10
+ anchor: string;
11
+ replacement: string;
12
+ }
13
+
14
+ export const TARGET_MUTATIONS: readonly MutationDefinition[] = [
15
+ {
16
+ id: "target-dependent-gate-order",
17
+ description: "Permute les gates dependantes NEUTRAL et TARGET_STRENGTH.",
18
+ anchor: 'gates.push(notRun("REFERENCE"), notRun("NEUTRAL"), notRun("TARGET_STRENGTH"));',
19
+ replacement: 'gates.push(notRun("REFERENCE"), notRun("TARGET_STRENGTH"), notRun("NEUTRAL"));',
20
+ },
21
+ {
22
+ id: "target-threshold-strict-greater",
23
+ description: "Remplace la borne inclusive du seuil de poids par une borne stricte.",
24
+ anchor: "targetWeightKilled * 1_000 >= totalWeight * input.policy.minimumTargetWeightPermille;",
25
+ replacement:
26
+ "targetWeightKilled * 1_000 > totalWeight * input.policy.minimumTargetWeightPermille;",
27
+ },
28
+ {
29
+ id: "target-stability-completeness-only",
30
+ description: "Accepte des tentatives completes meme lorsque leurs outcomes se contredisent.",
31
+ anchor:
32
+ "if (!isComplete(runs, requiredAttempts)) return false;\n return runs.every((run) => run.outcome === runs[0]?.outcome);",
33
+ replacement: "if (!isComplete(runs, requiredAttempts)) return false;\n return true;",
34
+ },
35
+ {
36
+ id: "target-duration-in-decision-digest",
37
+ description: "Inclut la duree volatile dans le digest de decision.",
38
+ anchor: "durationMs: _durationMs,",
39
+ replacement: "",
40
+ },
41
+ ] as const;
42
+
43
+ export const NEUTRAL_REWRITE: MutationDefinition = {
44
+ id: "neutral-comparator-rewrite",
45
+ description: "Reecrit la collecte des runs NEUTRAL sans changer son comportement.",
46
+ anchor: `const neutralRuns = worldRuns
47
+ .filter(({ world }) => world.kind === "NEUTRAL")
48
+ .flatMap(({ runs }) => runs);`,
49
+ replacement: `const neutralRuns = worldRuns.flatMap(({ world, runs }) =>
50
+ world.kind === "NEUTRAL" ? runs : [],
51
+ );`,
52
+ };
53
+
54
+ const COMMON_FIXTURE = `
55
+ function digest(label) {
56
+ return core.sha256Canonical({ fixture: label });
57
+ }
58
+
59
+ function observation(candidateId, worldId, attempt, outcome, discovered = candidateId === null ? 0 : 1, attributed = candidateId !== null) {
60
+ return {
61
+ runId: \`\${candidateId ?? "control"}:\${worldId}:\${attempt}\`,
62
+ candidateId,
63
+ worldId,
64
+ attempt,
65
+ outcome,
66
+ testsDiscovered: candidateId === null ? 1 : 2,
67
+ candidateTestsDiscovered: discovered,
68
+ attributed,
69
+ durationMs: 10 + attempt,
70
+ exitCode: outcome === "PASS" ? 0 : 1,
71
+ stdoutDigest: digest(\`stdout:\${candidateId ?? "control"}:\${worldId}:\${attempt}\`),
72
+ stderrDigest: digest(\`stderr:\${candidateId ?? "control"}:\${worldId}:\${attempt}\`),
73
+ };
74
+ }
75
+
76
+ function campaign() {
77
+ const worlds = [
78
+ { id: "reference", kind: "REFERENCE", required: true, weight: 0 },
79
+ { id: "target-a", kind: "TARGET", required: true, weight: 1 },
80
+ { id: "target-b", kind: "TARGET", required: false, weight: 1 },
81
+ { id: "neutral", kind: "NEUTRAL", required: true, weight: 0 },
82
+ ];
83
+ const observations = worlds.flatMap((world) => [
84
+ observation(null, world.id, 1, "PASS"),
85
+ observation(null, world.id, 2, "PASS"),
86
+ observation("candidate", world.id, 1, world.kind === "TARGET" ? "ASSERTION_FAILURE" : "PASS"),
87
+ observation("candidate", world.id, 2, world.kind === "TARGET" ? "ASSERTION_FAILURE" : "PASS"),
88
+ ]);
89
+ return {
90
+ schemaVersion: "1.0.0",
91
+ repositoryDigest: digest("repository"),
92
+ evidenceContext: {
93
+ engine: { name: "testforge", version: "0.1.0" },
94
+ adapter: { name: "node-test", version: process.versions.node, configuration: {} },
95
+ execution: {
96
+ isolation: "UNSANDBOXED",
97
+ environmentAllowlist: [],
98
+ budgets: { timeoutMsPerExecution: 1000, maximumOutputBytes: 65536 },
99
+ candidateRoots: ["tests/candidates"],
100
+ },
101
+ worlds: worlds.map((world) => ({
102
+ id: world.id,
103
+ provenance: \`fixture:\${world.id}\`,
104
+ digest: digest(\`world:\${world.id}\`),
105
+ })),
106
+ },
107
+ policy: {
108
+ policyVersion: "1.0.0",
109
+ requiredAttempts: 2,
110
+ minimumTargetWeightPermille: 1000,
111
+ maximumSelectedCandidates: 1,
112
+ acceptedTargetOutcomes: ["ASSERTION_FAILURE"],
113
+ },
114
+ worlds,
115
+ candidates: [{ id: "candidate", digest: digest("candidate"), sizeBytes: 100 }],
116
+ observations,
117
+ };
118
+ }
119
+ `;
120
+
121
+ function candidateSource(name: string, body: string): string {
122
+ return `import assert from "node:assert/strict";\nimport { test } from "node:test";\nimport * as core from "../../../../src/core/index.js";\n${COMMON_FIXTURE}\ntest(${JSON.stringify(name)}, () => {\n${body}\n});\n`;
123
+ }
124
+
125
+ export const TEST_SLICES = {
126
+ gateOrder: candidateSource(
127
+ "dependent gates retain their public order",
128
+ ` const input = campaign();
129
+ for (const run of input.observations) {
130
+ if (run.candidateId === "candidate") run.candidateTestsDiscovered = 0;
131
+ }
132
+ const manifest = core.decideEvidence(input);
133
+ assert.deepEqual(manifest.candidates[0].gates.map((gate) => gate.name), [
134
+ "COMPLETENESS", "STABILITY", "DISCOVERY", "REFERENCE", "NEUTRAL", "TARGET_STRENGTH",
135
+ ]);`,
136
+ ),
137
+ thresholdBoundary: candidateSource(
138
+ "the exact target threshold is inclusive",
139
+ ` const input = campaign();
140
+ input.policy.minimumTargetWeightPermille = 500;
141
+ for (const run of input.observations) {
142
+ if (run.candidateId === "candidate" && run.worldId === "target-b") run.outcome = "PASS";
143
+ }
144
+ const manifest = core.decideEvidence(input);
145
+ assert.equal(manifest.decision.status, "VERIFIED");`,
146
+ ),
147
+ contradictoryStability: candidateSource(
148
+ "contradictory attempts are unstable",
149
+ ` const input = campaign();
150
+ const contradictory = input.observations.find((run) =>
151
+ run.candidateId === "candidate" && run.worldId === "reference" && run.attempt === 2,
152
+ );
153
+ assert.ok(contradictory);
154
+ contradictory.outcome = "ASSERTION_FAILURE";
155
+ const manifest = core.decideEvidence(input);
156
+ assert.equal(manifest.decision.status, "INCONCLUSIVE");
157
+ assert.equal(manifest.candidates[0].status, "UNSTABLE");`,
158
+ ),
159
+ durationExclusion: candidateSource(
160
+ "duration is excluded from the decision digest",
161
+ ` const manifest = core.decideEvidence(campaign());
162
+ manifest.observations[0].durationMs += 1;
163
+ assert.equal(core.verifyDecisionDigest(manifest).valid, true);`,
164
+ ),
165
+ } as const;
166
+
167
+ export interface CandidateDefinition {
168
+ id: string;
169
+ files: readonly { name: string; content: string }[];
170
+ }
171
+
172
+ export const CANDIDATE_DEFINITIONS: readonly CandidateDefinition[] = [
173
+ { id: "gate-order", files: [{ name: "gate-order.test.ts", content: TEST_SLICES.gateOrder }] },
174
+ {
175
+ id: "threshold-boundary",
176
+ files: [{ name: "threshold-boundary.test.ts", content: TEST_SLICES.thresholdBoundary }],
177
+ },
178
+ {
179
+ id: "contradictory-stability",
180
+ files: [
181
+ { name: "contradictory-stability.test.ts", content: TEST_SLICES.contradictoryStability },
182
+ ],
183
+ },
184
+ {
185
+ id: "duration-exclusion",
186
+ files: [{ name: "duration-exclusion.test.ts", content: TEST_SLICES.durationExclusion }],
187
+ },
188
+ {
189
+ id: "core-contract-complete",
190
+ files: [
191
+ { name: "gate-order.test.ts", content: TEST_SLICES.gateOrder },
192
+ { name: "threshold-boundary.test.ts", content: TEST_SLICES.thresholdBoundary },
193
+ { name: "contradictory-stability.test.ts", content: TEST_SLICES.contradictoryStability },
194
+ { name: "duration-exclusion.test.ts", content: TEST_SLICES.durationExclusion },
195
+ ],
196
+ },
197
+ {
198
+ id: "weak-canonicalization",
199
+ files: [
200
+ {
201
+ name: "weak-canonicalization.test.ts",
202
+ content: candidateSource(
203
+ "canonicalization ignores object insertion order",
204
+ ` assert.equal(core.canonicalize({ b: 2, a: 1 }), core.canonicalize({ a: 1, b: 2 }));`,
205
+ ),
206
+ },
207
+ ],
208
+ },
209
+ {
210
+ id: "weak-safe-path",
211
+ files: [
212
+ {
213
+ name: "weak-safe-path.test.ts",
214
+ content: candidateSource(
215
+ "safe relative paths are normalized",
216
+ ` assert.equal(core.assertSafeRelativePath("tests/example.test.ts", ["tests"]), "tests/example.test.ts");`,
217
+ ),
218
+ },
219
+ ],
220
+ },
221
+ ] as const;
222
+
223
+ export function sha256Bytes(content: string | Buffer): string {
224
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
225
+ }
226
+
227
+ export function replaceExactlyOnce(source: string, definition: MutationDefinition): string {
228
+ const first = source.indexOf(definition.anchor);
229
+ if (first < 0 || source.indexOf(definition.anchor, first + definition.anchor.length) >= 0) {
230
+ throw new Error(`MUTATION_ANCHOR_NOT_UNIQUE:${definition.id}`);
231
+ }
232
+ return `${source.slice(0, first)}${definition.replacement}${source.slice(first + definition.anchor.length)}`;
233
+ }
@@ -0,0 +1,217 @@
1
+ import { mkdir, readFile, realpath, stat, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { parseVerificationRequest } from "../../src/contracts/index.js";
5
+ import {
6
+ CAMPAIGN_SCHEMA_VERSION,
7
+ NEUTRAL_REWRITE,
8
+ replaceExactlyOnce,
9
+ sha256Bytes,
10
+ TARGET_MUTATIONS,
11
+ } from "./campaign.js";
12
+ import {
13
+ buildExistingTestCandidates,
14
+ EXISTING_TESTS_CAMPAIGN_ID,
15
+ EXISTING_TESTS_CANDIDATE_ROOT,
16
+ EXISTING_TESTS_SNAPSHOT_COUNT,
17
+ EXISTING_TESTS_SOURCE_PATH,
18
+ extractExistingCoreTests,
19
+ } from "./existing-tests.js";
20
+
21
+ interface ExistingTestsOperatorPlan {
22
+ schemaVersion: "1.0.0";
23
+ campaignId: typeof EXISTING_TESTS_CAMPAIGN_ID;
24
+ repositoryRoot: string;
25
+ sourceTestCount: typeof EXISTING_TESTS_SNAPSHOT_COUNT;
26
+ targetIds: string[];
27
+ requiredAttempts: 2;
28
+ minimumTargetWeightPermille: 250;
29
+ maximumSelectedCandidates: typeof EXISTING_TESTS_SNAPSHOT_COUNT;
30
+ }
31
+
32
+ function stableJson(value: unknown): string {
33
+ const sort = (item: unknown): unknown => {
34
+ if (Array.isArray(item)) return item.map(sort);
35
+ if (typeof item !== "object" || item === null) return item;
36
+ return Object.fromEntries(
37
+ Object.entries(item)
38
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
39
+ .map(([key, entry]) => [key, sort(entry)]),
40
+ );
41
+ };
42
+ return `${JSON.stringify(sort(value), null, 2)}\n`;
43
+ }
44
+
45
+ function parseOperatorPlan(value: unknown): ExistingTestsOperatorPlan {
46
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
47
+ throw new Error("INVALID_EXISTING_TESTS_OPERATOR_PLAN");
48
+ }
49
+ const plan = value as Record<string, unknown>;
50
+ const expected = [
51
+ "campaignId",
52
+ "maximumSelectedCandidates",
53
+ "minimumTargetWeightPermille",
54
+ "repositoryRoot",
55
+ "requiredAttempts",
56
+ "schemaVersion",
57
+ "sourceTestCount",
58
+ "targetIds",
59
+ ].sort();
60
+ if (
61
+ Object.keys(plan).sort().join("\0") !== expected.join("\0") ||
62
+ plan.schemaVersion !== CAMPAIGN_SCHEMA_VERSION ||
63
+ plan.campaignId !== EXISTING_TESTS_CAMPAIGN_ID ||
64
+ typeof plan.repositoryRoot !== "string" ||
65
+ plan.repositoryRoot.length === 0 ||
66
+ plan.sourceTestCount !== EXISTING_TESTS_SNAPSHOT_COUNT ||
67
+ plan.requiredAttempts !== 2 ||
68
+ plan.minimumTargetWeightPermille !== 250 ||
69
+ plan.maximumSelectedCandidates !== EXISTING_TESTS_SNAPSHOT_COUNT ||
70
+ !Array.isArray(plan.targetIds) ||
71
+ plan.targetIds.join("\0") !== TARGET_MUTATIONS.map(({ id }) => id).join("\0")
72
+ ) {
73
+ throw new Error("INVALID_EXISTING_TESTS_OPERATOR_PLAN");
74
+ }
75
+ return plan as unknown as ExistingTestsOperatorPlan;
76
+ }
77
+
78
+ export async function buildExistingCoreTestsCampaign(options: {
79
+ operatorPlanPath: string;
80
+ expectedOperatorPlanDigest: string;
81
+ outputDirectory: string;
82
+ }): Promise<{ request: ReturnType<typeof parseVerificationRequest>; files: string[] }> {
83
+ const planBytes = await readFile(options.operatorPlanPath);
84
+ if (sha256Bytes(planBytes) !== options.expectedOperatorPlanDigest) {
85
+ throw new Error("OPERATOR_PLAN_DIGEST_MISMATCH");
86
+ }
87
+ const plan = parseOperatorPlan(JSON.parse(planBytes.toString("utf8")) as unknown);
88
+ const repositoryRoot = path.resolve(plan.repositoryRoot);
89
+ const dependencyRoot = await realpath(path.join(repositoryRoot, "node_modules"));
90
+ if (!(await stat(dependencyRoot)).isDirectory()) throw new Error("DEPENDENCY_ROOT_NOT_DIRECTORY");
91
+ const coreSource = await readFile(path.join(repositoryRoot, "src/core/index.ts"), "utf8");
92
+ const testSourcePath = path.join(repositoryRoot, EXISTING_TESTS_SOURCE_PATH);
93
+ const testSource = await readFile(testSourcePath, "utf8");
94
+ const extracted = buildExistingTestCandidates(
95
+ testSource,
96
+ extractExistingCoreTests(testSourcePath),
97
+ );
98
+ if (extracted.catalogue.length !== plan.sourceTestCount) {
99
+ throw new Error(
100
+ `CORE_TEST_COUNT_DRIFT:expected=${plan.sourceTestCount}:actual=${extracted.catalogue.length}`,
101
+ );
102
+ }
103
+ const corePath = "src/core/index.ts";
104
+ const worlds = [
105
+ {
106
+ id: "reference",
107
+ kind: "REFERENCE" as const,
108
+ required: true,
109
+ weight: 0,
110
+ provenance: `${EXISTING_TESTS_CAMPAIGN_ID}:reference`,
111
+ files: [],
112
+ },
113
+ {
114
+ id: NEUTRAL_REWRITE.id,
115
+ kind: "NEUTRAL" as const,
116
+ required: true,
117
+ weight: 0,
118
+ provenance: `${EXISTING_TESTS_CAMPAIGN_ID}:${NEUTRAL_REWRITE.id}`,
119
+ files: [{ path: corePath, content: replaceExactlyOnce(coreSource, NEUTRAL_REWRITE) }],
120
+ },
121
+ ...TARGET_MUTATIONS.map((target) => ({
122
+ id: target.id,
123
+ kind: "TARGET" as const,
124
+ required: true,
125
+ weight: 250,
126
+ provenance: `${EXISTING_TESTS_CAMPAIGN_ID}:${target.id}`,
127
+ files: [{ path: corePath, content: replaceExactlyOnce(coreSource, target) }],
128
+ })),
129
+ ];
130
+ const candidates = extracted.candidates.map((candidate) => ({
131
+ id: candidate.id,
132
+ files: candidate.files.map((file) => ({
133
+ path: `${EXISTING_TESTS_CANDIDATE_ROOT}/${candidate.id}/${file.name}`,
134
+ content: file.content,
135
+ })),
136
+ }));
137
+ const maximumExecutions = (1 + candidates.length) * worlds.length * plan.requiredAttempts;
138
+ const request = parseVerificationRequest({
139
+ schemaVersion: "1.0.0",
140
+ repository: {
141
+ root: repositoryRoot,
142
+ exclude: ["node_modules", ".git", "graphify-out", "RESULT-existing-tests-v1.md"],
143
+ },
144
+ adapter: {
145
+ kind: "testforge-command",
146
+ executable: process.execPath,
147
+ arguments: ["benchmarks/self-hosted-core/adapter.mjs", dependencyRoot],
148
+ protocolVersion: "1.0.0",
149
+ },
150
+ isolation: {
151
+ kind: "trusted-local",
152
+ acknowledgedUnsafeExecution: true,
153
+ environmentAllowlist: ["PATH", "SystemRoot", "TEMP", "TMP"],
154
+ },
155
+ candidateRoots: [EXISTING_TESTS_CANDIDATE_ROOT],
156
+ budgets: {
157
+ maximumCandidates: candidates.length,
158
+ maximumWorlds: worlds.length,
159
+ maximumExecutions,
160
+ maximumRepositoryFiles: 10_000,
161
+ maximumRepositoryBytes: 100_000_000,
162
+ maximumWorldOverlayBytes: 10_000_000,
163
+ maximumCandidateBytes: 1_000_000,
164
+ maximumTotalCandidateBytes: 5_000_000,
165
+ timeoutMsPerExecution: 30_000,
166
+ maximumOutputBytes: 1_000_000,
167
+ },
168
+ policy: {
169
+ policyVersion: "1.0.0",
170
+ requiredAttempts: plan.requiredAttempts,
171
+ minimumTargetWeightPermille: plan.minimumTargetWeightPermille,
172
+ maximumSelectedCandidates: plan.maximumSelectedCandidates,
173
+ acceptedTargetOutcomes: ["ASSERTION_FAILURE"],
174
+ },
175
+ worlds,
176
+ candidates,
177
+ });
178
+
179
+ const artifacts = new Map<string, string>();
180
+ artifacts.set("operator-plan.json", planBytes.toString("utf8"));
181
+ artifacts.set("request.json", stableJson(request));
182
+ artifacts.set("worlds.json", stableJson(worlds));
183
+ artifacts.set("candidates.json", stableJson(candidates));
184
+ artifacts.set("existing-test-catalogue.json", stableJson(extracted.catalogue));
185
+ artifacts.set(
186
+ "mutation-definitions.json",
187
+ stableJson({ neutral: NEUTRAL_REWRITE, targets: TARGET_MUTATIONS }),
188
+ );
189
+ await mkdir(options.outputDirectory, { recursive: true });
190
+ const files = [...artifacts.keys()].sort();
191
+ for (const file of files) {
192
+ await writeFile(
193
+ path.join(options.outputDirectory, file),
194
+ artifacts.get(file) as string,
195
+ "utf8",
196
+ );
197
+ }
198
+ return { request, files };
199
+ }
200
+
201
+ async function main(): Promise<void> {
202
+ const [operatorPlanPath, expectedOperatorPlanDigest, outputDirectory] = process.argv.slice(2);
203
+ if (!operatorPlanPath || !expectedOperatorPlanDigest || !outputDirectory) {
204
+ throw new Error(
205
+ "usage: existing-tests-builder.ts OPERATOR_PLAN EXPECTED_SHA256 OUTPUT_DIRECTORY",
206
+ );
207
+ }
208
+ await buildExistingCoreTestsCampaign({
209
+ operatorPlanPath,
210
+ expectedOperatorPlanDigest,
211
+ outputDirectory,
212
+ });
213
+ }
214
+
215
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
216
+ await main();
217
+ }
@@ -0,0 +1,146 @@
1
+ import { createHash } from "node:crypto";
2
+ import path from "node:path";
3
+ import {
4
+ isCallExpression,
5
+ isIdentifier,
6
+ isStringLiteral,
7
+ type Node,
8
+ visitEachChild,
9
+ } from "typescript/unstable/ast";
10
+ import { API } from "typescript/unstable/sync";
11
+ import { sha256Bytes } from "./campaign.js";
12
+
13
+ export const EXISTING_TESTS_CAMPAIGN_ID =
14
+ "assertledger-self-hosted-core-existing-tests-v1" as const;
15
+ export const EXISTING_TESTS_CANDIDATE_ROOT =
16
+ "benchmarks/self-hosted-core/generated-existing-test-candidates" as const;
17
+ export const EXISTING_TESTS_SOURCE_PATH = "tests/core.test.ts" as const;
18
+ export const EXISTING_TESTS_SNAPSHOT_COUNT = 36 as const;
19
+
20
+ export interface ExistingTestCase {
21
+ id: string;
22
+ ordinal: number;
23
+ title: string;
24
+ pattern: string;
25
+ }
26
+
27
+ export interface ExistingTestSelection {
28
+ schemaVersion: "1.0.0";
29
+ mode: "existing-core-test";
30
+ candidateId: string;
31
+ title: string;
32
+ pattern: string;
33
+ sourcePath: string;
34
+ sourceDigest: string;
35
+ }
36
+
37
+ export interface ExistingTestCandidate {
38
+ id: string;
39
+ files: readonly { name: string; content: string }[];
40
+ }
41
+
42
+ function stableJson(value: unknown): string {
43
+ const sort = (item: unknown): unknown => {
44
+ if (Array.isArray(item)) return item.map(sort);
45
+ if (typeof item !== "object" || item === null) return item;
46
+ return Object.fromEntries(
47
+ Object.entries(item)
48
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
49
+ .map(([key, entry]) => [key, sort(entry)]),
50
+ );
51
+ };
52
+ return `${JSON.stringify(sort(value), null, 2)}\n`;
53
+ }
54
+
55
+ function exactPattern(title: string): string {
56
+ return `^${title.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`;
57
+ }
58
+
59
+ function stableId(title: string): string {
60
+ return `core-it-${createHash("sha256").update(title).digest("hex").slice(0, 16)}`;
61
+ }
62
+
63
+ export function extractExistingCoreTests(sourcePath: string): readonly ExistingTestCase[] {
64
+ const absolutePath = path.resolve(sourcePath);
65
+ const api = new API({ cwd: path.dirname(absolutePath) });
66
+ const titles: string[] = [];
67
+ try {
68
+ const snapshot = api.updateSnapshot({ openFiles: [absolutePath] });
69
+ const project = snapshot.getDefaultProjectForFile(absolutePath);
70
+ const sourceFile = project?.program.getSourceFile(absolutePath);
71
+ if (!project || !sourceFile) throw new Error("CORE_TEST_SOURCE_NOT_IN_TYPESCRIPT_PROJECT");
72
+ if (project.program.getSyntacticDiagnostics(absolutePath).length > 0) {
73
+ throw new Error("CORE_TEST_SOURCE_SYNTAX_INVALID");
74
+ }
75
+ const visit = (node: Node): void => {
76
+ if (
77
+ isCallExpression(node) &&
78
+ isIdentifier(node.expression) &&
79
+ node.expression.text === "it"
80
+ ) {
81
+ const title = node.arguments[0];
82
+ if (!title || !isStringLiteral(title)) throw new Error("CORE_TEST_TITLE_NOT_LITERAL");
83
+ titles.push(title.text);
84
+ }
85
+ visitEachChild(node, (child) => {
86
+ visit(child);
87
+ return child;
88
+ });
89
+ };
90
+ visit(sourceFile);
91
+ } finally {
92
+ api.close();
93
+ }
94
+
95
+ const seenTitles = new Set<string>();
96
+ const seenIds = new Set<string>();
97
+ return titles.map((title, index) => {
98
+ if (seenTitles.has(title)) throw new Error(`CORE_TEST_TITLE_COLLISION:${title}`);
99
+ seenTitles.add(title);
100
+ const id = stableId(title);
101
+ if (seenIds.has(id)) throw new Error(`CORE_TEST_ID_COLLISION:${id}`);
102
+ seenIds.add(id);
103
+ return { id, ordinal: index + 1, title, pattern: exactPattern(title) };
104
+ });
105
+ }
106
+
107
+ export function adaptExistingCoreTestSource(source: string): string {
108
+ const original = 'import("../src/core/index.js")';
109
+ const adapted = 'import("../../../../src/core/index.js")';
110
+ const first = source.indexOf(original);
111
+ if (first < 0 || source.indexOf(original, first + original.length) >= 0) {
112
+ throw new Error("CORE_TEST_IMPORT_NOT_UNIQUE");
113
+ }
114
+ return `${source.slice(0, first)}${adapted}${source.slice(first + original.length)}`;
115
+ }
116
+
117
+ export function buildExistingTestCandidates(
118
+ source: string,
119
+ catalogue: readonly ExistingTestCase[],
120
+ ): {
121
+ catalogue: readonly ExistingTestCase[];
122
+ candidates: readonly ExistingTestCandidate[];
123
+ adaptedSource: string;
124
+ } {
125
+ const adaptedSource = adaptExistingCoreTestSource(source);
126
+ const sourceDigest = sha256Bytes(adaptedSource);
127
+ const candidates = catalogue.map((testCase) => {
128
+ const selection: ExistingTestSelection = {
129
+ schemaVersion: "1.0.0",
130
+ mode: "existing-core-test",
131
+ candidateId: testCase.id,
132
+ title: testCase.title,
133
+ pattern: testCase.pattern,
134
+ sourcePath: "core.test.ts",
135
+ sourceDigest,
136
+ };
137
+ return {
138
+ id: testCase.id,
139
+ files: [
140
+ { name: "core.test.ts", content: adaptedSource },
141
+ { name: "selection.json", content: stableJson(selection) },
142
+ ],
143
+ };
144
+ });
145
+ return { catalogue, candidates, adaptedSource };
146
+ }
@@ -0,0 +1,8 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import * as core from "../../src/core/index.js";
4
+
5
+ test("the self-hosted adapter can import the mutated core surface", () => {
6
+ assert.equal(typeof core.decideEvidence, "function");
7
+ assert.equal(typeof core.verifyDecisionDigest, "function");
8
+ });