@quolu/lattice 0.52.2 → 0.52.4
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/package.json +1 -1
- package/src/artifact-contracts-v2.mjs +325 -325
- package/src/artifact-contracts.mjs +895 -895
- package/src/boundary-compiler.mjs +712 -712
- package/src/boundary-observation-compiler-v2.mjs +344 -344
- package/src/bounded-seam.mjs +230 -230
- package/src/bridge-address.mjs +107 -107
- package/src/bridge-cli.mjs +297 -297
- package/src/bridge-config.mjs +346 -346
- package/src/bridge-daemon.mjs +378 -378
- package/src/bridge-hub-protocol.mjs +198 -0
- package/src/bridge-launch-agent.mjs +323 -323
- package/src/bridge-registrar.mjs +102 -102
- package/src/bridge-server.mjs +376 -376
- package/src/cli-help.mjs +308 -308
- package/src/cli-stdio.mjs +40 -40
- package/src/control-compiler.mjs +532 -532
- package/src/dag-chain.mjs +261 -261
- package/src/factory-diagnostics.mjs +188 -188
- package/src/git-process.mjs +72 -0
- package/src/hash-chain.mjs +76 -76
- package/src/hooks-cli.mjs +1057 -1057
- package/src/isolation-runner.mjs +409 -409
- package/src/node-version-guard.mjs +44 -44
- package/src/project-cli.mjs +699 -697
- package/src/project-identity.mjs +130 -130
- package/src/rc1-black-box-oracle.mjs +906 -906
- package/src/rc1-comparison.mjs +154 -154
- package/src/rc1-evidence-bundle.mjs +456 -456
- package/src/rc1-v4-campaign.mjs +708 -708
- package/src/rc1-v4-transform.mjs +467 -467
- package/src/rc1-v5-artifact-set.mjs +855 -855
- package/src/rc1-v5-behavior-evidence.mjs +594 -594
- package/src/rc1-v5-campaign.mjs +797 -797
- package/src/rc1-v5-transform.mjs +421 -421
- package/src/rc1-v6-artifact-set.mjs +807 -807
- package/src/rc1-v6-behavior-evidence.mjs +273 -273
- package/src/rc1-v6-campaign.mjs +623 -623
- package/src/rc1-v6-causal-binding.mjs +473 -473
- package/src/rc1-v6-measurement.mjs +313 -313
- package/src/rc2-artifact-set.mjs +1584 -1584
- package/src/rc2-campaign.mjs +1506 -1506
- package/src/rc2-delivery-policy-front-end.mjs +1079 -1079
- package/src/rc2-delivery-policy-oracle.mjs +134 -134
- package/src/rc2-delivery-policy-transform.mjs +1127 -1127
- package/src/rc2-rc1-transfer-front-end.mjs +511 -511
- package/src/rc3-actual-dogfood.mjs +652 -652
- package/src/rc3-dogfood-scaffold.mjs +315 -315
- package/src/rc3-scripted-campaign.mjs +1383 -1383
- package/src/rc4-stage1-dogfood.mjs +673 -673
- package/src/runtime-adapter-registry.mjs +524 -524
- package/src/runtime-cli.mjs +4588 -4588
- package/src/runtime-contracts.mjs +824 -824
- package/src/runtime-control-store.mjs +604 -604
- package/src/runtime-controller-protocol.mjs +587 -587
- package/src/runtime-decision-verifier.mjs +701 -701
- package/src/runtime-diff-observer.mjs +361 -361
- package/src/runtime-direct-os-observer.mjs +301 -301
- package/src/runtime-driver-state.mjs +166 -166
- package/src/runtime-engine.mjs +779 -779
- package/src/runtime-errors.mjs +356 -356
- package/src/runtime-event-store.mjs +189 -189
- package/src/runtime-front-end.mjs +925 -925
- package/src/runtime-gate-store.mjs +481 -481
- package/src/runtime-hold-recompile.mjs +916 -916
- package/src/runtime-io-sentinel.mjs +391 -391
- package/src/runtime-lifecycle-lock.mjs +294 -294
- package/src/runtime-managed-supervisor.mjs +1490 -1490
- package/src/runtime-multi-epoch-store.mjs +838 -838
- package/src/runtime-projection.mjs +269 -269
- package/src/runtime-pull-intake.mjs +1192 -1192
- package/src/runtime-scripted-adapter-controller.mjs +1160 -1160
- package/src/runtime-scripted-executor.mjs +163 -163
- package/src/runtime-scripted-worktree.mjs +104 -104
- package/src/runtime-seam-resolve.mjs +428 -428
- package/src/runtime-seam-treatment.mjs +173 -173
- package/src/runtime-socket-owner.mjs +125 -125
- package/src/runtime-work-order-contracts.mjs +91 -91
- package/src/runtime-work-order-controller.mjs +1171 -1171
- package/src/runtime-worktree-executor.mjs +199 -199
- package/src/schedulability-compiler-v2.mjs +303 -303
- package/src/schedulability-verifier-v2.mjs +317 -317
- package/src/seam-apply.mjs +549 -549
- package/src/seam-commit-shared.mjs +22 -22
- package/src/seam-commit-transform.mjs +81 -81
- package/src/seam-commit.mjs +18 -18
- package/src/seam-cost.mjs +322 -322
- package/src/seam-derivation.mjs +188 -188
- package/src/seam-gate.mjs +146 -146
- package/src/seam-proposal-contracts.mjs +446 -446
- package/src/seam-proposal-queries.mjs +521 -521
- package/src/seam-proposal.mjs +2011 -2011
- package/src/seam-ref.mjs +33 -33
- package/src/seam-rewrite.mjs +286 -286
- package/src/seam-transform.mjs +554 -554
- package/src/seam-verification.mjs +260 -260
- package/src/sensor-adapter.mjs +432 -432
- package/src/sensor-cli.mjs +139 -139
- package/src/sensor-diff.mjs +661 -661
- package/src/sensor-node-runtime.mjs +53 -53
- package/src/sensor-runtime.mjs +52 -52
- package/src/timestamp-contract.mjs +8 -8
- package/src/todo-audit-pending.mjs +91 -91
- package/src/todo-chain.mjs +178 -178
- package/src/todo-cli.mjs +3143 -3141
- package/src/todo-contracts.mjs +728 -728
- package/src/todo-dashboard-registry.mjs +573 -573
- package/src/todo-dispatch-shape.mjs +190 -190
- package/src/todo-gantt-html-independence.mjs +239 -239
- package/src/todo-gantt-html-shared.mjs +226 -226
- package/src/todo-gantt-html-style.mjs +131 -131
- package/src/todo-gantt-html.mjs +248 -248
- package/src/todo-gantt-layout.mjs +974 -974
- package/src/todo-gantt-live.mjs +361 -361
- package/src/todo-gantt-nested.mjs +263 -263
- package/src/todo-gantt-presentation.mjs +217 -217
- package/src/todo-gantt-scope.mjs +123 -123
- package/src/todo-gantt-svg.mjs +353 -353
- package/src/todo-independence-contracts.mjs +595 -595
- package/src/todo-independence-guidance.mjs +322 -322
- package/src/todo-independence.mjs +640 -640
- package/src/todo-markdown-renderer.mjs +260 -260
- package/src/todo-migration.mjs +448 -448
- package/src/todo-narrative-anchor.mjs +130 -130
- package/src/todo-note-store.mjs +629 -629
- package/src/todo-parallel-candidates.mjs +114 -114
- package/src/todo-revision.mjs +995 -995
- package/src/todo-split.mjs +472 -472
- package/src/todo-status.mjs +690 -690
- package/src/todo-store-git-transaction.mjs +418 -418
- package/src/todo-store.mjs +4360 -4322
- package/src/treatment-compiler.mjs +728 -728
- package/src/treatment-runner.mjs +656 -656
- package/src/witness-scaffold.mjs +180 -180
package/src/rc1-v5-transform.mjs
CHANGED
|
@@ -1,421 +1,421 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { promisify } from 'node:util';
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
digestArtifact,
|
|
9
|
-
validateTransformArtifact,
|
|
10
|
-
} from './artifact-contracts.mjs';
|
|
11
|
-
import { runIsolatedTransform } from './isolation-runner.mjs';
|
|
12
|
-
import {
|
|
13
|
-
runRc1V5BlackBoxOracle,
|
|
14
|
-
validateRc1BlackBoxOracle,
|
|
15
|
-
} from './rc1-black-box-oracle.mjs';
|
|
16
|
-
import {
|
|
17
|
-
applyRc1V4Transform,
|
|
18
|
-
RC1_V4_TRANSFORM_PATHS,
|
|
19
|
-
} from './rc1-v4-transform.mjs';
|
|
20
|
-
import { compileRc1V5BehaviorEvidence } from './rc1-v5-behavior-evidence.mjs';
|
|
21
|
-
|
|
22
|
-
const execFileAsync = promisify(execFile);
|
|
23
|
-
const SHA256 = /^[0-9a-f]{64}$/;
|
|
24
|
-
const GIT_SHA1 = /^[0-9a-f]{40}$/;
|
|
25
|
-
const CANDIDATE_ID = 'extract-dispatch-production-and-test-policies';
|
|
26
|
-
const SOURCE_BINDING_KEYS = Object.freeze([
|
|
27
|
-
'boundary_manifest_digest',
|
|
28
|
-
'boundary_verdict_digest',
|
|
29
|
-
'control_plan_digest',
|
|
30
|
-
'query_set_digest',
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
export const RC1_V5_TRANSFORM_PATHS = Object.freeze([...RC1_V4_TRANSFORM_PATHS]);
|
|
34
|
-
|
|
35
|
-
const VERIFIERS = Object.freeze([{
|
|
36
|
-
id: 'production-test-seam',
|
|
37
|
-
command: 'node',
|
|
38
|
-
args: [
|
|
39
|
-
'--test',
|
|
40
|
-
'--test-reporter=dot',
|
|
41
|
-
'test/research-dispatch-channel.test.mjs',
|
|
42
|
-
'test/research-dispatch-label.test.mjs',
|
|
43
|
-
'test/research-dispatch-record.test.mjs',
|
|
44
|
-
],
|
|
45
|
-
}]);
|
|
46
|
-
|
|
47
|
-
function isPlainObject(value) {
|
|
48
|
-
return value !== null
|
|
49
|
-
&& typeof value === 'object'
|
|
50
|
-
&& !Array.isArray(value)
|
|
51
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function exactRecord(value, keys) {
|
|
55
|
-
if (!isPlainObject(value)) return false;
|
|
56
|
-
const actual = Object.keys(value).sort();
|
|
57
|
-
const expected = [...keys].sort();
|
|
58
|
-
return actual.length === expected.length
|
|
59
|
-
&& actual.every((key, index) => key === expected[index]);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function assertSourceBindings(value) {
|
|
63
|
-
if (!exactRecord(value, SOURCE_BINDING_KEYS)
|
|
64
|
-
|| Object.values(value).some((digest) => typeof digest !== 'string' || !SHA256.test(digest))) {
|
|
65
|
-
throw new TypeError('RC1 v5 source bindings are invalid');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function sha256(value) {
|
|
70
|
-
return createHash('sha256').update(value).digest('hex');
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function sameArray(left, right) {
|
|
74
|
-
return Array.isArray(left)
|
|
75
|
-
&& Array.isArray(right)
|
|
76
|
-
&& left.length === right.length
|
|
77
|
-
&& left.every((entry, index) => entry === right[index]);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function resolveBaseSha(repoRoot, baseRef) {
|
|
81
|
-
try {
|
|
82
|
-
const { stdout } = await execFileAsync(
|
|
83
|
-
'git',
|
|
84
|
-
['-C', repoRoot, 'rev-parse', '--verify', `${baseRef}^{commit}`],
|
|
85
|
-
{ encoding: 'utf8', maxBuffer: 64 * 1024 },
|
|
86
|
-
);
|
|
87
|
-
const baseSha = stdout.trim();
|
|
88
|
-
if (!GIT_SHA1.test(baseSha)) throw new TypeError('base SHA is not SHA-1');
|
|
89
|
-
return baseSha;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
throw new TypeError(`RC1 v5 base refを解決できない: ${String(error?.code ?? error?.name ?? 'error')}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function verificationArtifact(status, runnerReceipts) {
|
|
96
|
-
const receipts = runnerReceipts.map((receipt, index) => ({
|
|
97
|
-
id: VERIFIERS[index]?.id ?? `unexpected-${index}`,
|
|
98
|
-
command: receipt.command,
|
|
99
|
-
args: [...receipt.args],
|
|
100
|
-
outcome: receipt.outcome,
|
|
101
|
-
exit_code: receipt.exit_code,
|
|
102
|
-
stdout_digest: receipt.stdout_digest,
|
|
103
|
-
stderr_digest: receipt.stderr_digest,
|
|
104
|
-
}));
|
|
105
|
-
const value = { status, receipts };
|
|
106
|
-
return { ...value, digest: digestArtifact(value) };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function outputArtifact(worktreePath, changedPaths) {
|
|
110
|
-
const files = await Promise.all(changedPaths.map(async (relativePath) => ({
|
|
111
|
-
path: relativePath,
|
|
112
|
-
content_digest: sha256(await readFile(path.join(worktreePath, relativePath))),
|
|
113
|
-
})));
|
|
114
|
-
return { snapshot_digest: digestArtifact({ files }), files };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function flattenErrors(error) {
|
|
118
|
-
if (error instanceof AggregateError) return error.errors.flatMap(flattenErrors);
|
|
119
|
-
return [error];
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function transformEvidence(error) {
|
|
123
|
-
return flattenErrors(error).map((entry) => entry?.transformEvidence).find((entry) => (
|
|
124
|
-
entry && typeof entry.baseSha === 'string'
|
|
125
|
-
));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function rejectionFacts(error) {
|
|
129
|
-
const errors = flattenErrors(error);
|
|
130
|
-
const messages = errors.map((entry) => String(entry?.message ?? entry)).join('\n');
|
|
131
|
-
const codes = new Set(errors.map((entry) => entry?.code).filter(Boolean));
|
|
132
|
-
if (/source repository changed/i.test(messages) || codes.has('LATTICE_RC1_V5_BASE_DRIFT')) {
|
|
133
|
-
return {
|
|
134
|
-
kind: 'source_invariant_violation',
|
|
135
|
-
reason: 'canonical source or observed Git base changed during isolated transform',
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (/cleanup failed/i.test(messages)) {
|
|
139
|
-
return { kind: 'cleanup_failure', reason: 'disposable worktree cleanup failed' };
|
|
140
|
-
}
|
|
141
|
-
if (/outside allowed paths|symlink change|submodule change|special file change/i.test(messages)
|
|
142
|
-
|| codes.has('LATTICE_RC1_V5_SCOPE_VIOLATION')
|
|
143
|
-
|| codes.has('LATTICE_RC1_V5_SURFACE_INVALID')) {
|
|
144
|
-
return {
|
|
145
|
-
kind: 'scope_violation',
|
|
146
|
-
reason: 'isolated transform or fixed surface violated the accepted path scope',
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
if (/black-box oracle failed|verifier failed|production-test seam incomplete/i.test(messages)) {
|
|
150
|
-
return {
|
|
151
|
-
kind: 'behavior_verification_failed',
|
|
152
|
-
reason: 'fixed black-box oracle or production-test seam verifier failed',
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
if (/mutated isolated snapshot/i.test(messages)
|
|
156
|
-
|| codes.has('LATTICE_RC1_V5_SURFACE_DRIFT')) {
|
|
157
|
-
return {
|
|
158
|
-
kind: 'snapshot_mutation',
|
|
159
|
-
reason: 'oracle、verifier、observerの実行中にisolated snapshotが変化した',
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
return { kind: 'execution_failure', reason: 'isolated transform or behavior binding failed' };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function sourceSnapshotDigest(pre, baseSha) {
|
|
166
|
-
return pre?.surface_digest ?? digestArtifact({
|
|
167
|
-
schema: 'lattice.rc1.unobserved_behavior_surface.v1',
|
|
168
|
-
base_sha: baseSha,
|
|
169
|
-
allowed_paths: RC1_V5_TRANSFORM_PATHS,
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function makeReceipt({ status, artifactDigest, envelopeDigest, fixedInputsDigest, sourceInvariant }) {
|
|
174
|
-
const value = {
|
|
175
|
-
schema: 'lattice.rc1.seam_transform_receipt.v5',
|
|
176
|
-
status,
|
|
177
|
-
transform_artifact_digest: artifactDigest,
|
|
178
|
-
behavior_envelope_digest: envelopeDigest,
|
|
179
|
-
fixed_inputs_digest: fixedInputsDigest,
|
|
180
|
-
source_invariant: sourceInvariant ?? null,
|
|
181
|
-
};
|
|
182
|
-
return { ...value, receipt_digest: digestArtifact(value) };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function makeResult({ artifact, patch, behaviorEvidence, fixedInputsDigest, sourceInvariant }) {
|
|
186
|
-
const artifactDigest = digestArtifact(artifact);
|
|
187
|
-
const receipt = makeReceipt({
|
|
188
|
-
status: artifact.status,
|
|
189
|
-
artifactDigest,
|
|
190
|
-
envelopeDigest: behaviorEvidence?.envelope?.envelope_digest ?? null,
|
|
191
|
-
fixedInputsDigest,
|
|
192
|
-
sourceInvariant,
|
|
193
|
-
});
|
|
194
|
-
return {
|
|
195
|
-
schema: 'lattice.rc1.seam_transform_result.v5',
|
|
196
|
-
artifact,
|
|
197
|
-
artifact_digest: artifactDigest,
|
|
198
|
-
receipt,
|
|
199
|
-
receipt_digest: receipt.receipt_digest,
|
|
200
|
-
patch: Buffer.isBuffer(patch) ? Buffer.from(patch) : null,
|
|
201
|
-
behavior_evidence: behaviorEvidence ? structuredClone(behaviorEvidence) : null,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function rejectedResult({
|
|
206
|
-
error,
|
|
207
|
-
evidence,
|
|
208
|
-
sourceBindings,
|
|
209
|
-
pre,
|
|
210
|
-
output,
|
|
211
|
-
fixedInputsDigest,
|
|
212
|
-
}) {
|
|
213
|
-
const facts = rejectionFacts(error);
|
|
214
|
-
const runnerReceipts = Array.isArray(evidence.verifications) ? evidence.verifications : [];
|
|
215
|
-
const patch = Buffer.isBuffer(evidence.patch) ? evidence.patch : null;
|
|
216
|
-
const outputAdmitted = output
|
|
217
|
-
&& Array.isArray(evidence.changedPaths)
|
|
218
|
-
&& sameArray(output.files.map(({ path: relativePath }) => relativePath), evidence.changedPaths);
|
|
219
|
-
const changedPaths = outputAdmitted ? [...evidence.changedPaths] : [];
|
|
220
|
-
const messages = flattenErrors(error).map((entry) => String(entry?.message ?? entry));
|
|
221
|
-
const verificationStatus = runnerReceipts.length === 0 ? 'not_run' : 'failed';
|
|
222
|
-
const rejection = { kind: facts.kind, reasons: [facts.reason] };
|
|
223
|
-
const artifact = {
|
|
224
|
-
schema: 'lattice.transform_artifact.v1',
|
|
225
|
-
candidate_id: CANDIDATE_ID,
|
|
226
|
-
status: 'rejected',
|
|
227
|
-
source: {
|
|
228
|
-
base_sha: evidence.baseSha,
|
|
229
|
-
...sourceBindings,
|
|
230
|
-
code_snapshot_digest: sourceSnapshotDigest(pre, evidence.baseSha),
|
|
231
|
-
},
|
|
232
|
-
scope: {
|
|
233
|
-
allowed_paths: [...RC1_V5_TRANSFORM_PATHS],
|
|
234
|
-
changed_paths: changedPaths,
|
|
235
|
-
},
|
|
236
|
-
patch: { digest: patch ? sha256(patch) : null, bytes: patch?.byteLength ?? 0 },
|
|
237
|
-
verification: verificationArtifact(verificationStatus, runnerReceipts),
|
|
238
|
-
output: outputAdmitted ? structuredClone(output) : { snapshot_digest: null, files: [] },
|
|
239
|
-
cleanup: {
|
|
240
|
-
status: messages.some((message) => /cleanup failed/i.test(message)) ? 'failed' : 'passed',
|
|
241
|
-
source_status: messages.some((message) => /source repository changed/i.test(message))
|
|
242
|
-
? 'changed'
|
|
243
|
-
: 'unchanged',
|
|
244
|
-
},
|
|
245
|
-
rejection: { ...rejection, evidence_digest: digestArtifact(rejection) },
|
|
246
|
-
unknowns: ['behavior evidence was not admitted'],
|
|
247
|
-
};
|
|
248
|
-
if (!validateTransformArtifact(artifact)) {
|
|
249
|
-
throw new TypeError('RC1 v5 rejected transform artifact is invalid');
|
|
250
|
-
}
|
|
251
|
-
return makeResult({
|
|
252
|
-
artifact,
|
|
253
|
-
patch,
|
|
254
|
-
behaviorEvidence: null,
|
|
255
|
-
fixedInputsDigest,
|
|
256
|
-
sourceInvariant: evidence.sourceInvariant,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** v4で固定済みの決定的production+test seam writerをv5 isolationへ適用する。 */
|
|
261
|
-
export async function applyRc1V5Transform(context = {}) {
|
|
262
|
-
return applyRc1V4Transform(context);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* 一つのdisposable worktreeでpre観測、seam変換、post観測を順序固定し、accepted artifactだけをenvelopeへbindする。
|
|
267
|
-
*/
|
|
268
|
-
export async function runRc1V5SeamTransform(options = {}) {
|
|
269
|
-
if (!exactRecord(options, [
|
|
270
|
-
'repoRoot',
|
|
271
|
-
'baseRef',
|
|
272
|
-
'oracle',
|
|
273
|
-
'sourceBindings',
|
|
274
|
-
]) && !exactRecord(options, [
|
|
275
|
-
'repoRoot',
|
|
276
|
-
'baseRef',
|
|
277
|
-
'oracle',
|
|
278
|
-
'sourceBindings',
|
|
279
|
-
'transform',
|
|
280
|
-
])) {
|
|
281
|
-
throw new TypeError('RC1 v5 seam transform options must have the fixed exact shape');
|
|
282
|
-
}
|
|
283
|
-
const {
|
|
284
|
-
repoRoot,
|
|
285
|
-
baseRef,
|
|
286
|
-
oracle,
|
|
287
|
-
sourceBindings,
|
|
288
|
-
transform = applyRc1V5Transform,
|
|
289
|
-
} = options;
|
|
290
|
-
if (typeof repoRoot !== 'string' || repoRoot.length === 0
|
|
291
|
-
|| typeof baseRef !== 'string' || baseRef.length === 0
|
|
292
|
-
|| typeof transform !== 'function'
|
|
293
|
-
|| !validateRc1BlackBoxOracle(oracle)) {
|
|
294
|
-
throw new TypeError('RC1 v5 seam transform arguments are invalid');
|
|
295
|
-
}
|
|
296
|
-
assertSourceBindings(sourceBindings);
|
|
297
|
-
|
|
298
|
-
const fixedOracle = structuredClone(oracle);
|
|
299
|
-
const fixedSourceBindings = structuredClone(sourceBindings);
|
|
300
|
-
const oracleDigest = digestArtifact(fixedOracle);
|
|
301
|
-
const fixedInputsDigest = digestArtifact({
|
|
302
|
-
candidate_id: CANDIDATE_ID,
|
|
303
|
-
allowed_paths: RC1_V5_TRANSFORM_PATHS,
|
|
304
|
-
verifiers: VERIFIERS,
|
|
305
|
-
oracle_digest: oracleDigest,
|
|
306
|
-
source_bindings: fixedSourceBindings,
|
|
307
|
-
});
|
|
308
|
-
const baseSha = await resolveBaseSha(repoRoot, baseRef);
|
|
309
|
-
let pre;
|
|
310
|
-
let post;
|
|
311
|
-
let output;
|
|
312
|
-
let isolated;
|
|
313
|
-
try {
|
|
314
|
-
isolated = await runIsolatedTransform({
|
|
315
|
-
repoRoot,
|
|
316
|
-
baseRef: baseSha,
|
|
317
|
-
allowedPaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
318
|
-
transform: async (context) => {
|
|
319
|
-
pre = await runRc1V5BlackBoxOracle({
|
|
320
|
-
repoRoot: context.worktreePath,
|
|
321
|
-
oracle: fixedOracle,
|
|
322
|
-
role: 'pre',
|
|
323
|
-
baseSha,
|
|
324
|
-
surfacePaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
325
|
-
});
|
|
326
|
-
if (pre.outcome !== 'passed') throw new Error('black-box oracle failed before transform');
|
|
327
|
-
await transform(context);
|
|
328
|
-
post = await runRc1V5BlackBoxOracle({
|
|
329
|
-
repoRoot: context.worktreePath,
|
|
330
|
-
oracle: fixedOracle,
|
|
331
|
-
role: 'post',
|
|
332
|
-
baseSha,
|
|
333
|
-
surfacePaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
334
|
-
});
|
|
335
|
-
if (post.outcome !== 'passed') throw new Error('black-box oracle failed after transform');
|
|
336
|
-
},
|
|
337
|
-
verifyCommands: VERIFIERS.map(({ command, args }) => ({ command, args: [...args] })),
|
|
338
|
-
observe: async ({ worktreePath, changedPaths }) => {
|
|
339
|
-
output = await outputArtifact(worktreePath, changedPaths);
|
|
340
|
-
},
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
const postProjection = post?.surface?.files?.map((file) => (
|
|
344
|
-
file.state === 'present'
|
|
345
|
-
? { path: file.path, content_digest: file.content_digest }
|
|
346
|
-
: null
|
|
347
|
-
));
|
|
348
|
-
if (!pre || !post || !output
|
|
349
|
-
|| pre.outcome !== 'passed' || post.outcome !== 'passed'
|
|
350
|
-
|| pre.oracle_digest !== oracleDigest || post.oracle_digest !== oracleDigest
|
|
351
|
-
|| pre.base_sha !== baseSha || post.base_sha !== baseSha
|
|
352
|
-
|| !sameArray(
|
|
353
|
-
pre.surface.files.map(({ path: relativePath }) => relativePath),
|
|
354
|
-
RC1_V5_TRANSFORM_PATHS,
|
|
355
|
-
)
|
|
356
|
-
|| !Array.isArray(postProjection) || postProjection.some((file) => file === null)
|
|
357
|
-
|| !sameArray(isolated.changedPaths, RC1_V5_TRANSFORM_PATHS)
|
|
358
|
-
|| !Buffer.isBuffer(isolated.patch)
|
|
359
|
-
|| isolated.verifications.length !== VERIFIERS.length
|
|
360
|
-
|| isolated.sourceInvariant?.outcome !== 'passed'
|
|
361
|
-
|| digestArtifact({ files: postProjection }) !== output.snapshot_digest
|
|
362
|
-
|| digestArtifact(postProjection) !== digestArtifact(output.files)) {
|
|
363
|
-
throw new Error('production-test seam incomplete');
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
const artifact = {
|
|
367
|
-
schema: 'lattice.transform_artifact.v1',
|
|
368
|
-
candidate_id: CANDIDATE_ID,
|
|
369
|
-
status: 'accepted',
|
|
370
|
-
source: {
|
|
371
|
-
base_sha: isolated.baseSha,
|
|
372
|
-
...fixedSourceBindings,
|
|
373
|
-
code_snapshot_digest: pre.surface_digest,
|
|
374
|
-
},
|
|
375
|
-
scope: {
|
|
376
|
-
allowed_paths: [...RC1_V5_TRANSFORM_PATHS],
|
|
377
|
-
changed_paths: [...isolated.changedPaths],
|
|
378
|
-
},
|
|
379
|
-
patch: { digest: sha256(isolated.patch), bytes: isolated.patch.byteLength },
|
|
380
|
-
verification: verificationArtifact('passed', isolated.verifications),
|
|
381
|
-
output,
|
|
382
|
-
cleanup: { status: 'passed', source_status: 'unchanged' },
|
|
383
|
-
rejection: null,
|
|
384
|
-
unknowns: [],
|
|
385
|
-
};
|
|
386
|
-
if (!validateTransformArtifact(artifact)) {
|
|
387
|
-
throw new TypeError('RC1 v5 accepted transform artifact is invalid');
|
|
388
|
-
}
|
|
389
|
-
const envelope = compileRc1V5BehaviorEvidence({
|
|
390
|
-
preReceipt: pre,
|
|
391
|
-
postReceipt: post,
|
|
392
|
-
transformArtifact: artifact,
|
|
393
|
-
patchDigest: artifact.patch.digest,
|
|
394
|
-
});
|
|
395
|
-
const behaviorEvidence = {
|
|
396
|
-
pre_receipt: pre,
|
|
397
|
-
post_receipt: post,
|
|
398
|
-
envelope,
|
|
399
|
-
transform_artifact: artifact,
|
|
400
|
-
patch_digest: artifact.patch.digest,
|
|
401
|
-
};
|
|
402
|
-
return makeResult({
|
|
403
|
-
artifact,
|
|
404
|
-
patch: isolated.patch,
|
|
405
|
-
behaviorEvidence,
|
|
406
|
-
fixedInputsDigest,
|
|
407
|
-
sourceInvariant: isolated.sourceInvariant,
|
|
408
|
-
});
|
|
409
|
-
} catch (error) {
|
|
410
|
-
const evidence = transformEvidence(error) ?? isolated;
|
|
411
|
-
if (!evidence || typeof evidence.baseSha !== 'string') throw error;
|
|
412
|
-
return rejectedResult({
|
|
413
|
-
error,
|
|
414
|
-
evidence,
|
|
415
|
-
sourceBindings: fixedSourceBindings,
|
|
416
|
-
pre,
|
|
417
|
-
output,
|
|
418
|
-
fixedInputsDigest,
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
|
-
}
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
digestArtifact,
|
|
9
|
+
validateTransformArtifact,
|
|
10
|
+
} from './artifact-contracts.mjs';
|
|
11
|
+
import { runIsolatedTransform } from './isolation-runner.mjs';
|
|
12
|
+
import {
|
|
13
|
+
runRc1V5BlackBoxOracle,
|
|
14
|
+
validateRc1BlackBoxOracle,
|
|
15
|
+
} from './rc1-black-box-oracle.mjs';
|
|
16
|
+
import {
|
|
17
|
+
applyRc1V4Transform,
|
|
18
|
+
RC1_V4_TRANSFORM_PATHS,
|
|
19
|
+
} from './rc1-v4-transform.mjs';
|
|
20
|
+
import { compileRc1V5BehaviorEvidence } from './rc1-v5-behavior-evidence.mjs';
|
|
21
|
+
|
|
22
|
+
const execFileAsync = promisify(execFile);
|
|
23
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
24
|
+
const GIT_SHA1 = /^[0-9a-f]{40}$/;
|
|
25
|
+
const CANDIDATE_ID = 'extract-dispatch-production-and-test-policies';
|
|
26
|
+
const SOURCE_BINDING_KEYS = Object.freeze([
|
|
27
|
+
'boundary_manifest_digest',
|
|
28
|
+
'boundary_verdict_digest',
|
|
29
|
+
'control_plan_digest',
|
|
30
|
+
'query_set_digest',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const RC1_V5_TRANSFORM_PATHS = Object.freeze([...RC1_V4_TRANSFORM_PATHS]);
|
|
34
|
+
|
|
35
|
+
const VERIFIERS = Object.freeze([{
|
|
36
|
+
id: 'production-test-seam',
|
|
37
|
+
command: 'node',
|
|
38
|
+
args: [
|
|
39
|
+
'--test',
|
|
40
|
+
'--test-reporter=dot',
|
|
41
|
+
'test/research-dispatch-channel.test.mjs',
|
|
42
|
+
'test/research-dispatch-label.test.mjs',
|
|
43
|
+
'test/research-dispatch-record.test.mjs',
|
|
44
|
+
],
|
|
45
|
+
}]);
|
|
46
|
+
|
|
47
|
+
function isPlainObject(value) {
|
|
48
|
+
return value !== null
|
|
49
|
+
&& typeof value === 'object'
|
|
50
|
+
&& !Array.isArray(value)
|
|
51
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function exactRecord(value, keys) {
|
|
55
|
+
if (!isPlainObject(value)) return false;
|
|
56
|
+
const actual = Object.keys(value).sort();
|
|
57
|
+
const expected = [...keys].sort();
|
|
58
|
+
return actual.length === expected.length
|
|
59
|
+
&& actual.every((key, index) => key === expected[index]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function assertSourceBindings(value) {
|
|
63
|
+
if (!exactRecord(value, SOURCE_BINDING_KEYS)
|
|
64
|
+
|| Object.values(value).some((digest) => typeof digest !== 'string' || !SHA256.test(digest))) {
|
|
65
|
+
throw new TypeError('RC1 v5 source bindings are invalid');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function sha256(value) {
|
|
70
|
+
return createHash('sha256').update(value).digest('hex');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sameArray(left, right) {
|
|
74
|
+
return Array.isArray(left)
|
|
75
|
+
&& Array.isArray(right)
|
|
76
|
+
&& left.length === right.length
|
|
77
|
+
&& left.every((entry, index) => entry === right[index]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function resolveBaseSha(repoRoot, baseRef) {
|
|
81
|
+
try {
|
|
82
|
+
const { stdout } = await execFileAsync(
|
|
83
|
+
'git',
|
|
84
|
+
['-C', repoRoot, 'rev-parse', '--verify', `${baseRef}^{commit}`],
|
|
85
|
+
{ encoding: 'utf8', maxBuffer: 64 * 1024 },
|
|
86
|
+
);
|
|
87
|
+
const baseSha = stdout.trim();
|
|
88
|
+
if (!GIT_SHA1.test(baseSha)) throw new TypeError('base SHA is not SHA-1');
|
|
89
|
+
return baseSha;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new TypeError(`RC1 v5 base refを解決できない: ${String(error?.code ?? error?.name ?? 'error')}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function verificationArtifact(status, runnerReceipts) {
|
|
96
|
+
const receipts = runnerReceipts.map((receipt, index) => ({
|
|
97
|
+
id: VERIFIERS[index]?.id ?? `unexpected-${index}`,
|
|
98
|
+
command: receipt.command,
|
|
99
|
+
args: [...receipt.args],
|
|
100
|
+
outcome: receipt.outcome,
|
|
101
|
+
exit_code: receipt.exit_code,
|
|
102
|
+
stdout_digest: receipt.stdout_digest,
|
|
103
|
+
stderr_digest: receipt.stderr_digest,
|
|
104
|
+
}));
|
|
105
|
+
const value = { status, receipts };
|
|
106
|
+
return { ...value, digest: digestArtifact(value) };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function outputArtifact(worktreePath, changedPaths) {
|
|
110
|
+
const files = await Promise.all(changedPaths.map(async (relativePath) => ({
|
|
111
|
+
path: relativePath,
|
|
112
|
+
content_digest: sha256(await readFile(path.join(worktreePath, relativePath))),
|
|
113
|
+
})));
|
|
114
|
+
return { snapshot_digest: digestArtifact({ files }), files };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function flattenErrors(error) {
|
|
118
|
+
if (error instanceof AggregateError) return error.errors.flatMap(flattenErrors);
|
|
119
|
+
return [error];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function transformEvidence(error) {
|
|
123
|
+
return flattenErrors(error).map((entry) => entry?.transformEvidence).find((entry) => (
|
|
124
|
+
entry && typeof entry.baseSha === 'string'
|
|
125
|
+
));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function rejectionFacts(error) {
|
|
129
|
+
const errors = flattenErrors(error);
|
|
130
|
+
const messages = errors.map((entry) => String(entry?.message ?? entry)).join('\n');
|
|
131
|
+
const codes = new Set(errors.map((entry) => entry?.code).filter(Boolean));
|
|
132
|
+
if (/source repository changed/i.test(messages) || codes.has('LATTICE_RC1_V5_BASE_DRIFT')) {
|
|
133
|
+
return {
|
|
134
|
+
kind: 'source_invariant_violation',
|
|
135
|
+
reason: 'canonical source or observed Git base changed during isolated transform',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (/cleanup failed/i.test(messages)) {
|
|
139
|
+
return { kind: 'cleanup_failure', reason: 'disposable worktree cleanup failed' };
|
|
140
|
+
}
|
|
141
|
+
if (/outside allowed paths|symlink change|submodule change|special file change/i.test(messages)
|
|
142
|
+
|| codes.has('LATTICE_RC1_V5_SCOPE_VIOLATION')
|
|
143
|
+
|| codes.has('LATTICE_RC1_V5_SURFACE_INVALID')) {
|
|
144
|
+
return {
|
|
145
|
+
kind: 'scope_violation',
|
|
146
|
+
reason: 'isolated transform or fixed surface violated the accepted path scope',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (/black-box oracle failed|verifier failed|production-test seam incomplete/i.test(messages)) {
|
|
150
|
+
return {
|
|
151
|
+
kind: 'behavior_verification_failed',
|
|
152
|
+
reason: 'fixed black-box oracle or production-test seam verifier failed',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (/mutated isolated snapshot/i.test(messages)
|
|
156
|
+
|| codes.has('LATTICE_RC1_V5_SURFACE_DRIFT')) {
|
|
157
|
+
return {
|
|
158
|
+
kind: 'snapshot_mutation',
|
|
159
|
+
reason: 'oracle、verifier、observerの実行中にisolated snapshotが変化した',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return { kind: 'execution_failure', reason: 'isolated transform or behavior binding failed' };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sourceSnapshotDigest(pre, baseSha) {
|
|
166
|
+
return pre?.surface_digest ?? digestArtifact({
|
|
167
|
+
schema: 'lattice.rc1.unobserved_behavior_surface.v1',
|
|
168
|
+
base_sha: baseSha,
|
|
169
|
+
allowed_paths: RC1_V5_TRANSFORM_PATHS,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function makeReceipt({ status, artifactDigest, envelopeDigest, fixedInputsDigest, sourceInvariant }) {
|
|
174
|
+
const value = {
|
|
175
|
+
schema: 'lattice.rc1.seam_transform_receipt.v5',
|
|
176
|
+
status,
|
|
177
|
+
transform_artifact_digest: artifactDigest,
|
|
178
|
+
behavior_envelope_digest: envelopeDigest,
|
|
179
|
+
fixed_inputs_digest: fixedInputsDigest,
|
|
180
|
+
source_invariant: sourceInvariant ?? null,
|
|
181
|
+
};
|
|
182
|
+
return { ...value, receipt_digest: digestArtifact(value) };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function makeResult({ artifact, patch, behaviorEvidence, fixedInputsDigest, sourceInvariant }) {
|
|
186
|
+
const artifactDigest = digestArtifact(artifact);
|
|
187
|
+
const receipt = makeReceipt({
|
|
188
|
+
status: artifact.status,
|
|
189
|
+
artifactDigest,
|
|
190
|
+
envelopeDigest: behaviorEvidence?.envelope?.envelope_digest ?? null,
|
|
191
|
+
fixedInputsDigest,
|
|
192
|
+
sourceInvariant,
|
|
193
|
+
});
|
|
194
|
+
return {
|
|
195
|
+
schema: 'lattice.rc1.seam_transform_result.v5',
|
|
196
|
+
artifact,
|
|
197
|
+
artifact_digest: artifactDigest,
|
|
198
|
+
receipt,
|
|
199
|
+
receipt_digest: receipt.receipt_digest,
|
|
200
|
+
patch: Buffer.isBuffer(patch) ? Buffer.from(patch) : null,
|
|
201
|
+
behavior_evidence: behaviorEvidence ? structuredClone(behaviorEvidence) : null,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function rejectedResult({
|
|
206
|
+
error,
|
|
207
|
+
evidence,
|
|
208
|
+
sourceBindings,
|
|
209
|
+
pre,
|
|
210
|
+
output,
|
|
211
|
+
fixedInputsDigest,
|
|
212
|
+
}) {
|
|
213
|
+
const facts = rejectionFacts(error);
|
|
214
|
+
const runnerReceipts = Array.isArray(evidence.verifications) ? evidence.verifications : [];
|
|
215
|
+
const patch = Buffer.isBuffer(evidence.patch) ? evidence.patch : null;
|
|
216
|
+
const outputAdmitted = output
|
|
217
|
+
&& Array.isArray(evidence.changedPaths)
|
|
218
|
+
&& sameArray(output.files.map(({ path: relativePath }) => relativePath), evidence.changedPaths);
|
|
219
|
+
const changedPaths = outputAdmitted ? [...evidence.changedPaths] : [];
|
|
220
|
+
const messages = flattenErrors(error).map((entry) => String(entry?.message ?? entry));
|
|
221
|
+
const verificationStatus = runnerReceipts.length === 0 ? 'not_run' : 'failed';
|
|
222
|
+
const rejection = { kind: facts.kind, reasons: [facts.reason] };
|
|
223
|
+
const artifact = {
|
|
224
|
+
schema: 'lattice.transform_artifact.v1',
|
|
225
|
+
candidate_id: CANDIDATE_ID,
|
|
226
|
+
status: 'rejected',
|
|
227
|
+
source: {
|
|
228
|
+
base_sha: evidence.baseSha,
|
|
229
|
+
...sourceBindings,
|
|
230
|
+
code_snapshot_digest: sourceSnapshotDigest(pre, evidence.baseSha),
|
|
231
|
+
},
|
|
232
|
+
scope: {
|
|
233
|
+
allowed_paths: [...RC1_V5_TRANSFORM_PATHS],
|
|
234
|
+
changed_paths: changedPaths,
|
|
235
|
+
},
|
|
236
|
+
patch: { digest: patch ? sha256(patch) : null, bytes: patch?.byteLength ?? 0 },
|
|
237
|
+
verification: verificationArtifact(verificationStatus, runnerReceipts),
|
|
238
|
+
output: outputAdmitted ? structuredClone(output) : { snapshot_digest: null, files: [] },
|
|
239
|
+
cleanup: {
|
|
240
|
+
status: messages.some((message) => /cleanup failed/i.test(message)) ? 'failed' : 'passed',
|
|
241
|
+
source_status: messages.some((message) => /source repository changed/i.test(message))
|
|
242
|
+
? 'changed'
|
|
243
|
+
: 'unchanged',
|
|
244
|
+
},
|
|
245
|
+
rejection: { ...rejection, evidence_digest: digestArtifact(rejection) },
|
|
246
|
+
unknowns: ['behavior evidence was not admitted'],
|
|
247
|
+
};
|
|
248
|
+
if (!validateTransformArtifact(artifact)) {
|
|
249
|
+
throw new TypeError('RC1 v5 rejected transform artifact is invalid');
|
|
250
|
+
}
|
|
251
|
+
return makeResult({
|
|
252
|
+
artifact,
|
|
253
|
+
patch,
|
|
254
|
+
behaviorEvidence: null,
|
|
255
|
+
fixedInputsDigest,
|
|
256
|
+
sourceInvariant: evidence.sourceInvariant,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** v4で固定済みの決定的production+test seam writerをv5 isolationへ適用する。 */
|
|
261
|
+
export async function applyRc1V5Transform(context = {}) {
|
|
262
|
+
return applyRc1V4Transform(context);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 一つのdisposable worktreeでpre観測、seam変換、post観測を順序固定し、accepted artifactだけをenvelopeへbindする。
|
|
267
|
+
*/
|
|
268
|
+
export async function runRc1V5SeamTransform(options = {}) {
|
|
269
|
+
if (!exactRecord(options, [
|
|
270
|
+
'repoRoot',
|
|
271
|
+
'baseRef',
|
|
272
|
+
'oracle',
|
|
273
|
+
'sourceBindings',
|
|
274
|
+
]) && !exactRecord(options, [
|
|
275
|
+
'repoRoot',
|
|
276
|
+
'baseRef',
|
|
277
|
+
'oracle',
|
|
278
|
+
'sourceBindings',
|
|
279
|
+
'transform',
|
|
280
|
+
])) {
|
|
281
|
+
throw new TypeError('RC1 v5 seam transform options must have the fixed exact shape');
|
|
282
|
+
}
|
|
283
|
+
const {
|
|
284
|
+
repoRoot,
|
|
285
|
+
baseRef,
|
|
286
|
+
oracle,
|
|
287
|
+
sourceBindings,
|
|
288
|
+
transform = applyRc1V5Transform,
|
|
289
|
+
} = options;
|
|
290
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0
|
|
291
|
+
|| typeof baseRef !== 'string' || baseRef.length === 0
|
|
292
|
+
|| typeof transform !== 'function'
|
|
293
|
+
|| !validateRc1BlackBoxOracle(oracle)) {
|
|
294
|
+
throw new TypeError('RC1 v5 seam transform arguments are invalid');
|
|
295
|
+
}
|
|
296
|
+
assertSourceBindings(sourceBindings);
|
|
297
|
+
|
|
298
|
+
const fixedOracle = structuredClone(oracle);
|
|
299
|
+
const fixedSourceBindings = structuredClone(sourceBindings);
|
|
300
|
+
const oracleDigest = digestArtifact(fixedOracle);
|
|
301
|
+
const fixedInputsDigest = digestArtifact({
|
|
302
|
+
candidate_id: CANDIDATE_ID,
|
|
303
|
+
allowed_paths: RC1_V5_TRANSFORM_PATHS,
|
|
304
|
+
verifiers: VERIFIERS,
|
|
305
|
+
oracle_digest: oracleDigest,
|
|
306
|
+
source_bindings: fixedSourceBindings,
|
|
307
|
+
});
|
|
308
|
+
const baseSha = await resolveBaseSha(repoRoot, baseRef);
|
|
309
|
+
let pre;
|
|
310
|
+
let post;
|
|
311
|
+
let output;
|
|
312
|
+
let isolated;
|
|
313
|
+
try {
|
|
314
|
+
isolated = await runIsolatedTransform({
|
|
315
|
+
repoRoot,
|
|
316
|
+
baseRef: baseSha,
|
|
317
|
+
allowedPaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
318
|
+
transform: async (context) => {
|
|
319
|
+
pre = await runRc1V5BlackBoxOracle({
|
|
320
|
+
repoRoot: context.worktreePath,
|
|
321
|
+
oracle: fixedOracle,
|
|
322
|
+
role: 'pre',
|
|
323
|
+
baseSha,
|
|
324
|
+
surfacePaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
325
|
+
});
|
|
326
|
+
if (pre.outcome !== 'passed') throw new Error('black-box oracle failed before transform');
|
|
327
|
+
await transform(context);
|
|
328
|
+
post = await runRc1V5BlackBoxOracle({
|
|
329
|
+
repoRoot: context.worktreePath,
|
|
330
|
+
oracle: fixedOracle,
|
|
331
|
+
role: 'post',
|
|
332
|
+
baseSha,
|
|
333
|
+
surfacePaths: [...RC1_V5_TRANSFORM_PATHS],
|
|
334
|
+
});
|
|
335
|
+
if (post.outcome !== 'passed') throw new Error('black-box oracle failed after transform');
|
|
336
|
+
},
|
|
337
|
+
verifyCommands: VERIFIERS.map(({ command, args }) => ({ command, args: [...args] })),
|
|
338
|
+
observe: async ({ worktreePath, changedPaths }) => {
|
|
339
|
+
output = await outputArtifact(worktreePath, changedPaths);
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const postProjection = post?.surface?.files?.map((file) => (
|
|
344
|
+
file.state === 'present'
|
|
345
|
+
? { path: file.path, content_digest: file.content_digest }
|
|
346
|
+
: null
|
|
347
|
+
));
|
|
348
|
+
if (!pre || !post || !output
|
|
349
|
+
|| pre.outcome !== 'passed' || post.outcome !== 'passed'
|
|
350
|
+
|| pre.oracle_digest !== oracleDigest || post.oracle_digest !== oracleDigest
|
|
351
|
+
|| pre.base_sha !== baseSha || post.base_sha !== baseSha
|
|
352
|
+
|| !sameArray(
|
|
353
|
+
pre.surface.files.map(({ path: relativePath }) => relativePath),
|
|
354
|
+
RC1_V5_TRANSFORM_PATHS,
|
|
355
|
+
)
|
|
356
|
+
|| !Array.isArray(postProjection) || postProjection.some((file) => file === null)
|
|
357
|
+
|| !sameArray(isolated.changedPaths, RC1_V5_TRANSFORM_PATHS)
|
|
358
|
+
|| !Buffer.isBuffer(isolated.patch)
|
|
359
|
+
|| isolated.verifications.length !== VERIFIERS.length
|
|
360
|
+
|| isolated.sourceInvariant?.outcome !== 'passed'
|
|
361
|
+
|| digestArtifact({ files: postProjection }) !== output.snapshot_digest
|
|
362
|
+
|| digestArtifact(postProjection) !== digestArtifact(output.files)) {
|
|
363
|
+
throw new Error('production-test seam incomplete');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const artifact = {
|
|
367
|
+
schema: 'lattice.transform_artifact.v1',
|
|
368
|
+
candidate_id: CANDIDATE_ID,
|
|
369
|
+
status: 'accepted',
|
|
370
|
+
source: {
|
|
371
|
+
base_sha: isolated.baseSha,
|
|
372
|
+
...fixedSourceBindings,
|
|
373
|
+
code_snapshot_digest: pre.surface_digest,
|
|
374
|
+
},
|
|
375
|
+
scope: {
|
|
376
|
+
allowed_paths: [...RC1_V5_TRANSFORM_PATHS],
|
|
377
|
+
changed_paths: [...isolated.changedPaths],
|
|
378
|
+
},
|
|
379
|
+
patch: { digest: sha256(isolated.patch), bytes: isolated.patch.byteLength },
|
|
380
|
+
verification: verificationArtifact('passed', isolated.verifications),
|
|
381
|
+
output,
|
|
382
|
+
cleanup: { status: 'passed', source_status: 'unchanged' },
|
|
383
|
+
rejection: null,
|
|
384
|
+
unknowns: [],
|
|
385
|
+
};
|
|
386
|
+
if (!validateTransformArtifact(artifact)) {
|
|
387
|
+
throw new TypeError('RC1 v5 accepted transform artifact is invalid');
|
|
388
|
+
}
|
|
389
|
+
const envelope = compileRc1V5BehaviorEvidence({
|
|
390
|
+
preReceipt: pre,
|
|
391
|
+
postReceipt: post,
|
|
392
|
+
transformArtifact: artifact,
|
|
393
|
+
patchDigest: artifact.patch.digest,
|
|
394
|
+
});
|
|
395
|
+
const behaviorEvidence = {
|
|
396
|
+
pre_receipt: pre,
|
|
397
|
+
post_receipt: post,
|
|
398
|
+
envelope,
|
|
399
|
+
transform_artifact: artifact,
|
|
400
|
+
patch_digest: artifact.patch.digest,
|
|
401
|
+
};
|
|
402
|
+
return makeResult({
|
|
403
|
+
artifact,
|
|
404
|
+
patch: isolated.patch,
|
|
405
|
+
behaviorEvidence,
|
|
406
|
+
fixedInputsDigest,
|
|
407
|
+
sourceInvariant: isolated.sourceInvariant,
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
const evidence = transformEvidence(error) ?? isolated;
|
|
411
|
+
if (!evidence || typeof evidence.baseSha !== 'string') throw error;
|
|
412
|
+
return rejectedResult({
|
|
413
|
+
error,
|
|
414
|
+
evidence,
|
|
415
|
+
sourceBindings: fixedSourceBindings,
|
|
416
|
+
pre,
|
|
417
|
+
output,
|
|
418
|
+
fixedInputsDigest,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|