create-tradejs 3.1.22 → 3.1.23
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.
- package/README.md +19 -3
- package/dist/index.js +36 -5
- package/dist/skill-bundle/.codex/skills/ai-train-local-research/SKILL.md +596 -0
- package/dist/skill-bundle/.codex/skills/ai-train-local-research/references/gate-ablation.md +324 -0
- package/dist/skill-bundle/.codex/skills/ai-train-local-research/references/reporting.md +227 -0
- package/dist/skill-bundle/.codex/skills/ai-train-local-research/scripts/ai-gate-ablation.mjs +5082 -0
- package/dist/skill-bundle/.codex/skills/ai-train-local-research/scripts/ai-gate-ablation.test.mjs +1170 -0
- package/dist/skill-bundle/.codex/skills/backtest-config-redis/SKILL.md +17 -0
- package/dist/skill-bundle/.codex/skills/backtest-config-redis/scripts/get_backtest_config.sh +21 -0
- package/dist/skill-bundle/.codex/skills/runtime-parity-mismatch-analysis/SKILL.md +146 -0
- package/dist/skill-bundle/.codex/skills/save-strategy-config-from-backtest/SKILL.md +58 -0
- package/dist/skill-bundle/.codex/skills/save-strategy-config-from-backtest/agents/openai.yaml +4 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/SKILL.md +334 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/references/research-notes.md +247 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/scripts/backtest-run-metrics.mjs +647 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/scripts/backtest-run-metrics.test.mjs +321 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/scripts/fast-ai-export-metrics.mjs +744 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/scripts/fast-ai-export-metrics.test.mjs +553 -0
- package/dist/skill-bundle/.codex/skills/strategy-backtest-research/scripts/research-notes-check.mjs +125 -0
- package/dist/skill-bundle/.codex/skills/strategy-improvement-research/SKILL.md +18 -1
- package/dist/skill-bundle/.codex/skills/strategy-release/SKILL.md +22 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/agents/openai.yaml +4 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/diagnose-live.md +126 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/direction-policy.md +141 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/directional-parameter-split.md +93 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/evidence-limitations.md +76 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/evidence-retention.md +157 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/historical-hypothesis-audit.md +163 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/professional-research-loop.md +198 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/release-workflow.md +755 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/research-objective.md +255 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/references/verdict-contract.md +200 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/direction-policy-checkpoint.mjs +137 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/direction-policy-checkpoint.test.mjs +85 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/directional-parameter-checkpoint.mjs +149 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/directional-parameter-checkpoint.test.mjs +120 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/release-progress-checkpoint.mjs +621 -0
- package/dist/skill-bundle/.codex/skills/strategy-release/scripts/release-progress-checkpoint.test.mjs +349 -0
- package/dist/skill-bundle/.codex/tradejs-skill-bundle.json +44 -3
- package/package.json +1 -1
package/dist/skill-bundle/.codex/skills/strategy-release/scripts/release-progress-checkpoint.mjs
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const INPUT_SCHEMA = 'tradejs-release-progress-input/v2';
|
|
9
|
+
const OUTPUT_SCHEMA = 'tradejs-release-progress/v2';
|
|
10
|
+
const SHA256_RE = /^[a-f0-9]{64}$/;
|
|
11
|
+
|
|
12
|
+
const HARD_LIMITATIONS = new Set([
|
|
13
|
+
'causal_leakage',
|
|
14
|
+
'reconciliation_failure',
|
|
15
|
+
'partial_or_failed_run',
|
|
16
|
+
'state_isolation_failure',
|
|
17
|
+
'missing_causal_data',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const MICRO_FORWARD_LIMITATIONS = new Set([
|
|
21
|
+
'retrospective_current_universe',
|
|
22
|
+
'exposed_holdout',
|
|
23
|
+
'missing_effective_dated_membership',
|
|
24
|
+
'missing_historical_patch',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const REVALIDATION_DISPOSITIONS = new Set([
|
|
28
|
+
'rescored',
|
|
29
|
+
'bridge-rerun',
|
|
30
|
+
'rejected',
|
|
31
|
+
'partial',
|
|
32
|
+
'unreconstructable',
|
|
33
|
+
'new-trial-required',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
function assert(condition, message) {
|
|
37
|
+
if (!condition) throw new Error(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertBoolean(value, label) {
|
|
41
|
+
assert(typeof value === 'boolean', `${label} must be boolean`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function assertIdentity(value, label) {
|
|
45
|
+
assert(typeof value === 'string' && value.trim(), `${label} is required`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function verifyArtifact(reference, label, artifactRoot, { json = true } = {}) {
|
|
49
|
+
assert(reference && typeof reference === 'object', `${label} is required`);
|
|
50
|
+
assertIdentity(reference.path, `${label}.path`);
|
|
51
|
+
assert(
|
|
52
|
+
SHA256_RE.test(reference.sha256),
|
|
53
|
+
`${label}.sha256 must be a lowercase SHA-256`,
|
|
54
|
+
);
|
|
55
|
+
const artifactPath = path.resolve(artifactRoot, reference.path);
|
|
56
|
+
let content;
|
|
57
|
+
try {
|
|
58
|
+
content = readFileSync(artifactPath);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`${label} cannot be read at ${artifactPath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
const actualSha256 = createHash('sha256').update(content).digest('hex');
|
|
65
|
+
assert(
|
|
66
|
+
actualSha256 === reference.sha256,
|
|
67
|
+
`${label} SHA-256 mismatch at ${artifactPath}`,
|
|
68
|
+
);
|
|
69
|
+
if (!json) return { artifactPath, content };
|
|
70
|
+
try {
|
|
71
|
+
return {
|
|
72
|
+
artifactPath,
|
|
73
|
+
content,
|
|
74
|
+
value: JSON.parse(content.toString('utf8')),
|
|
75
|
+
};
|
|
76
|
+
} catch {
|
|
77
|
+
throw new Error(`${label} must contain valid JSON`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function verifyResearchEvidence(
|
|
82
|
+
evidence,
|
|
83
|
+
label,
|
|
84
|
+
artifactRoot,
|
|
85
|
+
{ expectedRound } = {},
|
|
86
|
+
) {
|
|
87
|
+
assertIdentity(evidence?.researchId, `${label}.researchId`);
|
|
88
|
+
const manifest = verifyArtifact(
|
|
89
|
+
evidence.manifestArtifact,
|
|
90
|
+
`${label}.manifestArtifact`,
|
|
91
|
+
artifactRoot,
|
|
92
|
+
).value;
|
|
93
|
+
const resultReference = evidence.resultArtifact;
|
|
94
|
+
const result = verifyArtifact(
|
|
95
|
+
resultReference,
|
|
96
|
+
`${label}.resultArtifact`,
|
|
97
|
+
artifactRoot,
|
|
98
|
+
).value;
|
|
99
|
+
const handoff = verifyArtifact(
|
|
100
|
+
evidence.handoffArtifact,
|
|
101
|
+
`${label}.handoffArtifact`,
|
|
102
|
+
artifactRoot,
|
|
103
|
+
).value;
|
|
104
|
+
assert(
|
|
105
|
+
manifest?.schema === 'tradejs-core-research-manifest/v1' &&
|
|
106
|
+
manifest.researchId === evidence.researchId &&
|
|
107
|
+
manifest.status === 'completed',
|
|
108
|
+
`${label} requires a completed matching core-research manifest`,
|
|
109
|
+
);
|
|
110
|
+
assert(
|
|
111
|
+
result?.schema === 'tradejs-core-research-result/v1' &&
|
|
112
|
+
result.researchId === evidence.researchId,
|
|
113
|
+
`${label} requires a matching core-research result`,
|
|
114
|
+
);
|
|
115
|
+
assert(
|
|
116
|
+
handoff?.schema === 'tradejs-release-causal-handoff/v1' &&
|
|
117
|
+
handoff.researchId === evidence.researchId &&
|
|
118
|
+
handoff.traceCoverage === 'complete' &&
|
|
119
|
+
handoff.resultSha256 === resultReference.sha256,
|
|
120
|
+
`${label} requires a complete matching causal handoff bound to result SHA-256`,
|
|
121
|
+
);
|
|
122
|
+
if (expectedRound !== undefined) {
|
|
123
|
+
assert(
|
|
124
|
+
handoff.round === expectedRound,
|
|
125
|
+
`${label} causal handoff round must equal ${expectedRound}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
assert(
|
|
129
|
+
Array.isArray(evidence.traceArtifacts) && evidence.traceArtifacts.length,
|
|
130
|
+
`${label}.traceArtifacts must contain at least one trace`,
|
|
131
|
+
);
|
|
132
|
+
const declaredTraceHashes = new Set(
|
|
133
|
+
Object.values(result.artifactHashes ?? {}),
|
|
134
|
+
);
|
|
135
|
+
for (const [index, traceReference] of evidence.traceArtifacts.entries()) {
|
|
136
|
+
verifyArtifact(
|
|
137
|
+
traceReference,
|
|
138
|
+
`${label}.traceArtifacts[${index}]`,
|
|
139
|
+
artifactRoot,
|
|
140
|
+
{ json: false },
|
|
141
|
+
);
|
|
142
|
+
assert(
|
|
143
|
+
declaredTraceHashes.has(traceReference.sha256),
|
|
144
|
+
`${label}.traceArtifacts[${index}] is not declared by result.artifactHashes`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateObjectiveAndRevalidation(input, artifactRoot, history) {
|
|
150
|
+
const objectiveReference = input?.objectiveContract?.artifact;
|
|
151
|
+
const objective = verifyArtifact(
|
|
152
|
+
objectiveReference,
|
|
153
|
+
'objectiveContract.artifact',
|
|
154
|
+
artifactRoot,
|
|
155
|
+
).value;
|
|
156
|
+
assert(
|
|
157
|
+
objective?.schema === 'tradejs-release-objective/v2' &&
|
|
158
|
+
objective.strategy === input.strategy &&
|
|
159
|
+
objective.lineageId === input.lineageId,
|
|
160
|
+
'objectiveContract artifact must match the v2 strategy and lineage',
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
assertBoolean(
|
|
164
|
+
input?.candidateRevalidation?.required,
|
|
165
|
+
'candidateRevalidation.required',
|
|
166
|
+
);
|
|
167
|
+
assert(
|
|
168
|
+
input.candidateRevalidation.required,
|
|
169
|
+
'candidateRevalidation.required must be true in release mode',
|
|
170
|
+
);
|
|
171
|
+
assertBoolean(
|
|
172
|
+
input.candidateRevalidation.complete,
|
|
173
|
+
'candidateRevalidation.complete',
|
|
174
|
+
);
|
|
175
|
+
if (!input.candidateRevalidation.complete) {
|
|
176
|
+
return objectiveReference.sha256;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
assert(
|
|
180
|
+
history,
|
|
181
|
+
'candidateRevalidation cannot complete before the history inventory',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const revalidation = verifyArtifact(
|
|
185
|
+
input.candidateRevalidation.artifact,
|
|
186
|
+
'candidateRevalidation.artifact',
|
|
187
|
+
artifactRoot,
|
|
188
|
+
).value;
|
|
189
|
+
assert(
|
|
190
|
+
revalidation?.schema === 'tradejs-release-candidate-revalidation/v2' &&
|
|
191
|
+
revalidation.strategy === input.strategy &&
|
|
192
|
+
revalidation.lineageId === input.lineageId &&
|
|
193
|
+
revalidation.objectiveFingerprint === objectiveReference.sha256 &&
|
|
194
|
+
revalidation.historyInventorySha256 === history.reference.sha256 &&
|
|
195
|
+
revalidation.status === 'complete',
|
|
196
|
+
'candidateRevalidation artifact must match the v2 objective, strategy, lineage, and history inventory',
|
|
197
|
+
);
|
|
198
|
+
const revalidatedIndexes = Array.isArray(revalidation.candidates)
|
|
199
|
+
? revalidation.candidates.map((candidate) => candidate?.historyEntryIndex)
|
|
200
|
+
: [];
|
|
201
|
+
assert(
|
|
202
|
+
Number.isInteger(revalidation.priorCandidateCount) &&
|
|
203
|
+
revalidation.priorCandidateCount >= 0 &&
|
|
204
|
+
Array.isArray(revalidation.candidates) &&
|
|
205
|
+
revalidation.candidates.length === revalidation.priorCandidateCount &&
|
|
206
|
+
revalidation.priorCandidateCount === history.candidateIndexes.length &&
|
|
207
|
+
revalidation.candidates.every((candidate) =>
|
|
208
|
+
REVALIDATION_DISPOSITIONS.has(candidate?.disposition),
|
|
209
|
+
) &&
|
|
210
|
+
revalidatedIndexes.every(Number.isInteger) &&
|
|
211
|
+
new Set(revalidatedIndexes).size === revalidatedIndexes.length &&
|
|
212
|
+
[...revalidatedIndexes]
|
|
213
|
+
.sort((a, b) => a - b)
|
|
214
|
+
.every(
|
|
215
|
+
(entryIndex, index) => entryIndex === history.candidateIndexes[index],
|
|
216
|
+
) &&
|
|
217
|
+
Array.isArray(revalidation.unresolved) &&
|
|
218
|
+
revalidation.unresolved.length === 0,
|
|
219
|
+
'candidateRevalidation must dispose every deduplicated prior candidate from the bound history inventory exactly once',
|
|
220
|
+
);
|
|
221
|
+
const trialLedger = verifyArtifact(
|
|
222
|
+
revalidation.trialLedger,
|
|
223
|
+
'candidateRevalidation.trialLedger',
|
|
224
|
+
artifactRoot,
|
|
225
|
+
).value;
|
|
226
|
+
assert(
|
|
227
|
+
trialLedger?.schema === 'tradejs-release-trial-ledger/v2' &&
|
|
228
|
+
Array.isArray(trialLedger.candidates),
|
|
229
|
+
'candidateRevalidation.trialLedger must use the v2 trial-ledger schema',
|
|
230
|
+
);
|
|
231
|
+
return objectiveReference.sha256;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function validateHistoryArtifact(input, artifactRoot) {
|
|
235
|
+
assertBoolean(input?.historyAudit?.complete, 'historyAudit.complete');
|
|
236
|
+
if (!input.historyAudit.complete) return null;
|
|
237
|
+
const reference = input.historyAudit.artifact;
|
|
238
|
+
const inventory = verifyArtifact(
|
|
239
|
+
reference,
|
|
240
|
+
'historyAudit.artifact',
|
|
241
|
+
artifactRoot,
|
|
242
|
+
).value;
|
|
243
|
+
assert(
|
|
244
|
+
inventory?.schema === 'tradejs-strategy-hypothesis-inventory/v1' &&
|
|
245
|
+
inventory.strategy === input.strategy &&
|
|
246
|
+
Array.isArray(inventory.entries),
|
|
247
|
+
'historyAudit artifact must be a matching hypothesis inventory',
|
|
248
|
+
);
|
|
249
|
+
const excludedStatuses = new Set([
|
|
250
|
+
'refactor-no-hypothesis',
|
|
251
|
+
'superseded-duplicate',
|
|
252
|
+
]);
|
|
253
|
+
return {
|
|
254
|
+
reference,
|
|
255
|
+
inventory,
|
|
256
|
+
candidateIndexes: inventory.entries.flatMap((entry, index) =>
|
|
257
|
+
excludedStatuses.has(entry?.status) ? [] : [index],
|
|
258
|
+
),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function validateFamilies(families, artifactRoot) {
|
|
263
|
+
assert(
|
|
264
|
+
Array.isArray(families) && families.length === 3,
|
|
265
|
+
'families must contain exactly three entries',
|
|
266
|
+
);
|
|
267
|
+
for (const [index, family] of families.entries()) {
|
|
268
|
+
assert(
|
|
269
|
+
family?.id && ['active', 'retired'].includes(family.status),
|
|
270
|
+
`families[${index}] has invalid identity/status`,
|
|
271
|
+
);
|
|
272
|
+
assert(
|
|
273
|
+
Array.isArray(family.roundArtifacts) && family.roundArtifacts.length <= 3,
|
|
274
|
+
`families[${index}].roundArtifacts must contain 0..3 entries`,
|
|
275
|
+
);
|
|
276
|
+
family.roundArtifacts.forEach((roundEvidence, roundIndex) => {
|
|
277
|
+
const expectedRound = roundIndex + 1;
|
|
278
|
+
assert(
|
|
279
|
+
roundEvidence.round === expectedRound,
|
|
280
|
+
`families[${index}].roundArtifacts must be sequential from round 1`,
|
|
281
|
+
);
|
|
282
|
+
verifyResearchEvidence(
|
|
283
|
+
roundEvidence,
|
|
284
|
+
`families[${index}].roundArtifacts[${roundIndex}]`,
|
|
285
|
+
artifactRoot,
|
|
286
|
+
{ expectedRound },
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
if (family.status === 'retired') {
|
|
290
|
+
assertIdentity(
|
|
291
|
+
family.hardStopReason,
|
|
292
|
+
`retired family ${family.id}.hardStopReason`,
|
|
293
|
+
);
|
|
294
|
+
if (family.roundArtifacts.length < 3) {
|
|
295
|
+
verifyArtifact(
|
|
296
|
+
family.retirementArtifact,
|
|
297
|
+
`retired family ${family.id}.retirementArtifact`,
|
|
298
|
+
artifactRoot,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function nextIncompleteFamily(families) {
|
|
306
|
+
const active = families.filter(
|
|
307
|
+
(family) => family.status === 'active' && family.roundArtifacts.length < 3,
|
|
308
|
+
);
|
|
309
|
+
if (!active.length) return null;
|
|
310
|
+
const minimumRound = Math.min(
|
|
311
|
+
...active.map((family) => family.roundArtifacts.length),
|
|
312
|
+
);
|
|
313
|
+
return active.find((family) => family.roundArtifacts.length === minimumRound);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function validateRescueBoard(input, artifactRoot, objectiveFingerprint) {
|
|
317
|
+
assertBoolean(input?.rescueBoard?.complete, 'rescueBoard.complete');
|
|
318
|
+
if (!input.rescueBoard.complete) return;
|
|
319
|
+
const board = verifyArtifact(
|
|
320
|
+
input.rescueBoard.artifact,
|
|
321
|
+
'rescueBoard.artifact',
|
|
322
|
+
artifactRoot,
|
|
323
|
+
).value;
|
|
324
|
+
assert(
|
|
325
|
+
board?.schema === 'tradejs-release-rescue-board/v2' &&
|
|
326
|
+
board.strategy === input.strategy &&
|
|
327
|
+
board.lineageId === input.lineageId &&
|
|
328
|
+
board.objectiveFingerprint === objectiveFingerprint &&
|
|
329
|
+
Array.isArray(board.children) &&
|
|
330
|
+
board.children.length <= 3 &&
|
|
331
|
+
Array.isArray(board.missingSlots) &&
|
|
332
|
+
board.children.length + board.missingSlots.length === 3,
|
|
333
|
+
'rescueBoard artifact must bind the v2 objective and account for three slots',
|
|
334
|
+
);
|
|
335
|
+
board.children.forEach((child, index) =>
|
|
336
|
+
verifyResearchEvidence(
|
|
337
|
+
child,
|
|
338
|
+
`rescueBoard.children[${index}]`,
|
|
339
|
+
artifactRoot,
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function validateCheckpoint(checkpoint, label, artifactRoot) {
|
|
345
|
+
assertBoolean(checkpoint?.required, `${label}.required`);
|
|
346
|
+
assertBoolean(checkpoint?.complete, `${label}.complete`);
|
|
347
|
+
if (checkpoint.required && checkpoint.complete) {
|
|
348
|
+
verifyArtifact(checkpoint.artifact, `${label}.artifact`, artifactRoot);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function validateCompletionArtifact(section, label, artifactRoot) {
|
|
353
|
+
assertBoolean(section?.complete, `${label}.complete`);
|
|
354
|
+
if (section.complete) {
|
|
355
|
+
verifyArtifact(section.artifact, `${label}.artifact`, artifactRoot);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function validateSelectedComposition(
|
|
360
|
+
input,
|
|
361
|
+
artifactRoot,
|
|
362
|
+
objectiveFingerprint,
|
|
363
|
+
) {
|
|
364
|
+
if (!input.selectedComposition) return null;
|
|
365
|
+
assertIdentity(
|
|
366
|
+
input.selectedComposition.candidateId,
|
|
367
|
+
'selectedComposition.candidateId',
|
|
368
|
+
);
|
|
369
|
+
assert(
|
|
370
|
+
SHA256_RE.test(input.selectedComposition.compositionFingerprint),
|
|
371
|
+
'selectedComposition.compositionFingerprint must be a lowercase SHA-256',
|
|
372
|
+
);
|
|
373
|
+
const artifactReference = input.selectedComposition.artifact;
|
|
374
|
+
const selected = verifyArtifact(
|
|
375
|
+
artifactReference,
|
|
376
|
+
'selectedComposition.artifact',
|
|
377
|
+
artifactRoot,
|
|
378
|
+
).value;
|
|
379
|
+
assert(
|
|
380
|
+
selected?.schema === 'tradejs-release-selected-composition/v2' &&
|
|
381
|
+
selected.strategy === input.strategy &&
|
|
382
|
+
selected.lineageId === input.lineageId &&
|
|
383
|
+
selected.objectiveFingerprint === objectiveFingerprint &&
|
|
384
|
+
selected.candidateId === input.selectedComposition.candidateId &&
|
|
385
|
+
selected.compositionFingerprint ===
|
|
386
|
+
input.selectedComposition.compositionFingerprint &&
|
|
387
|
+
selected.chartSha256 === input.chart.artifact.sha256,
|
|
388
|
+
'selectedComposition artifact must match strategy, lineage, objective, candidate, composition, and chart',
|
|
389
|
+
);
|
|
390
|
+
return {
|
|
391
|
+
candidateId: selected.candidateId,
|
|
392
|
+
compositionFingerprint: selected.compositionFingerprint,
|
|
393
|
+
artifactSha256: artifactReference.sha256,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function evaluateReleaseProgress(input, options = {}) {
|
|
398
|
+
const artifactRoot = path.resolve(options.artifactRoot ?? process.cwd());
|
|
399
|
+
assert(input?.schema === INPUT_SCHEMA, `schema must equal ${INPUT_SCHEMA}`);
|
|
400
|
+
assertIdentity(input.strategy, 'strategy');
|
|
401
|
+
assertIdentity(input.lineageId, 'lineageId');
|
|
402
|
+
|
|
403
|
+
const history = validateHistoryArtifact(input, artifactRoot);
|
|
404
|
+
const objectiveFingerprint = validateObjectiveAndRevalidation(
|
|
405
|
+
input,
|
|
406
|
+
artifactRoot,
|
|
407
|
+
history,
|
|
408
|
+
);
|
|
409
|
+
assertBoolean(input?.baseline?.complete, 'baseline.complete');
|
|
410
|
+
assertBoolean(input?.baseline?.reconciled, 'baseline.reconciled');
|
|
411
|
+
if (input.baseline.complete) {
|
|
412
|
+
verifyArtifact(input.baseline.artifact, 'baseline.artifact', artifactRoot);
|
|
413
|
+
}
|
|
414
|
+
validateCompletionArtifact(
|
|
415
|
+
input.opportunityMap,
|
|
416
|
+
'opportunityMap',
|
|
417
|
+
artifactRoot,
|
|
418
|
+
);
|
|
419
|
+
assertBoolean(
|
|
420
|
+
input?.hypothesisPortfolio?.frozen,
|
|
421
|
+
'hypothesisPortfolio.frozen',
|
|
422
|
+
);
|
|
423
|
+
if (input.hypothesisPortfolio.frozen) {
|
|
424
|
+
verifyArtifact(
|
|
425
|
+
input.hypothesisPortfolio.artifact,
|
|
426
|
+
'hypothesisPortfolio.artifact',
|
|
427
|
+
artifactRoot,
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
validateFamilies(input.families, artifactRoot);
|
|
431
|
+
validateRescueBoard(input, artifactRoot, objectiveFingerprint);
|
|
432
|
+
validateCheckpoint(
|
|
433
|
+
input.directionalParameterCheckpoint,
|
|
434
|
+
'directionalParameterCheckpoint',
|
|
435
|
+
artifactRoot,
|
|
436
|
+
);
|
|
437
|
+
validateCheckpoint(
|
|
438
|
+
input.directionPolicyCheckpoint,
|
|
439
|
+
'directionPolicyCheckpoint',
|
|
440
|
+
artifactRoot,
|
|
441
|
+
);
|
|
442
|
+
validateCompletionArtifact(input.fullAiReport, 'fullAiReport', artifactRoot);
|
|
443
|
+
validateCompletionArtifact(input.chart, 'chart', artifactRoot);
|
|
444
|
+
|
|
445
|
+
const limitations = Array.isArray(input.limitations)
|
|
446
|
+
? [...new Set(input.limitations)]
|
|
447
|
+
: [];
|
|
448
|
+
const hardLimitations = limitations.filter((entry) =>
|
|
449
|
+
HARD_LIMITATIONS.has(entry),
|
|
450
|
+
);
|
|
451
|
+
const evidenceCeiling = hardLimitations.length
|
|
452
|
+
? 'invalid_evidence'
|
|
453
|
+
: limitations.some((entry) => MICRO_FORWARD_LIMITATIONS.has(entry))
|
|
454
|
+
? 'micro_forward_only'
|
|
455
|
+
: 'historical_ready_eligible';
|
|
456
|
+
|
|
457
|
+
const result = (
|
|
458
|
+
nextAction,
|
|
459
|
+
phase,
|
|
460
|
+
reason,
|
|
461
|
+
verdictAllowed = false,
|
|
462
|
+
selectedComposition = null,
|
|
463
|
+
) => ({
|
|
464
|
+
schema: OUTPUT_SCHEMA,
|
|
465
|
+
strategy: input.strategy,
|
|
466
|
+
lineageId: input.lineageId,
|
|
467
|
+
objectiveFingerprint,
|
|
468
|
+
status: verdictAllowed ? 'complete' : 'continue',
|
|
469
|
+
phase,
|
|
470
|
+
nextAction,
|
|
471
|
+
verdictAllowed,
|
|
472
|
+
evidenceCeiling,
|
|
473
|
+
limitations,
|
|
474
|
+
selectedComposition,
|
|
475
|
+
reason,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (hardLimitations.length) {
|
|
479
|
+
return result(
|
|
480
|
+
'REPAIR_INVALID_EVIDENCE',
|
|
481
|
+
'evidence',
|
|
482
|
+
`Repair hard-invalid evidence: ${hardLimitations.join(', ')}.`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
if (!input.historyAudit.complete) {
|
|
486
|
+
return result(
|
|
487
|
+
'COMPLETE_HISTORY_AUDIT',
|
|
488
|
+
'history',
|
|
489
|
+
'The history inventory and stronger-result bridge are incomplete.',
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
if (!input.candidateRevalidation.complete) {
|
|
493
|
+
return result(
|
|
494
|
+
'REVALIDATE_HISTORICAL_CANDIDATES',
|
|
495
|
+
'history',
|
|
496
|
+
'Re-score and bridge every reconstructable historical candidate under the frozen objective.',
|
|
497
|
+
);
|
|
498
|
+
}
|
|
499
|
+
if (!input.baseline.complete || !input.baseline.reconciled) {
|
|
500
|
+
return result(
|
|
501
|
+
'RUN_RECONCILED_BASELINE',
|
|
502
|
+
'baseline',
|
|
503
|
+
'A complete reconciled control baseline is required.',
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
if (!input.opportunityMap.complete) {
|
|
507
|
+
return result(
|
|
508
|
+
'BUILD_OPPORTUNITY_MAP',
|
|
509
|
+
'diagnosis',
|
|
510
|
+
'Translate revalidated candidates, metrics, traces, identities, and code semantics into ranked causal opportunities.',
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (!input.hypothesisPortfolio.frozen) {
|
|
514
|
+
return result(
|
|
515
|
+
'FREEZE_HYPOTHESIS_PORTFOLIO',
|
|
516
|
+
'diagnosis',
|
|
517
|
+
'Choose strategy-specific exploit, repair, and explore/falsify mechanisms before round 1.',
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
if (
|
|
521
|
+
input.directionalParameterCheckpoint.required &&
|
|
522
|
+
!input.directionalParameterCheckpoint.complete
|
|
523
|
+
) {
|
|
524
|
+
return result(
|
|
525
|
+
'FREEZE_DIRECTIONAL_PARAMETER_SPLIT',
|
|
526
|
+
'core',
|
|
527
|
+
'A supported opposing side effect requires a directional parameter ablation before more adaptation.',
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const family = nextIncompleteFamily(input.families);
|
|
532
|
+
if (family) {
|
|
533
|
+
return result(
|
|
534
|
+
`RUN_CORE_ROUND_${family.roundArtifacts.length + 1}`,
|
|
535
|
+
'core',
|
|
536
|
+
`Continue causal family ${family.id}; audit, revalidation, and provenance limitations do not consume this new round.`,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
if (!input.rescueBoard.complete) {
|
|
540
|
+
return result(
|
|
541
|
+
'RUN_CADENCE_RESCUE_BOARD',
|
|
542
|
+
'rescue',
|
|
543
|
+
'The artifact-backed bounded core rescue board is incomplete.',
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
if (
|
|
547
|
+
input.directionPolicyCheckpoint.required &&
|
|
548
|
+
!input.directionPolicyCheckpoint.complete
|
|
549
|
+
) {
|
|
550
|
+
return result(
|
|
551
|
+
'RUN_DIRECTION_POLICY_CHECKPOINT',
|
|
552
|
+
'gate',
|
|
553
|
+
'A useful/hidden side requires the frozen direction-policy checkpoint.',
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
if (!input.fullAiReport.complete) {
|
|
557
|
+
return result(
|
|
558
|
+
'GENERATE_FULL_AI_REPORT',
|
|
559
|
+
'report',
|
|
560
|
+
'The complete ai-train-local-research report is missing.',
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (!input.chart.complete) {
|
|
564
|
+
return result(
|
|
565
|
+
'GENERATE_FULL_PERIOD_CHART',
|
|
566
|
+
'report',
|
|
567
|
+
'The full-period local deterministic chart is missing.',
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
const selectedComposition = validateSelectedComposition(
|
|
571
|
+
input,
|
|
572
|
+
artifactRoot,
|
|
573
|
+
objectiveFingerprint,
|
|
574
|
+
);
|
|
575
|
+
if (!selectedComposition) {
|
|
576
|
+
return result(
|
|
577
|
+
'FREEZE_SELECTED_COMPOSITION',
|
|
578
|
+
'decision',
|
|
579
|
+
'Bind one candidate/composition to the objective, historical matrix, and chart before deciding.',
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return result(
|
|
583
|
+
evidenceCeiling === 'micro_forward_only'
|
|
584
|
+
? 'DECIDE_PROSPECTIVE_MICRO_FORWARD'
|
|
585
|
+
: 'DECIDE_RELEASE',
|
|
586
|
+
'decision',
|
|
587
|
+
evidenceCeiling === 'micro_forward_only'
|
|
588
|
+
? 'Historical claims are capped, but the immutable selected composition may proceed to prospective risk-1 decision.'
|
|
589
|
+
: 'The artifact-backed bounded release contour is complete.',
|
|
590
|
+
true,
|
|
591
|
+
selectedComposition,
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function main() {
|
|
596
|
+
const inputIndex = process.argv.indexOf('--input');
|
|
597
|
+
if (inputIndex === -1 || !process.argv[inputIndex + 1]) {
|
|
598
|
+
throw new Error(
|
|
599
|
+
'Usage: release-progress-checkpoint.mjs --input <progress.json>',
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
const input = JSON.parse(readFileSync(process.argv[inputIndex + 1], 'utf8'));
|
|
603
|
+
process.stdout.write(
|
|
604
|
+
`${JSON.stringify(
|
|
605
|
+
evaluateReleaseProgress(input, { artifactRoot: process.cwd() }),
|
|
606
|
+
null,
|
|
607
|
+
2,
|
|
608
|
+
)}\n`,
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
|
613
|
+
try {
|
|
614
|
+
main();
|
|
615
|
+
} catch (error) {
|
|
616
|
+
process.stderr.write(
|
|
617
|
+
`${error instanceof Error ? error.message : String(error)}\n`,
|
|
618
|
+
);
|
|
619
|
+
process.exitCode = 1;
|
|
620
|
+
}
|
|
621
|
+
}
|