@skill-harness/cli 0.24.3 → 0.25.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.
@@ -0,0 +1,2478 @@
1
+ // experiments/decision-shadow/main.mjs
2
+ import { createHash as createHash5 } from "node:crypto";
3
+ import { open as open4, readFile as readFile2, stat } from "node:fs/promises";
4
+ import { resolve as resolve2 } from "node:path";
5
+
6
+ // experiments/decision-shadow/research-commands.mjs
7
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
8
+ import { join as join2, resolve } from "node:path";
9
+
10
+ // experiments/decision-shadow/learning-data.mjs
11
+ import { createHash as createHash2 } from "node:crypto";
12
+ import { constants } from "node:fs";
13
+ import { open, mkdir, writeFile } from "node:fs/promises";
14
+ import { isAbsolute, join, normalize } from "node:path";
15
+
16
+ // experiments/decision-shadow/dataset.mjs
17
+ import { createHash } from "node:crypto";
18
+ var CASE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
19
+ var SHA256 = /^[0-9a-f]{64}$/;
20
+ var MAX_CASES = 100;
21
+ var MAX_RECORD_ID = 256;
22
+ var MAX_ACTOR = 256;
23
+ function plainObject(value, path) {
24
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
25
+ throw new TypeError(`${path} must be an object`);
26
+ }
27
+ return value;
28
+ }
29
+ function exactKeys(value, expected, path) {
30
+ const actual = Object.keys(value).sort();
31
+ const wanted = [...expected].sort();
32
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
33
+ throw new TypeError(`${path} must contain exactly: ${wanted.join(", ")}`);
34
+ }
35
+ }
36
+ function boundedString(value, min, max, path) {
37
+ if (typeof value !== "string") throw new TypeError(`${path} must be a string`);
38
+ const length = Array.from(value).length;
39
+ if (length < min || length > max) {
40
+ throw new RangeError(`${path} must contain ${min}..${max} characters`);
41
+ }
42
+ return value;
43
+ }
44
+ function sha(value, path) {
45
+ if (typeof value !== "string" || !SHA256.test(value)) {
46
+ throw new TypeError(`${path} must be a lowercase SHA-256 hex digest`);
47
+ }
48
+ return value;
49
+ }
50
+ function canonical(value) {
51
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
52
+ if (value !== null && typeof value === "object") {
53
+ return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
54
+ }
55
+ return JSON.stringify(value);
56
+ }
57
+ function hashCase(value) {
58
+ return createHash("sha256").update(canonical(value), "utf8").digest("hex");
59
+ }
60
+ function parseCases(value) {
61
+ const root = plainObject(value, "dataset");
62
+ exactKeys(root, ["schema", "cases"], "dataset");
63
+ if (root.schema !== 1) throw new TypeError("dataset.schema must be 1");
64
+ if (!Array.isArray(root.cases)) throw new TypeError("dataset.cases must be an array");
65
+ if (root.cases.length < 1 || root.cases.length > MAX_CASES) {
66
+ throw new RangeError(`dataset.cases must contain 1..${MAX_CASES} cases`);
67
+ }
68
+ const ids = /* @__PURE__ */ new Set();
69
+ return root.cases.map((candidate, index) => {
70
+ const path = `dataset.cases[${index}]`;
71
+ const item = plainObject(candidate, path);
72
+ exactKeys(item, ["id", "input", "question", "provenance", "source", "visibility"], path);
73
+ if (typeof item.id !== "string" || !CASE_ID.test(item.id)) {
74
+ throw new TypeError(`${path}.id has an invalid format`);
75
+ }
76
+ if (ids.has(item.id)) throw new TypeError(`duplicate case id: ${item.id}`);
77
+ ids.add(item.id);
78
+ const source = plainObject(item.source, `${path}.source`);
79
+ exactKeys(source, ["sha256", "recordId"], `${path}.source`);
80
+ const validated = {
81
+ id: item.id,
82
+ input: boundedString(item.input, 1, 16e3, `${path}.input`),
83
+ question: boundedString(item.question, 1, 4e3, `${path}.question`),
84
+ provenance: item.provenance,
85
+ source: {
86
+ sha256: sha(source.sha256, `${path}.source.sha256`),
87
+ recordId: boundedString(source.recordId, 1, MAX_RECORD_ID, `${path}.source.recordId`)
88
+ },
89
+ visibility: item.visibility
90
+ };
91
+ if (validated.source.recordId.trim().length === 0) {
92
+ throw new TypeError(`${path}.source.recordId must not be blank`);
93
+ }
94
+ if (!["synthetic", "observed"].includes(validated.provenance)) {
95
+ throw new TypeError(`${path}.provenance is invalid`);
96
+ }
97
+ if (!["public", "redacted"].includes(validated.visibility)) {
98
+ throw new TypeError(`${path}.visibility is invalid`);
99
+ }
100
+ return { ...validated, hash: hashCase(validated) };
101
+ });
102
+ }
103
+ function parseLabels(value, cases) {
104
+ if (!Array.isArray(cases)) throw new TypeError("cases must be an array");
105
+ const caseById = new Map(cases.map((item) => [item.id, item]));
106
+ if (caseById.size !== cases.length) throw new TypeError("cases contain duplicate ids");
107
+ const root = plainObject(value, "label dataset");
108
+ exactKeys(root, ["schema", "labels"], "label dataset");
109
+ if (root.schema !== 1) throw new TypeError("label dataset.schema must be 1");
110
+ if (!Array.isArray(root.labels)) throw new TypeError("label dataset.labels must be an array");
111
+ const seen = /* @__PURE__ */ new Set();
112
+ return root.labels.map((candidate, index) => {
113
+ const path = `label dataset.labels[${index}]`;
114
+ const item = plainObject(candidate, path);
115
+ exactKeys(item, ["caseId", "caseHash", "value", "kind", "actor", "evidenceSha256", "independent"], path);
116
+ if (typeof item.caseId !== "string" || !CASE_ID.test(item.caseId)) {
117
+ throw new TypeError(`${path}.caseId has an invalid format`);
118
+ }
119
+ if (seen.has(item.caseId)) throw new TypeError(`duplicate label for case: ${item.caseId}`);
120
+ seen.add(item.caseId);
121
+ const matched = caseById.get(item.caseId);
122
+ if (!matched) throw new TypeError(`label refers to unknown case: ${item.caseId}`);
123
+ sha(item.caseHash, `${path}.caseHash`);
124
+ if (item.caseHash !== matched.hash) throw new TypeError(`stale case hash for label: ${item.caseId}`);
125
+ if (typeof item.value !== "boolean") throw new TypeError(`${path}.value must be boolean`);
126
+ if (!["human", "test"].includes(item.kind)) throw new TypeError(`${path}.kind is invalid`);
127
+ if (item.independent !== true) throw new TypeError(`${path}.independent must be true`);
128
+ const validated = {
129
+ caseId: item.caseId,
130
+ caseHash: item.caseHash,
131
+ value: item.value,
132
+ kind: item.kind,
133
+ actor: boundedString(item.actor, 1, MAX_ACTOR, `${path}.actor`),
134
+ evidenceSha256: sha(item.evidenceSha256, `${path}.evidenceSha256`),
135
+ independent: true
136
+ };
137
+ if (validated.actor.trim().length === 0) {
138
+ throw new TypeError(`${path}.actor must not be blank`);
139
+ }
140
+ return validated;
141
+ });
142
+ }
143
+ function scorePredictions(cases, labels, records) {
144
+ if (!Array.isArray(cases) || !Array.isArray(labels) || !Array.isArray(records)) {
145
+ throw new TypeError("cases, labels, and records must be arrays");
146
+ }
147
+ const caseById = new Map(cases.map((item) => [item.id, item]));
148
+ if (caseById.size !== cases.length) throw new TypeError("cases contain duplicate ids");
149
+ const validatedLabels = parseLabels({ schema: 1, labels }, cases);
150
+ const labelById = new Map(validatedLabels.map((item) => [item.caseId, item]));
151
+ const recordById = /* @__PURE__ */ new Map();
152
+ for (const [index, candidate] of records.entries()) {
153
+ const path = `records[${index}]`;
154
+ const record = plainObject(candidate, path);
155
+ exactKeys(record, ["caseId", "caseHash", "status", "probability"], path);
156
+ if (recordById.has(record.caseId)) throw new TypeError(`duplicate prediction for case: ${record.caseId}`);
157
+ const matched = caseById.get(record.caseId);
158
+ if (!matched) throw new TypeError(`prediction refers to unknown case: ${record.caseId}`);
159
+ if (record.caseHash !== matched.hash) throw new TypeError(`stale case hash for prediction: ${record.caseId}`);
160
+ if (!["answered", "refused", "error"].includes(record.status)) {
161
+ throw new TypeError(`${path}.status is invalid`);
162
+ }
163
+ if (record.status === "answered") {
164
+ if (typeof record.probability !== "number" || !Number.isFinite(record.probability) || record.probability < 0 || record.probability > 1) {
165
+ throw new TypeError(`${path}.probability must be finite and within 0..1 when answered`);
166
+ }
167
+ } else if (record.probability !== null) {
168
+ throw new TypeError(`${path}.probability must be null when not answered`);
169
+ }
170
+ recordById.set(record.caseId, record);
171
+ }
172
+ let answered = 0;
173
+ let refused = 0;
174
+ let errors = 0;
175
+ let scored = 0;
176
+ let correct = 0;
177
+ let brierTotal = 0;
178
+ let falsePositives = 0;
179
+ let falseNegatives = 0;
180
+ for (const record of recordById.values()) {
181
+ if (record.status === "answered") answered++;
182
+ else if (record.status === "refused") refused++;
183
+ else errors++;
184
+ const label = labelById.get(record.caseId);
185
+ if (!label || record.status !== "answered") continue;
186
+ scored++;
187
+ const predicted = record.probability >= 0.5;
188
+ if (predicted === label.value) correct++;
189
+ else if (predicted) falsePositives++;
190
+ else falseNegatives++;
191
+ brierTotal += (record.probability - Number(label.value)) ** 2;
192
+ }
193
+ return {
194
+ totalCases: cases.length,
195
+ labeledCases: labels.length,
196
+ attempted: records.length,
197
+ answered,
198
+ refused,
199
+ errors,
200
+ missing: cases.length - records.length,
201
+ scored,
202
+ correct,
203
+ accuracy: scored === 0 ? null : correct / scored,
204
+ brier: scored === 0 ? null : brierTotal / scored,
205
+ falsePositives,
206
+ falseNegatives
207
+ };
208
+ }
209
+ function localCorpus(cases, labels) {
210
+ if (!Array.isArray(cases) || !Array.isArray(labels)) {
211
+ throw new TypeError("cases and labels must be arrays");
212
+ }
213
+ const validatedLabels = parseLabels({ schema: 1, labels }, cases);
214
+ const labelById = new Map(validatedLabels.map((item) => [item.caseId, item]));
215
+ return {
216
+ schema: 1,
217
+ kind: "independent-label-corpus",
218
+ trainingReady: false,
219
+ purpose: "Human/test curated observations for future lawful research; not a training dataset.",
220
+ rows: cases.filter((item) => labelById.has(item.id)).map((item) => ({
221
+ caseId: item.id,
222
+ caseHash: item.hash,
223
+ input: item.input,
224
+ question: item.question,
225
+ provenance: item.provenance,
226
+ source: { ...item.source },
227
+ visibility: item.visibility,
228
+ label: { ...labelById.get(item.id) }
229
+ }))
230
+ };
231
+ }
232
+
233
+ // experiments/decision-shadow/learning-training.mjs
234
+ var TRAINING_REQUIREMENTS = "torch==2.8.0\ntransformers==4.57.1\npeft==0.17.0\naccelerate==1.10.1\nsafetensors==0.6.2\n";
235
+ var TRAINING_CONFIG = { schema: 1, baseModel: { id: null, revision: null, localPath: null, files: [], license: { identifier: null, accepted: false, reviewer: null } }, eligibilityReview: { approved: false, reviewer: null, exportManifestSha256: null }, training: { device: "cpu", seed: 17, epochs: 1, maxLength: 1024, learningRate: 2e-4, loraRank: 8, loraAlpha: 16, targetModules: [] } };
236
+ var TRAINING_PY = String.raw`#!/usr/bin/env python3
237
+ """Local reviewed-data LoRA workflow. Default is validation only. Never downloads or uploads."""
238
+ import argparse, hashlib, importlib.metadata, json, os, pathlib, re, sys
239
+
240
+ PINS = {'torch':'2.8.0','transformers':'4.57.1','peft':'0.17.0','accelerate':'1.10.1','safetensors':'0.6.2'}
241
+
242
+ def require(condition, message):
243
+ if not condition: raise ValueError(message)
244
+
245
+ def digest(data): return hashlib.sha256(data).hexdigest()
246
+
247
+ def ordinary(path):
248
+ path = pathlib.Path(path)
249
+ require(path.is_absolute() and not path.is_symlink() and path.is_file(), 'Expected an explicit ordinary file')
250
+ require(path.resolve() == path, 'Symlinked file ancestors are unsupported')
251
+ return path.read_bytes()
252
+
253
+ def closed(value, fields, name):
254
+ require(isinstance(value, dict) and set(value) == set(fields), name + ' has unsupported or missing fields')
255
+
256
+ def export_data(root):
257
+ root = pathlib.Path(root).absolute()
258
+ raw = ordinary(root / 'export-manifest.json'); m = json.loads(raw)
259
+ require(m.get('schema') == 1 and m.get('kind') == 'decision-learning-export', 'Unsupported export')
260
+ require(m.get('trainingExecuted') is False and m.get('providerPredictionsIncluded') is False, 'Unsupported source provenance')
261
+ require(set(m['files']) == {'train.jsonl','validation.jsonl','test.jsonl','train-lora.py','requirements-training.txt','training-config.example.json','TRAINING.md'}, 'Unexpected export files')
262
+ for name, ref in m['files'].items():
263
+ closed(ref, ['sha256','bytes'], 'File reference')
264
+ b = ordinary(root / name)
265
+ require(len(b) == ref['bytes'] and digest(b) == ref['sha256'], 'Export file hash/length mismatch: ' + name)
266
+ require(digest(ordinary(pathlib.Path(__file__).absolute())) == m['files']['train-lora.py']['sha256'], 'Run the exact exported training script')
267
+ groups, seen_inputs, seen_cases = {}, {}, set()
268
+ for split in ['train','validation','test']:
269
+ rows = [json.loads(line) for line in ordinary(root / (split+'.jsonl')).decode('utf-8').splitlines()]
270
+ require(len(rows) == m['counts'][split], 'Split count mismatch')
271
+ for row in rows:
272
+ closed(row, ['caseId','caseHash','taskGroup','lineageGroup','sessionId','fixtureOnly','input','question','answer','label','source'], 'Dataset row')
273
+ require(row['caseId'] not in seen_cases and type(row['answer']) is bool, 'Duplicate case or invalid label')
274
+ seen_cases.add(row['caseId'])
275
+ require(row['label']['caseId'] == row['caseId'] and row['label']['caseHash'] == row['caseHash'] and row['label']['value'] == row['answer'] and row['label']['kind'] in ['human','test'] and row['label']['independent'] is True, 'Label identity mismatch')
276
+ for key in ['taskGroup','lineageGroup','sessionId']:
277
+ if row[key] is not None:
278
+ identity = key + ':' + row[key]
279
+ require(identity not in groups or groups[identity] == split, 'Related cases cross splits')
280
+ groups[identity] = split
281
+ normalized = ' '.join(row['input'].casefold().split())
282
+ require(normalized not in seen_inputs or seen_inputs[normalized] == split, 'Duplicate input crosses splits')
283
+ seen_inputs[normalized] = split
284
+ if m.get('trainingEligible') is True: require(row['fixtureOnly'] is False and row['sessionId'] is not None, 'Fixture/anonymous data cannot be training eligible')
285
+ return root, m, digest(raw)
286
+
287
+ def config_data(path, manifest, manifest_hash):
288
+ c = json.loads(ordinary(pathlib.Path(path).absolute()))
289
+ closed(c, ['schema','baseModel','eligibilityReview','training'], 'Configuration')
290
+ require(c['schema'] == 1, 'Unsupported configuration')
291
+ r = c['eligibilityReview']; closed(r, ['approved','reviewer','exportManifestSha256'], 'Eligibility review')
292
+ require(manifest.get('trainingEligible') is True and manifest.get('mode') == 'reviewed-data', 'Export is not eligible for training; fixtures remain plumbing demonstrations')
293
+ require(r['approved'] is True and isinstance(r['reviewer'], str) and r['reviewer'].strip() and r['exportManifestSha256'] == manifest_hash, 'Exact export eligibility review required')
294
+ b = c['baseModel']; closed(b, ['id','revision','localPath','files','license'], 'Base model')
295
+ require(isinstance(b['id'], str) and b['id'].strip() and isinstance(b['revision'], str) and re.fullmatch(r'[0-9a-f]{40}', b['revision']), 'Select an exact base model and full pinned revision')
296
+ license = b['license']; closed(license, ['identifier','accepted','reviewer'], 'License review')
297
+ require(license['accepted'] is True and all(isinstance(license[k],str) and license[k].strip() for k in ['identifier','reviewer']), 'Explicit model license review required')
298
+ model = pathlib.Path(b['localPath'] or '')
299
+ require(model.is_absolute() and model.is_dir() and model.resolve() == model, 'Base weights must already exist at a canonical local directory')
300
+ require(isinstance(b['files'], list) and b['files'], 'Pinned local model file manifest required')
301
+ expected = {}
302
+ for ref in b['files']:
303
+ closed(ref, ['path','sha256'], 'Model file reference'); name = ref['path']
304
+ require(isinstance(name,str) and name and not pathlib.PurePosixPath(name).is_absolute() and '..' not in pathlib.PurePosixPath(name).parts and '\\' not in name and name not in expected, 'Invalid model relative path')
305
+ require(isinstance(ref['sha256'],str) and re.fullmatch(r'[0-9a-f]{64}',ref['sha256']), 'Invalid model file digest')
306
+ expected[name] = ref['sha256']
307
+ require(digest(ordinary(model / name)) == ref['sha256'], 'Local model hash mismatch')
308
+ actual = set()
309
+ for item in model.rglob('*'):
310
+ require(not item.is_symlink(), 'Model symlinks unsupported')
311
+ if item.is_file(): actual.add(item.relative_to(model).as_posix())
312
+ require(actual == set(expected) and 'config.json' in actual and any(x.endswith('.safetensors') for x in actual), 'Every local model file must be pinned, including config and safe weights')
313
+ t = c['training']; closed(t, ['device','seed','epochs','maxLength','learningRate','loraRank','loraAlpha','targetModules'], 'Training')
314
+ require(t['device'] in ['cpu','cuda'], 'Explicit cpu/cuda device required')
315
+ for name, lo, hi in [('seed',0,2147483647),('epochs',1,10),('maxLength',32,8192),('loraRank',1,64),('loraAlpha',1,128)]:
316
+ require(type(t[name]) is int and lo <= t[name] <= hi, 'Invalid ' + name)
317
+ require(type(t['learningRate']) in [float,int] and 0 < t['learningRate'] <= 0.01, 'Invalid learning rate')
318
+ require(isinstance(t['targetModules'],list) and t['targetModules'] and all(isinstance(x,str) and x for x in t['targetModules']), 'Select target modules for the reviewed base architecture')
319
+ return c
320
+
321
+ def main():
322
+ p = argparse.ArgumentParser(description=__doc__)
323
+ p.add_argument('--export', required=True); p.add_argument('--config'); p.add_argument('--output'); p.add_argument('--check-export-only', action='store_true'); p.add_argument('--train', action='store_true')
324
+ args = p.parse_args()
325
+ require(not (args.train and args.check_export_only), 'Export-only check cannot train')
326
+ root, manifest, manifest_hash = export_data(args.export)
327
+ if args.check_export_only:
328
+ print(json.dumps({'mode':'offline-export-check','exportManifestSha256':manifest_hash,'trainingEligible':manifest['trainingEligible'],'trainingExecuted':False})); return
329
+ require(args.config is not None, 'Supply a reviewed config; the example has no selected model or approval')
330
+ config = config_data(args.config, manifest, manifest_hash)
331
+ versions = {}
332
+ for name in PINS:
333
+ try: versions[name] = importlib.metadata.version(name)
334
+ except importlib.metadata.PackageNotFoundError: versions[name] = None
335
+ receipt = {'mode':'validation-only','exportManifestSha256':manifest_hash,'configSha256':digest(ordinary(pathlib.Path(args.config).absolute())),'python':sys.version,'dependencyVersions':versions,'requiredVersions':PINS,'trainingExecuted':False,'networkAllowed':False}
336
+ if not args.train:
337
+ print(json.dumps(receipt)); return
338
+ require(sys.version_info[:2] == (3,12), 'Training workflow pins Python 3.12; validation itself is standard-library only')
339
+ require(versions == PINS, 'Install and independently verify the exact pinned dependencies before opting into training')
340
+ require(args.output is not None, 'Choose a new local output directory')
341
+ output = pathlib.Path(args.output).absolute(); require(not output.exists(), 'Training output must be new')
342
+ os.environ.update({'HF_HUB_OFFLINE':'1','TRANSFORMERS_OFFLINE':'1','HF_DATASETS_OFFLINE':'1','HF_HUB_DISABLE_TELEMETRY':'1','WANDB_DISABLED':'true'})
343
+ import torch
344
+ from transformers import AutoModelForCausalLM, AutoTokenizer, Trainer, TrainingArguments, set_seed
345
+ from peft import LoraConfig, TaskType, get_peft_model
346
+ t = config['training']; set_seed(t['seed'])
347
+ require(t['device'] != 'cuda' or torch.cuda.is_available(), 'Configured CUDA device unavailable')
348
+ local = config['baseModel']['localPath']
349
+ tokenizer = AutoTokenizer.from_pretrained(local, local_files_only=True, trust_remote_code=False)
350
+ require(tokenizer.eos_token_id is not None, 'Selected tokenizer requires an EOS token')
351
+ if tokenizer.pad_token_id is None: tokenizer.pad_token = tokenizer.eos_token
352
+ model = AutoModelForCausalLM.from_pretrained(local, local_files_only=True, trust_remote_code=False, use_safetensors=True)
353
+ model = get_peft_model(model, LoraConfig(task_type=TaskType.CAUSAL_LM, r=t['loraRank'], lora_alpha=t['loraAlpha'], target_modules=t['targetModules'], lora_dropout=0.0, bias='none'))
354
+ def rows(split):
355
+ data = []
356
+ for line in ordinary(root / (split + '.jsonl')).decode('utf-8').splitlines():
357
+ row = json.loads(line)
358
+ require(row['fixtureOnly'] is False and type(row['answer']) is bool and row['label']['kind'] in ['human','test'] and row['label']['independent'] is True, 'Only independently labeled reviewed real data may train')
359
+ prompt = 'Question: ' + row['question'] + '\nEvidence:\n' + row['input'] + '\nAnswer (true or false): '
360
+ prefix = tokenizer.encode(prompt, add_special_tokens=True)
361
+ target = tokenizer.encode('true' if row['answer'] else 'false', add_special_tokens=False) + [tokenizer.eos_token_id]
362
+ require(len(prefix) + len(target) <= t['maxLength'], 'Example exceeds reviewed token limit; do not silently truncate evidence')
363
+ data.append({'input_ids':prefix+target,'attention_mask':[1]*(len(prefix)+len(target)),'labels':[-100]*len(prefix)+target})
364
+ require(data, 'Training and validation splits must be nonempty'); return data
365
+ def collate(batch):
366
+ width = max(len(r['input_ids']) for r in batch)
367
+ return {key:torch.tensor([r[key]+[(-100 if key=='labels' else tokenizer.pad_token_id if key=='input_ids' else 0)]*(width-len(r[key])) for r in batch]) for key in ['input_ids','attention_mask','labels']}
368
+ train, validation = rows('train'), rows('validation')
369
+ output.mkdir(mode=0o700)
370
+ receipt['mode']='explicit-local-training'; receipt['trainingExecuted']=True
371
+ receipt['resolvedEnvironment']={d.metadata.get('Name','unknown'):d.version for d in importlib.metadata.distributions()}
372
+ (output/'run-config.json').write_text(json.dumps({'receipt':receipt,'config':config},indent=2)+'\n')
373
+ trainer = Trainer(model=model,args=TrainingArguments(output_dir=str(output/'checkpoints'),use_cpu=t['device']=='cpu',seed=t['seed'],data_seed=t['seed'],num_train_epochs=t['epochs'],learning_rate=t['learningRate'],per_device_train_batch_size=1,per_device_eval_batch_size=1,eval_strategy='epoch',save_strategy='no',report_to=[],push_to_hub=False,dataloader_num_workers=0),train_dataset=train,eval_dataset=validation,data_collator=collate)
374
+ trainer.train(); model.save_pretrained(output/'adapter',safe_serialization=True); tokenizer.save_pretrained(output/'adapter')
375
+ (output/'validation-metrics.json').write_text(json.dumps(trainer.evaluate(),indent=2)+'\n')
376
+ print(json.dumps({'adapter':str(output/'adapter'),'trainingExecuted':True,'heldOutTestUsedForTraining':False,'deploymentAuthorized':False}))
377
+
378
+ if __name__ == '__main__':
379
+ try: main()
380
+ except (ValueError, OSError, KeyError, TypeError, json.JSONDecodeError) as error:
381
+ print('learning workflow refused: ' + str(error), file=sys.stderr); sys.exit(1)
382
+ `;
383
+ var TRAINING_DOC = `# Local LoRA workflow
384
+
385
+ This export contains independent labels, never JEV predictions. A fixture demonstration is not training eligible. No training or installation has been performed by exporting these files.
386
+
387
+ 1. Check the intact export offline: python3 train-lora.py --export /absolute/export --check-export-only.
388
+ 2. For reviewed real data only, select a base model, immutable full revision, license and an existing canonical local weight directory. Pin every file by SHA-256 in a separate copy of training-config.example.json. Select architecture-specific LoRA target modules. Record an explicit eligibility review bound to the exact export-manifest.json digest. Storage consent alone is insufficient.
389
+ 3. Use an isolated Python 3.12 environment with the exact requirements-training.txt versions. Resolve dependencies separately, retain the complete resolved environment/wheel hashes, and assess the host memory requirements. The template pins direct versions; it is not a platform-complete dependency lock or proof of hardware support.
390
+ 4. Run python3 train-lora.py --export /absolute/export --config /absolute/reviewed-config.json. Default behavior validates only. It reports installed dependency versions and never loads a model.
391
+ 5. Only after separate approval, add --train --output /absolute/new-adapter-directory. Training requires the pinned environment and already available safe weights. The script disables Hub access, telemetry, remote code and publishing. It refuses occupied output or evidence truncation. Only train/validation examples feed the trainer; the test split is hash-checked but never loaded into the trainer.
392
+ 6. Evaluate the held-out test split against the unchanged base and deterministic oracle before considering deployment. Validation loss and an adapter file are not proof of decision quality, calibration, speed, generality or runtime authority.
393
+
394
+ The selected repository/task families and their variants must remain together across splits. Recheck consent, rights and eligibility for any new export. This local script is not an OS sandbox; do not run untrusted model code or packages. A seed records intended reproducibility, not bit-for-bit GPU determinism.
395
+
396
+ Implementation references: https://huggingface.co/docs/peft/v0.17.0/en/quicktour and https://huggingface.co/docs/transformers/v4.57.1/en/main_classes/trainer . Local-only loading: https://huggingface.co/docs/transformers/v4.57.1/en/installation . This workflow has offline validation tests only; no installed-library training qualification or trained model is claimed.
397
+ `;
398
+ function trainingAssets() {
399
+ return { "train-lora.py": TRAINING_PY, "requirements-training.txt": TRAINING_REQUIREMENTS, "training-config.example.json": JSON.stringify(TRAINING_CONFIG, null, 2) + "\n", "TRAINING.md": TRAINING_DOC };
400
+ }
401
+
402
+ // experiments/decision-shadow/learning-data.mjs
403
+ var learningDigest = (value) => createHash2("sha256").update(typeof value === "string" || Buffer.isBuffer(value) ? value : canonical2(value)).digest("hex");
404
+ function canonical2(value) {
405
+ return Array.isArray(value) ? `[${value.map(canonical2).join(",")}]` : value !== null && typeof value === "object" ? `{${Object.keys(value).sort().map((k) => `${JSON.stringify(k)}:${canonical2(value[k])}`).join(",")}}` : JSON.stringify(value);
406
+ }
407
+ function check(ok, message) {
408
+ if (!ok) throw new TypeError(message);
409
+ }
410
+ function keys(v, names, name) {
411
+ check(v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).sort().join("|") === [...names].sort().join("|"), `${name}: unsupported or missing fields`);
412
+ }
413
+ function text(v, name, max = 256) {
414
+ check(typeof v === "string" && v.trim().length > 0 && v.length <= max && !v.includes("\0"), `${name}: invalid text`);
415
+ }
416
+ function sha2(v) {
417
+ check(typeof v === "string" && /^[0-9a-f]{64}$/.test(v), "invalid SHA-256");
418
+ }
419
+ function date(v) {
420
+ check(typeof v === "string" && /^\d{4}-\d\d-\d\dT/.test(v) && Number.isFinite(Date.parse(v)), "invalid timestamp");
421
+ }
422
+ function bool(v) {
423
+ check(typeof v === "boolean", "expected boolean");
424
+ }
425
+ function choice(v, allowed, name) {
426
+ check(allowed.includes(v), `${name}: unsupported value`);
427
+ }
428
+ function parsedCases(cases) {
429
+ check(Array.isArray(cases), "cases must be parsed cases");
430
+ const raw = cases.map((c) => {
431
+ keys(c, ["id", "input", "question", "provenance", "source", "visibility", "hash"], "parsed case");
432
+ const { hash: hash3, ...rest } = c;
433
+ return rest;
434
+ });
435
+ const validated = parseCases({ schema: 1, cases: raw });
436
+ validated.forEach((c, i) => check(c.hash === cases[i].hash, "stale parsed case hash"));
437
+ return validated;
438
+ }
439
+ var CONSENT_KEYS = ["schema", "kind", "sessionId", "decision", "interactionId", "recordedAt", "source", "scope"];
440
+ function createStorageConsent({ sessionId, decision, interactionId, recordedAt }) {
441
+ return parseStorageConsent({ schema: 1, kind: "decision-session-storage-consent", sessionId, decision, interactionId, recordedAt, source: "explicit-user-response", scope: "selected-session-data-for-lora-review" }, sessionId);
442
+ }
443
+ function parseStorageConsent(record, expectedSessionId) {
444
+ keys(record, CONSENT_KEYS, "storage consent");
445
+ text(expectedSessionId, "expected session ID");
446
+ check(record.schema === 1 && record.kind === "decision-session-storage-consent" && record.source === "explicit-user-response" && record.scope === "selected-session-data-for-lora-review", "unsupported consent contract");
447
+ text(record.sessionId, "session ID");
448
+ text(record.interactionId, "interaction ID");
449
+ date(record.recordedAt);
450
+ check(record.sessionId === expectedSessionId, "storage consent belongs to another session");
451
+ choice(record.decision, ["granted", "declined"], "consent");
452
+ return structuredClone(record);
453
+ }
454
+ function consentFor(consents, sessionId) {
455
+ check(Array.isArray(consents), "consents must be an array");
456
+ const matches = consents.filter((c2) => c2.sessionId === sessionId);
457
+ check(matches.length === 1, "one explicit current-session storage consent required");
458
+ const c = parseStorageConsent(matches[0], sessionId);
459
+ check(c.decision === "granted", "session storage declined; advice remains allowed");
460
+ return c;
461
+ }
462
+ var ENTRY_KEYS = ["caseId", "caseHash", "taskGroup", "lineageGroup", "split", "sessionId", "fixtureOnly", "decisionTimeReviewed", "redactionReviewed", "rights", "exportApproved", "trainingApproved", "reviewer"];
463
+ function parseEntry(c, e) {
464
+ keys(e, ENTRY_KEYS, "experiment entry");
465
+ check(e.caseId === c.id && e.caseHash === c.hash, "experiment case identity mismatch");
466
+ for (const k of ["taskGroup", "lineageGroup", "reviewer"]) text(e[k], k);
467
+ choice(e.split, ["train", "validation", "test", "unassigned"], "split");
468
+ choice(e.rights, ["unknown", "local-export", "local-training"], "rights");
469
+ for (const k of ["fixtureOnly", "decisionTimeReviewed", "redactionReviewed", "exportApproved", "trainingApproved"]) bool(e[k]);
470
+ if (c.provenance === "observed") {
471
+ text(e.sessionId, "observed session ID");
472
+ check(!e.fixtureOnly, "observed case cannot be a synthetic fixture");
473
+ } else check(e.sessionId === null && e.fixtureOnly === true, "synthetic data must remain fixture-only, without session consent");
474
+ return structuredClone(e);
475
+ }
476
+ function parseExperiment(cases, manifest) {
477
+ cases = parsedCases(cases);
478
+ keys(manifest, ["schema", "kind", "id", "frozenAt", "entries"], "experiment");
479
+ check(manifest.schema === 1 && manifest.kind === "decision-learning-experiment", "unsupported experiment");
480
+ text(manifest.id, "experiment ID");
481
+ date(manifest.frozenAt);
482
+ check(Array.isArray(manifest.entries) && manifest.entries.length === cases.length, "one ordered experiment entry required per case");
483
+ const entries = cases.map((c, i) => parseEntry(c, manifest.entries[i]));
484
+ const groups = /* @__PURE__ */ new Map(), duplicates = /* @__PURE__ */ new Map();
485
+ function bind(map, key, split, what) {
486
+ if (map.has(key)) check(map.get(key) === split, `${what} crosses splits`);
487
+ else map.set(key, split);
488
+ }
489
+ entries.forEach((e, i) => {
490
+ bind(groups, "task:" + e.taskGroup, e.split, "task group");
491
+ bind(groups, "lineage:" + e.lineageGroup, e.split, "lineage group");
492
+ if (e.sessionId !== null) bind(groups, "session:" + e.sessionId, e.split, "session");
493
+ bind(duplicates, learningDigest(cases[i].input.normalize("NFKC").replace(/\s+/gu, " ").trim().toLowerCase()), e.split, "duplicate input");
494
+ bind(duplicates, "source:" + cases[i].source.sha256 + ":" + cases[i].source.recordId, e.split, "source record");
495
+ });
496
+ return { ...structuredClone(manifest), entries };
497
+ }
498
+ async function readExplicit(path, expected, limit = 1024 * 1024) {
499
+ text(path, "artifact path", 4096);
500
+ check(process.platform === "linux", "Evidence review requires Linux no-follow directory descriptors");
501
+ check(isAbsolute(path) && normalize(path) === path && !path.endsWith("/"), "artifact path must be canonical and absolute");
502
+ sha2(expected);
503
+ const components = path.split("/").filter(Boolean);
504
+ check(components.length <= 128, "artifact path too deep");
505
+ check(!components.some((p) => ["sessions", "native-sessions", "auth.json", ".env"].includes(p.toLowerCase())), "private session or credential paths are unsupported");
506
+ const directories = [], flags = constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
507
+ const identity2 = (s) => [s.dev, s.ino, s.mode].map(String).join(":");
508
+ const metadata2 = (s) => [s.dev, s.ino, s.mode, s.size, s.mtimeNs, s.ctimeNs].map(String).join(":");
509
+ let file;
510
+ try {
511
+ let handle = await open("/", flags);
512
+ directories.push({ handle, parent: null, name: "/", identity: identity2(await handle.stat({ bigint: true })) });
513
+ for (const name2 of components.slice(0, -1)) {
514
+ const parent2 = handle;
515
+ handle = await open(`/proc/self/fd/${parent2.fd}/${name2}`, flags);
516
+ directories.push({ handle, parent: parent2, name: name2, identity: identity2(await handle.stat({ bigint: true })) });
517
+ }
518
+ const name = components.at(-1), parent = handle;
519
+ file = await open(`/proc/self/fd/${parent.fd}/${name}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
520
+ const before = await file.stat({ bigint: true });
521
+ check(before.isFile() && before.size <= BigInt(limit), "artifact must be a bounded ordinary file");
522
+ const b = Buffer.alloc(Number(before.size) + 1);
523
+ let n = 0;
524
+ while (n < b.length) {
525
+ const r = await file.read(b, n, b.length - n, n);
526
+ if (!r.bytesRead) break;
527
+ n += r.bytesRead;
528
+ }
529
+ check(n === Number(before.size) && metadata2(before) === metadata2(await file.stat({ bigint: true })), "artifact changed while reading");
530
+ const reopened = await open(`/proc/self/fd/${parent.fd}/${name}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
531
+ try {
532
+ check(metadata2(before) === metadata2(await reopened.stat({ bigint: true })), "artifact path changed while reading");
533
+ } finally {
534
+ await reopened.close();
535
+ }
536
+ for (const d of directories) {
537
+ const h = await open(d.parent ? `/proc/self/fd/${d.parent.fd}/${d.name}` : "/", flags);
538
+ try {
539
+ check(identity2(await h.stat({ bigint: true })) === d.identity, "artifact ancestor changed");
540
+ } finally {
541
+ await h.close();
542
+ }
543
+ }
544
+ const bytes = b.subarray(0, n);
545
+ check(learningDigest(bytes) === expected, "artifact hash mismatch");
546
+ return bytes;
547
+ } finally {
548
+ if (file) await file.close();
549
+ await Promise.allSettled(directories.map((d) => d.handle.close()));
550
+ }
551
+ }
552
+ function json(bytes) {
553
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes));
554
+ }
555
+ async function reviewLabelEvidence({ cases, labels, references }) {
556
+ cases = parsedCases(cases);
557
+ labels = parseLabels({ schema: 1, labels }, cases);
558
+ check(labels.length <= 100 && Array.isArray(references) && references.length === labels.length && references.length <= cases.length, "one bounded label evidence reference required per label");
559
+ const seen = /* @__PURE__ */ new Set(), reviewed = [];
560
+ for (const label of labels) {
561
+ const matches = references.filter((r2) => r2.caseId === label.caseId);
562
+ check(matches.length === 1, "duplicate or missing label evidence");
563
+ const r = matches[0];
564
+ keys(r, ["caseId", "path", "sha256"], "label reference");
565
+ check(!seen.has(r.path), "label receipt reused");
566
+ seen.add(r.path);
567
+ check(r.sha256 === label.evidenceSha256, "label evidence digest mismatch");
568
+ const receipt = json(await readExplicit(r.path, r.sha256));
569
+ keys(receipt, ["schema", "kind", "caseId", "caseHash", "source", "value", "labelKind", "actor", "independent", "recordedAt", "method"], "label receipt");
570
+ check(receipt.schema === 1 && receipt.kind === "independent-decision-label", "model/audit output is not independent label evidence");
571
+ for (const k of ["caseId", "caseHash", "value", "actor", "independent"]) check(receipt[k] === label[k], "label receipt disagrees with " + k);
572
+ check(receipt.labelKind === label.kind, "label provenance mismatch");
573
+ date(receipt.recordedAt);
574
+ const c = cases.find((c2) => c2.id === label.caseId);
575
+ keys(receipt.source, ["sha256", "recordId"], "label source");
576
+ check(canonical2(receipt.source) === canonical2(c.source), "label source mismatch");
577
+ keys(receipt.method, ["kind", "id", "version"], "label method");
578
+ check(receipt.method.kind === (label.kind === "human" ? "human-review" : "deterministic-test"), "prediction cannot supply a label");
579
+ text(receipt.method.id, "method");
580
+ text(receipt.method.version, "method version");
581
+ reviewed.push({ caseId: c.id, caseHash: c.hash, reference: { ...r }, label: { ...label }, method: { ...receipt.method }, recordedAt: receipt.recordedAt });
582
+ }
583
+ return { schema: 1, kind: "decision-label-evidence-review", reviewed, meaning: "Exact receipt bytes and declared provenance checked; human identity, truth, independence and rights remain explicit reviewer assertions." };
584
+ }
585
+ async function importSelectedCase({ caseDocument, selection, sessionConsent, experimentEntry }) {
586
+ const cases = parseCases(caseDocument);
587
+ check(cases.length === 1 && cases[0].provenance === "observed", "import requires exactly one observed case");
588
+ const c = cases[0];
589
+ const e = parseEntry(c, experimentEntry);
590
+ const consent = parseStorageConsent(sessionConsent, e.sessionId);
591
+ check(consent.decision === "granted", "session storage declined; advice remains allowed");
592
+ check(e.decisionTimeReviewed && e.redactionReviewed, "decision-time and redaction review required");
593
+ keys(selection, ["schema", "kind", "sessionId", "source", "fragments"], "public selection");
594
+ check(selection.schema === 1 && selection.kind === "decision-selected-public-input" && selection.sessionId === e.sessionId, "selection session mismatch");
595
+ keys(selection.source, ["path", "sha256"], "selected source");
596
+ check(selection.source.sha256 === c.source.sha256, "selected source hash mismatch");
597
+ const primary = json(await readExplicit(selection.source.path, selection.source.sha256));
598
+ check(primary.toolCallId === c.source.recordId, "selected source tool-call identity mismatch");
599
+ check(Array.isArray(selection.fragments) && selection.fragments.length > 0 && selection.fragments.length <= 16, "select 1..16 explicit public fragments");
600
+ const parts = [], refs = [];
601
+ for (const f of selection.fragments) {
602
+ keys(f, ["path", "sha256", "jsonlLine", "jsonPointer", "start", "end", "replacements"], "fragment");
603
+ let s = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(await readExplicit(f.path, f.sha256));
604
+ if (f.jsonlLine !== null) {
605
+ check(Number.isSafeInteger(f.jsonlLine) && f.jsonlLine > 0, "invalid JSONL line");
606
+ s = s.split("\n")[f.jsonlLine - 1];
607
+ check(s !== void 0, "missing JSONL line");
608
+ }
609
+ if (f.jsonPointer !== null) {
610
+ check(typeof f.jsonPointer === "string" && f.jsonPointer.startsWith("/"), "invalid JSON pointer");
611
+ let v = JSON.parse(s);
612
+ for (const key of f.jsonPointer.slice(1).split("/").map((k) => k.replace(/~1/g, "/").replace(/~0/g, "~"))) {
613
+ check(v && typeof v === "object" && Object.hasOwn(v, key), "missing selected field");
614
+ v = v[key];
615
+ }
616
+ check(typeof v === "string", "selected field must be public text");
617
+ s = v;
618
+ }
619
+ const points = Array.from(s);
620
+ check(Number.isSafeInteger(f.start) && Number.isSafeInteger(f.end) && f.start >= 0 && f.end > f.start && f.end <= points.length, "invalid excerpt range");
621
+ let excerpt = points.slice(f.start, f.end).join("");
622
+ check(Array.isArray(f.replacements) && f.replacements.length <= 32, "invalid redactions");
623
+ for (const r of f.replacements) {
624
+ keys(r, ["from", "to"], "redaction");
625
+ text(r.from, "redaction source", 4096);
626
+ check(typeof r.to === "string" && /^<[A-Z0-9_ -]{1,80}>$/.test(r.to), "redaction must use a placeholder");
627
+ check(excerpt.includes(r.from), "redaction source absent");
628
+ excerpt = excerpt.replaceAll(r.from, r.to);
629
+ }
630
+ parts.push(excerpt);
631
+ refs.push({ ...f, replacements: f.replacements.map((r) => ({ fromSha256: learningDigest(r.from), fromCodepoints: Array.from(r.from).length, to: r.to })) });
632
+ }
633
+ check(parts.join("\n\n") === c.input, "case input must equal exact selected redacted fragments joined by two newlines");
634
+ return { caseDocument: structuredClone(caseDocument), experimentEntry: e, receipt: { schema: 1, kind: "decision-selected-public-import", caseId: c.id, caseHash: c.hash, sessionId: e.sessionId, consentHash: learningDigest(consent), source: structuredClone(selection.source), fragments: refs, trainingEligible: false, automaticCollection: false } };
635
+ }
636
+ async function prepareExport({ cases, labels, manifest, consents, labelEvidence, mode }) {
637
+ cases = parsedCases(cases);
638
+ manifest = parseExperiment(cases, manifest);
639
+ choice(mode, ["fixture-demo", "reviewed-data"], "export mode");
640
+ const reviewed = await reviewLabelEvidence({ cases, labels, references: labelEvidence });
641
+ const byId = new Map(reviewed.reviewed.map((r) => [r.caseId, r]));
642
+ const splitRows = { train: [], validation: [], test: [] }, excluded = [], included = [];
643
+ for (const [i, c] of cases.entries()) {
644
+ const e = manifest.entries[i], reasons = [];
645
+ const label = byId.get(c.id);
646
+ if (!label) reasons.push("independent label unavailable");
647
+ if (e.split === "unassigned") reasons.push("split unassigned");
648
+ if (!e.decisionTimeReviewed || !e.redactionReviewed) reasons.push("input review missing");
649
+ if (!e.exportApproved || e.rights === "unknown") reasons.push("local export permission or rights missing");
650
+ if (mode === "fixture-demo" && !e.fixtureOnly) reasons.push("fixture demo excludes observed data");
651
+ if (mode === "reviewed-data" && e.fixtureOnly) reasons.push("synthetic fixture excluded from reviewed training data");
652
+ if (!e.fixtureOnly) {
653
+ try {
654
+ consentFor(consents, e.sessionId);
655
+ } catch (error) {
656
+ reasons.push(error.message);
657
+ }
658
+ }
659
+ if (reasons.length) {
660
+ excluded.push({ caseId: c.id, reasons });
661
+ continue;
662
+ }
663
+ const row = { caseId: c.id, caseHash: c.hash, taskGroup: e.taskGroup, lineageGroup: e.lineageGroup, sessionId: e.sessionId, fixtureOnly: e.fixtureOnly, input: c.input, question: c.question, answer: label.label.value, label: { ...label.label }, source: { ...c.source } };
664
+ splitRows[e.split].push(row);
665
+ included.push(e);
666
+ }
667
+ check(included.length > 0, "no export-eligible cases");
668
+ const trainingEligible = mode === "reviewed-data" && excluded.length === 0 && Object.values(splitRows).every((rows) => rows.length > 0) && included.every((e) => !e.fixtureOnly && e.trainingApproved && e.rights === "local-training");
669
+ const files = { ...trainingAssets() };
670
+ for (const [split, rows] of Object.entries(splitRows)) files[`${split}.jsonl`] = rows.map((r) => JSON.stringify(r) + "\n").join("");
671
+ const metadata2 = { schema: 1, kind: "decision-learning-export", mode, experimentId: manifest.id, experimentHash: learningDigest(manifest), trainingEligible, trainingExecuted: false, providerPredictionsIncluded: false, counts: Object.fromEntries(Object.entries(splitRows).map(([s, r]) => [s, r.length])), excluded, review: { frozenAt: manifest.frozenAt, eligibilityRecords: included, independentLabelReceipts: reviewed.reviewed.map((r) => ({ ...r.reference, recordedAt: r.recordedAt })), sessionConsents: [...new Set(included.filter((e) => e.sessionId !== null).map((e) => e.sessionId))].map((sessionId) => {
672
+ const c = consentFor(consents, sessionId);
673
+ return { sessionId, sha256: learningDigest(c), decision: c.decision, interactionId: c.interactionId, recordedAt: c.recordedAt };
674
+ }) }, files: Object.fromEntries(Object.entries(files).map(([name, s]) => [name, { sha256: learningDigest(s), bytes: Buffer.byteLength(s) }])) };
675
+ files["export-manifest.json"] = JSON.stringify(metadata2, null, 2) + "\n";
676
+ return { schema: 1, kind: "prepared-decision-learning-export", metadata: metadata2, files };
677
+ }
678
+ async function writePreparedExport({ prepared, directory }) {
679
+ keys(prepared, ["schema", "kind", "metadata", "files"], "prepared export");
680
+ check(prepared.schema === 1 && prepared.kind === "prepared-decision-learning-export", "unsupported prepared export");
681
+ text(directory, "output directory", 4096);
682
+ check(isAbsolute(directory), "output directory must be absolute");
683
+ const allowed = ["train.jsonl", "validation.jsonl", "test.jsonl", "export-manifest.json", "train-lora.py", "training-config.example.json", "requirements-training.txt", "TRAINING.md"];
684
+ check(Object.keys(prepared.files).sort().join("|") === [...allowed].sort().join("|"), "unexpected export files");
685
+ check(prepared.files["export-manifest.json"] === JSON.stringify(prepared.metadata, null, 2) + "\n", "export metadata mismatch");
686
+ for (const [name, s] of Object.entries(prepared.files)) {
687
+ check(typeof s === "string", "export file must be text");
688
+ if (name !== "export-manifest.json") check(prepared.metadata.files[name]?.sha256 === learningDigest(s) && prepared.metadata.files[name]?.bytes === Buffer.byteLength(s), "prepared export bytes changed");
689
+ }
690
+ await mkdir(directory, { mode: 448 });
691
+ for (const [name, s] of Object.entries(prepared.files)) await writeFile(join(directory, name), s, { encoding: "utf8", flag: "wx", mode: 384 });
692
+ return { directory, trainingEligible: prepared.metadata.trainingEligible, files: Object.fromEntries(Object.entries(prepared.files).map(([name, s]) => [name, { sha256: learningDigest(s), bytes: Buffer.byteLength(s) }])) };
693
+ }
694
+
695
+ // experiments/decision-shadow/learning-fixtures.mjs
696
+ var FIXTURE_ORACLE_VERSION = "mechanical-fixture-oracles-v1";
697
+ var COMMIT = /^[a-f0-9]{40}$/;
698
+ var PHASES = ["planned", "implemented", "reviewed", "integrated", "verified"];
699
+ function evaluateFixture(family, e) {
700
+ if (!e || typeof e !== "object" || Array.isArray(e)) return false;
701
+ switch (family) {
702
+ case "candidate":
703
+ return typeof e.candidate === "string" && COMMIT.test(e.candidate) && e.tested === e.candidate && e.reviewed === e.candidate;
704
+ case "phases": {
705
+ if (!Array.isArray(e.required) || !e.required.length || new Set(e.required).size !== e.required.length || !Array.isArray(e.records)) return false;
706
+ const latest = /* @__PURE__ */ new Map();
707
+ for (const r of e.records) {
708
+ if (!r || typeof r.step !== "string" || !r.phases) return false;
709
+ latest.set(r.step, r.phases);
710
+ }
711
+ return latest.size === e.required.length && e.required.every((s) => typeof s === "string" && s.length > 0 && latest.has(s) && PHASES.every((p) => latest.get(s)[p] === "complete"));
712
+ }
713
+ case "findings": {
714
+ if (e.historyObserved !== true || !Array.isArray(e.records)) return false;
715
+ const latest = /* @__PURE__ */ new Map();
716
+ for (const r of e.records) {
717
+ if (!r || typeof r.step !== "string" || !r.step.trim() || !Array.isArray(r.findings)) return false;
718
+ for (const f of r.findings) {
719
+ if (!f || typeof f.id !== "string" || !f.id || typeof f.source !== "string" || !f.source) return false;
720
+ latest.set(JSON.stringify([r.step, f.source, f.id]), f.status);
721
+ }
722
+ }
723
+ return [...latest.values()].every((s) => s === "verified");
724
+ }
725
+ case "capability":
726
+ return e.inventoryObserved === true && Array.isArray(e.required) && e.required.length > 0 && Array.isArray(e.available) && e.required.every((t) => typeof t === "string" && e.available.includes(t)) && e.backendQualified === true;
727
+ case "report":
728
+ return e.protocol === "saved-full-report-and-five-line-summary" && (e.persistence === "available" && e.requestedDelivery === "saved-full-report-and-five-line-summary" && e.reportReference === "required" || e.persistence === "unavailable" && e.requestedDelivery === "blocked-no-invented-report" && e.reportReference === "not-saved");
729
+ case "cleanup":
730
+ return typeof e.executionId === "string" && e.executionId.length > 0 && e.state === "settled" && e.receipt?.state === "settled" && e.receipt.executionId === e.executionId && e.receipt.reapedAll === true;
731
+ default:
732
+ throw new TypeError("Unknown fixture oracle family");
733
+ }
734
+ }
735
+ function createLearningFixtures({ recordedAt = "2026-10-08T00:00:00.000Z" } = {}) {
736
+ const commit = "a".repeat(40), complete = Object.fromEntries(PHASES.map((p) => [p, "complete"]));
737
+ const families = [
738
+ { family: "candidate", split: "train", question: "Do the records establish one exact full 40-character lowercase Git candidate shared by candidate, tested and reviewed?", facts: [{ candidate: commit, tested: commit, reviewed: commit }, { candidate: commit, tested: "b".repeat(40), reviewed: commit }, { candidate: commit, tested: commit, reviewed: "c".repeat(40) }, { candidate: "aaaaaaa", tested: "aaaaaaa", reviewed: "aaaaaaa" }] },
739
+ { family: "phases", split: "train", question: "Do the latest snapshots cover exactly every required step with all five named phases complete? Earlier complete phases cannot fill later omissions.", facts: [{ required: ["step-1"], records: [{ step: "step-1", phases: complete }] }, { required: ["step-1", "step-2"], records: [{ step: "step-1", phases: complete }] }, { required: ["step-1"], records: [{ step: "step-1", phases: complete }, { step: "step-extra", phases: complete }] }, { required: ["step-1"], records: [{ step: "step-1", phases: complete }, { step: "step-1", phases: { ...complete, verified: "unknown" } }] }] },
740
+ { family: "findings", split: "train", question: "Does the observed finding history establish that every finding identity (step, source, id) has an explicit latest verified disposition? Omission never clears an earlier finding.", facts: [{ historyObserved: true, records: [{ step: "step-1", findings: [] }] }, { historyObserved: true, records: [{ step: "step-1", findings: [{ id: "f1", source: "report-a", status: "open" }] }, { step: "step-1", findings: [{ id: "f1", source: "report-a", status: "verified" }] }] }, { historyObserved: true, records: [{ step: "step-1", findings: [{ id: "f1", source: "report-a", status: "open" }] }, { step: "step-1", findings: [] }] }, { historyObserved: true, records: [{ step: "step-1", findings: [{ id: "f1", source: "report-a", status: "open" }] }, { step: "step-1", findings: [{ id: "f1", source: "report-b", status: "verified" }] }] }] },
741
+ { family: "capability", split: "validation", question: "Does the observed inventory establish every explicitly required tool and a qualified backend? Missing or unobserved capability evidence does not establish availability.", facts: [{ inventoryObserved: true, required: ["read", "grep", "find", "ls"], available: ["read", "grep", "find", "ls"], backendQualified: true }, { inventoryObserved: true, required: ["read", "grep", "find", "ls"], available: ["read", "bash"], backendQualified: true }, { inventoryObserved: true, required: ["read"], available: ["read"], backendQualified: false }, { inventoryObserved: false, required: ["read"], available: ["read"], backendQualified: true }] },
742
+ { family: "report", split: "test", question: "Does this structured requested delivery comply with the selected saved-full-report/five-line-summary protocol, including its explicit unavailable-persistence blocked exception?", facts: [{ protocol: "saved-full-report-and-five-line-summary", persistence: "available", requestedDelivery: "saved-full-report-and-five-line-summary", reportReference: "required" }, { protocol: "saved-full-report-and-five-line-summary", persistence: "available", requestedDelivery: "complete-report-only-in-final", reportReference: "forbidden" }, { protocol: "saved-full-report-and-five-line-summary", persistence: "available", requestedDelivery: "saved-full-report-and-five-line-summary", reportReference: "omitted" }, { protocol: "saved-full-report-and-five-line-summary", persistence: "unavailable", requestedDelivery: "blocked-no-invented-report", reportReference: "not-saved" }] },
743
+ { family: "cleanup", split: "test", question: "Do these records establish settled cleanup with a matching execution identity and reapedAll true? This does not establish task success or approval.", facts: [{ executionId: "exec:one", state: "settled", receipt: { state: "settled", executionId: "exec:one", reapedAll: true } }, { executionId: "exec:one", state: "settled", receipt: null }, { executionId: "exec:one", state: "settled", receipt: { state: "settled", executionId: "exec:other", reapedAll: true } }, { executionId: "exec:one", state: "settled", receipt: { state: "settled", executionId: "exec:one", reapedAll: false } }] }
744
+ ];
745
+ const caseDocument = { schema: 1, cases: families.flatMap((f) => f.facts.map((facts, i) => {
746
+ const input = JSON.stringify(facts);
747
+ return { id: `fixture-${f.family}-${i + 1}`, input, question: f.question, provenance: "synthetic", source: { sha256: learningDigest(input), recordId: `${FIXTURE_ORACLE_VERSION}/${f.family}/${i + 1}` }, visibility: "public" };
748
+ })) };
749
+ const cases = parseCases(caseDocument), receipts = [], labels = [], entries = [];
750
+ for (const c of cases) {
751
+ const family = c.id.split("-")[1], f = families.find((f2) => f2.family === family);
752
+ const value = evaluateFixture(family, JSON.parse(c.input));
753
+ const receipt = { schema: 1, kind: "independent-decision-label", caseId: c.id, caseHash: c.hash, source: c.source, value, labelKind: "test", actor: FIXTURE_ORACLE_VERSION, independent: true, recordedAt, method: { kind: "deterministic-test", id: family, version: FIXTURE_ORACLE_VERSION + ":" + learningDigest(evaluateFixture.toString()) } };
754
+ const bytes = JSON.stringify(receipt, null, 2) + "\n";
755
+ receipts.push({ caseId: c.id, filename: c.id + "-label.json", bytes, sha256: learningDigest(bytes) });
756
+ labels.push({ caseId: c.id, caseHash: c.hash, value, kind: "test", actor: receipt.actor, evidenceSha256: learningDigest(bytes), independent: true });
757
+ entries.push({ caseId: c.id, caseHash: c.hash, taskGroup: "fixture-family-" + family, lineageGroup: "fixture-family-" + family, split: f.split, sessionId: null, fixtureOnly: true, decisionTimeReviewed: true, redactionReviewed: true, rights: "local-export", exportApproved: true, trainingApproved: false, reviewer: "prospective-synthetic-fixture-rule" });
758
+ }
759
+ const labelDocument = { schema: 1, labels };
760
+ parseLabels(labelDocument, cases);
761
+ const manifest = parseExperiment(cases, { schema: 1, kind: "decision-learning-experiment", id: "mechanical-fixtures-24-v1", frozenAt: recordedAt, entries });
762
+ return { caseDocument, labelDocument, manifest, labelReceipts: receipts, fixtureOnly: true, trainingEligible: false, scope: "24 synthetic mechanical plumbing cases; no observed examples or model-quality claims." };
763
+ }
764
+
765
+ // experiments/decision-shadow/research.mjs
766
+ import { createHash as createHash3, randomUUID } from "node:crypto";
767
+ import { open as open2, readFile } from "node:fs/promises";
768
+ import { DECISION_SYSTEM_PROMPT } from "@skill-harness/adapters";
769
+ var PROMPT_REVISION = "decision-input-question-v1";
770
+ var decisionPrompt = (c) => JSON.stringify({ input: c.input, question: c.question });
771
+ var fullCaseSetHash = (cases) => learningDigest(cases.map((c) => ({ id: c.id, hash: c.hash })));
772
+ var hash = (s) => createHash3("sha256").update(s).digest("hex");
773
+ function check2(ok, message) {
774
+ if (!ok) throw TypeError(message);
775
+ }
776
+ function keys2(value, names, name) {
777
+ check2(
778
+ value && typeof value === "object" && !Array.isArray(value) && Object.keys(value).sort().join("|") === [...names].sort().join("|"),
779
+ name + " has unsupported/missing fields"
780
+ );
781
+ }
782
+ function sha3(value) {
783
+ check2(
784
+ typeof value === "string" && /^[0-9a-f]{64}$/.test(value),
785
+ "invalid digest"
786
+ );
787
+ }
788
+ function selected(cases, manifest, split) {
789
+ check2(
790
+ ["train", "validation", "test", "all"].includes(split),
791
+ "explicit train/validation/test/all split required"
792
+ );
793
+ return cases.filter(
794
+ (c, i) => split === "all" || manifest.entries[i].split === split
795
+ );
796
+ }
797
+ function previewPi(cases, manifest, { model, thinking, split }) {
798
+ manifest = parseExperiment(cases, manifest);
799
+ check2(
800
+ /^openai-codex:[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/.test(model),
801
+ "explicit subscription model required"
802
+ );
803
+ check2(
804
+ ["off", "minimal", "low", "medium", "high", "xhigh"].includes(thinking),
805
+ "explicit thinking level required"
806
+ );
807
+ const subset = selected(cases, manifest, split);
808
+ check2(subset.length > 0, "selected split is empty");
809
+ return {
810
+ schema: 1,
811
+ kind: "decision-pi-preview",
812
+ model,
813
+ thinking,
814
+ split,
815
+ experimentHash: learningDigest(manifest),
816
+ caseSetHash: fullCaseSetHash(cases),
817
+ promptRevision: PROMPT_REVISION,
818
+ trainingEligible: false,
819
+ requests: subset.map((c) => ({
820
+ caseId: c.id,
821
+ caseHash: c.hash,
822
+ prompt: decisionPrompt(c),
823
+ promptSha256: hash(decisionPrompt(c))
824
+ }))
825
+ };
826
+ }
827
+ async function runPiBaseline({
828
+ cases,
829
+ manifest,
830
+ model,
831
+ thinking,
832
+ split,
833
+ piPackage,
834
+ nodeExecutable,
835
+ authPath,
836
+ out,
837
+ timeoutMs,
838
+ runner,
839
+ emit = () => {
840
+ }
841
+ }) {
842
+ const preview = previewPi(cases, manifest, { model, thinking, split });
843
+ const adapter = await import("@skill-harness/adapters");
844
+ const call = runner ?? adapter.runPiDecision;
845
+ const header = {
846
+ schema: 2,
847
+ kind: "decision-pi-run",
848
+ runId: randomUUID(),
849
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
850
+ caseSetHash: preview.caseSetHash,
851
+ experimentHash: preview.experimentHash,
852
+ requestedModel: model,
853
+ thinking,
854
+ split,
855
+ selected: preview.requests.map((r) => ({
856
+ caseId: r.caseId,
857
+ caseHash: r.caseHash
858
+ })),
859
+ promptRevision: PROMPT_REVISION,
860
+ systemPromptSha256: hash(adapter.DECISION_SYSTEM_PROMPT),
861
+ trainingEligible: false
862
+ };
863
+ const file = await open2(out, "wx", 384);
864
+ const append = async (row) => {
865
+ await file.writeFile(JSON.stringify(row) + "\n");
866
+ await file.sync();
867
+ };
868
+ let failed = false;
869
+ try {
870
+ await append(header);
871
+ for (const request of preview.requests) {
872
+ let result;
873
+ try {
874
+ result = await call({
875
+ piPackage,
876
+ model,
877
+ thinking,
878
+ nodeExecutable,
879
+ authPath,
880
+ prompt: request.prompt,
881
+ caseId: request.caseId,
882
+ timeoutMs
883
+ });
884
+ } catch {
885
+ result = {
886
+ status: "error",
887
+ probability: null,
888
+ error: "Pi decision failed; no retry or fallback."
889
+ };
890
+ failed = true;
891
+ }
892
+ await append({
893
+ kind: "prediction",
894
+ caseId: request.caseId,
895
+ caseHash: request.caseHash,
896
+ promptSha256: request.promptSha256,
897
+ ...result,
898
+ trainingEligible: false
899
+ });
900
+ emit({ caseId: request.caseId, status: result.status });
901
+ if (failed) break;
902
+ }
903
+ } finally {
904
+ await file.close();
905
+ }
906
+ if (failed)
907
+ throw Error(
908
+ "Pi comparison stopped after an error; partial run retained without retry."
909
+ );
910
+ return header;
911
+ }
912
+ function parsePiRun(text2, cases, manifest) {
913
+ manifest = parseExperiment(cases, manifest);
914
+ let rows;
915
+ try {
916
+ rows = text2.trim().split("\n").map((line) => JSON.parse(line));
917
+ } catch {
918
+ throw Error("Invalid Pi run JSONL");
919
+ }
920
+ const [header, ...records] = rows;
921
+ keys2(
922
+ header,
923
+ [
924
+ "schema",
925
+ "kind",
926
+ "runId",
927
+ "createdAt",
928
+ "caseSetHash",
929
+ "experimentHash",
930
+ "requestedModel",
931
+ "thinking",
932
+ "split",
933
+ "selected",
934
+ "promptRevision",
935
+ "systemPromptSha256",
936
+ "trainingEligible"
937
+ ],
938
+ "Pi run header"
939
+ );
940
+ check2(
941
+ header.schema === 2 && header.kind === "decision-pi-run" && header.trainingEligible === false,
942
+ "unsupported Pi run"
943
+ );
944
+ check2(
945
+ /^[0-9a-f-]{36}$/.test(header.runId) && new Date(header.createdAt).toISOString() === header.createdAt,
946
+ "invalid run identity/time"
947
+ );
948
+ const preview = previewPi(cases, manifest, {
949
+ model: header.requestedModel,
950
+ thinking: header.thinking,
951
+ split: header.split
952
+ });
953
+ check2(
954
+ header.caseSetHash === preview.caseSetHash && header.experimentHash === preview.experimentHash && header.promptRevision === PROMPT_REVISION && learningDigest(header.selected) === learningDigest(
955
+ preview.requests.map((r) => ({
956
+ caseId: r.caseId,
957
+ caseHash: r.caseHash
958
+ }))
959
+ ),
960
+ "Pi run belongs to a different experiment/selection"
961
+ );
962
+ check2(
963
+ header.systemPromptSha256 === hash(DECISION_SYSTEM_PROMPT),
964
+ "Pi system prompt revision mismatch"
965
+ );
966
+ check2(records.length <= preview.requests.length, "too many Pi predictions");
967
+ let worker = null;
968
+ const sessions = /* @__PURE__ */ new Set();
969
+ for (const [i, r] of records.entries()) {
970
+ const expected = preview.requests[i];
971
+ check2(
972
+ r.caseId === expected.caseId && r.caseHash === expected.caseHash && r.promptSha256 === expected.promptSha256 && r.kind === "prediction" && r.trainingEligible === false,
973
+ "Pi prediction identity/order mismatch"
974
+ );
975
+ if (r.status === "error") {
976
+ keys2(
977
+ r,
978
+ [
979
+ "kind",
980
+ "caseId",
981
+ "caseHash",
982
+ "promptSha256",
983
+ "status",
984
+ "probability",
985
+ "error",
986
+ "trainingEligible"
987
+ ],
988
+ "Pi error"
989
+ );
990
+ check2(
991
+ r.probability === null && r.error === "Pi decision failed; no retry or fallback." && i === records.length - 1,
992
+ "invalid Pi error prefix"
993
+ );
994
+ continue;
995
+ }
996
+ keys2(
997
+ r,
998
+ [
999
+ "kind",
1000
+ "caseId",
1001
+ "caseHash",
1002
+ "promptSha256",
1003
+ "status",
1004
+ "probability",
1005
+ "resolvedModel",
1006
+ "piVersion",
1007
+ "nativeFinal",
1008
+ "usage",
1009
+ "route",
1010
+ "trainingEligible",
1011
+ "latencyMs",
1012
+ "workerSha256",
1013
+ "systemPromptSha256"
1014
+ ],
1015
+ "Pi prediction"
1016
+ );
1017
+ check2(
1018
+ ["answered", "abstained"].includes(r.status) && r.resolvedModel === header.requestedModel && r.piVersion === "1.0.4" && r.systemPromptSha256 === header.systemPromptSha256,
1019
+ "Pi result route/version mismatch"
1020
+ );
1021
+ check2(
1022
+ r.status === "abstained" ? r.probability === null : typeof r.probability === "number" && Number.isFinite(r.probability) && r.probability >= 0 && r.probability <= 1,
1023
+ "invalid Pi probability"
1024
+ );
1025
+ keys2(r.nativeFinal, ["sessionId", "messageId", "sha256"], "native final");
1026
+ for (const k of ["sessionId", "messageId"])
1027
+ check2(
1028
+ typeof r.nativeFinal[k] === "string" && r.nativeFinal[k].length > 0 && r.nativeFinal[k].length <= 256,
1029
+ "invalid native identity"
1030
+ );
1031
+ sha3(r.nativeFinal.sha256);
1032
+ check2(
1033
+ !sessions.has(r.nativeFinal.sessionId),
1034
+ "Pi cases must have independent fresh sessions"
1035
+ );
1036
+ sessions.add(r.nativeFinal.sessionId);
1037
+ keys2(r.route, ["oauth", "subscription"], "route");
1038
+ check2(
1039
+ r.route.oauth === true && r.route.subscription === true,
1040
+ "non-subscription Pi result"
1041
+ );
1042
+ keys2(r.usage, ["inputTokens", "outputTokens", "costUsd"], "usage");
1043
+ check2(r.usage.costUsd === null, "subscription cost must not be estimated");
1044
+ for (const k of ["inputTokens", "outputTokens"])
1045
+ check2(
1046
+ r.usage[k] === null || Number.isSafeInteger(r.usage[k]) && r.usage[k] >= 0,
1047
+ "invalid Pi usage"
1048
+ );
1049
+ check2(Number.isFinite(r.latencyMs) && r.latencyMs >= 0, "invalid latency");
1050
+ sha3(r.workerSha256);
1051
+ if (worker !== null)
1052
+ check2(worker === r.workerSha256, "mixed worker revisions");
1053
+ worker = r.workerSha256;
1054
+ }
1055
+ return {
1056
+ header,
1057
+ records,
1058
+ runSha256: hash(text2),
1059
+ cases: preview.requests.map((r) => cases.find((c) => c.id === r.caseId)),
1060
+ experimentBound: true
1061
+ };
1062
+ }
1063
+ function metrics(cases, labels, records) {
1064
+ const normalized = records.map((r) => ({
1065
+ caseId: r.caseId,
1066
+ caseHash: r.caseHash,
1067
+ status: r.status === "abstained" ? "refused" : r.status,
1068
+ probability: r.probability
1069
+ }));
1070
+ const score = scorePredictions(cases, labels, normalized);
1071
+ const bins = Array.from({ length: 5 }, (_, i) => ({
1072
+ lower: i / 5,
1073
+ upper: (i + 1) / 5,
1074
+ count: 0,
1075
+ meanProbability: null,
1076
+ observedPositiveFraction: null
1077
+ }));
1078
+ for (const r of records) {
1079
+ const label = labels.find((l) => l.caseId === r.caseId);
1080
+ if (r.status !== "answered" || !label) continue;
1081
+ const bin = bins[Math.min(4, Math.floor(r.probability * 5))];
1082
+ bin.count++;
1083
+ bin.meanProbability = (bin.meanProbability ?? 0) + (r.probability - (bin.meanProbability ?? 0)) / bin.count;
1084
+ bin.observedPositiveFraction = (bin.observedPositiveFraction ?? 0) + (Number(label.value) - (bin.observedPositiveFraction ?? 0)) / bin.count;
1085
+ }
1086
+ const latency = records.filter((r) => Number.isFinite(r.latencyMs));
1087
+ let mean = null;
1088
+ latency.forEach((r, i) => {
1089
+ mean = (mean ?? 0) + (r.latencyMs - (mean ?? 0)) / (i + 1);
1090
+ });
1091
+ return {
1092
+ ...score,
1093
+ abstained: records.filter((r) => r.status === "abstained").length,
1094
+ calibration: bins,
1095
+ latencyMs: { reported: latency.length, attempted: records.length, mean },
1096
+ errorCases: records.filter((r) => r.status === "error").map((r) => r.caseId),
1097
+ falsePositiveCases: records.filter(
1098
+ (r) => r.status === "answered" && r.probability >= 0.5 && labels.some((l) => l.caseId === r.caseId && !l.value)
1099
+ ).map((r) => r.caseId),
1100
+ falseNegativeCases: records.filter(
1101
+ (r) => r.status === "answered" && r.probability < 0.5 && labels.some((l) => l.caseId === r.caseId && l.value)
1102
+ ).map((r) => r.caseId)
1103
+ };
1104
+ }
1105
+ function compareRuns(cases, labels, manifest, runs) {
1106
+ manifest = parseExperiment(cases, manifest);
1107
+ check2(
1108
+ Array.isArray(runs) && runs.length >= 1 && runs.length <= 8,
1109
+ "compare 1..8 explicit runs"
1110
+ );
1111
+ check2(
1112
+ new Set(runs.map((r) => r.runSha256)).size === runs.length,
1113
+ "duplicate run files"
1114
+ );
1115
+ const answered = runs.map(
1116
+ (run) => new Set(
1117
+ run.records.filter((r) => r.status === "answered").map((r) => r.caseId)
1118
+ )
1119
+ );
1120
+ const common = cases.filter(
1121
+ (c) => labels.some((l) => l.caseId === c.id) && answered.every((set) => set.has(c.id))
1122
+ );
1123
+ const arms = runs.map((run) => {
1124
+ const observedModels = [
1125
+ ...new Set(
1126
+ run.records.filter((r) => r.status === "answered" || r.status === "abstained").map((r) => r.resolvedModel)
1127
+ )
1128
+ ];
1129
+ check2(
1130
+ observedModels.length <= 1,
1131
+ "mixed resolved models in one comparison arm"
1132
+ );
1133
+ const selectedCases = run.cases ?? cases;
1134
+ const selectedIds = new Set(selectedCases.map((c) => c.id));
1135
+ const armLabels = labels.filter((l) => selectedIds.has(l.caseId));
1136
+ return {
1137
+ runSha256: run.runSha256,
1138
+ runId: run.header.runId ?? null,
1139
+ model: run.header.requestedModel,
1140
+ observedModels,
1141
+ createdAt: run.header.createdAt,
1142
+ thinking: run.header.thinking ?? null,
1143
+ split: run.header.split ?? null,
1144
+ promptRevision: run.header.promptRevision ?? null,
1145
+ systemPromptSha256: run.header.systemPromptSha256 ?? null,
1146
+ workerSha256: run.records.find((r) => r.workerSha256)?.workerSha256 ?? null,
1147
+ experimentBound: run.experimentBound === true,
1148
+ selection: metrics(selectedCases, armLabels, run.records),
1149
+ common: metrics(
1150
+ common,
1151
+ labels.filter((l) => common.some((c) => c.id === l.caseId)),
1152
+ run.records.filter((r) => common.some((c) => c.id === r.caseId))
1153
+ )
1154
+ };
1155
+ });
1156
+ return {
1157
+ schema: 1,
1158
+ kind: "decision-comparison",
1159
+ caseSetHash: fullCaseSetHash(cases),
1160
+ experimentHash: learningDigest(manifest),
1161
+ labelSetHash: learningDigest(labels),
1162
+ threshold: 0.5,
1163
+ thresholdSelection: "fixed-before-run",
1164
+ commonLabeledAnswered: common.length,
1165
+ commonCaseIds: common.map((c) => c.id),
1166
+ arms,
1167
+ polarity: "false positive means the recorded question was answered true for a false independent label; it means false-ready only for a question whose true polarity is readiness",
1168
+ population: {
1169
+ cases: cases.length,
1170
+ groups: new Set(manifest.entries.map((e) => e.taskGroup)).size,
1171
+ synthetic: cases.filter((c) => c.provenance === "synthetic").length,
1172
+ observed: cases.filter((c) => c.provenance === "observed").length
1173
+ },
1174
+ qualification: "descriptive comparison only; no runtime qualification or speed improvement claim",
1175
+ trainingReady: false
1176
+ };
1177
+ }
1178
+
1179
+ // experiments/decision-shadow/research-commands.mjs
1180
+ var RESEARCH_OPTIONS = {
1181
+ "validate-experiment": ["cases", "experiment"],
1182
+ "preview-pi": ["cases", "experiment", "model", "thinking", "split"],
1183
+ "run-pi": [
1184
+ "cases",
1185
+ "experiment",
1186
+ "model",
1187
+ "thinking",
1188
+ "split",
1189
+ "pi-package",
1190
+ "out",
1191
+ "allow-subscription"
1192
+ ],
1193
+ compare: ["cases", "experiment", "labels", "label-evidence", "run", "out"],
1194
+ "export-learning": [
1195
+ "cases",
1196
+ "experiment",
1197
+ "labels",
1198
+ "label-evidence",
1199
+ "consents",
1200
+ "mode",
1201
+ "out"
1202
+ ],
1203
+ "import-session": ["cases", "selection", "consent", "entry", "out"],
1204
+ fixtures: ["out"]
1205
+ };
1206
+ var RESEARCH_OPTIONAL = {
1207
+ "run-pi": ["pi-node", "auth-path", "timeout-ms"]
1208
+ };
1209
+ async function researchCommand(command, flags, helpers, options) {
1210
+ const { cases, jsonFile: jsonFile2, textFile: textFile2, writeNew: writeNew2, readLegacyRun } = helpers;
1211
+ const { emit = console.log, piRunner } = options;
1212
+ if (command === "fixtures") {
1213
+ const f = createLearningFixtures({});
1214
+ const dir = resolve(flags.out);
1215
+ await mkdir2(dir, { mode: 448 });
1216
+ const refs = [];
1217
+ for (const r of f.labelReceipts) {
1218
+ const path = join2(dir, r.filename);
1219
+ await writeFile2(path, r.bytes, { flag: "wx", mode: 384 });
1220
+ refs.push({ caseId: r.caseId, path, sha256: r.sha256 });
1221
+ }
1222
+ for (const [name, value] of Object.entries({
1223
+ "cases.json": f.caseDocument,
1224
+ "labels.json": f.labelDocument,
1225
+ "experiment.json": f.manifest,
1226
+ "label-evidence.json": refs,
1227
+ "consents.json": []
1228
+ }))
1229
+ await writeNew2(join2(dir, name), value);
1230
+ emit(
1231
+ JSON.stringify({
1232
+ saved: dir,
1233
+ cases: f.caseDocument.cases.length,
1234
+ fixtureOnly: true,
1235
+ trainingEligible: false
1236
+ })
1237
+ );
1238
+ return;
1239
+ }
1240
+ if (command === "import-session") {
1241
+ const result = await importSelectedCase({
1242
+ caseDocument: await jsonFile2(flags.cases),
1243
+ selection: await jsonFile2(flags.selection),
1244
+ sessionConsent: await jsonFile2(flags.consent),
1245
+ experimentEntry: await jsonFile2(flags.entry)
1246
+ });
1247
+ await writeNew2(flags.out, result);
1248
+ emit(JSON.stringify({ saved: resolve(flags.out), trainingReady: false }));
1249
+ return;
1250
+ }
1251
+ const manifest = parseExperiment(cases, await jsonFile2(flags.experiment));
1252
+ if (command === "validate-experiment") {
1253
+ emit(
1254
+ JSON.stringify({
1255
+ valid: true,
1256
+ cases: cases.length,
1257
+ groups: new Set(manifest.entries.map((e) => e.taskGroup)).size,
1258
+ trainingReady: false
1259
+ })
1260
+ );
1261
+ return;
1262
+ }
1263
+ if (command === "preview-pi") {
1264
+ emit(
1265
+ JSON.stringify(
1266
+ previewPi(cases, manifest, {
1267
+ model: flags.model,
1268
+ thinking: flags.thinking,
1269
+ split: flags.split
1270
+ }),
1271
+ null,
1272
+ 2
1273
+ )
1274
+ );
1275
+ return;
1276
+ }
1277
+ if (command === "run-pi") {
1278
+ const header = await runPiBaseline({
1279
+ cases,
1280
+ manifest,
1281
+ model: flags.model,
1282
+ thinking: flags.thinking,
1283
+ split: flags.split,
1284
+ piPackage: flags["pi-package"],
1285
+ nodeExecutable: flags["pi-node"],
1286
+ authPath: flags["auth-path"],
1287
+ timeoutMs: flags["timeout-ms"] === void 0 ? void 0 : Number(flags["timeout-ms"]),
1288
+ out: flags.out,
1289
+ runner: piRunner
1290
+ });
1291
+ emit(
1292
+ JSON.stringify({
1293
+ saved: resolve(flags.out),
1294
+ runId: header.runId,
1295
+ trainingEligible: false
1296
+ })
1297
+ );
1298
+ return;
1299
+ }
1300
+ const labels = parseLabels(await jsonFile2(flags.labels), cases);
1301
+ const labelEvidence = await jsonFile2(flags["label-evidence"]);
1302
+ if (command === "export-learning") {
1303
+ const prepared = await prepareExport({
1304
+ cases,
1305
+ labels,
1306
+ manifest,
1307
+ consents: await jsonFile2(flags.consents),
1308
+ labelEvidence,
1309
+ mode: flags.mode
1310
+ });
1311
+ const result = await writePreparedExport({
1312
+ prepared,
1313
+ directory: resolve(flags.out)
1314
+ });
1315
+ emit(JSON.stringify(result));
1316
+ return;
1317
+ }
1318
+ if (command === "compare") {
1319
+ const evidence = await reviewLabelEvidence({
1320
+ cases,
1321
+ labels,
1322
+ references: labelEvidence
1323
+ });
1324
+ const runs = [];
1325
+ for (const path of flags.run) {
1326
+ const text2 = await textFile2(path);
1327
+ let first;
1328
+ try {
1329
+ first = JSON.parse(text2.split("\n")[0]);
1330
+ } catch {
1331
+ throw Error("Invalid run header");
1332
+ }
1333
+ if (first.kind === "decision-pi-run")
1334
+ runs.push(parsePiRun(text2, cases, manifest));
1335
+ else
1336
+ runs.push({
1337
+ ...await readLegacyRun(path, cases),
1338
+ cases,
1339
+ experimentBound: false
1340
+ });
1341
+ }
1342
+ const report = compareRuns(cases, labels, manifest, runs);
1343
+ report.labelEvidenceReview = evidence;
1344
+ await writeNew2(flags.out, report);
1345
+ emit(
1346
+ JSON.stringify({
1347
+ saved: resolve(flags.out),
1348
+ commonLabeledAnswered: report.commonLabeledAnswered,
1349
+ trainingReady: false
1350
+ })
1351
+ );
1352
+ return;
1353
+ }
1354
+ throw Error("Unhandled research command");
1355
+ }
1356
+
1357
+ // experiments/decision-shadow/consent.mjs
1358
+ import { randomUUID as randomUUID2 } from "node:crypto";
1359
+ import { createInterface } from "node:readline/promises";
1360
+ import { stdin, stderr } from "node:process";
1361
+ import { openSync, writeFileSync, fsyncSync, closeSync } from "node:fs";
1362
+ var STORAGE_QUESTION = "Retain this session\u2019s explicitly selected inputs and questions for future LoRA dataset review? This grants storage only, not training or use of JEV predictions as labels.";
1363
+ async function requestSessionStorage({
1364
+ sessionId = randomUUID2(),
1365
+ storage,
1366
+ promptStorage
1367
+ } = {}) {
1368
+ let answer = storage;
1369
+ if (answer === void 0) {
1370
+ if (promptStorage) answer = await promptStorage(STORAGE_QUESTION);
1371
+ else {
1372
+ if (!stdin.isTTY || !stderr.isTTY)
1373
+ throw Error(
1374
+ "JEV activation requires this session\u2019s storage choice: --storage yes|no (no inherited preference)."
1375
+ );
1376
+ const rl = createInterface({ input: stdin, output: stderr });
1377
+ try {
1378
+ answer = /^(y|yes)$/i.test(
1379
+ (await rl.question(STORAGE_QUESTION + " [y/N] ")).trim()
1380
+ ) ? "yes" : "no";
1381
+ } finally {
1382
+ rl.close();
1383
+ }
1384
+ }
1385
+ }
1386
+ if (answer === true) answer = "yes";
1387
+ if (answer === false || answer === null) answer = "no";
1388
+ if (!["yes", "no"].includes(answer))
1389
+ throw Error("--storage must be yes or no");
1390
+ return createStorageConsent({
1391
+ sessionId,
1392
+ decision: answer === "yes" ? "granted" : "declined",
1393
+ interactionId: randomUUID2(),
1394
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString()
1395
+ });
1396
+ }
1397
+ async function retainSelectedSessionData({
1398
+ cases,
1399
+ consent,
1400
+ out,
1401
+ assertCurrent = () => {
1402
+ }
1403
+ }) {
1404
+ consent = parseStorageConsent(consent, consent.sessionId);
1405
+ if (consent.decision === "declined")
1406
+ return {
1407
+ retained: false,
1408
+ reason: "session-storage-declined",
1409
+ trainingReady: false
1410
+ };
1411
+ const document = {
1412
+ schema: 1,
1413
+ kind: "decision-session-selected-data",
1414
+ sessionId: consent.sessionId,
1415
+ consent,
1416
+ consentSha256: learningDigest(consent),
1417
+ trainingReady: false,
1418
+ trainingEligible: false,
1419
+ labelStatus: "unlabeled",
1420
+ providerOutputsIncluded: false,
1421
+ cases: cases.map(({ hash: hash3, ...c }) => ({ caseHash: hash3, ...c }))
1422
+ };
1423
+ assertCurrent();
1424
+ const fd = openSync(out, "wx", 384);
1425
+ try {
1426
+ writeFileSync(fd, JSON.stringify(document, null, 2) + "\n");
1427
+ fsyncSync(fd);
1428
+ } finally {
1429
+ closeSync(fd);
1430
+ }
1431
+ return { retained: true, path: out, trainingReady: false };
1432
+ }
1433
+
1434
+ // experiments/decision-shadow/providers.mjs
1435
+ var MAX_RESPONSE_BYTES = 64 * 1024;
1436
+ var MAX_INPUT_LENGTH = 64 * 1024;
1437
+ var MAX_QUESTION_LENGTH = 8 * 1024;
1438
+ var PROVIDER = Object.freeze({
1439
+ name: "jev",
1440
+ model: "typesafe/jev-1.13",
1441
+ url: "https://openrouter.ai/api/alpha/decisions"
1442
+ });
1443
+ var emptyUsage = () => ({ inputTokens: null, outputTokens: null, costUsd: null });
1444
+ function requiredString(value, name, maxLength) {
1445
+ if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
1446
+ throw new TypeError(`${name} must be a non-empty bounded string`);
1447
+ }
1448
+ }
1449
+ function validateSelection(provider, model) {
1450
+ if (provider !== PROVIDER.name) throw new TypeError("unsupported provider");
1451
+ if (model !== PROVIDER.model) throw new TypeError("unsupported model");
1452
+ }
1453
+ function makeRequest(provider, model, example) {
1454
+ validateSelection(provider, model);
1455
+ if (!example || typeof example !== "object" || Array.isArray(example)) {
1456
+ throw new TypeError("example must be an object");
1457
+ }
1458
+ requiredString(example.input, "input", MAX_INPUT_LENGTH);
1459
+ requiredString(example.question, "question", MAX_QUESTION_LENGTH);
1460
+ return {
1461
+ url: PROVIDER.url,
1462
+ body: {
1463
+ model,
1464
+ state: example.input,
1465
+ questions: { decision: { type: "noul", instructions: example.question } }
1466
+ }
1467
+ };
1468
+ }
1469
+ function isRecord(value) {
1470
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1471
+ }
1472
+ function probability(value) {
1473
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1474
+ throw new Error("invalid provider response");
1475
+ }
1476
+ return value;
1477
+ }
1478
+ function tokenCount(value) {
1479
+ if (!Number.isSafeInteger(value) || value < 0) {
1480
+ throw new Error("invalid provider response");
1481
+ }
1482
+ return value;
1483
+ }
1484
+ function cost(value) {
1485
+ if (value === void 0 || value === null) return null;
1486
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
1487
+ throw new Error("invalid provider response");
1488
+ }
1489
+ return value;
1490
+ }
1491
+ function isSupportedResolvedModel(value) {
1492
+ if (value === PROVIDER.model) return true;
1493
+ const prefix = `${PROVIDER.model}-`;
1494
+ if (typeof value !== "string" || !value.startsWith(prefix)) return false;
1495
+ const snapshot = value.slice(prefix.length);
1496
+ if (snapshot.length !== 8 || !/^\d{8}$/.test(snapshot)) return false;
1497
+ const year = Number(snapshot.slice(0, 4));
1498
+ const month = Number(snapshot.slice(4, 6));
1499
+ const day = Number(snapshot.slice(6, 8));
1500
+ const leapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
1501
+ const monthLengths = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1502
+ return year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= monthLengths[month - 1];
1503
+ }
1504
+ function resolvedModel(value) {
1505
+ if (!isSupportedResolvedModel(value)) {
1506
+ throw new Error("invalid provider response");
1507
+ }
1508
+ return value;
1509
+ }
1510
+ function normalize2(payload) {
1511
+ if (!isRecord(payload) || !isRecord(payload.answers) || Object.keys(payload.answers).length !== 1 || !isRecord(payload.usage)) {
1512
+ throw new Error("invalid provider response");
1513
+ }
1514
+ const answer = payload.answers.decision;
1515
+ if (!isRecord(answer) || answer.type !== "noul") {
1516
+ throw new Error("invalid provider response");
1517
+ }
1518
+ return {
1519
+ status: "answered",
1520
+ probability: probability(answer.noul),
1521
+ resolvedModel: resolvedModel(payload.model),
1522
+ usage: {
1523
+ inputTokens: tokenCount(payload.usage.input_tokens),
1524
+ outputTokens: tokenCount(payload.usage.output_tokens),
1525
+ costUsd: cost(payload.usage.cost)
1526
+ },
1527
+ error: null
1528
+ };
1529
+ }
1530
+ function deadline(signal) {
1531
+ return new Promise((_, reject) => {
1532
+ if (signal.aborted) return reject(new Error("provider request timed out"));
1533
+ signal.addEventListener("abort", () => reject(new Error("provider request timed out")), { once: true });
1534
+ });
1535
+ }
1536
+ async function readBounded(response, timedOut) {
1537
+ if (!response.body || typeof response.body.getReader !== "function") {
1538
+ throw new Error("invalid provider response");
1539
+ }
1540
+ const reader = response.body.getReader();
1541
+ const chunks = [];
1542
+ let length = 0;
1543
+ let complete = false;
1544
+ try {
1545
+ for (; ; ) {
1546
+ const { done, value } = await Promise.race([reader.read(), timedOut]);
1547
+ if (done) {
1548
+ complete = true;
1549
+ break;
1550
+ }
1551
+ if (!(value instanceof Uint8Array)) throw new Error("invalid provider response");
1552
+ length += value.byteLength;
1553
+ if (length > MAX_RESPONSE_BYTES) throw new Error("provider response exceeded limit");
1554
+ chunks.push(value);
1555
+ }
1556
+ } finally {
1557
+ if (!complete) void reader.cancel().catch(() => {
1558
+ });
1559
+ }
1560
+ const bytes = new Uint8Array(length);
1561
+ let offset = 0;
1562
+ for (const chunk of chunks) {
1563
+ bytes.set(chunk, offset);
1564
+ offset += chunk.byteLength;
1565
+ }
1566
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
1567
+ }
1568
+ function failure(startedAt, error) {
1569
+ const publicErrors = ["invalid provider response", "provider request timed out", "provider response exceeded limit"];
1570
+ const message = error instanceof Error && publicErrors.includes(error.message) ? error.message : "provider request failed";
1571
+ return {
1572
+ status: "error",
1573
+ probability: null,
1574
+ resolvedModel: null,
1575
+ usage: emptyUsage(),
1576
+ latencyMs: Math.max(0, performance.now() - startedAt),
1577
+ error: message
1578
+ };
1579
+ }
1580
+ async function callProvider(provider, model, example, options = {}) {
1581
+ const startedAt = performance.now();
1582
+ let controller;
1583
+ let timer;
1584
+ try {
1585
+ const request = makeRequest(provider, model, example);
1586
+ requiredString(options.apiKey, "apiKey", 16 * 1024);
1587
+ if (options.fetchImpl !== void 0 && typeof options.fetchImpl !== "function") {
1588
+ throw new TypeError("fetchImpl must be a function");
1589
+ }
1590
+ const timeoutMs = options.timeoutMs ?? 3e4;
1591
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 3e5) {
1592
+ throw new TypeError("invalid timeout");
1593
+ }
1594
+ controller = new AbortController();
1595
+ timer = setTimeout(() => controller.abort(), timeoutMs);
1596
+ const timedOut = deadline(controller.signal);
1597
+ const response = await Promise.race([
1598
+ (options.fetchImpl ?? fetch)(request.url, {
1599
+ method: "POST",
1600
+ headers: { authorization: `Bearer ${options.apiKey}`, "content-type": "application/json" },
1601
+ body: JSON.stringify(request.body),
1602
+ redirect: "error",
1603
+ signal: controller.signal
1604
+ }),
1605
+ timedOut
1606
+ ]);
1607
+ if (!response || response.ok !== true) throw new Error("provider request failed");
1608
+ const text2 = await readBounded(response, timedOut);
1609
+ let payload;
1610
+ try {
1611
+ payload = JSON.parse(text2);
1612
+ } catch {
1613
+ throw new Error("invalid provider response");
1614
+ }
1615
+ return { ...normalize2(payload), latencyMs: Math.max(0, performance.now() - startedAt) };
1616
+ } catch (error) {
1617
+ return failure(startedAt, error);
1618
+ } finally {
1619
+ if (timer !== void 0) clearTimeout(timer);
1620
+ controller?.abort();
1621
+ }
1622
+ }
1623
+
1624
+ // experiments/decision-shadow/public-evidence.mjs
1625
+ import { constants as constants2 } from "node:fs";
1626
+ import { open as open3 } from "node:fs/promises";
1627
+ import { createHash as createHash4 } from "node:crypto";
1628
+ import { isDeepStrictEqual } from "node:util";
1629
+ import { basename, dirname, isAbsolute as isAbsolute2, join as join3, normalize as normalize3 } from "node:path";
1630
+ var MAX_PUBLIC_SOURCE_FILE_BYTES = 64 * 1024 * 1024;
1631
+ var MAX_PUBLIC_SOURCE_TOTAL_BYTES = 128 * 1024 * 1024;
1632
+ var MAX_PUBLIC_SOURCE_FILES = 256;
1633
+ var MAX_PUBLIC_SOURCE_MANIFEST_BYTES = 1024 * 1024;
1634
+ var LIMITS = Object.freeze({
1635
+ fileBytes: MAX_PUBLIC_SOURCE_FILE_BYTES,
1636
+ totalBytes: MAX_PUBLIC_SOURCE_TOTAL_BYTES,
1637
+ files: MAX_PUBLIC_SOURCE_FILES,
1638
+ manifestBytes: MAX_PUBLIC_SOURCE_MANIFEST_BYTES
1639
+ });
1640
+ var HASH = /^[0-9a-f]{64}$/;
1641
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
1642
+ var TOOLS = ["delegate_describe", "delegate", "delegate_all", "delegate_chain"];
1643
+ var RUNTIME_PREFIX = "Runtime execution evidence (process settlement; not workspace cleanup or task acceptance):\n```json\n";
1644
+ var DIR_FLAGS = constants2.O_RDONLY | constants2.O_DIRECTORY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK;
1645
+ var FILE_FLAGS = constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK;
1646
+ var digest = (bytes) => createHash4("sha256").update(bytes).digest("hex");
1647
+ function check3(ok, message) {
1648
+ if (!ok) throw new TypeError(message);
1649
+ }
1650
+ function object(value, name) {
1651
+ check3(value !== null && typeof value === "object" && !Array.isArray(value), `${name} must be an object`);
1652
+ }
1653
+ function keys3(value, expected, name) {
1654
+ object(value, name);
1655
+ check3(isDeepStrictEqual(Object.keys(value).sort(), [...expected].sort()), `${name} has unsupported or missing fields`);
1656
+ }
1657
+ function string(value, name, nullable = false) {
1658
+ if (nullable && value === null) return;
1659
+ check3(typeof value === "string" && value.length > 0 && value.length <= 4096 && !value.includes("\0"), `${name} must be a bounded nonempty string`);
1660
+ }
1661
+ function publicText(value, name) {
1662
+ check3(typeof value === "string", name + " must be a string");
1663
+ }
1664
+ function hash2(value, name, nullable = false) {
1665
+ check3(nullable && value === null || typeof value === "string" && HASH.test(value), `${name} must be a lowercase SHA-256`);
1666
+ }
1667
+ function integer(value, name, min = 0) {
1668
+ check3(Number.isSafeInteger(value) && value >= min, `${name} must be a bounded integer`);
1669
+ }
1670
+ function enumeration(value, allowed, name) {
1671
+ check3(allowed.includes(value), `${name} has an unsupported value`);
1672
+ }
1673
+ function array(value, name, max = 64) {
1674
+ check3(Array.isArray(value) && value.length <= max, `${name} must be a bounded array`);
1675
+ }
1676
+ function canonicalPath(path, name) {
1677
+ string(path, name);
1678
+ check3(isAbsolute2(path) && normalize3(path) === path && (path === "/" || !path.endsWith("/")), `${name} must be canonical and absolute`);
1679
+ check3(path.split("/").length <= 128, `${name} exceeds directory depth limit`);
1680
+ }
1681
+ function metadata(stat2) {
1682
+ return ["dev", "ino", "mode", "uid", "gid", "size", "mtimeNs", "ctimeNs"].map((key) => stat2[key].toString()).join(":");
1683
+ }
1684
+ function identity(stat2) {
1685
+ return ["dev", "ino", "mode"].map((key) => stat2[key].toString()).join(":");
1686
+ }
1687
+ function json2(bytes, name) {
1688
+ try {
1689
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes));
1690
+ } catch {
1691
+ throw new TypeError(`${name} must be UTF-8 JSON without a BOM`);
1692
+ }
1693
+ }
1694
+ var EvidenceReader = class {
1695
+ directories = [];
1696
+ captures = /* @__PURE__ */ new Map();
1697
+ files = /* @__PURE__ */ new Map();
1698
+ totalBytes = 0;
1699
+ constructor(root) {
1700
+ this.root = root;
1701
+ }
1702
+ async directory(parent, component, path, stable = false) {
1703
+ const handle = await open3(parent ? `/proc/self/fd/${parent.handle.fd}/${component}` : "/", DIR_FLAGS);
1704
+ try {
1705
+ const stat2 = await handle.stat({ bigint: true });
1706
+ check3(stat2.isDirectory(), "Evidence path component is not a directory");
1707
+ const record = { handle, parent, component, path, metadata: metadata(stat2), identity: identity(stat2), stable };
1708
+ this.directories.push(record);
1709
+ return record;
1710
+ } catch (error) {
1711
+ await handle.close();
1712
+ throw error;
1713
+ }
1714
+ }
1715
+ async start() {
1716
+ let current = await this.directory(null, "", "/");
1717
+ let path = "";
1718
+ for (const component of this.root.split("/").filter(Boolean)) {
1719
+ path += "/" + component;
1720
+ current = await this.directory(current, component, path);
1721
+ }
1722
+ current.stable = true;
1723
+ this.rootDirectory = current;
1724
+ }
1725
+ async read(path, limit = MAX_PUBLIC_SOURCE_FILE_BYTES) {
1726
+ canonicalPath(path, "Copied evidence path");
1727
+ const capturePath = dirname(path), captureId = basename(capturePath);
1728
+ check3(dirname(capturePath) === this.root && UUID.test(captureId), "Copied evidence must be directly inside a capture directory beneath the evidence root");
1729
+ const cached = this.files.get(path);
1730
+ if (cached) {
1731
+ check3(cached.bytes.length <= limit, "Evidence file exceeds its byte limit");
1732
+ return cached.bytes;
1733
+ }
1734
+ check3(this.files.size < MAX_PUBLIC_SOURCE_FILES, "Evidence file-count limit exceeded");
1735
+ let directory = this.captures.get(captureId);
1736
+ if (!directory) {
1737
+ directory = await this.directory(this.rootDirectory, captureId, capturePath, true);
1738
+ this.captures.set(captureId, directory);
1739
+ }
1740
+ const name = basename(path);
1741
+ const handle = await open3(`/proc/self/fd/${directory.handle.fd}/${name}`, FILE_FLAGS);
1742
+ try {
1743
+ const before = await handle.stat({ bigint: true });
1744
+ check3(before.isFile(), "Evidence must be an ordinary file");
1745
+ check3(before.size <= BigInt(limit), "Evidence file exceeds its byte limit");
1746
+ const size = Number(before.size);
1747
+ check3(this.totalBytes + size <= MAX_PUBLIC_SOURCE_TOTAL_BYTES, "Evidence total-byte limit exceeded");
1748
+ const buffer = Buffer.alloc(size + 1);
1749
+ let length = 0;
1750
+ while (length < buffer.length) {
1751
+ const result = await handle.read(buffer, length, buffer.length - length, length);
1752
+ if (!result.bytesRead) break;
1753
+ length += result.bytesRead;
1754
+ }
1755
+ check3(length === size, "Evidence file changed size while reading");
1756
+ check3(metadata(await handle.stat({ bigint: true })) === metadata(before), "Evidence file metadata changed while reading");
1757
+ const bytes = buffer.subarray(0, length);
1758
+ this.files.set(path, { handle, directory, name, metadata: metadata(before), bytes });
1759
+ this.totalBytes += length;
1760
+ return bytes;
1761
+ } catch (error) {
1762
+ await handle.close();
1763
+ throw error;
1764
+ }
1765
+ }
1766
+ async stable() {
1767
+ for (const directory of this.directories) {
1768
+ const current = await directory.handle.stat({ bigint: true });
1769
+ check3(identity(current) === directory.identity && (!directory.stable || metadata(current) === directory.metadata), "Evidence directory changed during verification");
1770
+ const reopened = await open3(directory.parent ? `/proc/self/fd/${directory.parent.handle.fd}/${directory.component}` : "/", DIR_FLAGS);
1771
+ try {
1772
+ check3(identity(await reopened.stat({ bigint: true })) === directory.identity, "Evidence directory path changed during verification");
1773
+ } finally {
1774
+ await reopened.close();
1775
+ }
1776
+ }
1777
+ for (const file of this.files.values()) {
1778
+ check3(metadata(await file.handle.stat({ bigint: true })) === file.metadata, "Evidence file changed during verification");
1779
+ const reopened = await open3(`/proc/self/fd/${file.directory.handle.fd}/${file.name}`, FILE_FLAGS);
1780
+ try {
1781
+ check3(metadata(await reopened.stat({ bigint: true })) === file.metadata, "Evidence file path changed during verification");
1782
+ } finally {
1783
+ await reopened.close();
1784
+ }
1785
+ }
1786
+ }
1787
+ async close() {
1788
+ await Promise.allSettled([...this.files.values()].map((file) => file.handle.close()));
1789
+ await Promise.allSettled(this.directories.map((directory) => directory.handle.close()));
1790
+ }
1791
+ };
1792
+ function requestedRow(row, index, tool) {
1793
+ keys3(row, ["ordinal", "agent", "requestedDefinitionId", "definitionId", "executionId"], "Requested row");
1794
+ check3(row.ordinal === index + 1, "Requested ordinals must be a contiguous ordered sequence");
1795
+ string(row.agent, "Requested agent", true);
1796
+ hash2(row.requestedDefinitionId, "Requested definition identity", true);
1797
+ hash2(row.definitionId, "Observed definition identity", true);
1798
+ string(row.executionId, "Execution identity", tool === "delegate_describe");
1799
+ check3(row.definitionId === null || row.agent !== null, "An observed definition must identify its agent");
1800
+ if (tool === "delegate_describe") check3(row.executionId === null && row.requestedDefinitionId === null && row.agent !== null && row.definitionId !== null, "Describe has a definition but no execution/requested definition identity");
1801
+ }
1802
+ function finalIdentity(final) {
1803
+ keys3(final, ["state", "sessionId", "messageId", "leafId", "sha256"], "Native final");
1804
+ check3(final.state === "complete", "Native final must be complete");
1805
+ for (const key of ["sessionId", "messageId", "leafId"]) string(final[key], "Native final " + key);
1806
+ hash2(final.sha256, "Native final hash");
1807
+ }
1808
+ function workerIdentity(value, executionId) {
1809
+ keys3(value, ["revision", "executionId", "nonce", "root", "rootDevice", "rootInode", "bootId", "pidNamespace", "helperPid", "helperStartTicks", "helperSha256", "workerPid", "ownershipPath", "receiptPath"], "Cleanup identity");
1810
+ check3(value.revision === 1 && value.executionId === executionId, "Cleanup identity must bind the requested execution");
1811
+ for (const key of ["executionId", "nonce", "root", "rootDevice", "rootInode", "bootId", "pidNamespace", "helperStartTicks", "ownershipPath", "receiptPath"]) string(value[key], "Cleanup identity " + key);
1812
+ integer(value.helperPid, "Helper pid", 1);
1813
+ integer(value.workerPid, "Worker pid");
1814
+ hash2(value.helperSha256, "Helper hash");
1815
+ }
1816
+ function cleanup(value, executionId) {
1817
+ if (value === null) return;
1818
+ object(value, "Cleanup");
1819
+ if (value.state === "not-started" || value.state === "unknown") {
1820
+ const fields = ["state", "reason"];
1821
+ if (value.state === "unknown" && Object.hasOwn(value, "identity")) fields.push("identity");
1822
+ keys3(value, fields, "Cleanup");
1823
+ publicText(value.reason, "Cleanup reason");
1824
+ if (Object.hasOwn(value, "identity")) workerIdentity(value.identity, executionId);
1825
+ return;
1826
+ }
1827
+ keys3(value, ["state", "identity", "receipt"], "Cleanup");
1828
+ check3(value.state === "settled", "Unsupported cleanup state");
1829
+ workerIdentity(value.identity, executionId);
1830
+ keys3(value.receipt, ["state", "identity", "workerCode", "workerSignal", "reason", "reapedAll"], "Cleanup receipt");
1831
+ check3(value.receipt.state === "settled" && value.receipt.reapedAll === true && isDeepStrictEqual(value.receipt.identity, value.identity), "Cleanup receipt identity or settlement mismatch");
1832
+ if (value.receipt.workerCode !== null) integer(value.receipt.workerCode, "Worker exit code");
1833
+ integer(value.receipt.workerSignal, "Worker signal");
1834
+ enumeration(value.receipt.reason, ["worker-exit", "owner-loss", "cancelled", "helper-signal", "ownership-write-failed", "start-failed"], "Cleanup receipt reason");
1835
+ }
1836
+ function runtimeOutcome(outcome, index, requested) {
1837
+ keys3(outcome, ["ordinal", "ok", "work", "control", "reason", "exitCode", "timedOut", "aborted", "truncated", "spawnFailed", "final", "cleanup", "observation", "retention"], "Runtime outcome");
1838
+ check3(outcome.ordinal === index + 1, "Runtime ordinals must follow requested order");
1839
+ check3(typeof outcome.ok === "boolean", "Runtime ok must be boolean");
1840
+ enumeration(outcome.work, [null, "succeeded", "failed", "unknown"], "Work state");
1841
+ enumeration(outcome.control, [null, "failed"], "Control state");
1842
+ if (outcome.reason !== null) publicText(outcome.reason, "Runtime reason");
1843
+ if (outcome.exitCode !== null) integer(outcome.exitCode, "Runtime exit code");
1844
+ for (const key of ["timedOut", "aborted", "truncated", "spawnFailed"]) check3(outcome[key] === null || typeof outcome[key] === "boolean", "Runtime flags must be nullable booleans");
1845
+ if (outcome.final !== null) {
1846
+ object(outcome.final, "Runtime final");
1847
+ if (outcome.final.state === "unavailable") {
1848
+ keys3(outcome.final, ["state", "reason"], "Unavailable final");
1849
+ publicText(outcome.final.reason, "Unavailable final reason");
1850
+ } else finalIdentity(outcome.final);
1851
+ }
1852
+ cleanup(outcome.cleanup, requested.executionId);
1853
+ if (outcome.observation !== null) {
1854
+ keys3(outcome.observation, ["state", "reasons"], "Observation");
1855
+ enumeration(outcome.observation.state, ["complete", "incomplete"], "Observation state");
1856
+ array(outcome.observation.reasons, "Observation reasons");
1857
+ for (const reason of outcome.observation.reasons) publicText(reason, "Observation reason");
1858
+ }
1859
+ if (outcome.retention !== null) {
1860
+ keys3(outcome.retention, ["status"], "Retention");
1861
+ enumeration(outcome.retention.status, ["disabled", "pending", "retained", "lost"], "Retention status");
1862
+ }
1863
+ }
1864
+ function binding(value) {
1865
+ if (value === null) return;
1866
+ keys3(value, ["package", "phase"], "Definition binding");
1867
+ check3(value.package === "principal-pi-skills", "Unsupported definition binding package");
1868
+ string(value.phase, "Definition phase");
1869
+ }
1870
+ async function capture(reader, reference) {
1871
+ keys3(reference, ["path", "sha256"], "Manifest reference");
1872
+ hash2(reference.sha256, "Manifest hash");
1873
+ const manifestBytes = await reader.read(reference.path, MAX_PUBLIC_SOURCE_MANIFEST_BYTES);
1874
+ check3(digest(manifestBytes) === reference.sha256, "Manifest hash mismatch");
1875
+ const manifest = json2(manifestBytes, "Manifest");
1876
+ keys3(manifest, ["schema", "version", "captureId", "ownerId", "toolCallId", "tool", "state", "response", "requested", "runtimeEvidence", "finals", "definitions"], "Public manifest");
1877
+ check3(manifest.schema === "pi-daddy-public-evidence-v1" && manifest.version === 1 && manifest.state === "returned", "Unsupported public manifest version or state");
1878
+ check3(UUID.test(manifest.captureId) && UUID.test(manifest.ownerId), "Capture/owner identity must be a UUID");
1879
+ string(manifest.toolCallId, "Tool-call identity");
1880
+ enumeration(manifest.tool, TOOLS, "Tool");
1881
+ const directory = join3(reader.root, manifest.captureId);
1882
+ check3(reference.path === join3(directory, "manifest.json"), "Manifest path does not bind its capture identity");
1883
+ const refs = [{ kind: "manifest", ...reference, bytes: manifestBytes.length }];
1884
+ const seen = /* @__PURE__ */ new Set([reference.path]);
1885
+ async function copied(ref, filename, kind) {
1886
+ keys3(ref, ["path", "sha256", "bytes"], "Copied reference");
1887
+ hash2(ref.sha256, "Copied hash");
1888
+ integer(ref.bytes, "Copied byte count");
1889
+ check3(ref.path === join3(directory, filename) && !seen.has(ref.path), "Copied reference path is foreign or reused");
1890
+ seen.add(ref.path);
1891
+ check3(ref.bytes <= MAX_PUBLIC_SOURCE_FILE_BYTES, "Copied reference exceeds file-byte limit");
1892
+ const bytes = await reader.read(ref.path);
1893
+ check3(bytes.length === ref.bytes && digest(bytes) === ref.sha256, "Copied reference byte count or hash mismatch");
1894
+ refs.push({ kind, ...ref });
1895
+ return bytes;
1896
+ }
1897
+ array(manifest.requested, "Requested", 8);
1898
+ check3(manifest.requested.length > 0, "Capture must identify a requested row");
1899
+ if (manifest.tool === "delegate" || manifest.tool === "delegate_describe") check3(manifest.requested.length === 1, "Single tool requires one requested row");
1900
+ manifest.requested.forEach((row, index) => requestedRow(row, index, manifest.tool));
1901
+ const executionIds = manifest.requested.map((row) => row.executionId).filter((value) => value !== null);
1902
+ check3(new Set(executionIds).size === executionIds.length, "Requested execution identities must be unique");
1903
+ const response = json2(await copied(manifest.response, "response.json", "response"), "Public response");
1904
+ keys3(response, ["isError", "content"], "Public response");
1905
+ check3(typeof response.isError === "boolean", "Public isError must be boolean");
1906
+ array(response.content, "Public content", 2);
1907
+ for (const block of response.content) {
1908
+ keys3(block, ["type", "text"], "Public block");
1909
+ check3(block.type === "text" && typeof block.text === "string", "Only public text blocks are supported");
1910
+ }
1911
+ array(manifest.finals, "Final copies", 8);
1912
+ array(manifest.definitions, "Definition copies", 8);
1913
+ let outcomes = [];
1914
+ if (manifest.tool === "delegate_describe") {
1915
+ check3(manifest.runtimeEvidence === null && manifest.finals.length === 0 && response.content.length === 1 && response.isError === false, "Describe capture has no runtime/final outcome");
1916
+ } else {
1917
+ const runtime = manifest.runtimeEvidence;
1918
+ keys3(runtime, ["version", "tool", "requested", "outcomes"], "Runtime evidence");
1919
+ check3(runtime.version === 1 && runtime.tool === manifest.tool && runtime.requested === manifest.requested.length, "Runtime header does not match capture");
1920
+ array(runtime.outcomes, "Runtime outcomes", 8);
1921
+ outcomes = runtime.outcomes;
1922
+ check3(outcomes.length > 0 && outcomes.length <= manifest.requested.length, "Runtime outcome count is invalid");
1923
+ if (manifest.tool !== "delegate_chain") check3(outcomes.length === manifest.requested.length, "Non-chain captures must retain every outcome");
1924
+ outcomes.forEach((outcome, index) => runtimeOutcome(outcome, index, manifest.requested[index]));
1925
+ check3(response.content.length === 2 && response.content[1].text.startsWith(RUNTIME_PREFIX) && response.content[1].text.endsWith("\n```"), "Public response lacks its exact runtime evidence block");
1926
+ const projection = json2(Buffer.from(response.content[1].text.slice(RUNTIME_PREFIX.length, -4)), "Public runtime projection");
1927
+ check3(isDeepStrictEqual(projection, runtime), "Public runtime projection differs from manifest");
1928
+ const failed = outcomes.some((outcome) => !outcome.ok || outcome.control === "failed");
1929
+ if (manifest.tool !== "delegate_chain") check3(response.isError === failed, "Public isError differs from runtime outcome");
1930
+ else if (failed || outcomes.length < manifest.requested.length || outcomes.some((outcome) => outcome.final?.state === "unavailable")) check3(response.isError, "Stopped chain must remain an error response");
1931
+ check3(manifest.finals.length === outcomes.length, "Every returned outcome must retain its final availability");
1932
+ }
1933
+ for (const [index, row] of manifest.finals.entries()) {
1934
+ keys3(row, ["ordinal", "executionId", "final"], "Final copy row");
1935
+ check3(row.ordinal === index + 1 && row.executionId === manifest.requested[index].executionId, "Final copy identity differs from requested execution");
1936
+ const final = outcomes[index].final;
1937
+ if (final?.state !== "complete") {
1938
+ check3(row.final === null, "Unavailable native final cannot have a complete copy");
1939
+ continue;
1940
+ }
1941
+ keys3(row.final, ["state", "sessionId", "messageId", "leafId", "sha256", "content"], "Final copy");
1942
+ const { content, ...identity2 } = row.final;
1943
+ check3(isDeepStrictEqual(identity2, final), "Final copy identity differs from runtime final");
1944
+ const bytes = await copied(content, `outcome-${row.ordinal}-final.txt`, "final");
1945
+ check3(bytes.length > 0 && digest(bytes) === final.sha256, "Final copy differs from native final hash");
1946
+ check3(Buffer.from(response.content[0].text, "utf8").includes(bytes), "Complete final bytes are absent from authored public content");
1947
+ }
1948
+ const expectedDefinitions = manifest.requested.filter((row) => row.definitionId !== null).map((row) => JSON.stringify([row.agent, row.definitionId])).sort();
1949
+ const observedDefinitions = [];
1950
+ for (const [index, definition] of manifest.definitions.entries()) {
1951
+ keys3(definition, ["agent", "definitionId", "sourceHash", "bodySha256", "binding", "resources", "body"], "Definition copy");
1952
+ string(definition.agent, "Definition agent");
1953
+ hash2(definition.definitionId, "Definition identity");
1954
+ hash2(definition.sourceHash, "Definition source hash", true);
1955
+ hash2(definition.bodySha256, "Definition body hash");
1956
+ binding(definition.binding);
1957
+ observedDefinitions.push(JSON.stringify([definition.agent, definition.definitionId]));
1958
+ array(definition.resources, "Definition resources");
1959
+ check3(definition.resources.length > 0, "Definition source resources are missing");
1960
+ for (const [resourceIndex, resource] of definition.resources.entries()) {
1961
+ keys3(resource, ["kind", "path", "copy"], "Definition resource");
1962
+ enumeration(resource.kind, ["selected-skill", "package", "binding-manifest", "delegated-agent"], "Source kind");
1963
+ string(resource.path, "Original source identity");
1964
+ await copied(resource.copy, `definition-${index}-source-${resourceIndex}.bin`, "source-copy");
1965
+ }
1966
+ const body = await copied(definition.body, `definition-${index}-body.txt`, "definition-body");
1967
+ check3(digest(body) === definition.bodySha256, "Definition body copy differs from bodySha256");
1968
+ }
1969
+ check3(isDeepStrictEqual(observedDefinitions.sort(), expectedDefinitions), "Copied definitions differ from observed requested definitions");
1970
+ if (manifest.tool === "delegate_describe") {
1971
+ const described = json2(Buffer.from(response.content[0].text), "Described definition");
1972
+ const definition = manifest.definitions[0];
1973
+ const expected = { version: 1, agent: definition.agent, bodySha256: definition.bodySha256, binding: definition.binding, definitionId: definition.definitionId };
1974
+ if (Object.hasOwn(described, "sourceHash")) expected.sourceHash = definition.sourceHash;
1975
+ else check3(definition.sourceHash === null, "Described source hash is missing");
1976
+ check3(isDeepStrictEqual(described, expected), "Described definition differs from manifest source/body identity");
1977
+ }
1978
+ return { manifest, refs, responseIsError: response.isError, outcomes };
1979
+ }
1980
+ async function verifySources(cases, selection, evidenceRoot) {
1981
+ check3(process.platform === "linux", "Public source verification requires Linux held-directory-FD reads");
1982
+ canonicalPath(evidenceRoot, "Evidence root");
1983
+ check3(evidenceRoot !== "/", "Evidence root cannot be the filesystem root");
1984
+ array(cases, "Cases", 100);
1985
+ const raw = cases.map((item) => {
1986
+ keys3(item, ["id", "input", "question", "provenance", "source", "visibility", "hash"], "Parsed case");
1987
+ const { hash: ignored, ...original } = item;
1988
+ return original;
1989
+ });
1990
+ const validated = parseCases({ schema: 1, cases: raw });
1991
+ validated.forEach((item, index) => check3(item.hash === cases[index].hash, "Incoming parsed case hash is stale"));
1992
+ keys3(selection, ["schema", "kind", "selections"], "Source selection");
1993
+ check3(selection.schema === 1 && selection.kind === "decision-public-sources", "Unsupported source selection version");
1994
+ array(selection.selections, "Selections", 100);
1995
+ check3(selection.selections.length === validated.length, "Require exactly one ordered selection per case");
1996
+ for (const [index, row] of selection.selections.entries()) {
1997
+ keys3(row, ["caseId", "caseHash", "manifest", "captureId", "toolCallId", "ordinal", "agent", "definitionId", "executionId"], "Source selection row");
1998
+ const item = validated[index];
1999
+ check3(row.caseId === item.id && row.caseHash === item.hash, "Selections must match exact ordered case identities");
2000
+ keys3(row.manifest, ["path", "sha256"], "Manifest selection");
2001
+ hash2(row.manifest.sha256, "Selected manifest hash");
2002
+ check3(item.source.sha256 === row.manifest.sha256 && item.source.recordId === row.toolCallId, "Case source must bind the manifest hash and tool-call identity");
2003
+ check3(UUID.test(row.captureId), "Selected capture identity must be a UUID");
2004
+ string(row.toolCallId, "Selected tool-call identity");
2005
+ integer(row.ordinal, "Selected ordinal", 1);
2006
+ string(row.agent, "Selected agent", true);
2007
+ hash2(row.definitionId, "Selected observed definition identity", true);
2008
+ string(row.executionId, "Selected execution identity", true);
2009
+ }
2010
+ const reader = new EvidenceReader(evidenceRoot);
2011
+ try {
2012
+ await reader.start();
2013
+ const captures = /* @__PURE__ */ new Map(), rows = [];
2014
+ for (const row of selection.selections) {
2015
+ let verified = captures.get(row.manifest.path);
2016
+ if (!verified) {
2017
+ verified = await capture(reader, row.manifest);
2018
+ captures.set(row.manifest.path, verified);
2019
+ }
2020
+ check3(verified.refs[0].sha256 === row.manifest.sha256, "Repeated manifest selections disagree on hash");
2021
+ const { manifest, refs, outcomes } = verified;
2022
+ check3(manifest.captureId === row.captureId && manifest.toolCallId === row.toolCallId, "Selected capture/tool-call identity mismatch");
2023
+ const requested = manifest.requested[row.ordinal - 1];
2024
+ check3(requested !== void 0 && ["ordinal", "agent", "definitionId", "executionId"].every((key) => requested[key] === row[key]), "Selected requested row identity mismatch");
2025
+ const outcome = outcomes[row.ordinal - 1];
2026
+ rows.push({
2027
+ caseId: row.caseId,
2028
+ caseHash: row.caseHash,
2029
+ source: { sha256: row.manifest.sha256, recordId: manifest.toolCallId },
2030
+ captureId: manifest.captureId,
2031
+ ownerId: manifest.ownerId,
2032
+ toolCallId: manifest.toolCallId,
2033
+ tool: manifest.tool,
2034
+ requested: { ...requested },
2035
+ manifest: { ...row.manifest },
2036
+ copiedRefs: refs.map((ref) => ({ ...ref })),
2037
+ observed: {
2038
+ responseIsError: verified.responseIsError,
2039
+ returnedOutcomeCount: outcomes.length,
2040
+ selectedOutcome: outcome ? {
2041
+ ok: outcome.ok,
2042
+ work: outcome.work,
2043
+ control: outcome.control,
2044
+ finalState: outcome.final?.state ?? null,
2045
+ cleanupState: outcome.cleanup?.state ?? null,
2046
+ observationState: outcome.observation?.state ?? null,
2047
+ retentionStatus: outcome.retention?.status ?? null
2048
+ } : null
2049
+ }
2050
+ });
2051
+ }
2052
+ await reader.stable();
2053
+ return {
2054
+ schema: 1,
2055
+ kind: "decision-public-source-verification",
2056
+ status: "verified-bytes-and-record-identities",
2057
+ platform: "linux",
2058
+ evidenceRoot,
2059
+ limits: { ...LIMITS },
2060
+ artifactCount: reader.files.size,
2061
+ totalBytes: reader.totalBytes,
2062
+ captureCount: captures.size,
2063
+ cases: rows,
2064
+ trainingReady: false,
2065
+ trainingEligible: false,
2066
+ assessments: { approval: "not-assessed", taskAcceptance: "not-assessed", decisionTimeAvailability: "not-assessed", redaction: "not-assessed", rights: "not-assessed" }
2067
+ };
2068
+ } finally {
2069
+ await reader.close();
2070
+ }
2071
+ }
2072
+
2073
+ // experiments/decision-shadow/main.mjs
2074
+ var terms = { jev: "https://typesafe.ai/legal/mca" };
2075
+ var providerErrors = /* @__PURE__ */ new Set([
2076
+ "invalid provider response",
2077
+ "provider request timed out",
2078
+ "provider response exceeded limit",
2079
+ "provider request failed",
2080
+ "Provider call failed."
2081
+ ]);
2082
+ var help = `Decision research workflow (Node >=20; qualified Pi execution needs Linux / Pi 1.0.4 / Node >=22.19)
2083
+ preview --cases FILE --provider jev --model MODEL
2084
+ run --cases FILE --provider jev --model MODEL --out NEW.jsonl --allow-remote
2085
+ score --cases FILE --labels FILE --run RESULT.jsonl [--run RESULT2.jsonl]
2086
+ corpus --cases FILE --labels FILE --out NEW.json
2087
+ verify-sources --cases FILE --sources SELECTION.json --evidence-root DIR --out NEW.json
2088
+ fixtures --out NEW_DIR
2089
+ validate-experiment --cases FILE --experiment FILE
2090
+ preview-pi --cases FILE --experiment FILE --model openai-codex:MODEL --thinking LEVEL --split test
2091
+ run-pi --cases FILE --experiment FILE --model openai-codex:MODEL --thinking LEVEL --split test --pi-package DIR --out NEW.jsonl --allow-subscription [--pi-node PATH] [--auth-path FILE] [--timeout-ms N]
2092
+ compare --cases FILE --experiment FILE --labels FILE --label-evidence FILE --run FILE [--run FILE] --out NEW.json
2093
+ import-session --cases FILE --selection FILE --consent FILE --entry FILE --out NEW.json
2094
+ export-learning --cases FILE --experiment FILE --labels FILE --label-evidence FILE --consents FILE --mode fixture-demo|reviewed-data --out NEW_DIR
2095
+
2096
+ JEV run asks this session whether to retain selected data for later LoRA review.
2097
+ Noninteractive runs must supply --storage yes|no [--session-id ID]. No choice is inherited.
2098
+
2099
+ preview, score, corpus and verify-sources are offline. verify-sources reads only
2100
+ explicitly selected Linux public captures; it does not assess decision readiness,
2101
+ decision-time availability, redaction, rights or labels. run sends only the curated input and
2102
+ question to the selected provider, bills its API, and requires its API key in
2103
+ OPENROUTER_API_KEY. No retries. Output must be new.
2104
+ Labels/predictions are research records, never runtime authority.
2105
+ Corpus excludes predictions and is NOT approved for training.`;
2106
+ function argumentsFor(argv) {
2107
+ const [command, ...rest] = argv;
2108
+ if (!command || command === "--help") return { command: "help", flags: {} };
2109
+ const commands = {
2110
+ ...RESEARCH_OPTIONS,
2111
+ preview: ["cases", "provider", "model"],
2112
+ run: ["cases", "provider", "model", "out", "allow-remote"],
2113
+ score: ["cases", "labels", "run"],
2114
+ corpus: ["cases", "labels", "out"],
2115
+ "verify-sources": ["cases", "sources", "evidence-root", "out"]
2116
+ };
2117
+ if (!Object.hasOwn(commands, command))
2118
+ throw new Error("Unknown command; use --help.");
2119
+ const required = commands[command];
2120
+ const optional = command === "run" ? ["storage", "session-id"] : RESEARCH_OPTIONAL[command] ?? [];
2121
+ const allowed = [...required, ...optional];
2122
+ const flags = {};
2123
+ for (let i = 0; i < rest.length; i++) {
2124
+ const key = rest[i].slice(2);
2125
+ if (!rest[i].startsWith("--") || !allowed.includes(key))
2126
+ throw new Error("Unknown option.");
2127
+ if (key in flags && !(["score", "compare"].includes(command) && key === "run"))
2128
+ throw new Error("Duplicate --" + key);
2129
+ if (["allow-remote", "allow-subscription"].includes(key)) {
2130
+ flags[key] = true;
2131
+ continue;
2132
+ }
2133
+ const value = rest[++i];
2134
+ if (!value || value.startsWith("--"))
2135
+ throw new Error("Missing --" + key + " value.");
2136
+ if (["score", "compare"].includes(command) && key === "run")
2137
+ (flags.run ??= []).push(value);
2138
+ else flags[key] = value;
2139
+ }
2140
+ for (const key of required)
2141
+ if (!(key in flags)) throw new Error("Required --" + key);
2142
+ return { command, flags };
2143
+ }
2144
+ async function textFile(path) {
2145
+ const info = await stat(path);
2146
+ if (!info.isFile() || info.size > 4 * 1024 * 1024)
2147
+ throw new Error("Expected regular file <=4 MiB.");
2148
+ const bytes = await readFile2(path);
2149
+ if (bytes.length > 4 * 1024 * 1024) throw new Error("File exceeded 4 MiB.");
2150
+ try {
2151
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
2152
+ bytes
2153
+ );
2154
+ } catch {
2155
+ throw new Error("Input file is not valid UTF-8.");
2156
+ }
2157
+ }
2158
+ async function jsonFile(path) {
2159
+ const text2 = await textFile(path);
2160
+ try {
2161
+ return JSON.parse(text2);
2162
+ } catch {
2163
+ throw new Error("Invalid JSON input file.");
2164
+ }
2165
+ }
2166
+ function sha256(text2) {
2167
+ return createHash5("sha256").update(text2, "utf8").digest("hex");
2168
+ }
2169
+ function caseSetHash(cases) {
2170
+ return sha256(JSON.stringify(cases.map((c) => ({ id: c.id, hash: c.hash }))));
2171
+ }
2172
+ function labelSetHash(labels) {
2173
+ return sha256(
2174
+ JSON.stringify(
2175
+ [...labels].sort(
2176
+ (a, b) => a.caseId < b.caseId ? -1 : a.caseId > b.caseId ? 1 : 0
2177
+ )
2178
+ )
2179
+ );
2180
+ }
2181
+ function validCreatedAt(value) {
2182
+ return typeof value === "string" && !Number.isNaN(Date.parse(value)) && new Date(value).toISOString() === value;
2183
+ }
2184
+ async function readRun(path, cases) {
2185
+ let rows;
2186
+ const text2 = await textFile(path);
2187
+ try {
2188
+ rows = text2.trimEnd().split("\n").map((line) => JSON.parse(line));
2189
+ } catch {
2190
+ throw new Error("Invalid run JSONL; retain damaged evidence separately.");
2191
+ }
2192
+ const [header, ...records] = rows;
2193
+ if (header?.schema !== 1 || header.kind !== "decision-shadow-run" || header.caseSetHash !== caseSetHash(cases) || header.caseCount !== cases.length || header.trainingEligible !== false || !validCreatedAt(header.createdAt) || !Object.hasOwn(terms, header.provider))
2194
+ throw new Error("Run header does not match this case set.");
2195
+ makeRequest(header.provider, header.requestedModel, cases[0]);
2196
+ if (records.length > cases.length)
2197
+ throw new Error("Too many prediction records.");
2198
+ for (const [index, r] of records.entries()) {
2199
+ const validState = r?.status === "answered" ? isSupportedResolvedModel(r.resolvedModel) && r.error === null : r?.status === "error" && r.probability === null && providerErrors.has(r.error) && (r.resolvedModel === null || isSupportedResolvedModel(r.resolvedModel)) && index === records.length - 1;
2200
+ if (!r || r.kind !== "prediction" || r.trainingEligible !== false || r.caseId !== cases[index].id || r.caseHash !== cases[index].hash || !validState || !nullableMetric(r.latencyMs) || !r.usage || !["inputTokens", "outputTokens"].every(
2201
+ (key) => nullableTokenCount(r.usage[key])
2202
+ ) || !nullableMetric(r.usage.costUsd))
2203
+ throw new Error("Invalid prediction record.");
2204
+ }
2205
+ return { header, records, runSha256: sha256(text2) };
2206
+ }
2207
+ function nullableMetric(value) {
2208
+ return value === null || typeof value === "number" && Number.isFinite(value) && value >= 0;
2209
+ }
2210
+ function nullableTokenCount(value) {
2211
+ return value === null || Number.isSafeInteger(value) && value >= 0;
2212
+ }
2213
+ function measurements(records) {
2214
+ const summarize = (values, integer2 = false) => {
2215
+ const reported = values.filter((value) => value !== null);
2216
+ let total = 0;
2217
+ for (const value of reported) {
2218
+ total += value;
2219
+ if (!Number.isFinite(total) || integer2 && !Number.isSafeInteger(total)) {
2220
+ throw new Error("Measurement totals exceed supported numeric bounds.");
2221
+ }
2222
+ }
2223
+ return {
2224
+ reported: reported.length,
2225
+ attempted: records.length,
2226
+ total: reported.length ? total : null
2227
+ };
2228
+ };
2229
+ const latency = records.map((r) => r.latencyMs).filter((value) => value !== null);
2230
+ const meanLatency = latency.reduce(
2231
+ (mean, value, index) => mean + (value - mean) / (index + 1),
2232
+ 0
2233
+ );
2234
+ return {
2235
+ latencyMs: {
2236
+ reported: latency.length,
2237
+ attempted: records.length,
2238
+ mean: latency.length ? meanLatency : null
2239
+ },
2240
+ inputTokens: summarize(
2241
+ records.map((r) => r.usage.inputTokens),
2242
+ true
2243
+ ),
2244
+ outputTokens: summarize(
2245
+ records.map((r) => r.usage.outputTokens),
2246
+ true
2247
+ ),
2248
+ costUsd: summarize(records.map((r) => r.usage.costUsd))
2249
+ };
2250
+ }
2251
+ async function writeNew(path, value) {
2252
+ const file = await open4(path, "wx", 384);
2253
+ try {
2254
+ await file.writeFile(JSON.stringify(value, null, 2) + "\n");
2255
+ await file.sync();
2256
+ } finally {
2257
+ await file.close();
2258
+ }
2259
+ }
2260
+ async function main(argv, {
2261
+ emit = console.log,
2262
+ env = process.env,
2263
+ providerCall = callProvider,
2264
+ promptStorage,
2265
+ piRunner,
2266
+ sessionConsent,
2267
+ expectedCaseSetHash,
2268
+ beforeProviderCall
2269
+ } = {}) {
2270
+ const { command, flags } = argumentsFor(argv);
2271
+ if (command === "help") {
2272
+ emit(help);
2273
+ return;
2274
+ }
2275
+ const cases = command === "fixtures" ? null : parseCases(await jsonFile(flags.cases));
2276
+ if (Object.hasOwn(RESEARCH_OPTIONS, command))
2277
+ return researchCommand(
2278
+ command,
2279
+ flags,
2280
+ { cases, jsonFile, textFile, writeNew, readLegacyRun: readRun },
2281
+ { emit, piRunner }
2282
+ );
2283
+ if (command === "verify-sources") {
2284
+ const sourceText = await textFile(flags.sources);
2285
+ let selection;
2286
+ try {
2287
+ selection = JSON.parse(sourceText);
2288
+ } catch {
2289
+ throw new Error("Invalid JSON source selection.");
2290
+ }
2291
+ const verification = await verifySources(
2292
+ cases,
2293
+ selection,
2294
+ flags["evidence-root"]
2295
+ );
2296
+ const receipt = {
2297
+ ...verification,
2298
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2299
+ caseSetHash: caseSetHash(cases),
2300
+ selectionFileSha256: sha256(sourceText)
2301
+ };
2302
+ await writeNew(flags.out, receipt);
2303
+ emit(
2304
+ JSON.stringify({
2305
+ saved: resolve2(flags.out),
2306
+ status: receipt.status,
2307
+ caseCount: cases.length,
2308
+ artifactCount: receipt.artifactCount,
2309
+ trainingReady: false,
2310
+ trainingEligible: false
2311
+ })
2312
+ );
2313
+ return;
2314
+ }
2315
+ if (command === "preview" || command === "run") {
2316
+ if (expectedCaseSetHash !== void 0 && caseSetHash(cases) !== expectedCaseSetHash)
2317
+ throw new Error("Cases changed after confirmation.");
2318
+ const requests = cases.map(
2319
+ (c) => makeRequest(flags.provider, flags.model, c)
2320
+ );
2321
+ if (command === "preview") {
2322
+ emit(
2323
+ JSON.stringify(
2324
+ {
2325
+ schema: 1,
2326
+ caseSetHash: caseSetHash(cases),
2327
+ count: cases.length,
2328
+ trainingEligible: false,
2329
+ requests: requests.map((request, i) => ({
2330
+ caseId: cases[i].id,
2331
+ caseHash: cases[i].hash,
2332
+ ...request
2333
+ }))
2334
+ },
2335
+ null,
2336
+ 2
2337
+ )
2338
+ );
2339
+ return;
2340
+ }
2341
+ const consent = sessionConsent ?? await requestSessionStorage({
2342
+ sessionId: flags["session-id"],
2343
+ storage: flags.storage,
2344
+ promptStorage
2345
+ });
2346
+ const apiKey = env.OPENROUTER_API_KEY;
2347
+ if (!apiKey?.trim())
2348
+ throw new Error("Selected provider API key is not configured.");
2349
+ const file = await open4(flags.out, "wx", 384);
2350
+ const append = async (row) => {
2351
+ await file.writeFile(JSON.stringify(row) + "\n");
2352
+ await file.sync();
2353
+ };
2354
+ let failures = 0;
2355
+ try {
2356
+ if (beforeProviderCall) beforeProviderCall();
2357
+ await writeNew(flags.out + ".consent.json", consent);
2358
+ await retainSelectedSessionData({
2359
+ cases,
2360
+ consent,
2361
+ out: flags.out + ".learning.json",
2362
+ assertCurrent: beforeProviderCall
2363
+ });
2364
+ await append({
2365
+ schema: 1,
2366
+ kind: "decision-shadow-run",
2367
+ provider: flags.provider,
2368
+ requestedModel: flags.model,
2369
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2370
+ caseCount: cases.length,
2371
+ caseSetHash: caseSetHash(cases),
2372
+ trainingEligible: false,
2373
+ termsUrl: terms[flags.provider]
2374
+ });
2375
+ for (const c of cases) {
2376
+ let result;
2377
+ try {
2378
+ if (beforeProviderCall) beforeProviderCall();
2379
+ result = await providerCall(flags.provider, flags.model, c, {
2380
+ apiKey
2381
+ });
2382
+ } catch {
2383
+ result = {
2384
+ status: "error",
2385
+ probability: null,
2386
+ resolvedModel: null,
2387
+ usage: { inputTokens: null, outputTokens: null, costUsd: null },
2388
+ latencyMs: null,
2389
+ error: "Provider call failed."
2390
+ };
2391
+ }
2392
+ await append({
2393
+ kind: "prediction",
2394
+ caseId: c.id,
2395
+ caseHash: c.hash,
2396
+ ...result,
2397
+ trainingEligible: false
2398
+ });
2399
+ if (result.status === "error") {
2400
+ failures++;
2401
+ break;
2402
+ }
2403
+ }
2404
+ } finally {
2405
+ await file.close();
2406
+ }
2407
+ emit(
2408
+ JSON.stringify({
2409
+ saved: resolve2(flags.out),
2410
+ errors: failures,
2411
+ trainingEligible: false
2412
+ })
2413
+ );
2414
+ if (failures)
2415
+ throw new Error(
2416
+ "Run stopped after a provider error; saved partial evidence. No retry was made."
2417
+ );
2418
+ return;
2419
+ }
2420
+ const labels = parseLabels(await jsonFile(flags.labels), cases);
2421
+ if (command === "corpus") {
2422
+ await writeNew(flags.out, localCorpus(cases, labels));
2423
+ emit(JSON.stringify({ saved: resolve2(flags.out), trainingReady: false }));
2424
+ return;
2425
+ }
2426
+ const reports = [];
2427
+ for (const path of flags.run) {
2428
+ const { header, records, runSha256 } = await readRun(path, cases);
2429
+ const resolvedModels = [
2430
+ ...new Set(records.map((r) => r.resolvedModel).filter(Boolean))
2431
+ ];
2432
+ if (resolvedModels.length > 1) {
2433
+ throw new Error(
2434
+ "Run contains multiple resolved models; retain this evidence and use a new single-snapshot run."
2435
+ );
2436
+ }
2437
+ reports.push({
2438
+ provider: header.provider,
2439
+ requestedModel: header.requestedModel,
2440
+ resolvedModels,
2441
+ runSha256,
2442
+ createdAt: header.createdAt,
2443
+ ...scorePredictions(
2444
+ cases,
2445
+ labels,
2446
+ records.map(({ caseId, caseHash, status, probability: probability2 }) => ({
2447
+ caseId,
2448
+ caseHash,
2449
+ status,
2450
+ probability: probability2
2451
+ }))
2452
+ ),
2453
+ measurements: measurements(records),
2454
+ trainingEligible: false
2455
+ });
2456
+ }
2457
+ emit(
2458
+ JSON.stringify(
2459
+ {
2460
+ schema: 1,
2461
+ threshold: 0.5,
2462
+ caseSetHash: caseSetHash(cases),
2463
+ labelSetHash: labelSetHash(labels),
2464
+ reports
2465
+ },
2466
+ null,
2467
+ 2
2468
+ )
2469
+ );
2470
+ }
2471
+
2472
+ // packages/cli/src/decision.ts
2473
+ async function cmdDecision(argv) {
2474
+ await main(argv);
2475
+ }
2476
+ export {
2477
+ cmdDecision
2478
+ };