agentwrangler 0.1.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 (157) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +116 -0
  3. package/dist/apply/jobs.js +429 -0
  4. package/dist/apply/open-terminal-child.mjs +98 -0
  5. package/dist/apply/open-terminal.js +221 -0
  6. package/dist/apply/settings-gen.js +35 -0
  7. package/dist/cli/agentwrangler.js +18 -0
  8. package/dist/daemon/config.js +51 -0
  9. package/dist/daemon/http.js +258 -0
  10. package/dist/daemon/index.js +372 -0
  11. package/dist/daemon/outcomes-pass.js +82 -0
  12. package/dist/daemon/readiness.js +15 -0
  13. package/dist/daemon/router.js +756 -0
  14. package/dist/daemon/static.js +146 -0
  15. package/dist/db/migrate.js +72 -0
  16. package/dist/db/migrations/001_observe.sql +196 -0
  17. package/dist/db/migrations/002_indexes.sql +6 -0
  18. package/dist/db/migrations/003_context_inventory_history.sql +20 -0
  19. package/dist/db/migrations/004_apply_jobs.sql +17 -0
  20. package/dist/db/migrations/005_tool_event_metadata.sql +17 -0
  21. package/dist/db/migrations/006_d7_query_indexes.sql +9 -0
  22. package/dist/db/migrations/007_work_item_branch_keys.sql +11 -0
  23. package/dist/db/migrations/008_thinking_tokens.sql +1 -0
  24. package/dist/db/migrations/009_user_turn_count.sql +1 -0
  25. package/dist/db/migrations/010_workspace_cwd.sql +1 -0
  26. package/dist/db/migrations/011_reports.sql +1 -0
  27. package/dist/db/migrations/012_reconcile_indexes.sql +2 -0
  28. package/dist/db/migrations/013_friction_fields.sql +5 -0
  29. package/dist/db/migrations/014_session_churn.sql +11 -0
  30. package/dist/db/migrations/015_gap_aggregates.sql +6 -0
  31. package/dist/db/open.js +30 -0
  32. package/dist/detector/benchmark-anchors.js +36 -0
  33. package/dist/detector/calibration.js +302 -0
  34. package/dist/detector/context-history-retention.js +312 -0
  35. package/dist/detector/context-probe.js +574 -0
  36. package/dist/detector/d1-source-identity.js +25 -0
  37. package/dist/detector/detectors/d10_catalog_footprint.js +146 -0
  38. package/dist/detector/detectors/d1_ctx_always_loaded.js +203 -0
  39. package/dist/detector/detectors/d2_session_long_full_context.js +119 -0
  40. package/dist/detector/detectors/d4_model_mismatch.js +258 -0
  41. package/dist/detector/detectors/d5_limit_burn_forecast.js +138 -0
  42. package/dist/detector/detectors/d6_tool_result_bloat.js +301 -0
  43. package/dist/detector/detectors/d7_loop_retry_waste.js +345 -0
  44. package/dist/detector/detectors/d8_cache_write_churn.js +201 -0
  45. package/dist/detector/detectors/d9_idle_background_session.js +101 -0
  46. package/dist/detector/engine.js +88 -0
  47. package/dist/detector/index.js +17 -0
  48. package/dist/detector/measurement.js +426 -0
  49. package/dist/detector/practice-registry.js +259 -0
  50. package/dist/detector/registry.js +32 -0
  51. package/dist/detector/savings.js +249 -0
  52. package/dist/detector/types.js +14 -0
  53. package/dist/evidence/common/approved-input.js +632 -0
  54. package/dist/evidence/common/boundary.js +84 -0
  55. package/dist/evidence/common/canonical.js +55 -0
  56. package/dist/evidence/common/redaction.js +321 -0
  57. package/dist/evidence/common/sqlite.js +25 -0
  58. package/dist/evidence/common/state.js +29 -0
  59. package/dist/evidence/cond1/cli.js +289 -0
  60. package/dist/evidence/cond1/packet.js +407 -0
  61. package/dist/evidence/cond1/prepare.js +295 -0
  62. package/dist/evidence/cond1/score.js +349 -0
  63. package/dist/evidence/cond1/types.js +1 -0
  64. package/dist/evidence/create-approval.js +365 -0
  65. package/dist/evidence/create-scratch.js +542 -0
  66. package/dist/evidence/d7/cli.js +113 -0
  67. package/dist/evidence/d7/measure.js +193 -0
  68. package/dist/evidence/d7/types.js +1 -0
  69. package/dist/evidence/discover-approval.js +492 -0
  70. package/dist/evidence/g2/adjudicate.js +20 -0
  71. package/dist/evidence/g2/cli.js +207 -0
  72. package/dist/evidence/g2/kappa.js +39 -0
  73. package/dist/evidence/g2/pipeline.js +92 -0
  74. package/dist/evidence/g2/store.js +14 -0
  75. package/dist/evidence/github/client.js +1 -0
  76. package/dist/evidence/github/gh-cli-client.js +301 -0
  77. package/dist/evidence/r3/cli.js +209 -0
  78. package/dist/evidence/r3/evaluate.js +417 -0
  79. package/dist/evidence/r3/packet.js +162 -0
  80. package/dist/evidence/r3/prepare.js +405 -0
  81. package/dist/evidence/r3/score.js +341 -0
  82. package/dist/evidence/r3/transcript.js +155 -0
  83. package/dist/evidence/r3/types.js +4 -0
  84. package/dist/hook/context-budget-hook.mjs +138 -0
  85. package/dist/hook/danger-guard-denylist.json +27 -0
  86. package/dist/hook/danger-guard-hook.mjs +167 -0
  87. package/dist/hook/install.js +0 -0
  88. package/dist/hook/limit-burn-hook.mjs +127 -0
  89. package/dist/hook/loop-guard-hook.mjs +104 -0
  90. package/dist/hook/precompact-checkpoint-hook.mjs +123 -0
  91. package/dist/ingest/churn-collector.js +122 -0
  92. package/dist/ingest/detector-hook.js +52 -0
  93. package/dist/ingest/discovery.js +207 -0
  94. package/dist/ingest/health.js +43 -0
  95. package/dist/ingest/index.js +28 -0
  96. package/dist/ingest/ingestor.js +509 -0
  97. package/dist/ingest/parser.js +344 -0
  98. package/dist/ingest/pricing.js +153 -0
  99. package/dist/ingest/reconcile.js +52 -0
  100. package/dist/ingest/tail.js +152 -0
  101. package/dist/ingest/types.js +24 -0
  102. package/dist/ingest/workspace-mapping.js +114 -0
  103. package/dist/oauth/anthropic-api-key.js +88 -0
  104. package/dist/oauth/count-tokens.js +86 -0
  105. package/dist/oauth/credentials.js +171 -0
  106. package/dist/oauth/judge-g2-client.js +154 -0
  107. package/dist/oauth/usage.js +167 -0
  108. package/dist/outcomes/branch-key.js +49 -0
  109. package/dist/outcomes/conclusions.js +45 -0
  110. package/dist/outcomes/derive.js +94 -0
  111. package/dist/outcomes/finding-extractors.js +131 -0
  112. package/dist/outcomes/findings.js +237 -0
  113. package/dist/outcomes/github/client.js +367 -0
  114. package/dist/outcomes/github/credential.js +195 -0
  115. package/dist/outcomes/github/gh-cli-client.js +340 -0
  116. package/dist/outcomes/linker.js +486 -0
  117. package/dist/outcomes/pool.js +24 -0
  118. package/dist/outcomes/sync.js +276 -0
  119. package/dist/query/api/agents-liveness.js +182 -0
  120. package/dist/query/api/burn-status.js +50 -0
  121. package/dist/query/api/context-budget.js +114 -0
  122. package/dist/query/api/context-composition.js +67 -0
  123. package/dist/query/api/cost-per-success.js +104 -0
  124. package/dist/query/api/delivery.js +92 -0
  125. package/dist/query/api/effectiveness.js +254 -0
  126. package/dist/query/api/efficiency-headroom.js +74 -0
  127. package/dist/query/api/headroom-trend.js +105 -0
  128. package/dist/query/api/hook-config.js +75 -0
  129. package/dist/query/api/hook-install.js +8 -0
  130. package/dist/query/api/hot-sessions.js +17 -0
  131. package/dist/query/api/idle-sessions.js +52 -0
  132. package/dist/query/api/index.js +40 -0
  133. package/dist/query/api/loop-guard.js +90 -0
  134. package/dist/query/api/offload-share.js +41 -0
  135. package/dist/query/api/outcomes.js +218 -0
  136. package/dist/query/api/overview.js +535 -0
  137. package/dist/query/api/rec-prompt.js +138 -0
  138. package/dist/query/api/recommendations-ledger.js +111 -0
  139. package/dist/query/api/recommendations.js +514 -0
  140. package/dist/query/api/reports.js +78 -0
  141. package/dist/query/api/self-churn.js +77 -0
  142. package/dist/query/api/self-percentiles.js +109 -0
  143. package/dist/query/api/session-drivers.js +153 -0
  144. package/dist/query/api/settings.js +85 -0
  145. package/dist/query/api/spend-flavor.js +234 -0
  146. package/dist/query/api/trends.js +155 -0
  147. package/dist/query/cap-weighted.js +119 -0
  148. package/dist/query/db-context.js +42 -0
  149. package/dist/query/envelope.js +71 -0
  150. package/dist/query/forecast.js +191 -0
  151. package/dist/query/settings-store.js +441 -0
  152. package/dist/query/spend.js +171 -0
  153. package/dist/query/trends.js +194 -0
  154. package/dist/ui/assets/index-DnRKgc21.css +1 -0
  155. package/dist/ui/assets/index-h1Q1wWq5.js +168 -0
  156. package/dist/ui/index.html +39 -0
  157. package/package.json +59 -0
