orizu 0.5.20 → 0.5.21

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 (260) hide show
  1. package/dist/archive-artifacts-cli.d.ts +50 -0
  2. package/dist/archive-artifacts-cli.d.ts.map +1 -0
  3. package/dist/artifact-archive.d.ts +48 -0
  4. package/dist/artifact-archive.d.ts.map +1 -0
  5. package/dist/artifact-pull.d.ts +22 -0
  6. package/dist/artifact-pull.d.ts.map +1 -0
  7. package/dist/banner.d.ts +3 -0
  8. package/dist/banner.d.ts.map +1 -0
  9. package/dist/connectors-cli.d.ts +31 -0
  10. package/dist/connectors-cli.d.ts.map +1 -0
  11. package/dist/credentials.d.ts +46 -0
  12. package/dist/credentials.d.ts.map +1 -0
  13. package/dist/csv.d.ts +49 -0
  14. package/dist/csv.d.ts.map +1 -0
  15. package/dist/dataset-download.d.ts +6 -0
  16. package/dist/dataset-download.d.ts.map +1 -0
  17. package/dist/dataset-edit-rows.d.ts +10 -0
  18. package/dist/dataset-edit-rows.d.ts.map +1 -0
  19. package/dist/dataset-upload-snapshot.d.ts +13 -0
  20. package/dist/dataset-upload-snapshot.d.ts.map +1 -0
  21. package/dist/daytona-slice-rehearsal.d.ts +89 -0
  22. package/dist/daytona-slice-rehearsal.d.ts.map +1 -0
  23. package/dist/daytona-slice.d.ts +106 -0
  24. package/dist/daytona-slice.d.ts.map +1 -0
  25. package/dist/deploy-key-merge-core.d.ts +121 -0
  26. package/dist/deploy-key-merge-core.d.ts.map +1 -0
  27. package/dist/diff-comments-cli.d.ts +12 -0
  28. package/dist/diff-comments-cli.d.ts.map +1 -0
  29. package/dist/diff-comments-cli.js +3 -0
  30. package/dist/egress-allowlist-cli.d.ts +23 -0
  31. package/dist/egress-allowlist-cli.d.ts.map +1 -0
  32. package/dist/egress-policy.d.ts +108 -0
  33. package/dist/egress-policy.d.ts.map +1 -0
  34. package/dist/error-response.d.ts +9 -0
  35. package/dist/error-response.d.ts.map +1 -0
  36. package/dist/file-parser.d.ts +5 -0
  37. package/dist/file-parser.d.ts.map +1 -0
  38. package/dist/gepa-engine-dispatch.d.ts +9 -0
  39. package/dist/gepa-engine-dispatch.d.ts.map +1 -0
  40. package/dist/gepa-engine-dispatch.js +48 -2
  41. package/dist/gepa-python-command.d.ts +6 -0
  42. package/dist/gepa-python-command.d.ts.map +1 -0
  43. package/dist/gepa-python-paths.d.ts +6 -0
  44. package/dist/gepa-python-paths.d.ts.map +1 -0
  45. package/dist/gepa-python-paths.js +6 -3
  46. package/dist/git-credential.d.ts +69 -0
  47. package/dist/git-credential.d.ts.map +1 -0
  48. package/dist/git-recovery-environment.d.ts +31 -0
  49. package/dist/git-recovery-environment.d.ts.map +1 -0
  50. package/dist/github-setup.d.ts +122 -0
  51. package/dist/github-setup.d.ts.map +1 -0
  52. package/dist/global-flags.d.ts +12 -0
  53. package/dist/global-flags.d.ts.map +1 -0
  54. package/dist/help.d.ts +46 -0
  55. package/dist/help.d.ts.map +1 -0
  56. package/dist/help.js +91 -23
  57. package/dist/hosted-boot.d.ts +273 -0
  58. package/dist/hosted-boot.d.ts.map +1 -0
  59. package/dist/hosted-bootstrap-rehearsal.d.ts +86 -0
  60. package/dist/hosted-bootstrap-rehearsal.d.ts.map +1 -0
  61. package/dist/hosted-bootstrap.d.ts +188 -0
  62. package/dist/hosted-bootstrap.d.ts.map +1 -0
  63. package/dist/hosted-broker-server.d.ts +50 -0
  64. package/dist/hosted-broker-server.d.ts.map +1 -0
  65. package/dist/hosted-git-http-server.d.ts +36 -0
  66. package/dist/hosted-git-http-server.d.ts.map +1 -0
  67. package/dist/hosted-harness-claude.d.ts +195 -0
  68. package/dist/hosted-harness-claude.d.ts.map +1 -0
  69. package/dist/hosted-harness-opencode.d.ts +227 -0
  70. package/dist/hosted-harness-opencode.d.ts.map +1 -0
  71. package/dist/hosted-harness.d.ts +118 -0
  72. package/dist/hosted-harness.d.ts.map +1 -0
  73. package/dist/hosted-harvest.d.ts +68 -0
  74. package/dist/hosted-harvest.d.ts.map +1 -0
  75. package/dist/hosted-headless.d.ts +72 -0
  76. package/dist/hosted-headless.d.ts.map +1 -0
  77. package/dist/hosted-loop-lifecycle.d.ts +206 -0
  78. package/dist/hosted-loop-lifecycle.d.ts.map +1 -0
  79. package/dist/hosted-loop.d.ts +170 -0
  80. package/dist/hosted-loop.d.ts.map +1 -0
  81. package/dist/hosted-run-event-sink.d.ts +222 -0
  82. package/dist/hosted-run-event-sink.d.ts.map +1 -0
  83. package/dist/hosted-runtime-assets.d.ts +186 -0
  84. package/dist/hosted-runtime-assets.d.ts.map +1 -0
  85. package/dist/hosted-session-cli.d.ts +258 -0
  86. package/dist/hosted-session-cli.d.ts.map +1 -0
  87. package/dist/hosted-skill-staging.d.ts +73 -0
  88. package/dist/hosted-skill-staging.d.ts.map +1 -0
  89. package/dist/http.d.ts +8 -0
  90. package/dist/http.d.ts.map +1 -0
  91. package/dist/index.d.ts +22 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +88 -92
  94. package/dist/instruction-set-gepa-launch.d.ts +3 -0
  95. package/dist/instruction-set-gepa-launch.d.ts.map +1 -0
  96. package/dist/instruction-set-gepa-launch.js +15 -0
  97. package/dist/instruction-set-loader.d.ts +6 -0
  98. package/dist/instruction-set-loader.d.ts.map +1 -0
  99. package/dist/instruction-set-loader.js +59 -0
  100. package/dist/instruction-set-manifest.d.ts +14 -0
  101. package/dist/instruction-set-manifest.d.ts.map +1 -0
  102. package/dist/instruction-set-manifest.js +162 -0
  103. package/dist/instruction-sets-cli.d.ts +40 -0
  104. package/dist/instruction-sets-cli.d.ts.map +1 -0
  105. package/dist/instruction-sets-cli.js +361 -0
  106. package/dist/json-response.d.ts +4 -0
  107. package/dist/json-response.d.ts.map +1 -0
  108. package/dist/jsonl-stream.d.ts +8 -0
  109. package/dist/jsonl-stream.d.ts.map +1 -0
  110. package/dist/kill-switch-cli.d.ts +30 -0
  111. package/dist/kill-switch-cli.d.ts.map +1 -0
  112. package/dist/loopback-serve.d.ts +25 -0
  113. package/dist/loopback-serve.d.ts.map +1 -0
  114. package/dist/manifests-cli.d.ts +22 -0
  115. package/dist/manifests-cli.d.ts.map +1 -0
  116. package/dist/markdown-report-input.d.ts +7 -0
  117. package/dist/markdown-report-input.d.ts.map +1 -0
  118. package/dist/merge-job.d.ts +155 -0
  119. package/dist/merge-job.d.ts.map +1 -0
  120. package/dist/model-configs-cli.d.ts +19 -0
  121. package/dist/model-configs-cli.d.ts.map +1 -0
  122. package/dist/model-configs-cli.js +104 -0
  123. package/dist/optimization-export-cli.d.ts +19 -0
  124. package/dist/optimization-export-cli.d.ts.map +1 -0
  125. package/dist/optimizations-list-cli.d.ts +20 -0
  126. package/dist/optimizations-list-cli.d.ts.map +1 -0
  127. package/dist/optimizations-promote-cli.d.ts +8 -0
  128. package/dist/optimizations-promote-cli.d.ts.map +1 -0
  129. package/dist/optimizations-promote-cli.js +34 -0
  130. package/dist/option-validation.d.ts +15 -0
  131. package/dist/option-validation.d.ts.map +1 -0
  132. package/dist/preview-runtime.d.ts +63 -0
  133. package/dist/preview-runtime.d.ts.map +1 -0
  134. package/dist/prompt-draft-push.d.ts +60 -0
  135. package/dist/prompt-draft-push.d.ts.map +1 -0
  136. package/dist/prompt-label-cli.d.ts +11 -0
  137. package/dist/prompt-label-cli.d.ts.map +1 -0
  138. package/dist/prompt-label-cli.js +29 -0
  139. package/dist/prompt-length-wire.d.ts +28 -0
  140. package/dist/prompt-length-wire.d.ts.map +1 -0
  141. package/dist/prompt-report-cli.d.ts +10 -0
  142. package/dist/prompt-report-cli.d.ts.map +1 -0
  143. package/dist/prompt-summary-table.d.ts +15 -0
  144. package/dist/prompt-summary-table.d.ts.map +1 -0
  145. package/dist/prompt-summary-table.js +6 -0
  146. package/dist/registration-tag-warning.d.ts +32 -0
  147. package/dist/registration-tag-warning.d.ts.map +1 -0
  148. package/dist/report-comments-cli.d.ts +16 -0
  149. package/dist/report-comments-cli.d.ts.map +1 -0
  150. package/dist/report-markdown-blankness.d.ts +2 -0
  151. package/dist/report-markdown-blankness.d.ts.map +1 -0
  152. package/dist/report-replacement-warning.d.ts +17 -0
  153. package/dist/report-replacement-warning.d.ts.map +1 -0
  154. package/dist/repository-backup-core.d.ts +225 -0
  155. package/dist/repository-backup-core.d.ts.map +1 -0
  156. package/dist/repository-recovery-manifest.d.ts +83 -0
  157. package/dist/repository-recovery-manifest.d.ts.map +1 -0
  158. package/dist/run-event-digest.d.ts +74 -0
  159. package/dist/run-event-digest.d.ts.map +1 -0
  160. package/dist/runner-dir-verify.d.ts +66 -0
  161. package/dist/runner-dir-verify.d.ts.map +1 -0
  162. package/dist/runner-instruction-set-contract.d.ts +24 -0
  163. package/dist/runner-instruction-set-contract.d.ts.map +1 -0
  164. package/dist/runner-instruction-set-contract.js +50 -0
  165. package/dist/sandbox-hygiene.d.ts +45 -0
  166. package/dist/sandbox-hygiene.d.ts.map +1 -0
  167. package/dist/sandbox-provider.d.ts +193 -0
  168. package/dist/sandbox-provider.d.ts.map +1 -0
  169. package/dist/scorer-draft-push.d.ts +50 -0
  170. package/dist/scorer-draft-push.d.ts.map +1 -0
  171. package/dist/scorers-cli.d.ts +20 -0
  172. package/dist/scorers-cli.d.ts.map +1 -0
  173. package/dist/scorers-cli.js +22 -0
  174. package/dist/scorers-verify-parity-bridge.d.ts +16 -0
  175. package/dist/scorers-verify-parity-bridge.d.ts.map +1 -0
  176. package/dist/scorers-verify-parity-bridge.js +90 -0
  177. package/dist/scorers-verify-parity.d.ts +81 -0
  178. package/dist/scorers-verify-parity.d.ts.map +1 -0
  179. package/dist/scorers-verify-parity.js +502 -0
  180. package/dist/scores-accept-cli.d.ts +21 -0
  181. package/dist/scores-accept-cli.d.ts.map +1 -0
  182. package/dist/secret-redaction.d.ts +47 -0
  183. package/dist/secret-redaction.d.ts.map +1 -0
  184. package/dist/session-timeline.d.ts +54 -0
  185. package/dist/session-timeline.d.ts.map +1 -0
  186. package/dist/setup-prompt.d.ts +5 -0
  187. package/dist/setup-prompt.d.ts.map +1 -0
  188. package/dist/skill-installer.d.ts +68 -0
  189. package/dist/skill-installer.d.ts.map +1 -0
  190. package/dist/skilled-proposer-launch.d.ts +7 -0
  191. package/dist/skilled-proposer-launch.d.ts.map +1 -0
  192. package/dist/skilled-proposer-launch.js +60 -0
  193. package/dist/smoke.d.ts +102 -0
  194. package/dist/smoke.d.ts.map +1 -0
  195. package/dist/start-intent.d.ts +12 -0
  196. package/dist/start-intent.d.ts.map +1 -0
  197. package/dist/task-assignment-manifest.d.ts +15 -0
  198. package/dist/task-assignment-manifest.d.ts.map +1 -0
  199. package/dist/task-create-error.d.ts +18 -0
  200. package/dist/task-create-error.d.ts.map +1 -0
  201. package/dist/team-connectors-cli.d.ts +29 -0
  202. package/dist/team-connectors-cli.d.ts.map +1 -0
  203. package/dist/types.d.ts +39 -0
  204. package/dist/types.d.ts.map +1 -0
  205. package/dist/vercel-sandbox-provider.d.ts +156 -0
  206. package/dist/vercel-sandbox-provider.d.ts.map +1 -0
  207. package/dist/workbench-cli.d.ts +233 -0
  208. package/dist/workbench-cli.d.ts.map +1 -0
  209. package/dist/workspace-sync.d.ts +207 -0
  210. package/dist/workspace-sync.d.ts.map +1 -0
  211. package/dist/workspace.d.ts +92 -0
  212. package/dist/workspace.d.ts.map +1 -0
  213. package/dist/zip-draft-push.d.ts +55 -0
  214. package/dist/zip-draft-push.d.ts.map +1 -0
  215. package/package.json +14 -1
  216. package/requirements/README.md +138 -0
  217. package/requirements/skilled-proposer.in +66 -0
  218. package/requirements/skilled-proposer.lock +265 -0
  219. package/scripts/ensure-skilled-proposer-venv.mjs +5 -0
  220. package/scripts/refresh-skilled-proposer-lock.mjs +256 -0
  221. package/src/diff-comments-cli.ts +4 -0
  222. package/src/gepa-engine-dispatch.ts +42 -2
  223. package/src/gepa-python-paths.ts +6 -3
  224. package/src/help.ts +93 -23
  225. package/src/index.ts +99 -126
  226. package/src/instruction-set-gepa-launch.ts +28 -0
  227. package/src/instruction-set-loader.ts +37 -0
  228. package/src/instruction-set-manifest.ts +120 -0
  229. package/src/instruction-sets-cli.ts +306 -0
  230. package/src/model-configs-cli.ts +119 -0
  231. package/src/optimizations-promote-cli.ts +41 -0
  232. package/src/prompt-label-cli.ts +55 -0
  233. package/src/prompt-summary-table.ts +12 -0
  234. package/src/runner-instruction-set-contract.ts +64 -0
  235. package/src/scorers-cli.ts +40 -0
  236. package/src/scorers-verify-parity-bridge.ts +90 -0
  237. package/src/scorers-verify-parity.ts +564 -0
  238. package/src/skilled-proposer-launch.ts +89 -0
  239. package/src/skilled-proposer-venv-manager.mjs +757 -0
  240. package/tsconfig.json +2 -0
  241. package/vendor/orizu-gepa/src/orizu_gepa_connector/__init__.py +2 -1
  242. package/vendor/orizu-gepa/src/orizu_gepa_connector/adapter.py +8 -2
  243. package/vendor/orizu-gepa/src/orizu_gepa_connector/callbacks.py +17 -3
  244. package/vendor/orizu-gepa/src/orizu_gepa_connector/engine.py +100 -8
  245. package/vendor/orizu-gepa/src/orizu_gepa_connector/instruction_set_loader.py +52 -0
  246. package/vendor/orizu-gepa/src/orizu_gepa_connector/reflection.py +92 -12
  247. package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +100 -22
  248. package/vendor/orizu-gepa/src/orizu_gepa_connector/skilled_proposer_bridge.py +381 -0
  249. package/vendor/orizu-gepa/src/orizu_gepa_connector/stop_conditions.py +17 -0
  250. package/vendor/orizu-gepa/src/orizu_gepa_connector/translator.py +2 -1
  251. package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +52 -7
  252. package/vendor/orizu-gepa-python/src/orizu_gepa/local_log.py +4 -1
  253. package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +10 -5
  254. package/vendor/orizu-gepa-python/src/orizu_gepa/reflection.py +323 -68
  255. package/vendor/orizu-gepa-python/src/orizu_gepa/runner.py +207 -13
  256. package/vendor/skills/orizu-cli/SKILL.md +2 -0
  257. package/vendor/skills/orizu-cli/references/cli-reference.md +85 -1
  258. package/vendor/skills/orizu-cli/references/migrate-existing-gepa-setup.md +303 -0
  259. package/vendor/skills/orizu-cli/references/optimization-with-gepa.md +25 -0
  260. package/vendor/skills/orizu-cli/references/prompt-control-plane.md +108 -8
@@ -0,0 +1,502 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { extractErrorMessage } from './error-response.js';
6
+ import { getGepaPythonCommand } from './gepa-python-command.js';
7
+ import { authedFetch } from './http.js';
8
+ import { sanitizeTerminalText } from './json-response.js';
9
+ import { assertSnapshotManifestConfined, verifyRunnerDirRegistered } from './runner-dir-verify.js';
10
+ import { runnerInputPrompt } from './runner-instruction-set-contract.js';
11
+ import { PARITY_BRIDGE_SOURCE } from './scorers-verify-parity-bridge.js';
12
+ const USAGE = 'Usage: orizu scorers verify-parity --scorer-version <id> --dataset-version <id> --split-set <id> [--split <name>] --outputs <outputs.jsonl> --original <module:function> [--scorer-input-contract gepa|flat_row] [--scorer-candidate-field <row-field>] [--runner-dir <dir>] [--python <cmd>] [--tolerance <float>] [--limit <n>] [--json]';
13
+ function isRecord(value) {
14
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
15
+ }
16
+ /**
17
+ * LAST occurrence wins, mirroring run-gepa's scalar options
18
+ * (`gepa-engine-dispatch.ts` `translateOfficialOptions` assigns
19
+ * `connectorEnvironment[option.environment]` on every occurrence, so the final
20
+ * value overwrites earlier ones). First-wins would check parity against a
21
+ * wrapper's default while the optimization used the operator's override.
22
+ */
23
+ function option(args, name) {
24
+ const index = args.lastIndexOf(name);
25
+ const value = index === -1 ? undefined : args[index + 1];
26
+ return value === undefined || value.startsWith('--') ? null : value;
27
+ }
28
+ function messageOf(error) {
29
+ return sanitizeTerminalText(error instanceof Error ? error.message : String(error));
30
+ }
31
+ /**
32
+ * The score extraction rule, mirroring `orizu_gepa.optimizer._score_from_scorer`
33
+ * so the number this command compares is the number GEPA would optimize:
34
+ * a top-level `score` beats `model_response.score`, numeric strings coerce,
35
+ * non-finite is an error, and the result clamps to [0, 1].
36
+ */
37
+ const PYTHON_FLOAT_SPECIAL = /^[+-]?(inf(inity)?|nan)$/i;
38
+ const PYTHON_FLOAT_DECIMAL = /^[+-]?(\d(_?\d)*(\.(\d(_?\d)*)?)?|\.\d(_?\d)*)([eE][+-]?\d(_?\d)*)?$/;
39
+ /**
40
+ * Python's `float()` grammar, not JavaScript's `Number()`. Measured 2026-08-23:
41
+ * `float` REJECTS '0x10', '0b1', '0o7', '' and '1,5' (all of which `Number`
42
+ * happily converts or turns into 0), and ACCEPTS ' 0.5 ', '1e3', '1_0', '.5',
43
+ * '5.', '+3', 'inf', 'Infinity' and 'nan'. Anything `_score_from_scorer` would
44
+ * refuse during the real optimization must be refused here too, or this gate
45
+ * certifies a scorer run-gepa cannot use. `inf`/`nan` parse and are then
46
+ * rejected by the finite check, exactly as Python does.
47
+ */
48
+ export function pythonFloat(text) {
49
+ const trimmed = text.trim();
50
+ if (PYTHON_FLOAT_SPECIAL.test(trimmed))
51
+ return Number(trimmed.replace(/infinity/i, 'Infinity'));
52
+ if (!PYTHON_FLOAT_DECIMAL.test(trimmed))
53
+ return null;
54
+ const parsed = Number(trimmed.replace(/_/g, ''));
55
+ return Number.isNaN(parsed) ? null : parsed;
56
+ }
57
+ export function extractParityScore(value, label) {
58
+ if (!isRecord(value))
59
+ throw new Error(`${label} must be a JSON object with a numeric score`);
60
+ const source = !('score' in value) && isRecord(value.model_response)
61
+ ? { ...value.model_response, ...value }
62
+ : value;
63
+ let raw = source.score;
64
+ // Measured: `_score_from_scorer(extra={'score': True})` -> (1.0, None) and
65
+ // `False` -> (0.0, None) — bool is an int subclass, so Python's numeric branch
66
+ // accepts it. An exact-match DSPy metric returning a bool must compare, not error.
67
+ if (typeof raw === 'boolean')
68
+ raw = raw ? 1 : 0;
69
+ if (typeof raw === 'string') {
70
+ const parsed = pythonFloat(raw);
71
+ raw = parsed === null ? undefined : parsed;
72
+ }
73
+ if (typeof raw !== 'number')
74
+ throw new Error(`${label} must include a numeric score`);
75
+ if (!Number.isFinite(raw))
76
+ throw new Error(`${label} score must be finite`);
77
+ return { raw, score: clamp(raw) };
78
+ }
79
+ function clamp(value) {
80
+ return Math.max(0, Math.min(1, value));
81
+ }
82
+ function readOutputs(path) {
83
+ const byRowId = new Map();
84
+ const text = readFileSync(path, 'utf8');
85
+ let lineNumber = 0;
86
+ for (const line of text.split('\n')) {
87
+ lineNumber += 1;
88
+ if (!line.trim())
89
+ continue;
90
+ let parsed;
91
+ try {
92
+ parsed = JSON.parse(line);
93
+ }
94
+ catch {
95
+ throw new Error(`--outputs line ${lineNumber} is not valid JSON`);
96
+ }
97
+ if (!isRecord(parsed) || typeof parsed.row_id !== 'string' || !parsed.row_id) {
98
+ throw new Error(`--outputs line ${lineNumber} must have a non-empty string row_id`);
99
+ }
100
+ if (typeof parsed.model_output !== 'string') {
101
+ throw new Error(`--outputs line ${lineNumber} (row ${parsed.row_id}) must have a string model_output`);
102
+ }
103
+ if (byRowId.has(parsed.row_id)) {
104
+ throw new Error(`--outputs has duplicate row_id ${parsed.row_id} on line ${lineNumber}`);
105
+ }
106
+ byRowId.set(parsed.row_id, parsed.model_output);
107
+ }
108
+ return { byRowId };
109
+ }
110
+ /** One hour / 64 MiB: a bridge asking for more is a runaway, not a big split. */
111
+ const BRIDGE_MAX_TIMEOUT_MS = 60 * 60_000;
112
+ const BRIDGE_MAX_OUTPUT_BYTES = 64 * 1024 * 1024;
113
+ /**
114
+ * The Orizu side spawns the runner ONCE PER ROW, so each row gets the full
115
+ * per-row budget. The original side scores the whole sample in ONE bridge
116
+ * process, so handing it the per-row budget gives an N-row split 1/N of the
117
+ * time the Orizu side had: a 40-row split with an LLM-judge metric is SIGTERMed
118
+ * before it can write output.json and every row's work is lost (round-2 review).
119
+ * Scale by the sample length, bounded so a huge split cannot ask for forever.
120
+ */
121
+ export function bridgeBudget(perRowTimeoutMs, perRowMaxOutputBytes, sampleLength) {
122
+ // Never 0: spawnSync reads a 0 timeout/maxBuffer as "kill immediately".
123
+ const rows = Math.max(1, sampleLength);
124
+ return {
125
+ timeoutMs: Math.min(perRowTimeoutMs * rows, BRIDGE_MAX_TIMEOUT_MS),
126
+ maxOutputBytes: Math.min(perRowMaxOutputBytes * rows, BRIDGE_MAX_OUTPUT_BYTES),
127
+ };
128
+ }
129
+ const SCORER_INPUT_CONTRACTS = ['gepa', 'flat_row'];
130
+ const DEFAULT_CANDIDATE_OUTPUT_FIELD = 'model_output';
131
+ /**
132
+ * Resolve the scorer input contract and candidate-output row field with the
133
+ * SAME precedence `run-gepa` uses (`orizu_gepa.runner.resolve_scorer_input_contract`):
134
+ * explicit flag > runner manifest (`scorer_input_contract`, `candidate_output_field`)
135
+ * > defaults (`gepa`, `model_output`). Resolution is by PRESENCE, not truthiness,
136
+ * so a blank manifest value fails validation instead of silently defaulting.
137
+ *
138
+ * Parity may only be claimed for the payload run-gepa will actually send, so a
139
+ * contract that resolves to anything but `flat_row` is refused here rather than
140
+ * assumed: proving parity on a shape the optimization never uses is the exact
141
+ * silent-agreement failure this command exists to prevent.
142
+ */
143
+ export function resolveScorerInputContract(manifest, flagContract, flagField) {
144
+ const manifestContract = manifest.scorer_input_contract;
145
+ const contract = flagContract !== null ? flagContract : manifestContract !== undefined ? manifestContract : 'gepa';
146
+ if (typeof contract !== 'string' || !SCORER_INPUT_CONTRACTS.includes(contract)) {
147
+ throw new Error(`Unknown scorer input contract ${JSON.stringify(contract)}; expected one of: ${SCORER_INPUT_CONTRACTS.join(', ')}`);
148
+ }
149
+ if (contract !== 'flat_row') {
150
+ throw new Error(`The scorer runner's input contract resolves to '${contract}', but verify-parity can only prove parity ` +
151
+ "under the 'flat_row' payload. Declare \"scorer_input_contract\": \"flat_row\" in the runner manifest " +
152
+ '(or pass --scorer-input-contract flat_row), and pass the same --scorer-input-contract flat_row to ' +
153
+ '`orizu optimizations run-gepa` so the payload proven here is the payload the optimization sends.');
154
+ }
155
+ const explicitField = flagField !== null ? flagField : manifest.candidate_output_field;
156
+ const field = explicitField !== undefined ? explicitField : DEFAULT_CANDIDATE_OUTPUT_FIELD;
157
+ if (typeof field !== 'string' || field.trim() === '') {
158
+ throw new Error(`candidate output field must be a non-empty string (got ${JSON.stringify(field)} via ` +
159
+ '--scorer-candidate-field / manifest candidate_output_field)');
160
+ }
161
+ if (field === 'candidate_error') {
162
+ throw new Error("candidate output field 'candidate_error' is reserved for the adapter's error companion; choose a different row field");
163
+ }
164
+ return { contract, field };
165
+ }
166
+ /** The runner manifest's raw JSON, for the two keys `readRunnerManifest` does not surface. */
167
+ function readManifestExtras(runnerDir) {
168
+ const parsed = JSON.parse(readFileSync(join(runnerDir, 'manifest.json'), 'utf8'));
169
+ return isRecord(parsed) ? parsed : {};
170
+ }
171
+ /**
172
+ * The flat_row payload EXACTLY as `run-gepa` builds it
173
+ * (`orizu_gepa.runner.make_scorer_runner`, flat_row branch): the dataset row
174
+ * with the candidate output injected under the candidate field, `candidate_error`
175
+ * reserved, plus the top-level score-run companions. Not the bare `runners exec`
176
+ * shape, which reads `model_output` from a stale dataset field.
177
+ */
178
+ function flatRowInput(context, entry, candidate, candidateField) {
179
+ const scorerVersionId = context.scorer?.versionId ?? context.prompt.promptVersionId;
180
+ return {
181
+ model_output: candidate,
182
+ subject: { type: 'scorer_row', row_id: entry.id, scorer_version_id: scorerVersionId, prompt_version_id: context.prompt.promptVersionId },
183
+ scorer: { version_id: scorerVersionId, metric_key: context.scorer?.metricKey ?? 'score', higher_is_better: context.scorer?.higherIsBetter ?? true },
184
+ gepa: { candidate_id: 'verify-parity', candidate_raw_response: null, candidate_error: null },
185
+ row: { ...entry.row, [candidateField]: candidate, candidate_error: null },
186
+ prompt: runnerInputPrompt(context.prompt),
187
+ prompt_version_id: context.prompt.promptVersionId,
188
+ runner_version_id: context.prompt.runnerVersionId,
189
+ run_id: null,
190
+ };
191
+ }
192
+ export async function verifyScorerParityCommand(args, io) {
193
+ const tolerance = Number(option(args, '--tolerance') ?? '0');
194
+ const clamped = [];
195
+ const mismatches = [];
196
+ const errors = [];
197
+ // Resolved from the runner manifest once it is readable; reported either way so
198
+ // the operator can see which payload parity was (or was not) proven under.
199
+ let resolved = null;
200
+ const fail = (message) => {
201
+ io.printErr(sanitizeTerminalText(message));
202
+ if (io.json) {
203
+ // Carry the evidence already collected: row errors gathered before the
204
+ // failure are exactly what the agent has to fix next, and dropping them
205
+ // leaves an empty `errors` behind an opaque "could not run".
206
+ io.print(JSON.stringify({
207
+ parity: false, compared: 0, mismatches: [], errors, tolerance, clamped,
208
+ scorer_input_contract: resolved?.contract ?? null,
209
+ candidate_output_field: resolved?.field ?? null,
210
+ error: sanitizeTerminalText(message),
211
+ }));
212
+ }
213
+ return 2;
214
+ };
215
+ let python;
216
+ let rest;
217
+ try {
218
+ const pythonCommand = getGepaPythonCommand(args, process.env);
219
+ python = pythonCommand.python;
220
+ rest = pythonCommand.args;
221
+ }
222
+ catch (error) {
223
+ return fail(messageOf(error));
224
+ }
225
+ const scorerVersion = option(rest, '--scorer-version');
226
+ const datasetVersion = option(rest, '--dataset-version');
227
+ const splitSet = option(rest, '--split-set');
228
+ const split = option(rest, '--split') ?? 'validation';
229
+ const outputsPath = option(rest, '--outputs');
230
+ const original = option(rest, '--original');
231
+ const contractFlag = option(rest, '--scorer-input-contract');
232
+ const candidateFieldFlag = option(rest, '--scorer-candidate-field');
233
+ const runnerDirArg = option(rest, '--runner-dir');
234
+ const limitArg = option(rest, '--limit');
235
+ if (!scorerVersion || !datasetVersion || !splitSet || !outputsPath || !original)
236
+ return fail(USAGE);
237
+ if (!Number.isFinite(tolerance) || tolerance < 0)
238
+ return fail('--tolerance must be a finite number >= 0');
239
+ const limit = limitArg === null ? null : Number(limitArg);
240
+ if (limit !== null && (!Number.isInteger(limit) || limit < 1))
241
+ return fail('--limit must be a positive integer');
242
+ let outputs;
243
+ try {
244
+ outputs = readOutputs(outputsPath);
245
+ }
246
+ catch (error) {
247
+ return fail(messageOf(error));
248
+ }
249
+ const fetcher = io.fetcher ?? authedFetch;
250
+ const query = new URLSearchParams({ scorerVersion, datasetVersion, splitSet, split });
251
+ let contextResponse;
252
+ try {
253
+ // A transport rejection (server unreachable) is a check that COULD NOT RUN,
254
+ // not a mismatch: uncaught it escapes the command, exits 1 and prints no
255
+ // --json failure report at all.
256
+ contextResponse = await fetcher(`/api/cli/runners/exec-context?${query.toString()}`);
257
+ }
258
+ catch (error) {
259
+ return fail(`Failed to reach the server for the runner execution context: ${messageOf(error)}`);
260
+ }
261
+ if (!contextResponse.ok) {
262
+ return fail(`Failed to fetch runner execution context: ${await extractErrorMessage(contextResponse)}`);
263
+ }
264
+ let context;
265
+ try {
266
+ context = await contextResponse.json();
267
+ }
268
+ catch (error) {
269
+ return fail(`Runner exec context was not valid JSON: ${messageOf(error)}`);
270
+ }
271
+ if (!Array.isArray(context.rows))
272
+ return fail('Runner exec context did not return rows');
273
+ if (context.instructionSet !== undefined && context.instructionSet !== null) {
274
+ return fail('This scorer is backed by an instruction set, and verify-parity cannot yet prove parity for one: ' +
275
+ 'run-gepa sends the runner an `instruction_set` payload key and an ORIZU_INSTRUCTION_SET_DIR that ' +
276
+ 'this command does not build (ALI-1556). Refusing rather than proving parity for a payload the ' +
277
+ 'optimization will not send.');
278
+ }
279
+ const sample = limit === null ? context.rows : context.rows.slice(0, limit);
280
+ if (sample.length === 0) {
281
+ return fail('The selected split has no rows, so parity cannot be proven. Pick a split with rows.');
282
+ }
283
+ const sampleIds = new Set(sample.map(entry => entry.id));
284
+ // Outputs must COVER the limited sample, but membership is checked against the
285
+ // whole partition: `--limit` is a smoke check over the head of the same
286
+ // outputs.jsonl the recipe builds for the full split, not a reason to hand-trim it.
287
+ const partitionIds = new Set(context.rows.map(entry => entry.id));
288
+ for (const entry of sample) {
289
+ if (!outputs.byRowId.has(entry.id))
290
+ return fail(`--outputs has no model_output for sample row ${entry.id}`);
291
+ }
292
+ for (const rowId of outputs.byRowId.keys()) {
293
+ if (!partitionIds.has(rowId))
294
+ return fail(`--outputs row_id ${rowId} is not in the ${split} rows of this split set`);
295
+ }
296
+ const runnerVersionId = context.prompt?.runnerVersionId;
297
+ if (!runnerVersionId)
298
+ return fail('Runner exec context did not resolve a runner version id for this scorer');
299
+ let runner;
300
+ try {
301
+ runner = runnerDirArg
302
+ ? await (async () => {
303
+ const verified = await verifyRunnerDirRegistered({ runnerVersionId, dir: runnerDirArg, flag: '--runner-dir', fetcher: io.fetcher });
304
+ return { runnerDir: verified.snapshotDir, cleanup: verified.cleanup };
305
+ })()
306
+ : await io.materializeRunnerVersion(runnerVersionId);
307
+ }
308
+ catch (error) {
309
+ return fail(messageOf(error));
310
+ }
311
+ const orizuScores = new Map();
312
+ // Resolved from the runner manifest below; the ORIGINAL side must see the row
313
+ // injected at the SAME field, or the two sides read different candidates.
314
+ let candidateField = DEFAULT_CANDIDATE_OUTPUT_FIELD;
315
+ try {
316
+ let manifest;
317
+ try {
318
+ assertSnapshotManifestConfined(runner.runnerDir, runnerDirArg ? '--runner-dir' : '--runner-version');
319
+ manifest = io.readRunnerManifest(runner.runnerDir);
320
+ resolved = resolveScorerInputContract(readManifestExtras(runner.runnerDir), contractFlag, candidateFieldFlag);
321
+ candidateField = resolved.field;
322
+ // run-gepa overwrites a colliding key too (`{**source_row, field: ...}`,
323
+ // runner.py:449-458), but the CUSTOMER'S ORIGINAL run never did: their
324
+ // metric read the row's own value. Injecting over it hands both sides the
325
+ // candidate, they agree, and the agreement says nothing about the run being
326
+ // migrated. Refuse and make the operator pick a free field.
327
+ const collision = sample.find(entry => Object.prototype.hasOwnProperty.call(entry.row, candidateField));
328
+ if (collision) {
329
+ throw new Error(`Dataset row ${collision.id} already has a '${candidateField}' field, so injecting the candidate ` +
330
+ 'there would replace a value the original metric reads. run-gepa overwrites it silently, but the ' +
331
+ "customer's own run never did, so parity proven this way would be meaningless. Pass " +
332
+ '--scorer-candidate-field <a row field that does not exist> (and the same value to run-gepa).');
333
+ }
334
+ }
335
+ catch (error) {
336
+ return fail(messageOf(error));
337
+ }
338
+ for (const entry of sample) {
339
+ const candidate = outputs.byRowId.get(entry.id);
340
+ const tempDir = mkdtempSync(join(tmpdir(), 'orizu-parity-runner-'));
341
+ const inputPath = join(tempDir, 'input.json');
342
+ const outputPath = join(tempDir, 'output.json');
343
+ try {
344
+ writeFileSync(inputPath, JSON.stringify(flatRowInput(context, entry, candidate, candidateField)));
345
+ const result = spawnSync(manifest.command[0], manifest.command.slice(1), {
346
+ cwd: runner.runnerDir,
347
+ env: io.runnerSubprocessEnv(inputPath, outputPath),
348
+ encoding: 'utf8',
349
+ maxBuffer: io.maxOutputBytes,
350
+ timeout: io.timeoutMs,
351
+ });
352
+ if (result.error)
353
+ throw result.error;
354
+ if (result.status !== 0) {
355
+ throw new Error(`runner exited with code ${result.status}: ${(result.stderr || result.stdout || '').slice(0, 500)}`);
356
+ }
357
+ if (statSync(outputPath).size > io.maxOutputBytes)
358
+ throw new Error('runner output.json exceeds the output size limit');
359
+ const runnerOutput = JSON.parse(readFileSync(outputPath, 'utf8'));
360
+ // `run_file_contract_runner` preserves output.json `error` as
361
+ // RunnerCallResult.error and run-gepa records it on the RowEvaluation, so a
362
+ // seed answering `{score, error}` on every row is rejected as an all-error
363
+ // scorer. Accepting the score here would certify exactly that scorer.
364
+ const reportedError = isRecord(runnerOutput) ? runnerOutput.error : undefined;
365
+ if (typeof reportedError === 'string' && reportedError.trim() !== '') {
366
+ throw new Error(`runner reported an error alongside its score: ${reportedError}`);
367
+ }
368
+ const extracted = extractParityScore(runnerOutput, 'Runner output.json');
369
+ orizuScores.set(entry.id, extracted);
370
+ if (extracted.raw !== extracted.score)
371
+ clamped.push({ row_id: entry.id, side: 'orizu', raw: extracted.raw, score: extracted.score });
372
+ }
373
+ catch (error) {
374
+ errors.push({ row_id: entry.id, side: 'orizu', message: messageOf(error) });
375
+ }
376
+ finally {
377
+ rmSync(tempDir, { recursive: true, force: true });
378
+ }
379
+ }
380
+ }
381
+ finally {
382
+ runner.cleanup();
383
+ }
384
+ const bridgeDir = mkdtempSync(join(tmpdir(), 'orizu-parity-bridge-'));
385
+ const originalScores = new Map();
386
+ try {
387
+ const bridgePath = join(bridgeDir, 'orizu_parity_bridge.py');
388
+ const bridgeInput = join(bridgeDir, 'input.json');
389
+ const bridgeOutput = join(bridgeDir, 'output.json');
390
+ writeFileSync(bridgePath, io.bridgeSource ?? PARITY_BRIDGE_SOURCE);
391
+ writeFileSync(bridgeInput, JSON.stringify({
392
+ rows: sample.map(entry => {
393
+ const candidate = outputs.byRowId.get(entry.id);
394
+ return { row_id: entry.id, row: { ...entry.row, [candidateField]: candidate, candidate_error: null }, model_output: candidate };
395
+ }),
396
+ }));
397
+ // The customer's full environment, inherited UNSCRUBBED: it is their
398
+ // metric in their process, exactly as their own script would run it.
399
+ // (The runner side stays scrubbed by runnerSubprocessEnv.)
400
+ const budget = bridgeBudget(io.timeoutMs, io.maxOutputBytes, sample.length);
401
+ const result = spawnSync(python, [bridgePath], {
402
+ cwd: process.cwd(),
403
+ env: { ...process.env, ORIZU_PARITY_INPUT_PATH: bridgeInput, ORIZU_PARITY_OUTPUT_PATH: bridgeOutput, ORIZU_PARITY_ORIGINAL: original },
404
+ encoding: 'utf8',
405
+ maxBuffer: budget.maxOutputBytes,
406
+ timeout: budget.timeoutMs,
407
+ });
408
+ if (result.error)
409
+ return fail(`Failed to run the original metric with '${python}': ${messageOf(result.error)}`);
410
+ if (result.status !== 0) {
411
+ return fail(`The original metric could not run: ${(result.stderr || result.stdout || '').trim().slice(0, 1000)}`);
412
+ }
413
+ let bridgeScores;
414
+ try {
415
+ bridgeScores = JSON.parse(readFileSync(bridgeOutput, 'utf8')).scores;
416
+ }
417
+ catch (error) {
418
+ return fail(`The original metric produced no readable result: ${messageOf(error)}`);
419
+ }
420
+ if (!Array.isArray(bridgeScores))
421
+ return fail('The original metric produced no scores array');
422
+ for (const item of bridgeScores) {
423
+ if (!isRecord(item) || typeof item.row_id !== 'string') {
424
+ errors.push({ row_id: null, side: 'original', message: 'original result entry has no string row_id' });
425
+ continue;
426
+ }
427
+ if (!sampleIds.has(item.row_id)) {
428
+ errors.push({ row_id: item.row_id, side: 'original', message: `original scored row_id ${item.row_id}, which is not in the sample` });
429
+ continue;
430
+ }
431
+ if (typeof item.error === 'string') {
432
+ errors.push({ row_id: item.row_id, side: 'original', message: sanitizeTerminalText(item.error) });
433
+ continue;
434
+ }
435
+ try {
436
+ const extracted = extractParityScore({ score: item.score }, `Original metric result for row ${item.row_id}`);
437
+ originalScores.set(item.row_id, extracted);
438
+ if (extracted.raw !== extracted.score)
439
+ clamped.push({ row_id: item.row_id, side: 'original', raw: extracted.raw, score: extracted.score });
440
+ }
441
+ catch (error) {
442
+ errors.push({ row_id: item.row_id, side: 'original', message: messageOf(error) });
443
+ }
444
+ }
445
+ }
446
+ finally {
447
+ rmSync(bridgeDir, { recursive: true, force: true });
448
+ }
449
+ // Pairing is by row_id on BOTH sides, never positional: the bridge is a
450
+ // separate process whose result order is not this loop's order.
451
+ let compared = 0;
452
+ for (const entry of sample) {
453
+ const orizu = orizuScores.get(entry.id);
454
+ const originalScore = originalScores.get(entry.id);
455
+ if (!orizu)
456
+ continue;
457
+ if (!originalScore) {
458
+ if (!errors.some(item => item.row_id === entry.id && item.side === 'original')) {
459
+ errors.push({ row_id: entry.id, side: 'original', message: 'the original metric returned no score for this row' });
460
+ }
461
+ continue;
462
+ }
463
+ compared += 1;
464
+ if (Math.abs(orizu.score - originalScore.score) > tolerance) {
465
+ mismatches.push({ row_id: entry.id, orizu: orizu.score, original: originalScore.score });
466
+ }
467
+ }
468
+ // `compared >= 1` is NOT a conjunct here: the empty-split refusal guarantees
469
+ // sample.length >= 1 and every row that fails to pair pushes an error, so
470
+ // `compared === 0` already implies `errors.length > 0`. No test can reach a
471
+ // zero-compared, zero-error state, so asserting it would be an unkillable check.
472
+ const clean = mismatches.length === 0 && errors.length === 0;
473
+ const total = context.rows.length;
474
+ // A limited run never looked at rows compared+1..total, so a mismatch there is
475
+ // indistinguishable from a proof unless the two results are named differently.
476
+ const parity = clean && compared === total;
477
+ if (io.json) {
478
+ io.print(JSON.stringify({
479
+ parity, compared, scope: { compared, total }, mismatches, errors, tolerance, clamped,
480
+ scorer_input_contract: resolved?.contract ?? null,
481
+ candidate_output_field: resolved?.field ?? null,
482
+ }));
483
+ }
484
+ else {
485
+ io.print(parity
486
+ ? `Parity proven on all ${total} rows (tolerance ${tolerance}).`
487
+ : clean
488
+ ? `Smoke check passed on ${compared} of ${total} rows — rerun without --limit to prove parity.`
489
+ : `Parity NOT proven: ${compared} of ${total} rows compared, ${mismatches.length} mismatches, ${errors.length} errors (tolerance ${tolerance}).`);
490
+ for (const item of mismatches)
491
+ io.print(`mismatch ${sanitizeTerminalText(item.row_id)}: orizu ${item.orizu} vs original ${item.original}`);
492
+ for (const item of errors)
493
+ io.print(`error ${sanitizeTerminalText(item.row_id ?? '-')} (${item.side}): ${item.message}`);
494
+ for (const item of clamped)
495
+ io.print(`clamped ${sanitizeTerminalText(item.row_id)} (${item.side}): raw ${item.raw} -> ${item.score}`);
496
+ }
497
+ // Exit 0 covers a clean full proof AND a clean smoke check; the report is what
498
+ // distinguishes them, so a limited run cannot be mistaken for a proof by a
499
+ // reader while still being usable as a fast pre-flight. Exit 2 is reserved for
500
+ // the fail() paths above — checks that could not run at all.
501
+ return clean ? 0 : 1;
502
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ALI-1175: `orizu scores accept <score-run-id>` — record a human curator's
3
+ * explicit acceptance of an agent-reported score run as trusted evidence.
4
+ *
5
+ * The decision itself is enforced in the database: the
6
+ * accept_score_run_evidence RPC records actor + timestamp, refuses the agent
7
+ * principal, keeps the first acceptance, and only applies to succeeded
8
+ * agent-reported rows. This module is display plumbing.
9
+ *
10
+ * Lives outside index.ts per the CLI index line ratchet (ALI-976): owns its
11
+ * own argument parsing and printing via injected io.
12
+ */
13
+ export interface ScoresAcceptIo {
14
+ json: boolean;
15
+ print: (line: string) => void;
16
+ resolveProjectSlug: (projectArg: string | null) => Promise<string>;
17
+ fetcher?: (path: string, init?: RequestInit) => Promise<Response>;
18
+ }
19
+ /** args = cliArgs after `scores accept` (positional score-run id + flags). */
20
+ export declare function acceptScoreRunCommand(args: string[], io: ScoresAcceptIo): Promise<void>;
21
+ //# sourceMappingURL=scores-accept-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scores-accept-cli.d.ts","sourceRoot":"","sources":["../src/scores-accept-cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7B,kBAAkB,EAAE,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IAClE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAClE;AA4DD,8EAA8E;AAC9E,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC7F"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Secret redaction for hosted-sandbox bootstrap (G3 / ALI-1004).
3
+ *
4
+ * The bootstrap orchestrator records lifecycle events to the RunAPI and may
5
+ * capture hook / install output. None of that may ever carry the Orizu bearer,
6
+ * a minted repo token, or any token-shaped value. `redactSecrets` scrubs a
7
+ * value two ways:
8
+ *
9
+ * 1. exact-match on caller-supplied secrets (the bearer, any token the
10
+ * orchestrator itself minted) — these are known verbatim, so replace them
11
+ * wherever they appear, even mid-string;
12
+ * 2. shape-match on the well-known credential prefixes GitHub, Orizu, and the
13
+ * model providers emit (ghs_/ghp_/gho_/ghu_/ghr_/github_pat_ installation+PAT
14
+ * tokens, Orizu's own `orizu_pat_`/`orizu_agent_` tokens, Anthropic
15
+ * `sk-ant-…` and OpenAI `sk-proj-…`/legacy `sk-…` model keys, and a generic
16
+ * `Bearer <blob>` header) — a defense-in-depth net for tokens the
17
+ * orchestrator never held a copy of (e.g. a model key echoed by a customer
18
+ * setup hook or an OpenCode error message).
19
+ *
20
+ * SHAPES ARE DEFENSE IN DEPTH, NOT THE GUARANTEE. The guaranteed scrub is the
21
+ * exact-match pass (1): any secret that will be present in the sandbox MUST be
22
+ * handed to the caller's secret list (the RunEventSink's `redactSecretsList`)
23
+ * so it is stripped verbatim wherever it appears, even mid-string and even when
24
+ * its shape is unknown. The shape patterns below only catch credentials no one
25
+ * declared; do not rely on them as the primary control.
26
+ *
27
+ * The function is pure and recursive over JSON-ish structures so an entire
28
+ * event payload can be passed through before it is sent to the RunAPI.
29
+ */
30
+ export declare const REDACTION_PLACEHOLDER = "[redacted]";
31
+ export interface RedactOptions {
32
+ /** Verbatim secret values to strip wherever they appear (bearer, tokens). */
33
+ secrets?: readonly string[];
34
+ }
35
+ /**
36
+ * Deep-redact a JSON-ish value. Strings are scrubbed; arrays/objects are walked;
37
+ * everything else is returned as-is. Object KEYS are never treated as secrets
38
+ * (a key literally named after a token is not the token), but their VALUES are.
39
+ */
40
+ export declare function redactSecrets<T>(value: T, options?: RedactOptions): T;
41
+ /**
42
+ * True when a string still contains any of the supplied secrets or a
43
+ * token-shaped substring — the assertion tests use it to prove an event payload
44
+ * left the orchestrator clean.
45
+ */
46
+ export declare function containsSecret(input: string, secrets: readonly string[]): boolean;
47
+ //# sourceMappingURL=secret-redaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-redaction.d.ts","sourceRoot":"","sources":["../src/secret-redaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,eAAO,MAAM,qBAAqB,eAAe,CAAA;AAgDjD,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAC5B;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,CAAC,CAGzE;AAmBD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQjF"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * reconstructSessionTimeline — the G6 audit-replay proof (ALI-1007).
3
+ *
4
+ * Acceptance ("replaying run events reconstructs the session"): given ONLY the
5
+ * server-side, append-only workbench run events (each `{ sequence, eventType }`,
6
+ * fetched from the events cursor — nothing client-held), rebuild the ordered
7
+ * session timeline and assert its COMPLETENESS as a behavioral proof:
8
+ * - every lifecycle PHASE is represented (bootstrap → ready → work → completion);
9
+ * - sequences are gapless, or the gaps are enumerated (the sink documents that
10
+ * an ambiguous append burns a sequence, so a gap is tolerated-but-recorded);
11
+ * - the terminal status is derivable (the run row's terminal status, itself
12
+ * server-side, corroborated by the pre-terminal `agent_transcript` marker).
13
+ *
14
+ * Pure: no I/O, no clock. The caller supplies the events (and the server run
15
+ * status, which is also server-side) so this composes into an e2e test or a
16
+ * server-side watchdog without a network.
17
+ */
18
+ export interface TimelineEvent {
19
+ sequence: number;
20
+ eventType: string;
21
+ payload?: Record<string, unknown>;
22
+ }
23
+ export declare const SESSION_PHASES: readonly ["bootstrap", "ready", "work", "completion"];
24
+ export type SessionPhase = (typeof SESSION_PHASES)[number];
25
+ export declare const REQUIRED_EVENT_PHASES: readonly SessionPhase[];
26
+ export interface TimelineEntry {
27
+ sequence: number;
28
+ eventType: string;
29
+ phase: SessionPhase | null;
30
+ }
31
+ export interface ReconstructedTimeline {
32
+ /** Every event, ordered by sequence, each tagged with its lifecycle phase. */
33
+ ordered: TimelineEntry[];
34
+ /** Which lifecycle phases are represented. */
35
+ phasesPresent: Record<SessionPhase, boolean>;
36
+ /** Phases with no representative event (empty when complete). */
37
+ missingPhases: SessionPhase[];
38
+ /** Enumerated sequence gaps as [afterSequence, nextSequence] pairs. */
39
+ sequenceGaps: Array<[number, number]>;
40
+ /** True when sequences are strictly contiguous from the first observed. */
41
+ gapless: boolean;
42
+ /** The server run's terminal status, when one was supplied and is terminal. */
43
+ terminalStatus: string | null;
44
+ /** A terminal status is derivable (the server run status is terminal). */
45
+ terminalDerivable: boolean;
46
+ /** All REQUIRED event phases present AND a terminal is derivable. Gaps do NOT
47
+ * fail this — they are enumerated in `sequenceGaps` (tolerated-but-documented
48
+ * per the sink). */
49
+ complete: boolean;
50
+ }
51
+ export declare function reconstructSessionTimeline(events: readonly TimelineEvent[], opts?: {
52
+ runStatus?: string | null;
53
+ }): ReconstructedTimeline;
54
+ //# sourceMappingURL=session-timeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-timeline.d.ts","sourceRoot":"","sources":["../src/session-timeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAOD,eAAO,MAAM,cAAc,uDAAwD,CAAA;AACnF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAI1D,eAAO,MAAM,qBAAqB,EAAE,SAAS,YAAY,EAAmC,CAAA;AA6C5F,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC,YAAY,EAAE,OAAO,CAAC,CAAA;IAC5C,iEAAiE;IACjE,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,uEAAuE;IACvE,YAAY,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IACrC,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAA;IAChB,+EAA+E;IAC/E,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,0EAA0E;IAC1E,iBAAiB,EAAE,OAAO,CAAA;IAC1B;;yBAEqB;IACrB,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,aAAa,EAAE,EAChC,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GACvC,qBAAqB,CA0CvB"}
@@ -0,0 +1,5 @@
1
+ export interface AgentSetupPromptOptions {
2
+ workspacePath?: string | null;
3
+ }
4
+ export declare function renderAgentSetupPrompt(options?: AgentSetupPromptOptions): string;
5
+ //# sourceMappingURL=setup-prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-prompt.d.ts","sourceRoot":"","sources":["../src/setup-prompt.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,MAAM,CAwBhF"}