@@ -0,0 +1,407 @@
1
+ import { createHmac, randomBytes } from "node:crypto";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { EXTRACTOR_VERSIONS } from "../../outcomes/finding-extractors.js";
5
+ import { publishApprovedOutput } from "../common/approved-input.js";
6
+ import { EvidenceBoundaryError } from "../common/boundary.js";
7
+ import { canonicalJson, sha256Bytes } from "../common/canonical.js";
8
+ import { redactEvidenceExcerpt } from "../common/redaction.js";
9
+ import { COND1_RUNNER_VERSION, canonicalAnswerKeySha256 } from "./score.js";
10
+ function fail(code) {
11
+ throw new Error(code);
12
+ }
13
+ function exactKeys(value, keys, code) {
14
+ const actual = Object.keys(value).sort();
15
+ const expected = [...keys].sort();
16
+ if (actual.length !== expected.length || actual.some((key, index) => key !== expected[index])) {
17
+ fail(code);
18
+ }
19
+ }
20
+ function record(value, code) {
21
+ if (value === null || typeof value !== "object" || Array.isArray(value))
22
+ fail(code);
23
+ return value;
24
+ }
25
+ function text(value, code) {
26
+ if (typeof value !== "string" || value.length === 0)
27
+ fail(code);
28
+ return value;
29
+ }
30
+ function digest(value, code) {
31
+ const parsed = text(value, code);
32
+ if (!/^[0-9a-f]{64}$/u.test(parsed))
33
+ fail(code);
34
+ return parsed;
35
+ }
36
+ function count(value, code) {
37
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
38
+ fail(code);
39
+ return value;
40
+ }
41
+ function strictIdentity(value) {
42
+ const identity = record(value, "cond1_manifest_identity_invalid");
43
+ exactKeys(identity, [
44
+ "sourceCommit",
45
+ "runnerVersion",
46
+ "findingsModuleSha256",
47
+ "extractorVersions",
48
+ "packetVersion",
49
+ "scorerVersion",
50
+ "asOf",
51
+ ], "cond1_manifest_identity_keys_invalid");
52
+ const versions = record(identity.extractorVersions, "cond1_manifest_versions_invalid");
53
+ exactKeys(versions, ["E1", "E2", "E3"], "cond1_manifest_versions_keys_invalid");
54
+ const asOf = text(identity.asOf, "cond1_as_of_invalid");
55
+ const asOfMatch = /^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(?:\.\d{1,9})?Z$/u.exec(asOf);
56
+ const asOfTime = asOfMatch?.[1] === undefined ? Number.NaN : Date.parse(`${asOfMatch[1]}Z`);
57
+ if (!/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(text(identity.sourceCommit, "cond1_source_commit_invalid")) ||
58
+ identity.runnerVersion !== COND1_RUNNER_VERSION ||
59
+ !/^[0-9a-f]{64}$/u.test(text(identity.findingsModuleSha256, "cond1_findings_digest_invalid")) ||
60
+ versions.E1 !== EXTRACTOR_VERSIONS.E1 ||
61
+ versions.E2 !== EXTRACTOR_VERSIONS.E2 ||
62
+ versions.E3 !== EXTRACTOR_VERSIONS.E3 ||
63
+ identity.packetVersion !== "cond1-blinded-v1" ||
64
+ identity.scorerVersion !== "cond1-precision-v1" ||
65
+ !Number.isFinite(asOfTime) ||
66
+ new Date(asOfTime).toISOString().slice(0, 19) !== asOfMatch?.[1]) {
67
+ fail("cond1_manifest_identity_value_invalid");
68
+ }
69
+ return identity;
70
+ }
71
+ export function readApprovedArtifactJson(loaded, inputPath, expectedSha256) {
72
+ if (!/^[0-9a-f]{64}$/u.test(expectedSha256))
73
+ fail("cond1_input_digest_invalid");
74
+ const resolved = path.resolve(inputPath);
75
+ if (path.dirname(resolved) !== path.resolve(loaded.scratchStatePath)) {
76
+ fail("cond1_input_not_direct_state_child");
77
+ }
78
+ let stat;
79
+ try {
80
+ stat = fs.lstatSync(resolved);
81
+ }
82
+ catch {
83
+ fail("cond1_input_missing");
84
+ }
85
+ if (!stat.isFile() || stat.isSymbolicLink())
86
+ fail("cond1_input_not_regular_file");
87
+ let bytes;
88
+ try {
89
+ bytes = fs.readFileSync(resolved);
90
+ }
91
+ catch {
92
+ fail("cond1_input_read_failed");
93
+ }
94
+ if (sha256Bytes(bytes) !== expectedSha256)
95
+ fail("cond1_input_digest_mismatch");
96
+ try {
97
+ return JSON.parse(bytes.toString("utf8"));
98
+ }
99
+ catch {
100
+ return fail("cond1_input_json_invalid");
101
+ }
102
+ }
103
+ export function parseCond1Manifest(value) {
104
+ const manifest = record(value, "cond1_manifest_invalid");
105
+ exactKeys(manifest, [
106
+ "version",
107
+ "campaignId",
108
+ "identity",
109
+ "scratchDbSha256",
110
+ "repoMapSha256",
111
+ "eligiblePrN",
112
+ "readCompletion",
113
+ "corpusReadSummary",
114
+ "emittedFindingN",
115
+ "preparedArtifactSha256",
116
+ "answerCanonicalSha256",
117
+ ], "cond1_manifest_keys_invalid");
118
+ if (manifest.version !== "cond1-corpus-manifest-v1" ||
119
+ !/^cond1-[0-9a-f]{32}$/u.test(text(manifest.campaignId, "cond1_campaign_invalid")))
120
+ fail("cond1_manifest_version_or_campaign_invalid");
121
+ strictIdentity(manifest.identity);
122
+ digest(manifest.scratchDbSha256, "cond1_scratch_digest_invalid");
123
+ digest(manifest.repoMapSha256, "cond1_repo_map_digest_invalid");
124
+ digest(manifest.preparedArtifactSha256, "cond1_prepared_digest_invalid");
125
+ digest(manifest.answerCanonicalSha256, "cond1_answer_digest_invalid");
126
+ const eligiblePrN = count(manifest.eligiblePrN, "cond1_eligible_count_invalid");
127
+ exactKeys(record(manifest.readCompletion, "cond1_manifest_reads_invalid"), ["E1", "E2", "E3"], "cond1_manifest_reads_keys_invalid");
128
+ const succeededCounts = [];
129
+ const failedCounts = [];
130
+ for (const extractor of ["E1", "E2", "E3"]) {
131
+ const read = record(manifest.readCompletion[extractor], "cond1_manifest_read_invalid");
132
+ exactKeys(read, ["requiredN", "succeededN", "failedN"], "cond1_manifest_read_keys_invalid");
133
+ const requiredN = count(read.requiredN, "cond1_manifest_read_count_invalid");
134
+ const succeededN = count(read.succeededN, "cond1_manifest_read_count_invalid");
135
+ const failedN = count(read.failedN, "cond1_manifest_read_count_invalid");
136
+ if (requiredN !== eligiblePrN || succeededN + failedN !== requiredN)
137
+ fail("cond1_manifest_read_count_mismatch");
138
+ succeededCounts.push(succeededN);
139
+ failedCounts.push(failedN);
140
+ }
141
+ const summary = record(manifest.corpusReadSummary, "cond1_manifest_summary_invalid");
142
+ exactKeys(summary, ["fullyReadPrN", "failedPrN"], "cond1_manifest_summary_keys_invalid");
143
+ exactKeys(record(manifest.emittedFindingN, "cond1_manifest_counts_invalid"), ["E1", "E2", "E3"], "cond1_manifest_counts_keys_invalid");
144
+ const fullyReadPrN = count(summary.fullyReadPrN, "cond1_manifest_summary_count_invalid");
145
+ const failedPrN = count(summary.failedPrN, "cond1_manifest_summary_count_invalid");
146
+ if (fullyReadPrN + failedPrN !== eligiblePrN)
147
+ fail("cond1_manifest_summary_count_mismatch");
148
+ if (succeededCounts.some((succeededN) => succeededN < fullyReadPrN) ||
149
+ failedCounts.some((extractorFailedN) => extractorFailedN > failedPrN) ||
150
+ failedPrN > failedCounts.reduce((sum, failedN) => sum + failedN, 0)) {
151
+ fail("cond1_manifest_intersection_count_mismatch");
152
+ }
153
+ const emitted = manifest.emittedFindingN;
154
+ for (const extractor of ["E1", "E2", "E3"])
155
+ count(emitted[extractor], "cond1_manifest_emitted_count_invalid");
156
+ return value;
157
+ }
158
+ export function parseCond1Prepared(value) {
159
+ const prepared = record(value, "cond1_prepared_invalid");
160
+ exactKeys(prepared, [
161
+ "version",
162
+ "campaignId",
163
+ "identity",
164
+ "scratchDbSha256",
165
+ "repoMapSha256",
166
+ "eligiblePrN",
167
+ "findings",
168
+ ], "cond1_prepared_keys_invalid");
169
+ if (prepared.version !== "cond1-prepared-v1" ||
170
+ !/^cond1-[0-9a-f]{32}$/u.test(text(prepared.campaignId, "cond1_prepared_campaign_invalid")) ||
171
+ !Array.isArray(prepared.findings)) {
172
+ fail("cond1_prepared_shape_invalid");
173
+ }
174
+ strictIdentity(prepared.identity);
175
+ digest(prepared.scratchDbSha256, "cond1_prepared_scratch_digest_invalid");
176
+ digest(prepared.repoMapSha256, "cond1_prepared_repo_digest_invalid");
177
+ count(prepared.eligiblePrN, "cond1_prepared_eligible_count_invalid");
178
+ const sourceIdentities = new Set();
179
+ const corpusRepositories = new Map();
180
+ for (const item of prepared.findings) {
181
+ const finding = record(item, "cond1_prepared_finding_invalid");
182
+ exactKeys(finding, [
183
+ "extractor",
184
+ "extractorVersion",
185
+ "sourceFindingId",
186
+ "corpusPrKey",
187
+ "repoAlias",
188
+ "evidenceKind",
189
+ "evidence",
190
+ "evidenceSufficient",
191
+ "projectionFailure",
192
+ ], "cond1_prepared_finding_keys_invalid");
193
+ const evidence = record(finding.evidence, "cond1_prepared_evidence_invalid");
194
+ const allowedEvidenceKeys = new Set([
195
+ "stateAtRelevantTime",
196
+ "temporalBasis",
197
+ "boundedExcerpt",
198
+ "locationAlias",
199
+ ]);
200
+ if (Object.keys(evidence).some((key) => !allowedEvidenceKeys.has(key))) {
201
+ fail("cond1_prepared_evidence_keys_invalid");
202
+ }
203
+ const extractor = text(finding.extractor, "cond1_prepared_extractor_invalid");
204
+ if (!(extractor === "E1" || extractor === "E2" || extractor === "E3"))
205
+ fail("cond1_prepared_extractor_invalid");
206
+ if (finding.extractorVersion !== EXTRACTOR_VERSIONS[extractor])
207
+ fail("cond1_prepared_extractor_version_invalid");
208
+ const sourceFindingId = text(finding.sourceFindingId, "cond1_prepared_source_id_invalid");
209
+ const corpusPrKey = text(finding.corpusPrKey, "cond1_prepared_pr_key_invalid");
210
+ const repoAlias = text(finding.repoAlias, "cond1_prepared_repo_alias_invalid");
211
+ if (!/^[A-Za-z0-9_.-]+$/u.test(repoAlias))
212
+ fail("cond1_prepared_repo_alias_invalid");
213
+ const existingRepoAlias = corpusRepositories.get(corpusPrKey);
214
+ if (existingRepoAlias !== undefined && existingRepoAlias !== repoAlias) {
215
+ fail("cond1_prepared_corpus_repo_conflict");
216
+ }
217
+ corpusRepositories.set(corpusPrKey, repoAlias);
218
+ const expectedKind = {
219
+ E1: "REVIEW_THREAD_STATE",
220
+ E2: "DEFERRAL_LIST_ITEM",
221
+ E3: "ADDED_DIFF_MARKER",
222
+ }[extractor];
223
+ if (finding.evidenceKind !== expectedKind)
224
+ fail("cond1_prepared_evidence_kind_invalid");
225
+ if (typeof finding.evidenceSufficient !== "boolean")
226
+ fail("cond1_prepared_evidence_sufficiency_invalid");
227
+ if (!(finding.projectionFailure === null || finding.projectionFailure === "REDACTION_FAILED"))
228
+ fail("cond1_prepared_projection_failure_invalid");
229
+ if (finding.evidenceSufficient === (finding.projectionFailure !== null))
230
+ fail("cond1_prepared_projection_marker_mismatch");
231
+ if (extractor === "E1") {
232
+ exactKeys(evidence, ["stateAtRelevantTime", "temporalBasis"], "cond1_e1_evidence_keys_invalid");
233
+ if (!["RESOLVED", "UNRESOLVED"].includes(evidence.stateAtRelevantTime) ||
234
+ evidence.temporalBasis !== "CURRENT_STATE_ONLY" ||
235
+ !finding.evidenceSufficient)
236
+ fail("cond1_e1_evidence_invalid");
237
+ }
238
+ else {
239
+ const requiredKeys = extractor === "E3" ? ["locationAlias"] : [];
240
+ const actual = Object.keys(evidence)
241
+ .filter((key) => key !== "boundedExcerpt")
242
+ .sort();
243
+ if (actual.join("|") !== requiredKeys.sort().join("|"))
244
+ fail("cond1_text_evidence_keys_invalid");
245
+ if (extractor === "E3" &&
246
+ !/^loc-[0-9a-f]{20}$/u.test(text(evidence.locationAlias, "cond1_location_alias_invalid")))
247
+ fail("cond1_location_alias_invalid");
248
+ if (finding.evidenceSufficient) {
249
+ const excerpt = text(evidence.boundedExcerpt, "cond1_excerpt_invalid");
250
+ if (redactEvidenceExcerpt(excerpt) !== excerpt)
251
+ fail("cond1_excerpt_not_redacted");
252
+ }
253
+ else if (evidence.boundedExcerpt !== undefined)
254
+ fail("cond1_failed_projection_has_excerpt");
255
+ }
256
+ const sourceIdentity = JSON.stringify([extractor, corpusPrKey, sourceFindingId]);
257
+ if (sourceIdentities.has(sourceIdentity))
258
+ fail("cond1_prepared_source_duplicate");
259
+ sourceIdentities.add(sourceIdentity);
260
+ }
261
+ if (corpusRepositories.size > prepared.eligiblePrN) {
262
+ fail("cond1_prepared_corpus_count_invalid");
263
+ }
264
+ return value;
265
+ }
266
+ function opaque(random) {
267
+ const value = random(16);
268
+ if (!Buffer.isBuffer(value) || value.length !== 16)
269
+ fail("cond1_rng_invalid");
270
+ return value.toString("hex");
271
+ }
272
+ function uniqueOpaque(random, used) {
273
+ for (let attempt = 0; attempt < 128; attempt++) {
274
+ const candidate = opaque(random);
275
+ if (!used.has(candidate)) {
276
+ used.add(candidate);
277
+ return candidate;
278
+ }
279
+ }
280
+ return fail("cond1_rng_collision_limit");
281
+ }
282
+ function seededRandom(seed) {
283
+ let counter = 0;
284
+ return (size) => {
285
+ const chunks = [];
286
+ let length = 0;
287
+ while (length < size) {
288
+ const block = createHmac("sha256", seed).update(`cond1-randomization:${counter++}`).digest();
289
+ chunks.push(block);
290
+ length += block.length;
291
+ }
292
+ return Buffer.concat(chunks).subarray(0, size);
293
+ };
294
+ }
295
+ function preflightOutputs(loaded, outputs) {
296
+ const state = path.resolve(loaded.scratchStatePath);
297
+ const resolved = outputs.map((output) => path.resolve(output));
298
+ if (new Set(resolved).size !== resolved.length)
299
+ fail("cond1_output_paths_duplicate");
300
+ for (const output of resolved) {
301
+ if (path.dirname(output) !== state)
302
+ fail("cond1_output_not_direct_state_child");
303
+ try {
304
+ fs.lstatSync(output);
305
+ fail("cond1_output_already_exists");
306
+ }
307
+ catch (error) {
308
+ if (error instanceof Error && error.message === "cond1_output_already_exists")
309
+ throw error;
310
+ if (error.code !== "ENOENT")
311
+ fail("cond1_output_preflight_failed");
312
+ }
313
+ }
314
+ }
315
+ export function packetCond1(input) {
316
+ const manifest = parseCond1Manifest(input.manifest);
317
+ const prepared = parseCond1Prepared(input.prepared);
318
+ preflightOutputs(input.loaded, [input.packetOutPath, input.keyOutPath]);
319
+ const counts = { E1: 0, E2: 0, E3: 0 };
320
+ for (const finding of prepared.findings)
321
+ counts[finding.extractor]++;
322
+ if (manifest.scratchDbSha256 !== input.loaded.scratchDbSha256 ||
323
+ manifest.repoMapSha256 !== input.loaded.repoMapSha256 ||
324
+ input.preparedFileSha256 !== manifest.preparedArtifactSha256 ||
325
+ prepared.campaignId !== manifest.campaignId ||
326
+ prepared.scratchDbSha256 !== manifest.scratchDbSha256 ||
327
+ prepared.repoMapSha256 !== manifest.repoMapSha256 ||
328
+ prepared.eligiblePrN !== manifest.eligiblePrN ||
329
+ canonicalJson(prepared.identity) !== canonicalJson(manifest.identity) ||
330
+ counts.E1 !== manifest.emittedFindingN.E1 ||
331
+ counts.E2 !== manifest.emittedFindingN.E2 ||
332
+ counts.E3 !== manifest.emittedFindingN.E3) {
333
+ fail("cond1_prepared_manifest_mismatch");
334
+ }
335
+ const entropy = input.randomBytes ?? randomBytes;
336
+ const seed = entropy(32);
337
+ if (!Buffer.isBuffer(seed) || seed.length !== 32)
338
+ fail("cond1_rng_invalid");
339
+ const random = seededRandom(seed);
340
+ const prAliases = new Map();
341
+ const used = new Set();
342
+ const usedPrAliases = new Set();
343
+ const rows = [];
344
+ for (const finding of prepared.findings) {
345
+ const findingAlias = uniqueOpaque(random, used);
346
+ let prAlias = prAliases.get(finding.corpusPrKey);
347
+ if (prAlias === undefined) {
348
+ prAlias = uniqueOpaque(random, usedPrAliases);
349
+ prAliases.set(finding.corpusPrKey, prAlias);
350
+ }
351
+ rows.push({
352
+ order: opaque(random),
353
+ entry: {
354
+ findingAlias,
355
+ repoAlias: finding.repoAlias,
356
+ prAlias,
357
+ evidenceKind: finding.evidenceKind,
358
+ criterion: "DOES_THE_DISPLAYED_EVIDENCE_SUPPORT_THE_STATED_FINDING",
359
+ evidence: finding.evidence,
360
+ },
361
+ answer: {
362
+ findingAlias,
363
+ extractor: finding.extractor,
364
+ extractorVersion: finding.extractorVersion,
365
+ sourceFindingId: finding.sourceFindingId,
366
+ corpusPrKey: finding.corpusPrKey,
367
+ evidenceSufficient: finding.evidenceSufficient,
368
+ projectionFailure: finding.projectionFailure,
369
+ },
370
+ });
371
+ }
372
+ rows.sort((left, right) => (left.order < right.order ? -1 : left.order > right.order ? 1 : 0));
373
+ const answers = rows.map(({ answer }) => answer);
374
+ const answerCanonicalSha256 = canonicalAnswerKeySha256(answers);
375
+ if (answerCanonicalSha256 !== manifest.answerCanonicalSha256) {
376
+ fail("cond1_answer_canonical_digest_mismatch");
377
+ }
378
+ const packet = {
379
+ campaignId: manifest.campaignId,
380
+ packetVersion: "cond1-blinded-v1",
381
+ randomizationSeedCommitment: sha256Bytes(seed),
382
+ entries: rows.map(({ entry }) => entry),
383
+ };
384
+ const key = {
385
+ version: "cond1-sealed-key-v1",
386
+ campaignId: manifest.campaignId,
387
+ randomizationSeed: seed.toString("hex"),
388
+ answerCanonicalSha256,
389
+ answers,
390
+ };
391
+ const publish = input.publishOutput ?? publishApprovedOutput;
392
+ const packetPublication = publish(input.loaded, input.packetOutPath, `${canonicalJson(packet)}\n`);
393
+ let keyPublication;
394
+ try {
395
+ keyPublication = publish(input.loaded, input.keyOutPath, `${canonicalJson(key)}\n`);
396
+ }
397
+ catch (error) {
398
+ const code = error instanceof EvidenceBoundaryError ? error.code : "unexpected_failure";
399
+ fail(`cond1_key_publication_failed_packet_output_retained_${code}`);
400
+ }
401
+ return {
402
+ findingN: rows.length,
403
+ packetSha256: packetPublication.sha256,
404
+ keySha256: keyPublication.sha256,
405
+ answerCanonicalSha256,
406
+ };
407
+ }