@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/rc2-campaign.mjs
CHANGED
|
@@ -1,1506 +1,1506 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
|
-
import {
|
|
4
|
-
lstat,
|
|
5
|
-
mkdir,
|
|
6
|
-
mkdtemp,
|
|
7
|
-
open,
|
|
8
|
-
readFile,
|
|
9
|
-
realpath,
|
|
10
|
-
rename,
|
|
11
|
-
rm,
|
|
12
|
-
writeFile,
|
|
13
|
-
} from 'node:fs/promises';
|
|
14
|
-
import path from 'node:path';
|
|
15
|
-
import { performance } from 'node:perf_hooks';
|
|
16
|
-
import { isDeepStrictEqual } from 'node:util';
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
canonicalizeArtifact,
|
|
20
|
-
digestArtifact,
|
|
21
|
-
} from './artifact-contracts.mjs';
|
|
22
|
-
import { compileBoundaryCondition } from './boundary-compiler.mjs';
|
|
23
|
-
import { collectSensorEvidence } from './sensor-adapter.mjs';
|
|
24
|
-
import { invokeSensorCli, LATTICE_SENSOR_CLI } from './sensor-runtime.mjs';
|
|
25
|
-
import { runIsolatedTransform } from './isolation-runner.mjs';
|
|
26
|
-
import { runRc1BlackBoxOracle } from './rc1-black-box-oracle.mjs';
|
|
27
|
-
import { createRc1EvidenceBundle } from './rc1-evidence-bundle.mjs';
|
|
28
|
-
import {
|
|
29
|
-
RC2_ARTIFACT_PATHS,
|
|
30
|
-
verifyRc2CampaignArtifactSet,
|
|
31
|
-
} from './rc2-artifact-set.mjs';
|
|
32
|
-
import { compileDeliveryPolicyBoundaryBundleV2 } from './rc2-delivery-policy-front-end.mjs';
|
|
33
|
-
import { runRc2DeliveryPolicyOracle } from './rc2-delivery-policy-oracle.mjs';
|
|
34
|
-
import {
|
|
35
|
-
applyRc2DeliveryPolicyTransform,
|
|
36
|
-
RC2_DELIVERY_POLICY_TRANSFORM_PATHS,
|
|
37
|
-
runRc2DeliveryPolicySeamTransform,
|
|
38
|
-
} from './rc2-delivery-policy-transform.mjs';
|
|
39
|
-
import { compileRc1TransferBundleV2 } from './rc2-rc1-transfer-front-end.mjs';
|
|
40
|
-
import { compileSchedulabilityGraphV2 } from './schedulability-compiler-v2.mjs';
|
|
41
|
-
import { verifySchedulabilityPlanV2 } from './schedulability-verifier-v2.mjs';
|
|
42
|
-
|
|
43
|
-
const ARTIFACT_VERSION = 'v4';
|
|
44
|
-
const ARTIFACT_ROOTS = Object.freeze({
|
|
45
|
-
v1: 'research/campaigns/rc2/artifacts/v1',
|
|
46
|
-
v2: 'research/campaigns/rc2/artifacts/v2',
|
|
47
|
-
v3: 'research/campaigns/rc2/artifacts/v3',
|
|
48
|
-
v4: 'research/campaigns/rc2/artifacts/v4',
|
|
49
|
-
});
|
|
50
|
-
const SENSOR_CONFIG_REPO_PATH = 'lattice-sensor.json';
|
|
51
|
-
const SENSOR_CONFIG_ARTIFACT_PATH = 'identity/lattice-sensor-config.json';
|
|
52
|
-
// live実行は現在ratifiedなconfig(ADR 0053 Decision 3 epoch)との完全一致だけを受理する。
|
|
53
|
-
// 過去epochのartifact検証はrc2-artifact-set.mjsのratified epoch allowlistが担う。
|
|
54
|
-
const SENSOR_CONFIG_BYTES = Buffer.from(
|
|
55
|
-
'{"exclude":["research/campaigns/**/artifacts/**/identity/","research/runs/",".lattice/todo/",".lattice/generated/"]}\n',
|
|
56
|
-
'utf8',
|
|
57
|
-
);
|
|
58
|
-
const V2_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
59
|
-
SENSOR_CONFIG_ARTIFACT_PATH,
|
|
60
|
-
'predecessors/adr-0040.md',
|
|
61
|
-
'predecessors/rc2-v1-artifact-manifest.json',
|
|
62
|
-
'predecessors/rc2-v1-new-plan-version.json',
|
|
63
|
-
]);
|
|
64
|
-
const V3_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
65
|
-
'predecessors/adr-0041.md',
|
|
66
|
-
'predecessors/rc2-semantic-reseal-characterization.md',
|
|
67
|
-
'predecessors/rc2-v2-artifact-manifest.json',
|
|
68
|
-
'predecessors/rc2-v2-new-plan-version.json',
|
|
69
|
-
]);
|
|
70
|
-
const V4_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
71
|
-
'predecessors/adr-0042.md',
|
|
72
|
-
'predecessors/rc2-v3-version-downgrade-refutation.md',
|
|
73
|
-
'predecessors/rc2-v3-artifact-manifest.json',
|
|
74
|
-
'predecessors/rc2-v3-new-plan-version.json',
|
|
75
|
-
]);
|
|
76
|
-
const RC2_V3_ARTIFACT_PATHS = Object.freeze(RC2_ARTIFACT_PATHS.filter((relativePath) => (
|
|
77
|
-
!V4_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
78
|
-
)));
|
|
79
|
-
const RC2_V2_ARTIFACT_PATHS = Object.freeze(RC2_V3_ARTIFACT_PATHS.filter((relativePath) => (
|
|
80
|
-
!V3_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
81
|
-
)));
|
|
82
|
-
const RC2_V1_ARTIFACT_PATHS = Object.freeze(RC2_V2_ARTIFACT_PATHS.filter((relativePath) => (
|
|
83
|
-
!V2_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
84
|
-
)));
|
|
85
|
-
const GIT_SHA1 = /^[0-9a-f]{40}$/;
|
|
86
|
-
const SEMVER = /^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/;
|
|
87
|
-
const RAW_EVIDENCE_CHUNK_BYTES = 12_000;
|
|
88
|
-
|
|
89
|
-
const PRIMARY_INPUT_FILES = Object.freeze({
|
|
90
|
-
planInput: 'research/campaigns/rc2/inputs/plan-input.json',
|
|
91
|
-
candidateSpec: 'research/campaigns/rc2/inputs/candidate-spec-v1.json',
|
|
92
|
-
normalManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.normal.json',
|
|
93
|
-
partialManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.partial-state-negative.json',
|
|
94
|
-
unknownManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.third-only-unknown.json',
|
|
95
|
-
querySet: 'research/campaigns/rc2/inputs/query-set-v2.json',
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const RC1_ARTIFACT_ROOT = 'research/campaigns/rc1/artifacts/v6';
|
|
99
|
-
const RC1_INPUT_FILES = Object.freeze({
|
|
100
|
-
planInput: `${RC1_ARTIFACT_ROOT}/inputs/plan-input.json`,
|
|
101
|
-
candidateSpec: `${RC1_ARTIFACT_ROOT}/inputs/candidate-spec-v2.json`,
|
|
102
|
-
normalManualEvidence: `${RC1_ARTIFACT_ROOT}/inputs/manual-evidence.normal.json`,
|
|
103
|
-
negativeManualEvidence: `${RC1_ARTIFACT_ROOT}/inputs/manual-evidence.shared-state-negative.json`,
|
|
104
|
-
querySet: `${RC1_ARTIFACT_ROOT}/inputs/query-set-v2.json`,
|
|
105
|
-
oracle: `${RC1_ARTIFACT_ROOT}/inputs/behavior-oracle-v2.json`,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
const PRIMARY_ARTIFACT_INPUTS = Object.freeze({
|
|
109
|
-
planInput: 'inputs/primary/plan-input.json',
|
|
110
|
-
candidateSpec: 'inputs/primary/candidate-spec-v1.json',
|
|
111
|
-
normalManualEvidence: 'inputs/primary/manual-evidence.normal.json',
|
|
112
|
-
partialManualEvidence: 'inputs/primary/manual-evidence.partial-state-negative.json',
|
|
113
|
-
unknownManualEvidence: 'inputs/primary/manual-evidence.third-only-unknown.json',
|
|
114
|
-
querySet: 'inputs/primary/query-set-v2.json',
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
const RC1_ARTIFACT_INPUTS = Object.freeze({
|
|
118
|
-
planInput: 'inputs/rc1/plan-input.json',
|
|
119
|
-
candidateSpec: 'inputs/rc1/candidate-spec-v2.json',
|
|
120
|
-
normalManualEvidence: 'inputs/rc1/manual-evidence.normal.json',
|
|
121
|
-
negativeManualEvidence: 'inputs/rc1/manual-evidence.shared-state-negative.json',
|
|
122
|
-
querySet: 'inputs/rc1/query-set-v2.json',
|
|
123
|
-
oracle: 'inputs/rc1/behavior-oracle-v2.json',
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
const SOURCE_IDENTITIES = Object.freeze([
|
|
127
|
-
['src/sensor-adapter.mjs', 'identity/lattice-sensor-adapter.mjs', new URL('./sensor-adapter.mjs', import.meta.url)],
|
|
128
|
-
['src/rc1-black-box-oracle.mjs', 'identity/rc1-black-box-oracle.mjs', new URL('./rc1-black-box-oracle.mjs', import.meta.url)],
|
|
129
|
-
['src/boundary-compiler.mjs', 'identity/rc1-boundary-compiler.mjs', new URL('./boundary-compiler.mjs', import.meta.url)],
|
|
130
|
-
['src/rc1-evidence-bundle.mjs', 'identity/rc1-evidence-bundle.mjs', new URL('./rc1-evidence-bundle.mjs', import.meta.url)],
|
|
131
|
-
['src/rc2-artifact-set.mjs', 'identity/rc2-artifact-set.mjs', new URL('./rc2-artifact-set.mjs', import.meta.url)],
|
|
132
|
-
['src/rc2-campaign.mjs', 'identity/rc2-campaign.mjs', new URL(import.meta.url)],
|
|
133
|
-
['src/rc2-delivery-policy-front-end.mjs', 'identity/rc2-delivery-policy-front-end.mjs', new URL('./rc2-delivery-policy-front-end.mjs', import.meta.url)],
|
|
134
|
-
['src/rc2-delivery-policy-oracle.mjs', 'identity/rc2-delivery-policy-oracle.mjs', new URL('./rc2-delivery-policy-oracle.mjs', import.meta.url)],
|
|
135
|
-
['src/rc2-delivery-policy-transform.mjs', 'identity/rc2-delivery-policy-transform.mjs', new URL('./rc2-delivery-policy-transform.mjs', import.meta.url)],
|
|
136
|
-
['src/rc2-rc1-transfer-front-end.mjs', 'identity/rc2-rc1-transfer-front-end.mjs', new URL('./rc2-rc1-transfer-front-end.mjs', import.meta.url)],
|
|
137
|
-
['src/schedulability-compiler-v2.mjs', 'identity/schedulability-compiler-v2.mjs', new URL('./schedulability-compiler-v2.mjs', import.meta.url)],
|
|
138
|
-
['src/schedulability-verifier-v2.mjs', 'identity/schedulability-verifier-v2.mjs', new URL('./schedulability-verifier-v2.mjs', import.meta.url)],
|
|
139
|
-
]);
|
|
140
|
-
|
|
141
|
-
const PREDECESSOR_FILES = Object.freeze({
|
|
142
|
-
'predecessors/adr-0031.md': 'docs/adr/0031-rc1-v6-phase-gate-support.md',
|
|
143
|
-
'predecessors/adr-0032.md': 'docs/adr/0032-rc2-bounded-graph-compiler-and-three-way-seam.md',
|
|
144
|
-
'predecessors/adr-0037.md': 'docs/adr/0037-rc2-delivery-policy-transform-transaction.md',
|
|
145
|
-
'predecessors/adr-0038.md': 'docs/adr/0038-rc2-closed-loop-version-and-artifact-contract.md',
|
|
146
|
-
'predecessors/rc1-v6-artifact-manifest.json': `${RC1_ARTIFACT_ROOT}/artifact-manifest.json`,
|
|
147
|
-
'predecessors/rc1-v6-plan.md': 'docs/archive/2026-07-16-plan-lattice-research-campaign-1-v6-phase-supported.md',
|
|
148
|
-
'predecessors/rc1-v6-seam.patch': `${RC1_ARTIFACT_ROOT}/transform/seam.patch`,
|
|
149
|
-
'predecessors/rc1-v6-transform-artifact.json': `${RC1_ARTIFACT_ROOT}/transform/transform-artifact.json`,
|
|
150
|
-
'predecessors/rc1-v6-transform-receipt.json': `${RC1_ARTIFACT_ROOT}/transform/transform-receipt.json`,
|
|
151
|
-
'predecessors/adr-0040.md': 'docs/adr/0040-rc2-post-publication-lattice-sensor-scope-and-artifact-v2.md',
|
|
152
|
-
'predecessors/rc2-v1-artifact-manifest.json': `${ARTIFACT_ROOTS.v1}/artifact-manifest.json`,
|
|
153
|
-
'predecessors/rc2-v1-new-plan-version.json': `${ARTIFACT_ROOTS.v1}/new-plan-version.json`,
|
|
154
|
-
'predecessors/adr-0041.md': 'docs/adr/0041-rc2-artifact-semantic-oracle-mutation-binding.md',
|
|
155
|
-
'predecessors/rc2-semantic-reseal-characterization.md': 'docs/evidence/2026-07-16-rc2-artifact-semantic-reseal-characterization.md',
|
|
156
|
-
'predecessors/rc2-v2-artifact-manifest.json': `${ARTIFACT_ROOTS.v2}/artifact-manifest.json`,
|
|
157
|
-
'predecessors/rc2-v2-new-plan-version.json': `${ARTIFACT_ROOTS.v2}/new-plan-version.json`,
|
|
158
|
-
'predecessors/adr-0042.md': 'docs/adr/0042-rc2-artifact-version-witness-epoch-and-v4.md',
|
|
159
|
-
'predecessors/rc2-v3-version-downgrade-refutation.md': 'docs/evidence/2026-07-16-rc2-v3-version-downgrade-refutation.md',
|
|
160
|
-
'predecessors/rc2-v3-artifact-manifest.json': `${ARTIFACT_ROOTS.v3}/artifact-manifest.json`,
|
|
161
|
-
'predecessors/rc2-v3-new-plan-version.json': `${ARTIFACT_ROOTS.v3}/new-plan-version.json`,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const RUN_IDS = Object.freeze([
|
|
165
|
-
'primary-control-1',
|
|
166
|
-
'primary-control-2',
|
|
167
|
-
'primary-treatment-1',
|
|
168
|
-
'primary-treatment-2',
|
|
169
|
-
'rc1-transfer-control-1',
|
|
170
|
-
'rc1-transfer-treatment-1',
|
|
171
|
-
]);
|
|
172
|
-
|
|
173
|
-
function plainRecord(value) {
|
|
174
|
-
return value !== null
|
|
175
|
-
&& typeof value === 'object'
|
|
176
|
-
&& !Array.isArray(value)
|
|
177
|
-
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function exactRecord(value, keys) {
|
|
181
|
-
if (!plainRecord(value)) return false;
|
|
182
|
-
const actual = Object.keys(value).sort();
|
|
183
|
-
const expected = [...keys].sort();
|
|
184
|
-
return actual.length === expected.length
|
|
185
|
-
&& actual.every((key, index) => key === expected[index]);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function sha256(bytes) {
|
|
189
|
-
return createHash('sha256').update(bytes).digest('hex');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function jsonBytes(value) {
|
|
193
|
-
return Buffer.from(canonicalizeArtifact(value), 'utf8');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function roundedMilliseconds(startedAt) {
|
|
197
|
-
return Math.round((performance.now() - startedAt) * 1_000) / 1_000;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function run(command, args, { cwd, input, allowExitCodes = [0] } = {}) {
|
|
201
|
-
return new Promise((resolve, reject) => {
|
|
202
|
-
const child = spawn(command, args, {
|
|
203
|
-
cwd,
|
|
204
|
-
shell: false,
|
|
205
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
206
|
-
env: { ...process.env, NO_COLOR: '1' },
|
|
207
|
-
});
|
|
208
|
-
const stdout = [];
|
|
209
|
-
const stderr = [];
|
|
210
|
-
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
211
|
-
child.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
212
|
-
child.once('error', reject);
|
|
213
|
-
child.once('close', (code, signal) => {
|
|
214
|
-
const result = {
|
|
215
|
-
code,
|
|
216
|
-
signal,
|
|
217
|
-
stdout: Buffer.concat(stdout),
|
|
218
|
-
stderr: Buffer.concat(stderr),
|
|
219
|
-
};
|
|
220
|
-
if (allowExitCodes.includes(code) && signal === null) resolve(result);
|
|
221
|
-
else reject(Object.assign(
|
|
222
|
-
new Error(`${command} failed (${signal ?? code}): ${result.stderr.toString('utf8').trim()}`),
|
|
223
|
-
result,
|
|
224
|
-
));
|
|
225
|
-
});
|
|
226
|
-
child.stdin.end(input);
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async function captureExecutionIdentity(repoRoot) {
|
|
231
|
-
const sources = [];
|
|
232
|
-
const payloads = new Map();
|
|
233
|
-
for (const [runtimePath, artifactRef, sourceUrl] of SOURCE_IDENTITIES) {
|
|
234
|
-
const bytes = await readFile(sourceUrl);
|
|
235
|
-
payloads.set(artifactRef, bytes);
|
|
236
|
-
sources.push({ runtime_path: runtimePath, artifact_ref: artifactRef, digest: sha256(bytes) });
|
|
237
|
-
}
|
|
238
|
-
const executableBytes = await readFile(LATTICE_SENSOR_CLI);
|
|
239
|
-
const version = (await run(process.execPath, [LATTICE_SENSOR_CLI, '--version'])).stdout.toString('utf8').trim();
|
|
240
|
-
if (!SEMVER.test(version)) throw new TypeError('LatticeSensor versionがsemverではない');
|
|
241
|
-
const projectConfigBytes = await readFile(path.join(repoRoot, SENSOR_CONFIG_REPO_PATH));
|
|
242
|
-
if (!projectConfigBytes.equals(SENSOR_CONFIG_BYTES)) {
|
|
243
|
-
throw new TypeError('LatticeSensor project config bytesがRC2 v2 contractと一致しない');
|
|
244
|
-
}
|
|
245
|
-
const sensorIdentity = {
|
|
246
|
-
schema: 'lattice.rc2.sensor_identity.v2',
|
|
247
|
-
version,
|
|
248
|
-
executable_ref: 'lattice-sensor',
|
|
249
|
-
executable_digest: sha256(executableBytes),
|
|
250
|
-
project_config_ref: SENSOR_CONFIG_ARTIFACT_PATH,
|
|
251
|
-
project_config_digest: sha256(projectConfigBytes),
|
|
252
|
-
};
|
|
253
|
-
payloads.set('identity/lattice-sensor-executable', executableBytes);
|
|
254
|
-
payloads.set(SENSOR_CONFIG_ARTIFACT_PATH, projectConfigBytes);
|
|
255
|
-
const snapshot = { sources, sensor_identity: sensorIdentity };
|
|
256
|
-
return { snapshot, digest: digestArtifact(snapshot), payloads };
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
async function readJson(repoRoot, relativePath) {
|
|
260
|
-
return JSON.parse(await readFile(path.join(repoRoot, relativePath), 'utf8'));
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
async function loadInputs(repoRoot) {
|
|
264
|
-
const primary = {};
|
|
265
|
-
const rc1 = {};
|
|
266
|
-
for (const [key, relativePath] of Object.entries(PRIMARY_INPUT_FILES)) {
|
|
267
|
-
primary[key] = await readJson(repoRoot, relativePath);
|
|
268
|
-
}
|
|
269
|
-
for (const [key, relativePath] of Object.entries(RC1_INPUT_FILES)) {
|
|
270
|
-
rc1[key] = await readJson(repoRoot, relativePath);
|
|
271
|
-
}
|
|
272
|
-
return { primary, rc1 };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
async function loadPredecessors(repoRoot) {
|
|
276
|
-
const payloads = new Map();
|
|
277
|
-
for (const [artifactPath, repoPath] of Object.entries(PREDECESSOR_FILES)) {
|
|
278
|
-
payloads.set(artifactPath, await readFile(path.join(repoRoot, repoPath)));
|
|
279
|
-
}
|
|
280
|
-
return payloads;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function loadV3PlanPredecessor(payloads) {
|
|
284
|
-
const manifestBytes = payloads.get('predecessors/rc2-v3-artifact-manifest.json');
|
|
285
|
-
const planBytes = payloads.get('predecessors/rc2-v3-new-plan-version.json');
|
|
286
|
-
let manifest;
|
|
287
|
-
let plan;
|
|
288
|
-
try {
|
|
289
|
-
manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
290
|
-
plan = JSON.parse(planBytes.toString('utf8'));
|
|
291
|
-
} catch {
|
|
292
|
-
throw new TypeError('RC2 v3 predecessor JSONをparseできない');
|
|
293
|
-
}
|
|
294
|
-
const paths = Array.isArray(manifest?.files)
|
|
295
|
-
? manifest.files.map(({ path: relativePath }) => relativePath).sort()
|
|
296
|
-
: [];
|
|
297
|
-
const entry = manifest?.files?.find(({ path: relativePath }) => (
|
|
298
|
-
relativePath === 'new-plan-version.json'
|
|
299
|
-
));
|
|
300
|
-
if (!jsonBytes(manifest).equals(manifestBytes)
|
|
301
|
-
|| !jsonBytes(plan).equals(planBytes)
|
|
302
|
-
|| manifest.schema !== 'lattice.rc2.artifact_manifest.v3'
|
|
303
|
-
|| !isDeepStrictEqual(paths, [...RC2_V3_ARTIFACT_PATHS])
|
|
304
|
-
|| !exactRecord(entry, ['path', 'media_type', 'bytes', 'sha256'])
|
|
305
|
-
|| entry.media_type !== 'application/json'
|
|
306
|
-
|| entry.bytes !== planBytes.byteLength
|
|
307
|
-
|| entry.sha256 !== sha256(planBytes)
|
|
308
|
-
|| plan.schema !== 'lattice.rc2.plan_version.v1'
|
|
309
|
-
|| plan.version !== 'rc2-delivery-policy-v4'
|
|
310
|
-
|| plan.plan_digest !== digestArtifact(plan.plan)) {
|
|
311
|
-
throw new TypeError('RC2 v3 predecessor manifest/plan bindingが不正');
|
|
312
|
-
}
|
|
313
|
-
return plan;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
async function resolveRepository(repoRoot, baseRef) {
|
|
317
|
-
if (typeof repoRoot !== 'string' || repoRoot.length === 0
|
|
318
|
-
|| typeof baseRef !== 'string' || baseRef.length === 0) {
|
|
319
|
-
throw new TypeError('repoRoot and baseRef are required');
|
|
320
|
-
}
|
|
321
|
-
const root = await realpath(repoRoot);
|
|
322
|
-
const status = await run('git', ['status', '--porcelain=v1', '-z'], { cwd: root });
|
|
323
|
-
if (status.stdout.length > 0) throw new TypeError('RC2 campaign source repository must be clean');
|
|
324
|
-
const head = (await run('git', ['rev-parse', '--verify', 'HEAD^{commit}'], { cwd: root }))
|
|
325
|
-
.stdout.toString('utf8').trim();
|
|
326
|
-
const baseSha = (await run('git', ['rev-parse', '--verify', `${baseRef}^{commit}`], { cwd: root }))
|
|
327
|
-
.stdout.toString('utf8').trim();
|
|
328
|
-
if (!GIT_SHA1.test(head) || !GIT_SHA1.test(baseSha) || head !== baseSha) {
|
|
329
|
-
throw new TypeError('RC2 campaign requires clean HEAD === baseRef');
|
|
330
|
-
}
|
|
331
|
-
return { root, baseSha };
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function surfacePaths(candidateSpec) {
|
|
335
|
-
const result = new Set();
|
|
336
|
-
if (candidateSpec.fixed_oracle?.path) result.add(candidateSpec.fixed_oracle.path);
|
|
337
|
-
for (const todo of candidateSpec.todos) {
|
|
338
|
-
for (const mode of ['current', 'proposed']) {
|
|
339
|
-
result.add(todo[mode].production.path);
|
|
340
|
-
todo[mode].tests.forEach(({ path: testPath }) => result.add(testPath));
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
return [...result].sort();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
async function captureSourceSnapshot(repoRoot, paths) {
|
|
347
|
-
const files = [];
|
|
348
|
-
for (const relativePath of paths) {
|
|
349
|
-
const absolutePath = path.join(repoRoot, relativePath);
|
|
350
|
-
try {
|
|
351
|
-
const stat = await lstat(absolutePath);
|
|
352
|
-
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
353
|
-
throw new TypeError(`snapshot pathが通常fileではない: ${relativePath}`);
|
|
354
|
-
}
|
|
355
|
-
files.push({ path: relativePath, state: 'file', content_digest: sha256(await readFile(absolutePath)) });
|
|
356
|
-
} catch (error) {
|
|
357
|
-
if (error?.code !== 'ENOENT') throw error;
|
|
358
|
-
files.push({ path: relativePath, state: 'absent', content_digest: null });
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
return { schema_version: 'lattice.rc2.source_snapshot.v1', files };
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async function applyPatch(worktreePath, patch) {
|
|
365
|
-
if (!Buffer.isBuffer(patch) || patch.length === 0) throw new TypeError('accepted patch bytes are required');
|
|
366
|
-
await run('git', ['apply', '--check', '--binary', '-'], { cwd: worktreePath, input: patch });
|
|
367
|
-
await run('git', ['apply', '--binary', '-'], { cwd: worktreePath, input: patch });
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
async function captureLatticeSensorBootstrap(worktreePath) {
|
|
371
|
-
try {
|
|
372
|
-
return await readFile(path.join(worktreePath, '.lattice/sensor', '.gitignore'));
|
|
373
|
-
} catch (error) {
|
|
374
|
-
if (error?.code === 'ENOENT') return null;
|
|
375
|
-
throw error;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function restoreLatticeSensorBootstrap(worktreePath, bootstrap) {
|
|
380
|
-
const sensorPath = path.join(worktreePath, '.lattice/sensor');
|
|
381
|
-
await rm(sensorPath, { recursive: true, force: true });
|
|
382
|
-
if (bootstrap === null) return;
|
|
383
|
-
await mkdir(sensorPath, { recursive: true });
|
|
384
|
-
await writeFile(path.join(sensorPath, '.gitignore'), bootstrap);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
async function worktreeCount(repoRoot) {
|
|
388
|
-
const output = (await run('git', ['worktree', 'list', '--porcelain'], { cwd: repoRoot }))
|
|
389
|
-
.stdout.toString('utf8');
|
|
390
|
-
return output.split('\n').filter((line) => line.startsWith('worktree ')).length;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function measured(elapsedMs) {
|
|
394
|
-
return { state: 'measured', elapsed_ms: elapsedMs };
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
async function observeFreshIndex({
|
|
398
|
-
repoRoot,
|
|
399
|
-
baseSha,
|
|
400
|
-
family,
|
|
401
|
-
condition,
|
|
402
|
-
runId,
|
|
403
|
-
querySet,
|
|
404
|
-
paths,
|
|
405
|
-
patch,
|
|
406
|
-
allowedPaths,
|
|
407
|
-
sensorIdentity,
|
|
408
|
-
oracle,
|
|
409
|
-
rc1Inputs,
|
|
410
|
-
costMeasurements,
|
|
411
|
-
}) {
|
|
412
|
-
const beforeCount = await worktreeCount(repoRoot);
|
|
413
|
-
let rawEvidence;
|
|
414
|
-
let snapshot;
|
|
415
|
-
let oracleReceipt;
|
|
416
|
-
let isolationInstanceDigest;
|
|
417
|
-
let indexElapsedMs;
|
|
418
|
-
let queryElapsedMs;
|
|
419
|
-
let oracleElapsedMs;
|
|
420
|
-
const isolated = await runIsolatedTransform({
|
|
421
|
-
repoRoot,
|
|
422
|
-
baseRef: baseSha,
|
|
423
|
-
allowedPaths,
|
|
424
|
-
transform: async ({ worktreePath }) => {
|
|
425
|
-
if (condition === 'treatment') await applyPatch(worktreePath, patch);
|
|
426
|
-
},
|
|
427
|
-
verifyCommands: [],
|
|
428
|
-
observe: async ({ worktreePath }) => {
|
|
429
|
-
isolationInstanceDigest = sha256(Buffer.from(worktreePath, 'utf8'));
|
|
430
|
-
snapshot = await captureSourceSnapshot(worktreePath, paths);
|
|
431
|
-
const oracleStarted = performance.now();
|
|
432
|
-
oracleReceipt = family === 'primary'
|
|
433
|
-
? await runRc2DeliveryPolicyOracle({ repoRoot: worktreePath })
|
|
434
|
-
: await runRc1BlackBoxOracle({ repoRoot: worktreePath, oracle });
|
|
435
|
-
oracleElapsedMs = roundedMilliseconds(oracleStarted);
|
|
436
|
-
if (oracleReceipt.outcome !== 'passed') throw new TypeError(`${runId} oracle failed`);
|
|
437
|
-
const bootstrap = await captureLatticeSensorBootstrap(worktreePath);
|
|
438
|
-
try {
|
|
439
|
-
const indexStarted = performance.now();
|
|
440
|
-
await invokeSensorCli(run, ['init', '.'], { cwd: worktreePath });
|
|
441
|
-
indexElapsedMs = roundedMilliseconds(indexStarted);
|
|
442
|
-
const queryStarted = performance.now();
|
|
443
|
-
rawEvidence = await collectSensorEvidence({ cwd: worktreePath, querySet });
|
|
444
|
-
queryElapsedMs = roundedMilliseconds(queryStarted);
|
|
445
|
-
} finally {
|
|
446
|
-
await restoreLatticeSensorBootstrap(worktreePath, bootstrap);
|
|
447
|
-
}
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
const afterCount = await worktreeCount(repoRoot);
|
|
451
|
-
if (!rawEvidence || !snapshot || !oracleReceipt || !isolationInstanceDigest
|
|
452
|
-
|| isolated.sourceInvariant?.outcome !== 'passed'
|
|
453
|
-
|| beforeCount !== afterCount) {
|
|
454
|
-
throw new TypeError(`${runId} fresh observation is incomplete`);
|
|
455
|
-
}
|
|
456
|
-
if (condition === 'treatment' && !isolated.patch.equals(patch)) {
|
|
457
|
-
throw new TypeError(`${runId} did not replay the accepted patch exactly`);
|
|
458
|
-
}
|
|
459
|
-
if (condition === 'control' && isolated.patch.length !== 0) {
|
|
460
|
-
throw new TypeError(`${runId} control unexpectedly changed source`);
|
|
461
|
-
}
|
|
462
|
-
const evidence = createRc1EvidenceBundle({ condition, runId, querySet, rawEvidence });
|
|
463
|
-
const patchDigest = condition === 'treatment' ? sha256(isolated.patch) : null;
|
|
464
|
-
const measurement = {
|
|
465
|
-
schema: 'lattice.rc2.sensor_measurement.v1',
|
|
466
|
-
base_sha: baseSha,
|
|
467
|
-
patch_digest: patchDigest,
|
|
468
|
-
snapshot,
|
|
469
|
-
snapshot_digest: digestArtifact(snapshot),
|
|
470
|
-
sensor_identity: structuredClone(sensorIdentity),
|
|
471
|
-
sensor_identity_digest: digestArtifact(sensorIdentity),
|
|
472
|
-
query_set_digest: digestArtifact(querySet),
|
|
473
|
-
raw_evidence_digest: evidence.raw.payload_digest,
|
|
474
|
-
};
|
|
475
|
-
const cost = {
|
|
476
|
-
index: measured(indexElapsedMs),
|
|
477
|
-
query: measured(queryElapsedMs),
|
|
478
|
-
oracle: measured(oracleElapsedMs),
|
|
479
|
-
};
|
|
480
|
-
for (const [stage, value] of Object.entries(cost)) {
|
|
481
|
-
costMeasurements.push({
|
|
482
|
-
stage: `run.${runId}.${stage}`,
|
|
483
|
-
state: value.state,
|
|
484
|
-
elapsed_ms: value.elapsed_ms,
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
const runRecord = {
|
|
488
|
-
schema: 'lattice.rc2.fresh_sensor_run.v1',
|
|
489
|
-
family,
|
|
490
|
-
condition,
|
|
491
|
-
run_id: runId,
|
|
492
|
-
fresh_index: true,
|
|
493
|
-
isolation_instance_digest: isolationInstanceDigest,
|
|
494
|
-
evidence,
|
|
495
|
-
measurement,
|
|
496
|
-
oracle_receipt: oracleReceipt,
|
|
497
|
-
legacy_boundary_manifests: null,
|
|
498
|
-
source_invariant: isolated.sourceInvariant,
|
|
499
|
-
cleanup: {
|
|
500
|
-
schema: 'lattice.rc2.worktree_cleanup_receipt.v1',
|
|
501
|
-
outcome: 'passed',
|
|
502
|
-
worktree_count_before: beforeCount,
|
|
503
|
-
worktree_count_after: afterCount,
|
|
504
|
-
},
|
|
505
|
-
cost,
|
|
506
|
-
};
|
|
507
|
-
if (family === 'rc1_transfer') {
|
|
508
|
-
const raw = JSON.parse(Buffer.from(evidence.raw.payload_base64, 'base64').toString('utf8'));
|
|
509
|
-
const compileLegacy = (manualEvidence, label) => compileBoundaryCondition({
|
|
510
|
-
planInput: rc1Inputs.planInput,
|
|
511
|
-
candidateSpec: rc1Inputs.candidateSpec,
|
|
512
|
-
manualEvidence,
|
|
513
|
-
querySet: rc1Inputs.querySet,
|
|
514
|
-
sensorEvidence: raw,
|
|
515
|
-
codeSnapshotDigest: measurement.snapshot_digest,
|
|
516
|
-
planVersion: `rc2-${condition}-transfer-${label}`,
|
|
517
|
-
});
|
|
518
|
-
runRecord.legacy_boundary_manifests = {
|
|
519
|
-
normal: compileLegacy(rc1Inputs.normalManualEvidence, 'normal').boundary_manifest,
|
|
520
|
-
negative: compileLegacy(rc1Inputs.negativeManualEvidence, 'negative').boundary_manifest,
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
return runRecord;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
function verdictFor(bundle, compiled) {
|
|
527
|
-
return {
|
|
528
|
-
schema_version: 'lattice.boundary_verdict.v2',
|
|
529
|
-
normalized_graph_digest: bundle.graph_digest,
|
|
530
|
-
verdicts: compiled.pairwise_verdicts,
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function timedSync(stage, costMeasurements, execute) {
|
|
535
|
-
const startedAt = performance.now();
|
|
536
|
-
const value = execute();
|
|
537
|
-
costMeasurements.push({ stage, state: 'measured', elapsed_ms: roundedMilliseconds(startedAt) });
|
|
538
|
-
return value;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function compiledRecord({ condition, runId, bundle, costMeasurements }) {
|
|
542
|
-
const compiled = timedSync(`compile.${condition}.${runId}`, costMeasurements, () => (
|
|
543
|
-
compileSchedulabilityGraphV2(bundle.graph)
|
|
544
|
-
));
|
|
545
|
-
if (compiled.outcome !== 'compiled') throw new TypeError(`${condition} did not compile`);
|
|
546
|
-
const verdict = verdictFor(bundle, compiled);
|
|
547
|
-
const verification = timedSync(`verify.${condition}.${runId}`, costMeasurements, () => (
|
|
548
|
-
verifySchedulabilityPlanV2(bundle.graph, compiled.plan)
|
|
549
|
-
));
|
|
550
|
-
if (verification.outcome !== 'verified') throw new TypeError(`${condition} minimum is unverified`);
|
|
551
|
-
const stable = { bundle, verdict, plan: compiled.plan, verification };
|
|
552
|
-
return {
|
|
553
|
-
schema: 'lattice.rc2.compiled_condition.v1',
|
|
554
|
-
condition,
|
|
555
|
-
run_id: runId,
|
|
556
|
-
outcome: 'compiled',
|
|
557
|
-
...stable,
|
|
558
|
-
artifact_digest: digestArtifact(stable),
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
function compilePrimary({ runRecord, inputs, manualEvidence, planInput, condition, costMeasurements }) {
|
|
563
|
-
const bundle = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
564
|
-
compileDeliveryPolicyBoundaryBundleV2({
|
|
565
|
-
planInput,
|
|
566
|
-
candidateSpec: inputs.candidateSpec,
|
|
567
|
-
manualEvidence,
|
|
568
|
-
querySet: inputs.querySet,
|
|
569
|
-
sourceSnapshot: runRecord.measurement.snapshot,
|
|
570
|
-
sensorEvidence: runRecord.evidence.portable,
|
|
571
|
-
})
|
|
572
|
-
));
|
|
573
|
-
return compiledRecord({ condition, runId: runRecord.run_id, bundle, costMeasurements });
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function compileUnknown({ runRecord, inputs, costMeasurements }) {
|
|
577
|
-
const condition = 'primary-treatment-third-only-unknown';
|
|
578
|
-
const bundle = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
579
|
-
compileDeliveryPolicyBoundaryBundleV2({
|
|
580
|
-
planInput: inputs.planInput,
|
|
581
|
-
candidateSpec: inputs.candidateSpec,
|
|
582
|
-
manualEvidence: inputs.unknownManualEvidence,
|
|
583
|
-
querySet: inputs.querySet,
|
|
584
|
-
sourceSnapshot: runRecord.measurement.snapshot,
|
|
585
|
-
sensorEvidence: runRecord.evidence.portable,
|
|
586
|
-
})
|
|
587
|
-
));
|
|
588
|
-
const outcome = timedSync(`compile.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
589
|
-
compileSchedulabilityGraphV2(bundle.graph)
|
|
590
|
-
));
|
|
591
|
-
const verification = timedSync(`verify.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
592
|
-
verifySchedulabilityPlanV2(bundle.graph, {
|
|
593
|
-
schema_version: 'lattice.plan_graph.v2',
|
|
594
|
-
waves: [],
|
|
595
|
-
minimum_feasible_waves: 0,
|
|
596
|
-
})
|
|
597
|
-
));
|
|
598
|
-
if (outcome.outcome !== 'unknown' || verification.outcome !== 'unknown') {
|
|
599
|
-
throw new TypeError('third-only unknown became dispatchable');
|
|
600
|
-
}
|
|
601
|
-
return {
|
|
602
|
-
schema: 'lattice.rc2.non_dispatchable_condition.v1',
|
|
603
|
-
condition,
|
|
604
|
-
run_id: runRecord.run_id,
|
|
605
|
-
bundle,
|
|
606
|
-
outcome,
|
|
607
|
-
verification,
|
|
608
|
-
};
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
function compileTransfer({ runRecord, inputs, manualEvidence, label, costMeasurements }) {
|
|
612
|
-
const condition = `rc1-transfer-${runRecord.condition}-${label}`;
|
|
613
|
-
const transferred = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
614
|
-
compileRc1TransferBundleV2({
|
|
615
|
-
planInput: inputs.planInput,
|
|
616
|
-
candidateSpec: inputs.candidateSpec,
|
|
617
|
-
manualEvidence,
|
|
618
|
-
querySet: inputs.querySet,
|
|
619
|
-
boundaryManifest: runRecord.legacy_boundary_manifests[label],
|
|
620
|
-
})
|
|
621
|
-
));
|
|
622
|
-
const verification = timedSync(`verify.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
623
|
-
verifySchedulabilityPlanV2(transferred.bundle.graph, transferred.plan)
|
|
624
|
-
));
|
|
625
|
-
if (verification.outcome !== 'verified') throw new TypeError(`${condition} minimum is unverified`);
|
|
626
|
-
const stable = {
|
|
627
|
-
bundle: transferred.bundle,
|
|
628
|
-
verdict: transferred.verdict,
|
|
629
|
-
plan: transferred.plan,
|
|
630
|
-
verification,
|
|
631
|
-
};
|
|
632
|
-
return {
|
|
633
|
-
schema: 'lattice.rc2.compiled_condition.v1',
|
|
634
|
-
condition,
|
|
635
|
-
run_id: runRecord.run_id,
|
|
636
|
-
outcome: 'compiled',
|
|
637
|
-
...stable,
|
|
638
|
-
artifact_digest: digestArtifact(stable),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function compileAllConditions({ runs, inputs, costMeasurements }) {
|
|
643
|
-
const primaryControl = runs.primary.control.map((runRecord) => compilePrimary({
|
|
644
|
-
runRecord,
|
|
645
|
-
inputs: inputs.primary,
|
|
646
|
-
manualEvidence: inputs.primary.normalManualEvidence,
|
|
647
|
-
planInput: inputs.primary.planInput,
|
|
648
|
-
condition: 'primary-control-normal',
|
|
649
|
-
costMeasurements,
|
|
650
|
-
}));
|
|
651
|
-
const primaryTreatment = runs.primary.treatment.map((runRecord) => compilePrimary({
|
|
652
|
-
runRecord,
|
|
653
|
-
inputs: inputs.primary,
|
|
654
|
-
manualEvidence: inputs.primary.normalManualEvidence,
|
|
655
|
-
planInput: inputs.primary.planInput,
|
|
656
|
-
condition: 'primary-treatment-normal',
|
|
657
|
-
costMeasurements,
|
|
658
|
-
}));
|
|
659
|
-
if (new Set(primaryControl.map(({ artifact_digest: digest }) => digest)).size !== 1
|
|
660
|
-
|| new Set(primaryTreatment.map(({ artifact_digest: digest }) => digest)).size !== 1) {
|
|
661
|
-
throw new TypeError('primary repeated compilation is not structurally reproducible');
|
|
662
|
-
}
|
|
663
|
-
const treatmentRun = runs.primary.treatment[0];
|
|
664
|
-
const capacityPlan = structuredClone(inputs.primary.planInput);
|
|
665
|
-
capacityPlan.capacity.writers = 2;
|
|
666
|
-
const partialState = compilePrimary({
|
|
667
|
-
runRecord: treatmentRun,
|
|
668
|
-
inputs: inputs.primary,
|
|
669
|
-
manualEvidence: inputs.primary.partialManualEvidence,
|
|
670
|
-
planInput: inputs.primary.planInput,
|
|
671
|
-
condition: 'primary-treatment-partial-state',
|
|
672
|
-
costMeasurements,
|
|
673
|
-
});
|
|
674
|
-
const capacity2 = compilePrimary({
|
|
675
|
-
runRecord: treatmentRun,
|
|
676
|
-
inputs: inputs.primary,
|
|
677
|
-
manualEvidence: inputs.primary.normalManualEvidence,
|
|
678
|
-
planInput: capacityPlan,
|
|
679
|
-
condition: 'primary-treatment-capacity-2',
|
|
680
|
-
costMeasurements,
|
|
681
|
-
});
|
|
682
|
-
const unknown = compileUnknown({ runRecord: treatmentRun, inputs: inputs.primary, costMeasurements });
|
|
683
|
-
const transferControl = runs.rc1_transfer.control[0];
|
|
684
|
-
const transferTreatment = runs.rc1_transfer.treatment[0];
|
|
685
|
-
return {
|
|
686
|
-
primary: {
|
|
687
|
-
control: primaryControl,
|
|
688
|
-
treatment: primaryTreatment,
|
|
689
|
-
partial_state: partialState,
|
|
690
|
-
capacity_2: capacity2,
|
|
691
|
-
third_only_unknown: unknown,
|
|
692
|
-
},
|
|
693
|
-
rc1_transfer: {
|
|
694
|
-
control: {
|
|
695
|
-
normal: compileTransfer({
|
|
696
|
-
runRecord: transferControl,
|
|
697
|
-
inputs: inputs.rc1,
|
|
698
|
-
manualEvidence: inputs.rc1.normalManualEvidence,
|
|
699
|
-
label: 'normal',
|
|
700
|
-
costMeasurements,
|
|
701
|
-
}),
|
|
702
|
-
negative: compileTransfer({
|
|
703
|
-
runRecord: transferControl,
|
|
704
|
-
inputs: inputs.rc1,
|
|
705
|
-
manualEvidence: inputs.rc1.negativeManualEvidence,
|
|
706
|
-
label: 'negative',
|
|
707
|
-
costMeasurements,
|
|
708
|
-
}),
|
|
709
|
-
},
|
|
710
|
-
treatment: {
|
|
711
|
-
normal: compileTransfer({
|
|
712
|
-
runRecord: transferTreatment,
|
|
713
|
-
inputs: inputs.rc1,
|
|
714
|
-
manualEvidence: inputs.rc1.normalManualEvidence,
|
|
715
|
-
label: 'normal',
|
|
716
|
-
costMeasurements,
|
|
717
|
-
}),
|
|
718
|
-
negative: compileTransfer({
|
|
719
|
-
runRecord: transferTreatment,
|
|
720
|
-
inputs: inputs.rc1,
|
|
721
|
-
manualEvidence: inputs.rc1.negativeManualEvidence,
|
|
722
|
-
label: 'negative',
|
|
723
|
-
costMeasurements,
|
|
724
|
-
}),
|
|
725
|
-
},
|
|
726
|
-
},
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
function compiledPayloads(compiled) {
|
|
731
|
-
return new Map([
|
|
732
|
-
['compiled/primary-control-1-normal.json', jsonBytes(compiled.primary.control[0])],
|
|
733
|
-
['compiled/primary-control-2-normal.json', jsonBytes(compiled.primary.control[1])],
|
|
734
|
-
['compiled/primary-treatment-1-normal.json', jsonBytes(compiled.primary.treatment[0])],
|
|
735
|
-
['compiled/primary-treatment-2-normal.json', jsonBytes(compiled.primary.treatment[1])],
|
|
736
|
-
['compiled/primary-treatment-partial-state.json', jsonBytes(compiled.primary.partial_state)],
|
|
737
|
-
['compiled/primary-treatment-capacity-2.json', jsonBytes(compiled.primary.capacity_2)],
|
|
738
|
-
['compiled/primary-treatment-third-only-unknown.json', jsonBytes(compiled.primary.third_only_unknown)],
|
|
739
|
-
['compiled/rc1-transfer-control-normal.json', jsonBytes(compiled.rc1_transfer.control.normal)],
|
|
740
|
-
['compiled/rc1-transfer-control-negative.json', jsonBytes(compiled.rc1_transfer.control.negative)],
|
|
741
|
-
['compiled/rc1-transfer-treatment-normal.json', jsonBytes(compiled.rc1_transfer.treatment.normal)],
|
|
742
|
-
['compiled/rc1-transfer-treatment-negative.json', jsonBytes(compiled.rc1_transfer.treatment.negative)],
|
|
743
|
-
]);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function inputPayloads(inputs) {
|
|
747
|
-
const result = new Map();
|
|
748
|
-
for (const [key, relativePath] of Object.entries(PRIMARY_ARTIFACT_INPUTS)) {
|
|
749
|
-
result.set(relativePath, jsonBytes(inputs.primary[key]));
|
|
750
|
-
}
|
|
751
|
-
for (const [key, relativePath] of Object.entries(RC1_ARTIFACT_INPUTS)) {
|
|
752
|
-
result.set(relativePath, jsonBytes(inputs.rc1[key]));
|
|
753
|
-
}
|
|
754
|
-
return result;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function transformPayloads(transform) {
|
|
758
|
-
return new Map([
|
|
759
|
-
['transform/accepted-artifact.json', jsonBytes(transform.accepted.artifact)],
|
|
760
|
-
['transform/accepted-receipt.json', jsonBytes(transform.accepted.receipt)],
|
|
761
|
-
['transform/behavior-evidence.json', jsonBytes(transform.accepted.behavior_evidence)],
|
|
762
|
-
['transform/mutation-evidence.json', jsonBytes(transform.accepted.mutation_evidence)],
|
|
763
|
-
['transform/rejected-incomplete-artifact.json', jsonBytes(transform.rejected.incomplete.artifact)],
|
|
764
|
-
['transform/rejected-incomplete-receipt.json', jsonBytes(transform.rejected.incomplete.receipt)],
|
|
765
|
-
['transform/rejected-scope-artifact.json', jsonBytes(transform.rejected.scope.artifact)],
|
|
766
|
-
['transform/rejected-scope-receipt.json', jsonBytes(transform.rejected.scope.receipt)],
|
|
767
|
-
['transform/seam.patch', Buffer.from(transform.accepted.patch)],
|
|
768
|
-
]);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function storedEvidence(evidence) {
|
|
772
|
-
const raw = evidence?.raw;
|
|
773
|
-
if (!exactRecord(raw, [
|
|
774
|
-
'schema',
|
|
775
|
-
'media_type',
|
|
776
|
-
'encoding',
|
|
777
|
-
'canonical_bytes',
|
|
778
|
-
'payload_digest',
|
|
779
|
-
'payload_base64',
|
|
780
|
-
])
|
|
781
|
-
|| raw.schema !== 'lattice.sensor_raw_opaque_receipt.v1'
|
|
782
|
-
|| raw.encoding !== 'canonical-json-base64'
|
|
783
|
-
|| typeof raw.payload_base64 !== 'string') {
|
|
784
|
-
throw new TypeError('run raw evidence receipt is invalid');
|
|
785
|
-
}
|
|
786
|
-
const payload = Buffer.from(raw.payload_base64, 'base64');
|
|
787
|
-
if (payload.toString('base64') !== raw.payload_base64
|
|
788
|
-
|| payload.byteLength !== raw.canonical_bytes
|
|
789
|
-
|| sha256(payload) !== raw.payload_digest) {
|
|
790
|
-
throw new TypeError('run raw evidence payload binding is invalid');
|
|
791
|
-
}
|
|
792
|
-
const payloadBase64Chunks = [];
|
|
793
|
-
for (let offset = 0; offset < raw.payload_base64.length; offset += RAW_EVIDENCE_CHUNK_BYTES) {
|
|
794
|
-
payloadBase64Chunks.push(raw.payload_base64.slice(
|
|
795
|
-
offset,
|
|
796
|
-
offset + RAW_EVIDENCE_CHUNK_BYTES,
|
|
797
|
-
));
|
|
798
|
-
}
|
|
799
|
-
if (payloadBase64Chunks.length === 0
|
|
800
|
-
|| payloadBase64Chunks.slice(0, -1).some((chunk) => (
|
|
801
|
-
Buffer.byteLength(chunk, 'utf8') !== RAW_EVIDENCE_CHUNK_BYTES
|
|
802
|
-
))
|
|
803
|
-
|| Buffer.byteLength(payloadBase64Chunks.at(-1), 'utf8') > RAW_EVIDENCE_CHUNK_BYTES) {
|
|
804
|
-
throw new TypeError('run raw evidence chunks are invalid');
|
|
805
|
-
}
|
|
806
|
-
return {
|
|
807
|
-
...structuredClone(evidence),
|
|
808
|
-
raw: {
|
|
809
|
-
schema: 'lattice.rc2.chunked_sensor_raw_receipt.v1',
|
|
810
|
-
source_schema: raw.schema,
|
|
811
|
-
media_type: raw.media_type,
|
|
812
|
-
source_encoding: raw.encoding,
|
|
813
|
-
storage_encoding: 'ordered-base64-chunks',
|
|
814
|
-
canonical_bytes: raw.canonical_bytes,
|
|
815
|
-
payload_digest: raw.payload_digest,
|
|
816
|
-
payload_base64_chunks: payloadBase64Chunks,
|
|
817
|
-
},
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
function storedRunRecord(runRecord) {
|
|
822
|
-
return {
|
|
823
|
-
...structuredClone(runRecord),
|
|
824
|
-
evidence: storedEvidence(runRecord.evidence),
|
|
825
|
-
};
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
function runPayloads(runs) {
|
|
829
|
-
const records = [
|
|
830
|
-
...runs.primary.control,
|
|
831
|
-
...runs.primary.treatment,
|
|
832
|
-
...runs.rc1_transfer.control,
|
|
833
|
-
...runs.rc1_transfer.treatment,
|
|
834
|
-
];
|
|
835
|
-
return new Map(records.map((runRecord) => [
|
|
836
|
-
`evidence/${runRecord.run_id}.json`,
|
|
837
|
-
jsonBytes(storedRunRecord(runRecord)),
|
|
838
|
-
]));
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
function mergePayloads(...maps) {
|
|
842
|
-
const result = new Map();
|
|
843
|
-
for (const map of maps) {
|
|
844
|
-
for (const [relativePath, bytes] of map) {
|
|
845
|
-
if (result.has(relativePath)) throw new TypeError(`duplicate artifact payload: ${relativePath}`);
|
|
846
|
-
result.set(relativePath, Buffer.from(bytes));
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
return result;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function predecessorDescriptors(payloads) {
|
|
853
|
-
const descriptors = [];
|
|
854
|
-
const add = (kind, ref) => descriptors.push({ kind, ref, digest: sha256(payloads.get(ref)) });
|
|
855
|
-
for (const ref of Object.keys(PREDECESSOR_FILES).sort()) {
|
|
856
|
-
add(ref === 'predecessors/rc1-v6-plan.md' ? 'rc1_v6_phase_archive' : 'immutable_predecessor', ref);
|
|
857
|
-
}
|
|
858
|
-
add('accepted_transform', 'transform/accepted-artifact.json');
|
|
859
|
-
add('behavior_envelope', 'transform/behavior-evidence.json');
|
|
860
|
-
add('mutation_evidence', 'transform/mutation-evidence.json');
|
|
861
|
-
for (const runId of RUN_IDS) add('evidence_bundle', `evidence/${runId}.json`);
|
|
862
|
-
for (const ref of Object.values(PRIMARY_ARTIFACT_INPUTS).sort()) add('fixed_input', ref);
|
|
863
|
-
add('execution_identity', 'identity.json');
|
|
864
|
-
add('compiler_identity', 'identity/schedulability-compiler-v2.mjs');
|
|
865
|
-
add('verifier_identity', 'identity/schedulability-verifier-v2.mjs');
|
|
866
|
-
add('sensor_project_config', SENSOR_CONFIG_ARTIFACT_PATH);
|
|
867
|
-
return descriptors;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
function buildVersionBarrier({ inputs, compiled, predecessors, predecessorPlan }) {
|
|
871
|
-
const treatment = compiled.primary.treatment[0];
|
|
872
|
-
const affectedTodos = inputs.primary.planInput.todos.map(({ id }) => id);
|
|
873
|
-
const newPlanVersion = {
|
|
874
|
-
schema: 'lattice.rc2.plan_version.v1',
|
|
875
|
-
version: 'rc2-delivery-policy-v5',
|
|
876
|
-
predecessor_version: predecessorPlan.version,
|
|
877
|
-
plan: treatment.plan,
|
|
878
|
-
plan_digest: digestArtifact(treatment.plan),
|
|
879
|
-
bundle_digest: digestArtifact(treatment.bundle),
|
|
880
|
-
verdict_digest: digestArtifact(treatment.verdict),
|
|
881
|
-
affected_todos: affectedTodos,
|
|
882
|
-
causal_predecessors: predecessors,
|
|
883
|
-
};
|
|
884
|
-
const planDiff = {
|
|
885
|
-
schema: 'lattice.rc2.plan_diff.v1',
|
|
886
|
-
old_plan: {
|
|
887
|
-
version: predecessorPlan.version,
|
|
888
|
-
digest: digestArtifact(predecessorPlan),
|
|
889
|
-
},
|
|
890
|
-
new_plan: {
|
|
891
|
-
version: newPlanVersion.version,
|
|
892
|
-
digest: digestArtifact(newPlanVersion),
|
|
893
|
-
},
|
|
894
|
-
causal_predecessors: predecessors,
|
|
895
|
-
affected_todos: affectedTodos,
|
|
896
|
-
invalidated_contexts: [
|
|
897
|
-
{ kind: 'old_plan', ref: predecessorPlan.version,
|
|
898
|
-
reason: 'the v4 artifact semantics cannot receive v5 bindings by append' },
|
|
899
|
-
{ kind: 'agent_context', ref: `${predecessorPlan.version}-agent-context`,
|
|
900
|
-
reason: 'v4 agent context does not bind the version witness verifier identity' },
|
|
901
|
-
{ kind: 'partial_patch', ref: `${predecessorPlan.version}-partial-patch`,
|
|
902
|
-
reason: 'patches compiled before version witness binding cannot cross the barrier' },
|
|
903
|
-
{ kind: 'interface_assumption', ref: `${predecessorPlan.version}-interface-assumption`,
|
|
904
|
-
reason: 'v4 interface evidence does not bind the active witness epoch' },
|
|
905
|
-
{ kind: 'boundary_evidence', ref: `${predecessorPlan.version}-boundary-evidence`,
|
|
906
|
-
reason: 'only fresh version-bound treatment evidence is valid for plan v5' },
|
|
907
|
-
],
|
|
908
|
-
};
|
|
909
|
-
return { newPlanVersion, planDiff };
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
function distinctConflictPairs(record) {
|
|
913
|
-
return new Set(record.bundle.graph.conflicts.map(({ todo_ids: todoIds }) => (
|
|
914
|
-
[...todoIds].sort().join('\u0000')
|
|
915
|
-
))).size;
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function conditionMetrics(record) {
|
|
919
|
-
return {
|
|
920
|
-
conflict_records: record.bundle.graph.conflicts.length,
|
|
921
|
-
distinct_conflict_pairs: distinctConflictPairs(record),
|
|
922
|
-
minimum_feasible_waves: record.plan.minimum_feasible_waves,
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
function buildComparison({ baseSha, identity, transform, compiled, newPlanVersion, planDiff }) {
|
|
927
|
-
return {
|
|
928
|
-
schema: 'lattice.rc2.control_treatment_comparison.v1',
|
|
929
|
-
base_sha: baseSha,
|
|
930
|
-
identity_digest: digestArtifact(identity),
|
|
931
|
-
independent_variable: {
|
|
932
|
-
kind: 'accepted_registry_shard_patch',
|
|
933
|
-
transform_artifact_digest: digestArtifact(transform.accepted.artifact),
|
|
934
|
-
patch_digest: transform.accepted.artifact.patch.digest,
|
|
935
|
-
},
|
|
936
|
-
condition_artifact_digests: {
|
|
937
|
-
primary_control: compiled.primary.control.map(({ artifact_digest: digest }) => digest),
|
|
938
|
-
primary_treatment: compiled.primary.treatment.map(({ artifact_digest: digest }) => digest),
|
|
939
|
-
primary_partial_state: compiled.primary.partial_state.artifact_digest,
|
|
940
|
-
primary_capacity_2: compiled.primary.capacity_2.artifact_digest,
|
|
941
|
-
primary_unknown: digestArtifact(compiled.primary.third_only_unknown),
|
|
942
|
-
rc1_transfer_control_normal: compiled.rc1_transfer.control.normal.artifact_digest,
|
|
943
|
-
rc1_transfer_control_negative: compiled.rc1_transfer.control.negative.artifact_digest,
|
|
944
|
-
rc1_transfer_treatment_normal: compiled.rc1_transfer.treatment.normal.artifact_digest,
|
|
945
|
-
rc1_transfer_treatment_negative: compiled.rc1_transfer.treatment.negative.artifact_digest,
|
|
946
|
-
},
|
|
947
|
-
metrics: {
|
|
948
|
-
primary_control: conditionMetrics(compiled.primary.control[0]),
|
|
949
|
-
primary_treatment: conditionMetrics(compiled.primary.treatment[0]),
|
|
950
|
-
primary_partial_state: conditionMetrics(compiled.primary.partial_state),
|
|
951
|
-
primary_capacity_2: conditionMetrics(compiled.primary.capacity_2),
|
|
952
|
-
rc1_transfer_control_normal: conditionMetrics(compiled.rc1_transfer.control.normal),
|
|
953
|
-
rc1_transfer_control_negative: conditionMetrics(compiled.rc1_transfer.control.negative),
|
|
954
|
-
rc1_transfer_treatment_normal: conditionMetrics(compiled.rc1_transfer.treatment.normal),
|
|
955
|
-
rc1_transfer_treatment_negative: conditionMetrics(compiled.rc1_transfer.treatment.negative),
|
|
956
|
-
},
|
|
957
|
-
version_barrier: {
|
|
958
|
-
new_plan_version_digest: digestArtifact(newPlanVersion),
|
|
959
|
-
plan_diff_digest: digestArtifact(planDiff),
|
|
960
|
-
causal_predecessors_digest: digestArtifact(planDiff.causal_predecessors),
|
|
961
|
-
},
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
function evaluateHypothesis(comparison, compiled, transform) {
|
|
966
|
-
const checks = [
|
|
967
|
-
{ id: 'accepted_transform_only', passed: transform.accepted.status === 'accepted' },
|
|
968
|
-
{
|
|
969
|
-
id: 'primary_repeat_reproducible',
|
|
970
|
-
passed: new Set(compiled.primary.control.map(({ artifact_digest: digest }) => digest)).size === 1
|
|
971
|
-
&& new Set(compiled.primary.treatment.map(({ artifact_digest: digest }) => digest)).size === 1,
|
|
972
|
-
},
|
|
973
|
-
{
|
|
974
|
-
id: 'primary_conflicts_removed',
|
|
975
|
-
passed: comparison.metrics.primary_control.conflict_records === 12
|
|
976
|
-
&& comparison.metrics.primary_control.distinct_conflict_pairs === 3
|
|
977
|
-
&& comparison.metrics.primary_treatment.conflict_records === 0,
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
id: 'primary_waves_reduced',
|
|
981
|
-
passed: comparison.metrics.primary_control.minimum_feasible_waves === 3
|
|
982
|
-
&& comparison.metrics.primary_treatment.minimum_feasible_waves === 1,
|
|
983
|
-
},
|
|
984
|
-
{
|
|
985
|
-
id: 'partial_state_preserved',
|
|
986
|
-
passed: comparison.metrics.primary_partial_state.conflict_records === 1
|
|
987
|
-
&& comparison.metrics.primary_partial_state.minimum_feasible_waves === 2,
|
|
988
|
-
},
|
|
989
|
-
{
|
|
990
|
-
id: 'capacity_control_preserved',
|
|
991
|
-
passed: comparison.metrics.primary_capacity_2.conflict_records === 0
|
|
992
|
-
&& comparison.metrics.primary_capacity_2.minimum_feasible_waves === 2,
|
|
993
|
-
},
|
|
994
|
-
{
|
|
995
|
-
id: 'third_only_unknown_closed',
|
|
996
|
-
passed: compiled.primary.third_only_unknown.outcome.outcome === 'unknown'
|
|
997
|
-
&& compiled.primary.third_only_unknown.verification.outcome === 'unknown',
|
|
998
|
-
},
|
|
999
|
-
{
|
|
1000
|
-
id: 'rc1_transfer_isomorphic',
|
|
1001
|
-
passed: comparison.metrics.rc1_transfer_control_normal.conflict_records === 3
|
|
1002
|
-
&& comparison.metrics.rc1_transfer_control_normal.minimum_feasible_waves === 2
|
|
1003
|
-
&& comparison.metrics.rc1_transfer_treatment_normal.conflict_records === 0
|
|
1004
|
-
&& comparison.metrics.rc1_transfer_treatment_normal.minimum_feasible_waves === 1
|
|
1005
|
-
&& comparison.metrics.rc1_transfer_control_negative.minimum_feasible_waves === 2
|
|
1006
|
-
&& comparison.metrics.rc1_transfer_treatment_negative.minimum_feasible_waves === 2,
|
|
1007
|
-
},
|
|
1008
|
-
{
|
|
1009
|
-
id: 'all_minimum_verified',
|
|
1010
|
-
passed: [
|
|
1011
|
-
...compiled.primary.control,
|
|
1012
|
-
...compiled.primary.treatment,
|
|
1013
|
-
compiled.primary.partial_state,
|
|
1014
|
-
compiled.primary.capacity_2,
|
|
1015
|
-
compiled.rc1_transfer.control.normal,
|
|
1016
|
-
compiled.rc1_transfer.control.negative,
|
|
1017
|
-
compiled.rc1_transfer.treatment.normal,
|
|
1018
|
-
compiled.rc1_transfer.treatment.negative,
|
|
1019
|
-
].every(({ verification }) => verification.outcome === 'verified'),
|
|
1020
|
-
},
|
|
1021
|
-
];
|
|
1022
|
-
return {
|
|
1023
|
-
schema: 'lattice.rc2.hypothesis_evaluation.v1',
|
|
1024
|
-
checks,
|
|
1025
|
-
supported: checks.every(({ passed }) => passed),
|
|
1026
|
-
failed_conditions: checks.filter(({ passed }) => !passed).map(({ id }) => id),
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
function patchReviewLines(patch) {
|
|
1031
|
-
return patch.toString('utf8').split('\n').filter((line) => (
|
|
1032
|
-
(line.startsWith('+') && !line.startsWith('+++'))
|
|
1033
|
-
|| (line.startsWith('-') && !line.startsWith('---'))
|
|
1034
|
-
)).length;
|
|
1035
|
-
}
|
|
1036
|
-
|
|
1037
|
-
function buildCost(costMeasurements, transform) {
|
|
1038
|
-
const elapsed = Math.round(costMeasurements
|
|
1039
|
-
.reduce((sum, measurement) => sum + measurement.elapsed_ms, 0) * 1_000) / 1_000;
|
|
1040
|
-
return {
|
|
1041
|
-
schema: 'lattice.rc2.stage_cost.v1',
|
|
1042
|
-
measurements: costMeasurements,
|
|
1043
|
-
aggregate: {
|
|
1044
|
-
measured_count: costMeasurements.length,
|
|
1045
|
-
not_measured_count: 0,
|
|
1046
|
-
elapsed_ms: elapsed,
|
|
1047
|
-
},
|
|
1048
|
-
intervention: {
|
|
1049
|
-
patch_bytes: transform.accepted.patch.byteLength,
|
|
1050
|
-
files: transform.accepted.artifact.scope.changed_paths.length,
|
|
1051
|
-
review_lines: patchReviewLines(transform.accepted.patch),
|
|
1052
|
-
},
|
|
1053
|
-
rework: { rejected_attempts: 2, retries: 0, rollbacks: 0 },
|
|
1054
|
-
unverified: [
|
|
1055
|
-
'actual multi-agent wall-clock improvement',
|
|
1056
|
-
'elapsed measurement attestation beyond saved arithmetic binding',
|
|
1057
|
-
'arbitrary-repository seam success rate',
|
|
1058
|
-
],
|
|
1059
|
-
};
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function allCompiledRecords(compiled) {
|
|
1063
|
-
return [
|
|
1064
|
-
...compiled.primary.control,
|
|
1065
|
-
...compiled.primary.treatment,
|
|
1066
|
-
compiled.primary.partial_state,
|
|
1067
|
-
compiled.primary.capacity_2,
|
|
1068
|
-
compiled.primary.third_only_unknown,
|
|
1069
|
-
compiled.rc1_transfer.control.normal,
|
|
1070
|
-
compiled.rc1_transfer.control.negative,
|
|
1071
|
-
compiled.rc1_transfer.treatment.normal,
|
|
1072
|
-
compiled.rc1_transfer.treatment.negative,
|
|
1073
|
-
];
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
function buildExecutionEvidence({
|
|
1077
|
-
baseSha,
|
|
1078
|
-
identity,
|
|
1079
|
-
transform,
|
|
1080
|
-
runs,
|
|
1081
|
-
compiled,
|
|
1082
|
-
newPlanVersion,
|
|
1083
|
-
planDiff,
|
|
1084
|
-
comparison,
|
|
1085
|
-
hypothesis,
|
|
1086
|
-
cost,
|
|
1087
|
-
}) {
|
|
1088
|
-
const runRecords = [
|
|
1089
|
-
...runs.primary.control,
|
|
1090
|
-
...runs.primary.treatment,
|
|
1091
|
-
...runs.rc1_transfer.control,
|
|
1092
|
-
...runs.rc1_transfer.treatment,
|
|
1093
|
-
];
|
|
1094
|
-
return {
|
|
1095
|
-
schema: 'lattice.rc2.campaign_execution_evidence.v1',
|
|
1096
|
-
base_sha: baseSha,
|
|
1097
|
-
identity_digest: digestArtifact(identity),
|
|
1098
|
-
transform_artifact_digest: digestArtifact(transform.accepted.artifact),
|
|
1099
|
-
run_digests: runRecords.map((runRecord) => digestArtifact(storedRunRecord(runRecord))),
|
|
1100
|
-
compiled_condition_digests: allCompiledRecords(compiled).map(digestArtifact),
|
|
1101
|
-
new_plan_version_digest: digestArtifact(newPlanVersion),
|
|
1102
|
-
plan_diff_digest: digestArtifact(planDiff),
|
|
1103
|
-
comparison_digest: digestArtifact(comparison),
|
|
1104
|
-
hypothesis_evaluation_digest: digestArtifact(hypothesis),
|
|
1105
|
-
cost_digest: digestArtifact(cost),
|
|
1106
|
-
};
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
async function timedAsync(stage, costMeasurements, execute) {
|
|
1110
|
-
const startedAt = performance.now();
|
|
1111
|
-
const value = await execute();
|
|
1112
|
-
costMeasurements.push({ stage, state: 'measured', elapsed_ms: roundedMilliseconds(startedAt) });
|
|
1113
|
-
return value;
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
async function runTransforms({ root, baseSha, candidateSpec, costMeasurements }) {
|
|
1117
|
-
const accepted = await timedAsync('transform.accepted', costMeasurements, () => (
|
|
1118
|
-
runRc2DeliveryPolicySeamTransform({ repoRoot: root, baseRef: baseSha, candidateSpec })
|
|
1119
|
-
));
|
|
1120
|
-
if (accepted.status !== 'accepted' || !Buffer.isBuffer(accepted.patch)) {
|
|
1121
|
-
throw new TypeError('default RC2 transform was not accepted');
|
|
1122
|
-
}
|
|
1123
|
-
const incomplete = await timedAsync('transform.rejected.incomplete', costMeasurements, () => (
|
|
1124
|
-
runRc2DeliveryPolicySeamTransform({
|
|
1125
|
-
repoRoot: root,
|
|
1126
|
-
baseRef: baseSha,
|
|
1127
|
-
candidateSpec,
|
|
1128
|
-
transform: async ({ worktreePath }) => {
|
|
1129
|
-
await applyRc2DeliveryPolicyTransform({ worktreePath });
|
|
1130
|
-
await rm(path.join(worktreePath, 'test/rc2-delivery-policy-sms.test.mjs'));
|
|
1131
|
-
},
|
|
1132
|
-
})
|
|
1133
|
-
));
|
|
1134
|
-
const scope = await timedAsync('transform.rejected.scope', costMeasurements, () => (
|
|
1135
|
-
runRc2DeliveryPolicySeamTransform({
|
|
1136
|
-
repoRoot: root,
|
|
1137
|
-
baseRef: baseSha,
|
|
1138
|
-
candidateSpec,
|
|
1139
|
-
transform: async ({ worktreePath }) => {
|
|
1140
|
-
await applyRc2DeliveryPolicyTransform({ worktreePath });
|
|
1141
|
-
await writeFile(path.join(worktreePath, 'rc2-scope-violation.tmp'), 'forbidden\n');
|
|
1142
|
-
},
|
|
1143
|
-
})
|
|
1144
|
-
));
|
|
1145
|
-
if (incomplete.status !== 'rejected'
|
|
1146
|
-
|| incomplete.artifact.rejection.kind !== 'incomplete_transform'
|
|
1147
|
-
|| scope.status !== 'rejected'
|
|
1148
|
-
|| scope.artifact.rejection.kind !== 'scope_violation') {
|
|
1149
|
-
throw new TypeError('RC2 rejected transform controls did not preserve typed outcomes');
|
|
1150
|
-
}
|
|
1151
|
-
return { accepted, rejected: { incomplete, scope } };
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
/** accepted seamを独立変数にfresh sensor→v2 compile→new planまで実行する。 */
|
|
1155
|
-
export async function runRc2Campaign(options = {}) {
|
|
1156
|
-
if (!exactRecord(options, ['repoRoot', 'baseRef', 'artifactVersion'])
|
|
1157
|
-
|| options.artifactVersion !== ARTIFACT_VERSION) {
|
|
1158
|
-
throw new TypeError('runRc2Campaign options must select exact artifactVersion v4');
|
|
1159
|
-
}
|
|
1160
|
-
const { root, baseSha } = await resolveRepository(options.repoRoot, options.baseRef);
|
|
1161
|
-
const [inputs, predecessors, identityBefore] = await Promise.all([
|
|
1162
|
-
loadInputs(root),
|
|
1163
|
-
loadPredecessors(root),
|
|
1164
|
-
captureExecutionIdentity(root),
|
|
1165
|
-
]);
|
|
1166
|
-
const predecessorPlan = loadV3PlanPredecessor(predecessors);
|
|
1167
|
-
const costMeasurements = [];
|
|
1168
|
-
const transform = await runTransforms({
|
|
1169
|
-
root,
|
|
1170
|
-
baseSha,
|
|
1171
|
-
candidateSpec: inputs.primary.candidateSpec,
|
|
1172
|
-
costMeasurements,
|
|
1173
|
-
});
|
|
1174
|
-
const primaryPaths = surfacePaths(inputs.primary.candidateSpec);
|
|
1175
|
-
const rc1Paths = surfacePaths(inputs.rc1.candidateSpec);
|
|
1176
|
-
if (primaryPaths.length !== 9 || rc1Paths.length !== 6) {
|
|
1177
|
-
throw new TypeError('campaign fixed surface cardinality is invalid');
|
|
1178
|
-
}
|
|
1179
|
-
const rc1TransformArtifact = JSON.parse(
|
|
1180
|
-
predecessors.get('predecessors/rc1-v6-transform-artifact.json').toString('utf8'),
|
|
1181
|
-
);
|
|
1182
|
-
const rc1Patch = predecessors.get('predecessors/rc1-v6-seam.patch');
|
|
1183
|
-
if (rc1TransformArtifact.patch.digest !== sha256(rc1Patch)) {
|
|
1184
|
-
throw new TypeError('RC1 v6 predecessor patch binding is invalid');
|
|
1185
|
-
}
|
|
1186
|
-
const common = {
|
|
1187
|
-
repoRoot: root,
|
|
1188
|
-
baseSha,
|
|
1189
|
-
sensorIdentity: identityBefore.snapshot.sensor_identity,
|
|
1190
|
-
costMeasurements,
|
|
1191
|
-
};
|
|
1192
|
-
const primaryControl = [];
|
|
1193
|
-
const primaryTreatment = [];
|
|
1194
|
-
for (let index = 1; index <= 2; index += 1) {
|
|
1195
|
-
primaryControl.push(await observeFreshIndex({
|
|
1196
|
-
...common,
|
|
1197
|
-
family: 'primary',
|
|
1198
|
-
condition: 'control',
|
|
1199
|
-
runId: `primary-control-${index}`,
|
|
1200
|
-
querySet: inputs.primary.querySet,
|
|
1201
|
-
paths: primaryPaths,
|
|
1202
|
-
patch: null,
|
|
1203
|
-
allowedPaths: [],
|
|
1204
|
-
oracle: null,
|
|
1205
|
-
rc1Inputs: null,
|
|
1206
|
-
}));
|
|
1207
|
-
}
|
|
1208
|
-
for (let index = 1; index <= 2; index += 1) {
|
|
1209
|
-
primaryTreatment.push(await observeFreshIndex({
|
|
1210
|
-
...common,
|
|
1211
|
-
family: 'primary',
|
|
1212
|
-
condition: 'treatment',
|
|
1213
|
-
runId: `primary-treatment-${index}`,
|
|
1214
|
-
querySet: inputs.primary.querySet,
|
|
1215
|
-
paths: primaryPaths,
|
|
1216
|
-
patch: transform.accepted.patch,
|
|
1217
|
-
allowedPaths: [...RC2_DELIVERY_POLICY_TRANSFORM_PATHS],
|
|
1218
|
-
oracle: null,
|
|
1219
|
-
rc1Inputs: null,
|
|
1220
|
-
}));
|
|
1221
|
-
}
|
|
1222
|
-
const transferControl = await observeFreshIndex({
|
|
1223
|
-
...common,
|
|
1224
|
-
family: 'rc1_transfer',
|
|
1225
|
-
condition: 'control',
|
|
1226
|
-
runId: 'rc1-transfer-control-1',
|
|
1227
|
-
querySet: inputs.rc1.querySet,
|
|
1228
|
-
paths: rc1Paths,
|
|
1229
|
-
patch: null,
|
|
1230
|
-
allowedPaths: [],
|
|
1231
|
-
oracle: inputs.rc1.oracle,
|
|
1232
|
-
rc1Inputs: inputs.rc1,
|
|
1233
|
-
});
|
|
1234
|
-
const transferTreatment = await observeFreshIndex({
|
|
1235
|
-
...common,
|
|
1236
|
-
family: 'rc1_transfer',
|
|
1237
|
-
condition: 'treatment',
|
|
1238
|
-
runId: 'rc1-transfer-treatment-1',
|
|
1239
|
-
querySet: inputs.rc1.querySet,
|
|
1240
|
-
paths: rc1Paths,
|
|
1241
|
-
patch: rc1Patch,
|
|
1242
|
-
allowedPaths: [...rc1TransformArtifact.scope.allowed_paths],
|
|
1243
|
-
oracle: inputs.rc1.oracle,
|
|
1244
|
-
rc1Inputs: inputs.rc1,
|
|
1245
|
-
});
|
|
1246
|
-
const runs = {
|
|
1247
|
-
primary: { control: primaryControl, treatment: primaryTreatment },
|
|
1248
|
-
rc1_transfer: { control: [transferControl], treatment: [transferTreatment] },
|
|
1249
|
-
};
|
|
1250
|
-
const compiled = compileAllConditions({ runs, inputs, costMeasurements });
|
|
1251
|
-
|
|
1252
|
-
const identityAfter = await captureExecutionIdentity(root);
|
|
1253
|
-
if (!isDeepStrictEqual(identityBefore.snapshot, identityAfter.snapshot)
|
|
1254
|
-
|| identityBefore.payloads.size !== identityAfter.payloads.size
|
|
1255
|
-
|| [...identityBefore.payloads].some(([relativePath, bytes]) => (
|
|
1256
|
-
!identityAfter.payloads.get(relativePath)?.equals(bytes)
|
|
1257
|
-
))) {
|
|
1258
|
-
throw new TypeError('RC2 execution identity drifted during campaign');
|
|
1259
|
-
}
|
|
1260
|
-
const identity = {
|
|
1261
|
-
schema: 'lattice.rc2.execution_identity.v4',
|
|
1262
|
-
sources: identityBefore.snapshot.sources,
|
|
1263
|
-
sensor_identity: identityBefore.snapshot.sensor_identity,
|
|
1264
|
-
sensor_identity_digest: digestArtifact(identityBefore.snapshot.sensor_identity),
|
|
1265
|
-
before_digest: identityBefore.digest,
|
|
1266
|
-
after_digest: identityAfter.digest,
|
|
1267
|
-
};
|
|
1268
|
-
|
|
1269
|
-
const preliminaryPayloads = mergePayloads(
|
|
1270
|
-
inputPayloads(inputs),
|
|
1271
|
-
identityBefore.payloads,
|
|
1272
|
-
predecessors,
|
|
1273
|
-
transformPayloads(transform),
|
|
1274
|
-
runPayloads(runs),
|
|
1275
|
-
new Map([['identity.json', jsonBytes(identity)]]),
|
|
1276
|
-
);
|
|
1277
|
-
const causalPredecessors = predecessorDescriptors(preliminaryPayloads);
|
|
1278
|
-
const { newPlanVersion, planDiff } = buildVersionBarrier({
|
|
1279
|
-
inputs,
|
|
1280
|
-
compiled,
|
|
1281
|
-
predecessors: causalPredecessors,
|
|
1282
|
-
predecessorPlan,
|
|
1283
|
-
});
|
|
1284
|
-
const comparison = buildComparison({
|
|
1285
|
-
baseSha,
|
|
1286
|
-
identity,
|
|
1287
|
-
transform,
|
|
1288
|
-
compiled,
|
|
1289
|
-
newPlanVersion,
|
|
1290
|
-
planDiff,
|
|
1291
|
-
});
|
|
1292
|
-
const hypothesisEvaluation = evaluateHypothesis(comparison, compiled, transform);
|
|
1293
|
-
if (!hypothesisEvaluation.supported) {
|
|
1294
|
-
throw new TypeError(
|
|
1295
|
-
`RC2 hypothesis is not supported: ${hypothesisEvaluation.failed_conditions.join(', ')}`,
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
const cost = buildCost(costMeasurements, transform);
|
|
1299
|
-
const executionEvidence = buildExecutionEvidence({
|
|
1300
|
-
baseSha,
|
|
1301
|
-
identity,
|
|
1302
|
-
transform,
|
|
1303
|
-
runs,
|
|
1304
|
-
compiled,
|
|
1305
|
-
newPlanVersion,
|
|
1306
|
-
planDiff,
|
|
1307
|
-
comparison,
|
|
1308
|
-
hypothesis: hypothesisEvaluation,
|
|
1309
|
-
cost,
|
|
1310
|
-
});
|
|
1311
|
-
return {
|
|
1312
|
-
schema: 'lattice.rc2.campaign_result.v4',
|
|
1313
|
-
artifact_version: ARTIFACT_VERSION,
|
|
1314
|
-
base_sha: baseSha,
|
|
1315
|
-
fixed_inputs: inputs,
|
|
1316
|
-
identity,
|
|
1317
|
-
identity_payloads: identityBefore.payloads,
|
|
1318
|
-
predecessor_payloads: predecessors,
|
|
1319
|
-
transform,
|
|
1320
|
-
runs,
|
|
1321
|
-
compiled,
|
|
1322
|
-
new_plan_version: newPlanVersion,
|
|
1323
|
-
plan_diff: planDiff,
|
|
1324
|
-
comparison,
|
|
1325
|
-
hypothesis_evaluation: hypothesisEvaluation,
|
|
1326
|
-
cost,
|
|
1327
|
-
execution_evidence: executionEvidence,
|
|
1328
|
-
};
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
function artifactFiles(result) {
|
|
1332
|
-
const files = mergePayloads(
|
|
1333
|
-
inputPayloads(result.fixed_inputs),
|
|
1334
|
-
result.identity_payloads,
|
|
1335
|
-
result.predecessor_payloads,
|
|
1336
|
-
transformPayloads(result.transform),
|
|
1337
|
-
runPayloads(result.runs),
|
|
1338
|
-
compiledPayloads(result.compiled),
|
|
1339
|
-
new Map([
|
|
1340
|
-
['identity.json', jsonBytes(result.identity)],
|
|
1341
|
-
['new-plan-version.json', jsonBytes(result.new_plan_version)],
|
|
1342
|
-
['plan-diff.json', jsonBytes(result.plan_diff)],
|
|
1343
|
-
['comparison.json', jsonBytes(result.comparison)],
|
|
1344
|
-
['hypothesis-evaluation.json', jsonBytes(result.hypothesis_evaluation)],
|
|
1345
|
-
['cost.json', jsonBytes(result.cost)],
|
|
1346
|
-
['execution-evidence.json', jsonBytes(result.execution_evidence)],
|
|
1347
|
-
]),
|
|
1348
|
-
);
|
|
1349
|
-
if (!isDeepStrictEqual([...files.keys()].sort(), [...RC2_ARTIFACT_PATHS])) {
|
|
1350
|
-
throw new TypeError('RC2 artifact payload set differs from the exact contract');
|
|
1351
|
-
}
|
|
1352
|
-
return files;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
function artifactMediaType(relativePath) {
|
|
1356
|
-
if (relativePath.endsWith('.json')) return 'application/json';
|
|
1357
|
-
if (relativePath.endsWith('.mjs')) return 'application/javascript';
|
|
1358
|
-
if (relativePath.endsWith('.patch')) return 'text/x-diff';
|
|
1359
|
-
if (relativePath.endsWith('.md')) return 'text/markdown';
|
|
1360
|
-
return 'application/octet-stream';
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
async function syncDirectory(directory) {
|
|
1364
|
-
const handle = await open(directory, 'r');
|
|
1365
|
-
// Windowsはdirectory handleのfsyncを許さず常にEPERM/EINVALを返す(Node仕様)。
|
|
1366
|
-
// win32のこの2値だけ許容し、他OS・他エラーは従来どおり失敗させる。
|
|
1367
|
-
try {
|
|
1368
|
-
await handle.sync();
|
|
1369
|
-
} catch (error) {
|
|
1370
|
-
if (process.platform !== 'win32' || !['EPERM', 'EINVAL'].includes(error?.code)) throw error;
|
|
1371
|
-
} finally {
|
|
1372
|
-
await handle.close();
|
|
1373
|
-
}
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
async function writeDurableFile(target, bytes) {
|
|
1377
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
1378
|
-
const handle = await open(target, 'wx');
|
|
1379
|
-
try {
|
|
1380
|
-
await handle.writeFile(bytes);
|
|
1381
|
-
await handle.sync();
|
|
1382
|
-
} finally {
|
|
1383
|
-
await handle.close();
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
async function syncArtifactDirectories(root, relativePaths) {
|
|
1388
|
-
const directories = new Set([root]);
|
|
1389
|
-
for (const relativePath of relativePaths) {
|
|
1390
|
-
let current = path.dirname(path.join(root, relativePath));
|
|
1391
|
-
while (current.startsWith(root)) {
|
|
1392
|
-
directories.add(current);
|
|
1393
|
-
if (current === root) break;
|
|
1394
|
-
current = path.dirname(current);
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
const ordered = [...directories].sort((left, right) => right.length - left.length);
|
|
1398
|
-
for (const directory of ordered) await syncDirectory(directory);
|
|
1399
|
-
}
|
|
1400
|
-
|
|
1401
|
-
/** verified exact artifact setを既存rootへ上書きせずatomicに発行する。 */
|
|
1402
|
-
export async function writeRc2CampaignArtifacts(options = {}) {
|
|
1403
|
-
if (!exactRecord(options, ['repoRoot', 'result'])
|
|
1404
|
-
|| typeof options.repoRoot !== 'string'
|
|
1405
|
-
|| options.repoRoot.length === 0
|
|
1406
|
-
|| options.result?.schema !== 'lattice.rc2.campaign_result.v4'
|
|
1407
|
-
|| options.result?.artifact_version !== ARTIFACT_VERSION) {
|
|
1408
|
-
throw new TypeError('writeRc2CampaignArtifacts options are invalid');
|
|
1409
|
-
}
|
|
1410
|
-
const root = await realpath(options.repoRoot);
|
|
1411
|
-
const artifactRoot = path.join(root, ARTIFACT_ROOTS.v4);
|
|
1412
|
-
try {
|
|
1413
|
-
await lstat(artifactRoot);
|
|
1414
|
-
throw new Error('RC2 artifact root already exists; immutable evidence is not overwritten');
|
|
1415
|
-
} catch (error) {
|
|
1416
|
-
if (error?.code !== 'ENOENT') throw error;
|
|
1417
|
-
}
|
|
1418
|
-
const files = artifactFiles(options.result);
|
|
1419
|
-
const manifest = {
|
|
1420
|
-
schema: 'lattice.rc2.artifact_manifest.v4',
|
|
1421
|
-
base_sha: options.result.base_sha,
|
|
1422
|
-
result_digest: digestArtifact(options.result.hypothesis_evaluation),
|
|
1423
|
-
files: [...files].map(([relativePath, bytes]) => ({
|
|
1424
|
-
path: relativePath,
|
|
1425
|
-
media_type: artifactMediaType(relativePath),
|
|
1426
|
-
bytes: bytes.byteLength,
|
|
1427
|
-
sha256: sha256(bytes),
|
|
1428
|
-
})).sort((left, right) => left.path.localeCompare(right.path)),
|
|
1429
|
-
};
|
|
1430
|
-
const payloads = [...files].map(([relativePath, bytes]) => ({ path: relativePath, bytes }));
|
|
1431
|
-
const verification = verifyRc2CampaignArtifactSet({ manifest, payloads });
|
|
1432
|
-
if (!verification.valid) {
|
|
1433
|
-
throw new TypeError(
|
|
1434
|
-
`RC2 artifact set is invalid: ${verification.failed_conditions.join(', ')}`,
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
const parent = path.dirname(artifactRoot);
|
|
1438
|
-
await mkdir(parent, { recursive: true });
|
|
1439
|
-
const temporaryRoot = await mkdtemp(path.join(parent, '.v4-write-'));
|
|
1440
|
-
try {
|
|
1441
|
-
for (const [relativePath, bytes] of files) {
|
|
1442
|
-
await writeDurableFile(path.join(temporaryRoot, relativePath), bytes);
|
|
1443
|
-
}
|
|
1444
|
-
await writeDurableFile(path.join(temporaryRoot, 'artifact-manifest.json'), jsonBytes(manifest));
|
|
1445
|
-
await syncArtifactDirectories(temporaryRoot, [
|
|
1446
|
-
...RC2_ARTIFACT_PATHS,
|
|
1447
|
-
'artifact-manifest.json',
|
|
1448
|
-
]);
|
|
1449
|
-
await rename(temporaryRoot, artifactRoot);
|
|
1450
|
-
await syncDirectory(parent);
|
|
1451
|
-
} catch (error) {
|
|
1452
|
-
await rm(temporaryRoot, { recursive: true, force: true });
|
|
1453
|
-
throw error;
|
|
1454
|
-
}
|
|
1455
|
-
return manifest;
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
function artifactPathsForVersion(artifactVersion) {
|
|
1459
|
-
if (artifactVersion === 'v1') return RC2_V1_ARTIFACT_PATHS;
|
|
1460
|
-
if (artifactVersion === 'v2') return RC2_V2_ARTIFACT_PATHS;
|
|
1461
|
-
if (artifactVersion === 'v3') return RC2_V3_ARTIFACT_PATHS;
|
|
1462
|
-
if (artifactVersion === 'v4') return RC2_ARTIFACT_PATHS;
|
|
1463
|
-
return null;
|
|
1464
|
-
}
|
|
1465
|
-
|
|
1466
|
-
function invalidDiskReceipt(condition) {
|
|
1467
|
-
return {
|
|
1468
|
-
schema: 'lattice.rc2.artifact_set_verification.v1',
|
|
1469
|
-
valid: false,
|
|
1470
|
-
checks: [{ id: condition, passed: false }],
|
|
1471
|
-
failed_conditions: [condition],
|
|
1472
|
-
};
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
/** immutable rootを再読し、in-memory resultなしでpure verifierへ渡す。 */
|
|
1476
|
-
export async function verifyRc2CampaignArtifactsOnDisk(options = {}) {
|
|
1477
|
-
if (!exactRecord(options, ['repoRoot', 'artifactVersion'])
|
|
1478
|
-
|| typeof options.repoRoot !== 'string'
|
|
1479
|
-
|| options.repoRoot.length === 0
|
|
1480
|
-
|| artifactPathsForVersion(options.artifactVersion) === null) {
|
|
1481
|
-
throw new TypeError('verifyRc2CampaignArtifactsOnDisk options are invalid');
|
|
1482
|
-
}
|
|
1483
|
-
const artifactRoot = path.join(options.repoRoot, ARTIFACT_ROOTS[options.artifactVersion]);
|
|
1484
|
-
const artifactPaths = artifactPathsForVersion(options.artifactVersion);
|
|
1485
|
-
let manifestBytes;
|
|
1486
|
-
let manifest;
|
|
1487
|
-
try {
|
|
1488
|
-
manifestBytes = await readFile(path.join(artifactRoot, 'artifact-manifest.json'));
|
|
1489
|
-
manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
1490
|
-
} catch {
|
|
1491
|
-
return invalidDiskReceipt('manifest_canonical');
|
|
1492
|
-
}
|
|
1493
|
-
if (!jsonBytes(manifest).equals(manifestBytes)) {
|
|
1494
|
-
return invalidDiskReceipt('manifest_canonical');
|
|
1495
|
-
}
|
|
1496
|
-
let payloads;
|
|
1497
|
-
try {
|
|
1498
|
-
payloads = await Promise.all(artifactPaths.map(async (relativePath) => ({
|
|
1499
|
-
path: relativePath,
|
|
1500
|
-
bytes: await readFile(path.join(artifactRoot, relativePath)),
|
|
1501
|
-
})));
|
|
1502
|
-
} catch {
|
|
1503
|
-
return invalidDiskReceipt('exact_artifact_set');
|
|
1504
|
-
}
|
|
1505
|
-
return verifyRc2CampaignArtifactSet({ manifest, payloads });
|
|
1506
|
-
}
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import {
|
|
4
|
+
lstat,
|
|
5
|
+
mkdir,
|
|
6
|
+
mkdtemp,
|
|
7
|
+
open,
|
|
8
|
+
readFile,
|
|
9
|
+
realpath,
|
|
10
|
+
rename,
|
|
11
|
+
rm,
|
|
12
|
+
writeFile,
|
|
13
|
+
} from 'node:fs/promises';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { performance } from 'node:perf_hooks';
|
|
16
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
canonicalizeArtifact,
|
|
20
|
+
digestArtifact,
|
|
21
|
+
} from './artifact-contracts.mjs';
|
|
22
|
+
import { compileBoundaryCondition } from './boundary-compiler.mjs';
|
|
23
|
+
import { collectSensorEvidence } from './sensor-adapter.mjs';
|
|
24
|
+
import { invokeSensorCli, LATTICE_SENSOR_CLI } from './sensor-runtime.mjs';
|
|
25
|
+
import { runIsolatedTransform } from './isolation-runner.mjs';
|
|
26
|
+
import { runRc1BlackBoxOracle } from './rc1-black-box-oracle.mjs';
|
|
27
|
+
import { createRc1EvidenceBundle } from './rc1-evidence-bundle.mjs';
|
|
28
|
+
import {
|
|
29
|
+
RC2_ARTIFACT_PATHS,
|
|
30
|
+
verifyRc2CampaignArtifactSet,
|
|
31
|
+
} from './rc2-artifact-set.mjs';
|
|
32
|
+
import { compileDeliveryPolicyBoundaryBundleV2 } from './rc2-delivery-policy-front-end.mjs';
|
|
33
|
+
import { runRc2DeliveryPolicyOracle } from './rc2-delivery-policy-oracle.mjs';
|
|
34
|
+
import {
|
|
35
|
+
applyRc2DeliveryPolicyTransform,
|
|
36
|
+
RC2_DELIVERY_POLICY_TRANSFORM_PATHS,
|
|
37
|
+
runRc2DeliveryPolicySeamTransform,
|
|
38
|
+
} from './rc2-delivery-policy-transform.mjs';
|
|
39
|
+
import { compileRc1TransferBundleV2 } from './rc2-rc1-transfer-front-end.mjs';
|
|
40
|
+
import { compileSchedulabilityGraphV2 } from './schedulability-compiler-v2.mjs';
|
|
41
|
+
import { verifySchedulabilityPlanV2 } from './schedulability-verifier-v2.mjs';
|
|
42
|
+
|
|
43
|
+
const ARTIFACT_VERSION = 'v4';
|
|
44
|
+
const ARTIFACT_ROOTS = Object.freeze({
|
|
45
|
+
v1: 'research/campaigns/rc2/artifacts/v1',
|
|
46
|
+
v2: 'research/campaigns/rc2/artifacts/v2',
|
|
47
|
+
v3: 'research/campaigns/rc2/artifacts/v3',
|
|
48
|
+
v4: 'research/campaigns/rc2/artifacts/v4',
|
|
49
|
+
});
|
|
50
|
+
const SENSOR_CONFIG_REPO_PATH = 'lattice-sensor.json';
|
|
51
|
+
const SENSOR_CONFIG_ARTIFACT_PATH = 'identity/lattice-sensor-config.json';
|
|
52
|
+
// live実行は現在ratifiedなconfig(ADR 0053 Decision 3 epoch)との完全一致だけを受理する。
|
|
53
|
+
// 過去epochのartifact検証はrc2-artifact-set.mjsのratified epoch allowlistが担う。
|
|
54
|
+
const SENSOR_CONFIG_BYTES = Buffer.from(
|
|
55
|
+
'{"exclude":["research/campaigns/**/artifacts/**/identity/","research/runs/",".lattice/todo/",".lattice/generated/"]}\n',
|
|
56
|
+
'utf8',
|
|
57
|
+
);
|
|
58
|
+
const V2_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
59
|
+
SENSOR_CONFIG_ARTIFACT_PATH,
|
|
60
|
+
'predecessors/adr-0040.md',
|
|
61
|
+
'predecessors/rc2-v1-artifact-manifest.json',
|
|
62
|
+
'predecessors/rc2-v1-new-plan-version.json',
|
|
63
|
+
]);
|
|
64
|
+
const V3_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
65
|
+
'predecessors/adr-0041.md',
|
|
66
|
+
'predecessors/rc2-semantic-reseal-characterization.md',
|
|
67
|
+
'predecessors/rc2-v2-artifact-manifest.json',
|
|
68
|
+
'predecessors/rc2-v2-new-plan-version.json',
|
|
69
|
+
]);
|
|
70
|
+
const V4_ONLY_ARTIFACT_PATHS = Object.freeze([
|
|
71
|
+
'predecessors/adr-0042.md',
|
|
72
|
+
'predecessors/rc2-v3-version-downgrade-refutation.md',
|
|
73
|
+
'predecessors/rc2-v3-artifact-manifest.json',
|
|
74
|
+
'predecessors/rc2-v3-new-plan-version.json',
|
|
75
|
+
]);
|
|
76
|
+
const RC2_V3_ARTIFACT_PATHS = Object.freeze(RC2_ARTIFACT_PATHS.filter((relativePath) => (
|
|
77
|
+
!V4_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
78
|
+
)));
|
|
79
|
+
const RC2_V2_ARTIFACT_PATHS = Object.freeze(RC2_V3_ARTIFACT_PATHS.filter((relativePath) => (
|
|
80
|
+
!V3_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
81
|
+
)));
|
|
82
|
+
const RC2_V1_ARTIFACT_PATHS = Object.freeze(RC2_V2_ARTIFACT_PATHS.filter((relativePath) => (
|
|
83
|
+
!V2_ONLY_ARTIFACT_PATHS.includes(relativePath)
|
|
84
|
+
)));
|
|
85
|
+
const GIT_SHA1 = /^[0-9a-f]{40}$/;
|
|
86
|
+
const SEMVER = /^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9.-]+)?$/;
|
|
87
|
+
const RAW_EVIDENCE_CHUNK_BYTES = 12_000;
|
|
88
|
+
|
|
89
|
+
const PRIMARY_INPUT_FILES = Object.freeze({
|
|
90
|
+
planInput: 'research/campaigns/rc2/inputs/plan-input.json',
|
|
91
|
+
candidateSpec: 'research/campaigns/rc2/inputs/candidate-spec-v1.json',
|
|
92
|
+
normalManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.normal.json',
|
|
93
|
+
partialManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.partial-state-negative.json',
|
|
94
|
+
unknownManualEvidence: 'research/campaigns/rc2/inputs/manual-evidence.third-only-unknown.json',
|
|
95
|
+
querySet: 'research/campaigns/rc2/inputs/query-set-v2.json',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const RC1_ARTIFACT_ROOT = 'research/campaigns/rc1/artifacts/v6';
|
|
99
|
+
const RC1_INPUT_FILES = Object.freeze({
|
|
100
|
+
planInput: `${RC1_ARTIFACT_ROOT}/inputs/plan-input.json`,
|
|
101
|
+
candidateSpec: `${RC1_ARTIFACT_ROOT}/inputs/candidate-spec-v2.json`,
|
|
102
|
+
normalManualEvidence: `${RC1_ARTIFACT_ROOT}/inputs/manual-evidence.normal.json`,
|
|
103
|
+
negativeManualEvidence: `${RC1_ARTIFACT_ROOT}/inputs/manual-evidence.shared-state-negative.json`,
|
|
104
|
+
querySet: `${RC1_ARTIFACT_ROOT}/inputs/query-set-v2.json`,
|
|
105
|
+
oracle: `${RC1_ARTIFACT_ROOT}/inputs/behavior-oracle-v2.json`,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const PRIMARY_ARTIFACT_INPUTS = Object.freeze({
|
|
109
|
+
planInput: 'inputs/primary/plan-input.json',
|
|
110
|
+
candidateSpec: 'inputs/primary/candidate-spec-v1.json',
|
|
111
|
+
normalManualEvidence: 'inputs/primary/manual-evidence.normal.json',
|
|
112
|
+
partialManualEvidence: 'inputs/primary/manual-evidence.partial-state-negative.json',
|
|
113
|
+
unknownManualEvidence: 'inputs/primary/manual-evidence.third-only-unknown.json',
|
|
114
|
+
querySet: 'inputs/primary/query-set-v2.json',
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const RC1_ARTIFACT_INPUTS = Object.freeze({
|
|
118
|
+
planInput: 'inputs/rc1/plan-input.json',
|
|
119
|
+
candidateSpec: 'inputs/rc1/candidate-spec-v2.json',
|
|
120
|
+
normalManualEvidence: 'inputs/rc1/manual-evidence.normal.json',
|
|
121
|
+
negativeManualEvidence: 'inputs/rc1/manual-evidence.shared-state-negative.json',
|
|
122
|
+
querySet: 'inputs/rc1/query-set-v2.json',
|
|
123
|
+
oracle: 'inputs/rc1/behavior-oracle-v2.json',
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const SOURCE_IDENTITIES = Object.freeze([
|
|
127
|
+
['src/sensor-adapter.mjs', 'identity/lattice-sensor-adapter.mjs', new URL('./sensor-adapter.mjs', import.meta.url)],
|
|
128
|
+
['src/rc1-black-box-oracle.mjs', 'identity/rc1-black-box-oracle.mjs', new URL('./rc1-black-box-oracle.mjs', import.meta.url)],
|
|
129
|
+
['src/boundary-compiler.mjs', 'identity/rc1-boundary-compiler.mjs', new URL('./boundary-compiler.mjs', import.meta.url)],
|
|
130
|
+
['src/rc1-evidence-bundle.mjs', 'identity/rc1-evidence-bundle.mjs', new URL('./rc1-evidence-bundle.mjs', import.meta.url)],
|
|
131
|
+
['src/rc2-artifact-set.mjs', 'identity/rc2-artifact-set.mjs', new URL('./rc2-artifact-set.mjs', import.meta.url)],
|
|
132
|
+
['src/rc2-campaign.mjs', 'identity/rc2-campaign.mjs', new URL(import.meta.url)],
|
|
133
|
+
['src/rc2-delivery-policy-front-end.mjs', 'identity/rc2-delivery-policy-front-end.mjs', new URL('./rc2-delivery-policy-front-end.mjs', import.meta.url)],
|
|
134
|
+
['src/rc2-delivery-policy-oracle.mjs', 'identity/rc2-delivery-policy-oracle.mjs', new URL('./rc2-delivery-policy-oracle.mjs', import.meta.url)],
|
|
135
|
+
['src/rc2-delivery-policy-transform.mjs', 'identity/rc2-delivery-policy-transform.mjs', new URL('./rc2-delivery-policy-transform.mjs', import.meta.url)],
|
|
136
|
+
['src/rc2-rc1-transfer-front-end.mjs', 'identity/rc2-rc1-transfer-front-end.mjs', new URL('./rc2-rc1-transfer-front-end.mjs', import.meta.url)],
|
|
137
|
+
['src/schedulability-compiler-v2.mjs', 'identity/schedulability-compiler-v2.mjs', new URL('./schedulability-compiler-v2.mjs', import.meta.url)],
|
|
138
|
+
['src/schedulability-verifier-v2.mjs', 'identity/schedulability-verifier-v2.mjs', new URL('./schedulability-verifier-v2.mjs', import.meta.url)],
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const PREDECESSOR_FILES = Object.freeze({
|
|
142
|
+
'predecessors/adr-0031.md': 'docs/adr/0031-rc1-v6-phase-gate-support.md',
|
|
143
|
+
'predecessors/adr-0032.md': 'docs/adr/0032-rc2-bounded-graph-compiler-and-three-way-seam.md',
|
|
144
|
+
'predecessors/adr-0037.md': 'docs/adr/0037-rc2-delivery-policy-transform-transaction.md',
|
|
145
|
+
'predecessors/adr-0038.md': 'docs/adr/0038-rc2-closed-loop-version-and-artifact-contract.md',
|
|
146
|
+
'predecessors/rc1-v6-artifact-manifest.json': `${RC1_ARTIFACT_ROOT}/artifact-manifest.json`,
|
|
147
|
+
'predecessors/rc1-v6-plan.md': 'docs/archive/2026-07-16-plan-lattice-research-campaign-1-v6-phase-supported.md',
|
|
148
|
+
'predecessors/rc1-v6-seam.patch': `${RC1_ARTIFACT_ROOT}/transform/seam.patch`,
|
|
149
|
+
'predecessors/rc1-v6-transform-artifact.json': `${RC1_ARTIFACT_ROOT}/transform/transform-artifact.json`,
|
|
150
|
+
'predecessors/rc1-v6-transform-receipt.json': `${RC1_ARTIFACT_ROOT}/transform/transform-receipt.json`,
|
|
151
|
+
'predecessors/adr-0040.md': 'docs/adr/0040-rc2-post-publication-lattice-sensor-scope-and-artifact-v2.md',
|
|
152
|
+
'predecessors/rc2-v1-artifact-manifest.json': `${ARTIFACT_ROOTS.v1}/artifact-manifest.json`,
|
|
153
|
+
'predecessors/rc2-v1-new-plan-version.json': `${ARTIFACT_ROOTS.v1}/new-plan-version.json`,
|
|
154
|
+
'predecessors/adr-0041.md': 'docs/adr/0041-rc2-artifact-semantic-oracle-mutation-binding.md',
|
|
155
|
+
'predecessors/rc2-semantic-reseal-characterization.md': 'docs/evidence/2026-07-16-rc2-artifact-semantic-reseal-characterization.md',
|
|
156
|
+
'predecessors/rc2-v2-artifact-manifest.json': `${ARTIFACT_ROOTS.v2}/artifact-manifest.json`,
|
|
157
|
+
'predecessors/rc2-v2-new-plan-version.json': `${ARTIFACT_ROOTS.v2}/new-plan-version.json`,
|
|
158
|
+
'predecessors/adr-0042.md': 'docs/adr/0042-rc2-artifact-version-witness-epoch-and-v4.md',
|
|
159
|
+
'predecessors/rc2-v3-version-downgrade-refutation.md': 'docs/evidence/2026-07-16-rc2-v3-version-downgrade-refutation.md',
|
|
160
|
+
'predecessors/rc2-v3-artifact-manifest.json': `${ARTIFACT_ROOTS.v3}/artifact-manifest.json`,
|
|
161
|
+
'predecessors/rc2-v3-new-plan-version.json': `${ARTIFACT_ROOTS.v3}/new-plan-version.json`,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const RUN_IDS = Object.freeze([
|
|
165
|
+
'primary-control-1',
|
|
166
|
+
'primary-control-2',
|
|
167
|
+
'primary-treatment-1',
|
|
168
|
+
'primary-treatment-2',
|
|
169
|
+
'rc1-transfer-control-1',
|
|
170
|
+
'rc1-transfer-treatment-1',
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
function plainRecord(value) {
|
|
174
|
+
return value !== null
|
|
175
|
+
&& typeof value === 'object'
|
|
176
|
+
&& !Array.isArray(value)
|
|
177
|
+
&& Object.getPrototypeOf(value) === Object.prototype;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function exactRecord(value, keys) {
|
|
181
|
+
if (!plainRecord(value)) return false;
|
|
182
|
+
const actual = Object.keys(value).sort();
|
|
183
|
+
const expected = [...keys].sort();
|
|
184
|
+
return actual.length === expected.length
|
|
185
|
+
&& actual.every((key, index) => key === expected[index]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function sha256(bytes) {
|
|
189
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function jsonBytes(value) {
|
|
193
|
+
return Buffer.from(canonicalizeArtifact(value), 'utf8');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function roundedMilliseconds(startedAt) {
|
|
197
|
+
return Math.round((performance.now() - startedAt) * 1_000) / 1_000;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function run(command, args, { cwd, input, allowExitCodes = [0] } = {}) {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
const child = spawn(command, args, {
|
|
203
|
+
cwd,
|
|
204
|
+
shell: false,
|
|
205
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
206
|
+
env: { ...process.env, NO_COLOR: '1' },
|
|
207
|
+
});
|
|
208
|
+
const stdout = [];
|
|
209
|
+
const stderr = [];
|
|
210
|
+
child.stdout.on('data', (chunk) => stdout.push(chunk));
|
|
211
|
+
child.stderr.on('data', (chunk) => stderr.push(chunk));
|
|
212
|
+
child.once('error', reject);
|
|
213
|
+
child.once('close', (code, signal) => {
|
|
214
|
+
const result = {
|
|
215
|
+
code,
|
|
216
|
+
signal,
|
|
217
|
+
stdout: Buffer.concat(stdout),
|
|
218
|
+
stderr: Buffer.concat(stderr),
|
|
219
|
+
};
|
|
220
|
+
if (allowExitCodes.includes(code) && signal === null) resolve(result);
|
|
221
|
+
else reject(Object.assign(
|
|
222
|
+
new Error(`${command} failed (${signal ?? code}): ${result.stderr.toString('utf8').trim()}`),
|
|
223
|
+
result,
|
|
224
|
+
));
|
|
225
|
+
});
|
|
226
|
+
child.stdin.end(input);
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function captureExecutionIdentity(repoRoot) {
|
|
231
|
+
const sources = [];
|
|
232
|
+
const payloads = new Map();
|
|
233
|
+
for (const [runtimePath, artifactRef, sourceUrl] of SOURCE_IDENTITIES) {
|
|
234
|
+
const bytes = await readFile(sourceUrl);
|
|
235
|
+
payloads.set(artifactRef, bytes);
|
|
236
|
+
sources.push({ runtime_path: runtimePath, artifact_ref: artifactRef, digest: sha256(bytes) });
|
|
237
|
+
}
|
|
238
|
+
const executableBytes = await readFile(LATTICE_SENSOR_CLI);
|
|
239
|
+
const version = (await run(process.execPath, [LATTICE_SENSOR_CLI, '--version'])).stdout.toString('utf8').trim();
|
|
240
|
+
if (!SEMVER.test(version)) throw new TypeError('LatticeSensor versionがsemverではない');
|
|
241
|
+
const projectConfigBytes = await readFile(path.join(repoRoot, SENSOR_CONFIG_REPO_PATH));
|
|
242
|
+
if (!projectConfigBytes.equals(SENSOR_CONFIG_BYTES)) {
|
|
243
|
+
throw new TypeError('LatticeSensor project config bytesがRC2 v2 contractと一致しない');
|
|
244
|
+
}
|
|
245
|
+
const sensorIdentity = {
|
|
246
|
+
schema: 'lattice.rc2.sensor_identity.v2',
|
|
247
|
+
version,
|
|
248
|
+
executable_ref: 'lattice-sensor',
|
|
249
|
+
executable_digest: sha256(executableBytes),
|
|
250
|
+
project_config_ref: SENSOR_CONFIG_ARTIFACT_PATH,
|
|
251
|
+
project_config_digest: sha256(projectConfigBytes),
|
|
252
|
+
};
|
|
253
|
+
payloads.set('identity/lattice-sensor-executable', executableBytes);
|
|
254
|
+
payloads.set(SENSOR_CONFIG_ARTIFACT_PATH, projectConfigBytes);
|
|
255
|
+
const snapshot = { sources, sensor_identity: sensorIdentity };
|
|
256
|
+
return { snapshot, digest: digestArtifact(snapshot), payloads };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function readJson(repoRoot, relativePath) {
|
|
260
|
+
return JSON.parse(await readFile(path.join(repoRoot, relativePath), 'utf8'));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function loadInputs(repoRoot) {
|
|
264
|
+
const primary = {};
|
|
265
|
+
const rc1 = {};
|
|
266
|
+
for (const [key, relativePath] of Object.entries(PRIMARY_INPUT_FILES)) {
|
|
267
|
+
primary[key] = await readJson(repoRoot, relativePath);
|
|
268
|
+
}
|
|
269
|
+
for (const [key, relativePath] of Object.entries(RC1_INPUT_FILES)) {
|
|
270
|
+
rc1[key] = await readJson(repoRoot, relativePath);
|
|
271
|
+
}
|
|
272
|
+
return { primary, rc1 };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function loadPredecessors(repoRoot) {
|
|
276
|
+
const payloads = new Map();
|
|
277
|
+
for (const [artifactPath, repoPath] of Object.entries(PREDECESSOR_FILES)) {
|
|
278
|
+
payloads.set(artifactPath, await readFile(path.join(repoRoot, repoPath)));
|
|
279
|
+
}
|
|
280
|
+
return payloads;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function loadV3PlanPredecessor(payloads) {
|
|
284
|
+
const manifestBytes = payloads.get('predecessors/rc2-v3-artifact-manifest.json');
|
|
285
|
+
const planBytes = payloads.get('predecessors/rc2-v3-new-plan-version.json');
|
|
286
|
+
let manifest;
|
|
287
|
+
let plan;
|
|
288
|
+
try {
|
|
289
|
+
manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
290
|
+
plan = JSON.parse(planBytes.toString('utf8'));
|
|
291
|
+
} catch {
|
|
292
|
+
throw new TypeError('RC2 v3 predecessor JSONをparseできない');
|
|
293
|
+
}
|
|
294
|
+
const paths = Array.isArray(manifest?.files)
|
|
295
|
+
? manifest.files.map(({ path: relativePath }) => relativePath).sort()
|
|
296
|
+
: [];
|
|
297
|
+
const entry = manifest?.files?.find(({ path: relativePath }) => (
|
|
298
|
+
relativePath === 'new-plan-version.json'
|
|
299
|
+
));
|
|
300
|
+
if (!jsonBytes(manifest).equals(manifestBytes)
|
|
301
|
+
|| !jsonBytes(plan).equals(planBytes)
|
|
302
|
+
|| manifest.schema !== 'lattice.rc2.artifact_manifest.v3'
|
|
303
|
+
|| !isDeepStrictEqual(paths, [...RC2_V3_ARTIFACT_PATHS])
|
|
304
|
+
|| !exactRecord(entry, ['path', 'media_type', 'bytes', 'sha256'])
|
|
305
|
+
|| entry.media_type !== 'application/json'
|
|
306
|
+
|| entry.bytes !== planBytes.byteLength
|
|
307
|
+
|| entry.sha256 !== sha256(planBytes)
|
|
308
|
+
|| plan.schema !== 'lattice.rc2.plan_version.v1'
|
|
309
|
+
|| plan.version !== 'rc2-delivery-policy-v4'
|
|
310
|
+
|| plan.plan_digest !== digestArtifact(plan.plan)) {
|
|
311
|
+
throw new TypeError('RC2 v3 predecessor manifest/plan bindingが不正');
|
|
312
|
+
}
|
|
313
|
+
return plan;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function resolveRepository(repoRoot, baseRef) {
|
|
317
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0
|
|
318
|
+
|| typeof baseRef !== 'string' || baseRef.length === 0) {
|
|
319
|
+
throw new TypeError('repoRoot and baseRef are required');
|
|
320
|
+
}
|
|
321
|
+
const root = await realpath(repoRoot);
|
|
322
|
+
const status = await run('git', ['status', '--porcelain=v1', '-z'], { cwd: root });
|
|
323
|
+
if (status.stdout.length > 0) throw new TypeError('RC2 campaign source repository must be clean');
|
|
324
|
+
const head = (await run('git', ['rev-parse', '--verify', 'HEAD^{commit}'], { cwd: root }))
|
|
325
|
+
.stdout.toString('utf8').trim();
|
|
326
|
+
const baseSha = (await run('git', ['rev-parse', '--verify', `${baseRef}^{commit}`], { cwd: root }))
|
|
327
|
+
.stdout.toString('utf8').trim();
|
|
328
|
+
if (!GIT_SHA1.test(head) || !GIT_SHA1.test(baseSha) || head !== baseSha) {
|
|
329
|
+
throw new TypeError('RC2 campaign requires clean HEAD === baseRef');
|
|
330
|
+
}
|
|
331
|
+
return { root, baseSha };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function surfacePaths(candidateSpec) {
|
|
335
|
+
const result = new Set();
|
|
336
|
+
if (candidateSpec.fixed_oracle?.path) result.add(candidateSpec.fixed_oracle.path);
|
|
337
|
+
for (const todo of candidateSpec.todos) {
|
|
338
|
+
for (const mode of ['current', 'proposed']) {
|
|
339
|
+
result.add(todo[mode].production.path);
|
|
340
|
+
todo[mode].tests.forEach(({ path: testPath }) => result.add(testPath));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return [...result].sort();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function captureSourceSnapshot(repoRoot, paths) {
|
|
347
|
+
const files = [];
|
|
348
|
+
for (const relativePath of paths) {
|
|
349
|
+
const absolutePath = path.join(repoRoot, relativePath);
|
|
350
|
+
try {
|
|
351
|
+
const stat = await lstat(absolutePath);
|
|
352
|
+
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
353
|
+
throw new TypeError(`snapshot pathが通常fileではない: ${relativePath}`);
|
|
354
|
+
}
|
|
355
|
+
files.push({ path: relativePath, state: 'file', content_digest: sha256(await readFile(absolutePath)) });
|
|
356
|
+
} catch (error) {
|
|
357
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
358
|
+
files.push({ path: relativePath, state: 'absent', content_digest: null });
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return { schema_version: 'lattice.rc2.source_snapshot.v1', files };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function applyPatch(worktreePath, patch) {
|
|
365
|
+
if (!Buffer.isBuffer(patch) || patch.length === 0) throw new TypeError('accepted patch bytes are required');
|
|
366
|
+
await run('git', ['apply', '--check', '--binary', '-'], { cwd: worktreePath, input: patch });
|
|
367
|
+
await run('git', ['apply', '--binary', '-'], { cwd: worktreePath, input: patch });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function captureLatticeSensorBootstrap(worktreePath) {
|
|
371
|
+
try {
|
|
372
|
+
return await readFile(path.join(worktreePath, '.lattice/sensor', '.gitignore'));
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error?.code === 'ENOENT') return null;
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function restoreLatticeSensorBootstrap(worktreePath, bootstrap) {
|
|
380
|
+
const sensorPath = path.join(worktreePath, '.lattice/sensor');
|
|
381
|
+
await rm(sensorPath, { recursive: true, force: true });
|
|
382
|
+
if (bootstrap === null) return;
|
|
383
|
+
await mkdir(sensorPath, { recursive: true });
|
|
384
|
+
await writeFile(path.join(sensorPath, '.gitignore'), bootstrap);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function worktreeCount(repoRoot) {
|
|
388
|
+
const output = (await run('git', ['worktree', 'list', '--porcelain'], { cwd: repoRoot }))
|
|
389
|
+
.stdout.toString('utf8');
|
|
390
|
+
return output.split('\n').filter((line) => line.startsWith('worktree ')).length;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function measured(elapsedMs) {
|
|
394
|
+
return { state: 'measured', elapsed_ms: elapsedMs };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async function observeFreshIndex({
|
|
398
|
+
repoRoot,
|
|
399
|
+
baseSha,
|
|
400
|
+
family,
|
|
401
|
+
condition,
|
|
402
|
+
runId,
|
|
403
|
+
querySet,
|
|
404
|
+
paths,
|
|
405
|
+
patch,
|
|
406
|
+
allowedPaths,
|
|
407
|
+
sensorIdentity,
|
|
408
|
+
oracle,
|
|
409
|
+
rc1Inputs,
|
|
410
|
+
costMeasurements,
|
|
411
|
+
}) {
|
|
412
|
+
const beforeCount = await worktreeCount(repoRoot);
|
|
413
|
+
let rawEvidence;
|
|
414
|
+
let snapshot;
|
|
415
|
+
let oracleReceipt;
|
|
416
|
+
let isolationInstanceDigest;
|
|
417
|
+
let indexElapsedMs;
|
|
418
|
+
let queryElapsedMs;
|
|
419
|
+
let oracleElapsedMs;
|
|
420
|
+
const isolated = await runIsolatedTransform({
|
|
421
|
+
repoRoot,
|
|
422
|
+
baseRef: baseSha,
|
|
423
|
+
allowedPaths,
|
|
424
|
+
transform: async ({ worktreePath }) => {
|
|
425
|
+
if (condition === 'treatment') await applyPatch(worktreePath, patch);
|
|
426
|
+
},
|
|
427
|
+
verifyCommands: [],
|
|
428
|
+
observe: async ({ worktreePath }) => {
|
|
429
|
+
isolationInstanceDigest = sha256(Buffer.from(worktreePath, 'utf8'));
|
|
430
|
+
snapshot = await captureSourceSnapshot(worktreePath, paths);
|
|
431
|
+
const oracleStarted = performance.now();
|
|
432
|
+
oracleReceipt = family === 'primary'
|
|
433
|
+
? await runRc2DeliveryPolicyOracle({ repoRoot: worktreePath })
|
|
434
|
+
: await runRc1BlackBoxOracle({ repoRoot: worktreePath, oracle });
|
|
435
|
+
oracleElapsedMs = roundedMilliseconds(oracleStarted);
|
|
436
|
+
if (oracleReceipt.outcome !== 'passed') throw new TypeError(`${runId} oracle failed`);
|
|
437
|
+
const bootstrap = await captureLatticeSensorBootstrap(worktreePath);
|
|
438
|
+
try {
|
|
439
|
+
const indexStarted = performance.now();
|
|
440
|
+
await invokeSensorCli(run, ['init', '.'], { cwd: worktreePath });
|
|
441
|
+
indexElapsedMs = roundedMilliseconds(indexStarted);
|
|
442
|
+
const queryStarted = performance.now();
|
|
443
|
+
rawEvidence = await collectSensorEvidence({ cwd: worktreePath, querySet });
|
|
444
|
+
queryElapsedMs = roundedMilliseconds(queryStarted);
|
|
445
|
+
} finally {
|
|
446
|
+
await restoreLatticeSensorBootstrap(worktreePath, bootstrap);
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
const afterCount = await worktreeCount(repoRoot);
|
|
451
|
+
if (!rawEvidence || !snapshot || !oracleReceipt || !isolationInstanceDigest
|
|
452
|
+
|| isolated.sourceInvariant?.outcome !== 'passed'
|
|
453
|
+
|| beforeCount !== afterCount) {
|
|
454
|
+
throw new TypeError(`${runId} fresh observation is incomplete`);
|
|
455
|
+
}
|
|
456
|
+
if (condition === 'treatment' && !isolated.patch.equals(patch)) {
|
|
457
|
+
throw new TypeError(`${runId} did not replay the accepted patch exactly`);
|
|
458
|
+
}
|
|
459
|
+
if (condition === 'control' && isolated.patch.length !== 0) {
|
|
460
|
+
throw new TypeError(`${runId} control unexpectedly changed source`);
|
|
461
|
+
}
|
|
462
|
+
const evidence = createRc1EvidenceBundle({ condition, runId, querySet, rawEvidence });
|
|
463
|
+
const patchDigest = condition === 'treatment' ? sha256(isolated.patch) : null;
|
|
464
|
+
const measurement = {
|
|
465
|
+
schema: 'lattice.rc2.sensor_measurement.v1',
|
|
466
|
+
base_sha: baseSha,
|
|
467
|
+
patch_digest: patchDigest,
|
|
468
|
+
snapshot,
|
|
469
|
+
snapshot_digest: digestArtifact(snapshot),
|
|
470
|
+
sensor_identity: structuredClone(sensorIdentity),
|
|
471
|
+
sensor_identity_digest: digestArtifact(sensorIdentity),
|
|
472
|
+
query_set_digest: digestArtifact(querySet),
|
|
473
|
+
raw_evidence_digest: evidence.raw.payload_digest,
|
|
474
|
+
};
|
|
475
|
+
const cost = {
|
|
476
|
+
index: measured(indexElapsedMs),
|
|
477
|
+
query: measured(queryElapsedMs),
|
|
478
|
+
oracle: measured(oracleElapsedMs),
|
|
479
|
+
};
|
|
480
|
+
for (const [stage, value] of Object.entries(cost)) {
|
|
481
|
+
costMeasurements.push({
|
|
482
|
+
stage: `run.${runId}.${stage}`,
|
|
483
|
+
state: value.state,
|
|
484
|
+
elapsed_ms: value.elapsed_ms,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const runRecord = {
|
|
488
|
+
schema: 'lattice.rc2.fresh_sensor_run.v1',
|
|
489
|
+
family,
|
|
490
|
+
condition,
|
|
491
|
+
run_id: runId,
|
|
492
|
+
fresh_index: true,
|
|
493
|
+
isolation_instance_digest: isolationInstanceDigest,
|
|
494
|
+
evidence,
|
|
495
|
+
measurement,
|
|
496
|
+
oracle_receipt: oracleReceipt,
|
|
497
|
+
legacy_boundary_manifests: null,
|
|
498
|
+
source_invariant: isolated.sourceInvariant,
|
|
499
|
+
cleanup: {
|
|
500
|
+
schema: 'lattice.rc2.worktree_cleanup_receipt.v1',
|
|
501
|
+
outcome: 'passed',
|
|
502
|
+
worktree_count_before: beforeCount,
|
|
503
|
+
worktree_count_after: afterCount,
|
|
504
|
+
},
|
|
505
|
+
cost,
|
|
506
|
+
};
|
|
507
|
+
if (family === 'rc1_transfer') {
|
|
508
|
+
const raw = JSON.parse(Buffer.from(evidence.raw.payload_base64, 'base64').toString('utf8'));
|
|
509
|
+
const compileLegacy = (manualEvidence, label) => compileBoundaryCondition({
|
|
510
|
+
planInput: rc1Inputs.planInput,
|
|
511
|
+
candidateSpec: rc1Inputs.candidateSpec,
|
|
512
|
+
manualEvidence,
|
|
513
|
+
querySet: rc1Inputs.querySet,
|
|
514
|
+
sensorEvidence: raw,
|
|
515
|
+
codeSnapshotDigest: measurement.snapshot_digest,
|
|
516
|
+
planVersion: `rc2-${condition}-transfer-${label}`,
|
|
517
|
+
});
|
|
518
|
+
runRecord.legacy_boundary_manifests = {
|
|
519
|
+
normal: compileLegacy(rc1Inputs.normalManualEvidence, 'normal').boundary_manifest,
|
|
520
|
+
negative: compileLegacy(rc1Inputs.negativeManualEvidence, 'negative').boundary_manifest,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
return runRecord;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function verdictFor(bundle, compiled) {
|
|
527
|
+
return {
|
|
528
|
+
schema_version: 'lattice.boundary_verdict.v2',
|
|
529
|
+
normalized_graph_digest: bundle.graph_digest,
|
|
530
|
+
verdicts: compiled.pairwise_verdicts,
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function timedSync(stage, costMeasurements, execute) {
|
|
535
|
+
const startedAt = performance.now();
|
|
536
|
+
const value = execute();
|
|
537
|
+
costMeasurements.push({ stage, state: 'measured', elapsed_ms: roundedMilliseconds(startedAt) });
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function compiledRecord({ condition, runId, bundle, costMeasurements }) {
|
|
542
|
+
const compiled = timedSync(`compile.${condition}.${runId}`, costMeasurements, () => (
|
|
543
|
+
compileSchedulabilityGraphV2(bundle.graph)
|
|
544
|
+
));
|
|
545
|
+
if (compiled.outcome !== 'compiled') throw new TypeError(`${condition} did not compile`);
|
|
546
|
+
const verdict = verdictFor(bundle, compiled);
|
|
547
|
+
const verification = timedSync(`verify.${condition}.${runId}`, costMeasurements, () => (
|
|
548
|
+
verifySchedulabilityPlanV2(bundle.graph, compiled.plan)
|
|
549
|
+
));
|
|
550
|
+
if (verification.outcome !== 'verified') throw new TypeError(`${condition} minimum is unverified`);
|
|
551
|
+
const stable = { bundle, verdict, plan: compiled.plan, verification };
|
|
552
|
+
return {
|
|
553
|
+
schema: 'lattice.rc2.compiled_condition.v1',
|
|
554
|
+
condition,
|
|
555
|
+
run_id: runId,
|
|
556
|
+
outcome: 'compiled',
|
|
557
|
+
...stable,
|
|
558
|
+
artifact_digest: digestArtifact(stable),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function compilePrimary({ runRecord, inputs, manualEvidence, planInput, condition, costMeasurements }) {
|
|
563
|
+
const bundle = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
564
|
+
compileDeliveryPolicyBoundaryBundleV2({
|
|
565
|
+
planInput,
|
|
566
|
+
candidateSpec: inputs.candidateSpec,
|
|
567
|
+
manualEvidence,
|
|
568
|
+
querySet: inputs.querySet,
|
|
569
|
+
sourceSnapshot: runRecord.measurement.snapshot,
|
|
570
|
+
sensorEvidence: runRecord.evidence.portable,
|
|
571
|
+
})
|
|
572
|
+
));
|
|
573
|
+
return compiledRecord({ condition, runId: runRecord.run_id, bundle, costMeasurements });
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function compileUnknown({ runRecord, inputs, costMeasurements }) {
|
|
577
|
+
const condition = 'primary-treatment-third-only-unknown';
|
|
578
|
+
const bundle = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
579
|
+
compileDeliveryPolicyBoundaryBundleV2({
|
|
580
|
+
planInput: inputs.planInput,
|
|
581
|
+
candidateSpec: inputs.candidateSpec,
|
|
582
|
+
manualEvidence: inputs.unknownManualEvidence,
|
|
583
|
+
querySet: inputs.querySet,
|
|
584
|
+
sourceSnapshot: runRecord.measurement.snapshot,
|
|
585
|
+
sensorEvidence: runRecord.evidence.portable,
|
|
586
|
+
})
|
|
587
|
+
));
|
|
588
|
+
const outcome = timedSync(`compile.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
589
|
+
compileSchedulabilityGraphV2(bundle.graph)
|
|
590
|
+
));
|
|
591
|
+
const verification = timedSync(`verify.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
592
|
+
verifySchedulabilityPlanV2(bundle.graph, {
|
|
593
|
+
schema_version: 'lattice.plan_graph.v2',
|
|
594
|
+
waves: [],
|
|
595
|
+
minimum_feasible_waves: 0,
|
|
596
|
+
})
|
|
597
|
+
));
|
|
598
|
+
if (outcome.outcome !== 'unknown' || verification.outcome !== 'unknown') {
|
|
599
|
+
throw new TypeError('third-only unknown became dispatchable');
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
schema: 'lattice.rc2.non_dispatchable_condition.v1',
|
|
603
|
+
condition,
|
|
604
|
+
run_id: runRecord.run_id,
|
|
605
|
+
bundle,
|
|
606
|
+
outcome,
|
|
607
|
+
verification,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function compileTransfer({ runRecord, inputs, manualEvidence, label, costMeasurements }) {
|
|
612
|
+
const condition = `rc1-transfer-${runRecord.condition}-${label}`;
|
|
613
|
+
const transferred = timedSync(`front-end.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
614
|
+
compileRc1TransferBundleV2({
|
|
615
|
+
planInput: inputs.planInput,
|
|
616
|
+
candidateSpec: inputs.candidateSpec,
|
|
617
|
+
manualEvidence,
|
|
618
|
+
querySet: inputs.querySet,
|
|
619
|
+
boundaryManifest: runRecord.legacy_boundary_manifests[label],
|
|
620
|
+
})
|
|
621
|
+
));
|
|
622
|
+
const verification = timedSync(`verify.${condition}.${runRecord.run_id}`, costMeasurements, () => (
|
|
623
|
+
verifySchedulabilityPlanV2(transferred.bundle.graph, transferred.plan)
|
|
624
|
+
));
|
|
625
|
+
if (verification.outcome !== 'verified') throw new TypeError(`${condition} minimum is unverified`);
|
|
626
|
+
const stable = {
|
|
627
|
+
bundle: transferred.bundle,
|
|
628
|
+
verdict: transferred.verdict,
|
|
629
|
+
plan: transferred.plan,
|
|
630
|
+
verification,
|
|
631
|
+
};
|
|
632
|
+
return {
|
|
633
|
+
schema: 'lattice.rc2.compiled_condition.v1',
|
|
634
|
+
condition,
|
|
635
|
+
run_id: runRecord.run_id,
|
|
636
|
+
outcome: 'compiled',
|
|
637
|
+
...stable,
|
|
638
|
+
artifact_digest: digestArtifact(stable),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function compileAllConditions({ runs, inputs, costMeasurements }) {
|
|
643
|
+
const primaryControl = runs.primary.control.map((runRecord) => compilePrimary({
|
|
644
|
+
runRecord,
|
|
645
|
+
inputs: inputs.primary,
|
|
646
|
+
manualEvidence: inputs.primary.normalManualEvidence,
|
|
647
|
+
planInput: inputs.primary.planInput,
|
|
648
|
+
condition: 'primary-control-normal',
|
|
649
|
+
costMeasurements,
|
|
650
|
+
}));
|
|
651
|
+
const primaryTreatment = runs.primary.treatment.map((runRecord) => compilePrimary({
|
|
652
|
+
runRecord,
|
|
653
|
+
inputs: inputs.primary,
|
|
654
|
+
manualEvidence: inputs.primary.normalManualEvidence,
|
|
655
|
+
planInput: inputs.primary.planInput,
|
|
656
|
+
condition: 'primary-treatment-normal',
|
|
657
|
+
costMeasurements,
|
|
658
|
+
}));
|
|
659
|
+
if (new Set(primaryControl.map(({ artifact_digest: digest }) => digest)).size !== 1
|
|
660
|
+
|| new Set(primaryTreatment.map(({ artifact_digest: digest }) => digest)).size !== 1) {
|
|
661
|
+
throw new TypeError('primary repeated compilation is not structurally reproducible');
|
|
662
|
+
}
|
|
663
|
+
const treatmentRun = runs.primary.treatment[0];
|
|
664
|
+
const capacityPlan = structuredClone(inputs.primary.planInput);
|
|
665
|
+
capacityPlan.capacity.writers = 2;
|
|
666
|
+
const partialState = compilePrimary({
|
|
667
|
+
runRecord: treatmentRun,
|
|
668
|
+
inputs: inputs.primary,
|
|
669
|
+
manualEvidence: inputs.primary.partialManualEvidence,
|
|
670
|
+
planInput: inputs.primary.planInput,
|
|
671
|
+
condition: 'primary-treatment-partial-state',
|
|
672
|
+
costMeasurements,
|
|
673
|
+
});
|
|
674
|
+
const capacity2 = compilePrimary({
|
|
675
|
+
runRecord: treatmentRun,
|
|
676
|
+
inputs: inputs.primary,
|
|
677
|
+
manualEvidence: inputs.primary.normalManualEvidence,
|
|
678
|
+
planInput: capacityPlan,
|
|
679
|
+
condition: 'primary-treatment-capacity-2',
|
|
680
|
+
costMeasurements,
|
|
681
|
+
});
|
|
682
|
+
const unknown = compileUnknown({ runRecord: treatmentRun, inputs: inputs.primary, costMeasurements });
|
|
683
|
+
const transferControl = runs.rc1_transfer.control[0];
|
|
684
|
+
const transferTreatment = runs.rc1_transfer.treatment[0];
|
|
685
|
+
return {
|
|
686
|
+
primary: {
|
|
687
|
+
control: primaryControl,
|
|
688
|
+
treatment: primaryTreatment,
|
|
689
|
+
partial_state: partialState,
|
|
690
|
+
capacity_2: capacity2,
|
|
691
|
+
third_only_unknown: unknown,
|
|
692
|
+
},
|
|
693
|
+
rc1_transfer: {
|
|
694
|
+
control: {
|
|
695
|
+
normal: compileTransfer({
|
|
696
|
+
runRecord: transferControl,
|
|
697
|
+
inputs: inputs.rc1,
|
|
698
|
+
manualEvidence: inputs.rc1.normalManualEvidence,
|
|
699
|
+
label: 'normal',
|
|
700
|
+
costMeasurements,
|
|
701
|
+
}),
|
|
702
|
+
negative: compileTransfer({
|
|
703
|
+
runRecord: transferControl,
|
|
704
|
+
inputs: inputs.rc1,
|
|
705
|
+
manualEvidence: inputs.rc1.negativeManualEvidence,
|
|
706
|
+
label: 'negative',
|
|
707
|
+
costMeasurements,
|
|
708
|
+
}),
|
|
709
|
+
},
|
|
710
|
+
treatment: {
|
|
711
|
+
normal: compileTransfer({
|
|
712
|
+
runRecord: transferTreatment,
|
|
713
|
+
inputs: inputs.rc1,
|
|
714
|
+
manualEvidence: inputs.rc1.normalManualEvidence,
|
|
715
|
+
label: 'normal',
|
|
716
|
+
costMeasurements,
|
|
717
|
+
}),
|
|
718
|
+
negative: compileTransfer({
|
|
719
|
+
runRecord: transferTreatment,
|
|
720
|
+
inputs: inputs.rc1,
|
|
721
|
+
manualEvidence: inputs.rc1.negativeManualEvidence,
|
|
722
|
+
label: 'negative',
|
|
723
|
+
costMeasurements,
|
|
724
|
+
}),
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
function compiledPayloads(compiled) {
|
|
731
|
+
return new Map([
|
|
732
|
+
['compiled/primary-control-1-normal.json', jsonBytes(compiled.primary.control[0])],
|
|
733
|
+
['compiled/primary-control-2-normal.json', jsonBytes(compiled.primary.control[1])],
|
|
734
|
+
['compiled/primary-treatment-1-normal.json', jsonBytes(compiled.primary.treatment[0])],
|
|
735
|
+
['compiled/primary-treatment-2-normal.json', jsonBytes(compiled.primary.treatment[1])],
|
|
736
|
+
['compiled/primary-treatment-partial-state.json', jsonBytes(compiled.primary.partial_state)],
|
|
737
|
+
['compiled/primary-treatment-capacity-2.json', jsonBytes(compiled.primary.capacity_2)],
|
|
738
|
+
['compiled/primary-treatment-third-only-unknown.json', jsonBytes(compiled.primary.third_only_unknown)],
|
|
739
|
+
['compiled/rc1-transfer-control-normal.json', jsonBytes(compiled.rc1_transfer.control.normal)],
|
|
740
|
+
['compiled/rc1-transfer-control-negative.json', jsonBytes(compiled.rc1_transfer.control.negative)],
|
|
741
|
+
['compiled/rc1-transfer-treatment-normal.json', jsonBytes(compiled.rc1_transfer.treatment.normal)],
|
|
742
|
+
['compiled/rc1-transfer-treatment-negative.json', jsonBytes(compiled.rc1_transfer.treatment.negative)],
|
|
743
|
+
]);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function inputPayloads(inputs) {
|
|
747
|
+
const result = new Map();
|
|
748
|
+
for (const [key, relativePath] of Object.entries(PRIMARY_ARTIFACT_INPUTS)) {
|
|
749
|
+
result.set(relativePath, jsonBytes(inputs.primary[key]));
|
|
750
|
+
}
|
|
751
|
+
for (const [key, relativePath] of Object.entries(RC1_ARTIFACT_INPUTS)) {
|
|
752
|
+
result.set(relativePath, jsonBytes(inputs.rc1[key]));
|
|
753
|
+
}
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function transformPayloads(transform) {
|
|
758
|
+
return new Map([
|
|
759
|
+
['transform/accepted-artifact.json', jsonBytes(transform.accepted.artifact)],
|
|
760
|
+
['transform/accepted-receipt.json', jsonBytes(transform.accepted.receipt)],
|
|
761
|
+
['transform/behavior-evidence.json', jsonBytes(transform.accepted.behavior_evidence)],
|
|
762
|
+
['transform/mutation-evidence.json', jsonBytes(transform.accepted.mutation_evidence)],
|
|
763
|
+
['transform/rejected-incomplete-artifact.json', jsonBytes(transform.rejected.incomplete.artifact)],
|
|
764
|
+
['transform/rejected-incomplete-receipt.json', jsonBytes(transform.rejected.incomplete.receipt)],
|
|
765
|
+
['transform/rejected-scope-artifact.json', jsonBytes(transform.rejected.scope.artifact)],
|
|
766
|
+
['transform/rejected-scope-receipt.json', jsonBytes(transform.rejected.scope.receipt)],
|
|
767
|
+
['transform/seam.patch', Buffer.from(transform.accepted.patch)],
|
|
768
|
+
]);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function storedEvidence(evidence) {
|
|
772
|
+
const raw = evidence?.raw;
|
|
773
|
+
if (!exactRecord(raw, [
|
|
774
|
+
'schema',
|
|
775
|
+
'media_type',
|
|
776
|
+
'encoding',
|
|
777
|
+
'canonical_bytes',
|
|
778
|
+
'payload_digest',
|
|
779
|
+
'payload_base64',
|
|
780
|
+
])
|
|
781
|
+
|| raw.schema !== 'lattice.sensor_raw_opaque_receipt.v1'
|
|
782
|
+
|| raw.encoding !== 'canonical-json-base64'
|
|
783
|
+
|| typeof raw.payload_base64 !== 'string') {
|
|
784
|
+
throw new TypeError('run raw evidence receipt is invalid');
|
|
785
|
+
}
|
|
786
|
+
const payload = Buffer.from(raw.payload_base64, 'base64');
|
|
787
|
+
if (payload.toString('base64') !== raw.payload_base64
|
|
788
|
+
|| payload.byteLength !== raw.canonical_bytes
|
|
789
|
+
|| sha256(payload) !== raw.payload_digest) {
|
|
790
|
+
throw new TypeError('run raw evidence payload binding is invalid');
|
|
791
|
+
}
|
|
792
|
+
const payloadBase64Chunks = [];
|
|
793
|
+
for (let offset = 0; offset < raw.payload_base64.length; offset += RAW_EVIDENCE_CHUNK_BYTES) {
|
|
794
|
+
payloadBase64Chunks.push(raw.payload_base64.slice(
|
|
795
|
+
offset,
|
|
796
|
+
offset + RAW_EVIDENCE_CHUNK_BYTES,
|
|
797
|
+
));
|
|
798
|
+
}
|
|
799
|
+
if (payloadBase64Chunks.length === 0
|
|
800
|
+
|| payloadBase64Chunks.slice(0, -1).some((chunk) => (
|
|
801
|
+
Buffer.byteLength(chunk, 'utf8') !== RAW_EVIDENCE_CHUNK_BYTES
|
|
802
|
+
))
|
|
803
|
+
|| Buffer.byteLength(payloadBase64Chunks.at(-1), 'utf8') > RAW_EVIDENCE_CHUNK_BYTES) {
|
|
804
|
+
throw new TypeError('run raw evidence chunks are invalid');
|
|
805
|
+
}
|
|
806
|
+
return {
|
|
807
|
+
...structuredClone(evidence),
|
|
808
|
+
raw: {
|
|
809
|
+
schema: 'lattice.rc2.chunked_sensor_raw_receipt.v1',
|
|
810
|
+
source_schema: raw.schema,
|
|
811
|
+
media_type: raw.media_type,
|
|
812
|
+
source_encoding: raw.encoding,
|
|
813
|
+
storage_encoding: 'ordered-base64-chunks',
|
|
814
|
+
canonical_bytes: raw.canonical_bytes,
|
|
815
|
+
payload_digest: raw.payload_digest,
|
|
816
|
+
payload_base64_chunks: payloadBase64Chunks,
|
|
817
|
+
},
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function storedRunRecord(runRecord) {
|
|
822
|
+
return {
|
|
823
|
+
...structuredClone(runRecord),
|
|
824
|
+
evidence: storedEvidence(runRecord.evidence),
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function runPayloads(runs) {
|
|
829
|
+
const records = [
|
|
830
|
+
...runs.primary.control,
|
|
831
|
+
...runs.primary.treatment,
|
|
832
|
+
...runs.rc1_transfer.control,
|
|
833
|
+
...runs.rc1_transfer.treatment,
|
|
834
|
+
];
|
|
835
|
+
return new Map(records.map((runRecord) => [
|
|
836
|
+
`evidence/${runRecord.run_id}.json`,
|
|
837
|
+
jsonBytes(storedRunRecord(runRecord)),
|
|
838
|
+
]));
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function mergePayloads(...maps) {
|
|
842
|
+
const result = new Map();
|
|
843
|
+
for (const map of maps) {
|
|
844
|
+
for (const [relativePath, bytes] of map) {
|
|
845
|
+
if (result.has(relativePath)) throw new TypeError(`duplicate artifact payload: ${relativePath}`);
|
|
846
|
+
result.set(relativePath, Buffer.from(bytes));
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return result;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function predecessorDescriptors(payloads) {
|
|
853
|
+
const descriptors = [];
|
|
854
|
+
const add = (kind, ref) => descriptors.push({ kind, ref, digest: sha256(payloads.get(ref)) });
|
|
855
|
+
for (const ref of Object.keys(PREDECESSOR_FILES).sort()) {
|
|
856
|
+
add(ref === 'predecessors/rc1-v6-plan.md' ? 'rc1_v6_phase_archive' : 'immutable_predecessor', ref);
|
|
857
|
+
}
|
|
858
|
+
add('accepted_transform', 'transform/accepted-artifact.json');
|
|
859
|
+
add('behavior_envelope', 'transform/behavior-evidence.json');
|
|
860
|
+
add('mutation_evidence', 'transform/mutation-evidence.json');
|
|
861
|
+
for (const runId of RUN_IDS) add('evidence_bundle', `evidence/${runId}.json`);
|
|
862
|
+
for (const ref of Object.values(PRIMARY_ARTIFACT_INPUTS).sort()) add('fixed_input', ref);
|
|
863
|
+
add('execution_identity', 'identity.json');
|
|
864
|
+
add('compiler_identity', 'identity/schedulability-compiler-v2.mjs');
|
|
865
|
+
add('verifier_identity', 'identity/schedulability-verifier-v2.mjs');
|
|
866
|
+
add('sensor_project_config', SENSOR_CONFIG_ARTIFACT_PATH);
|
|
867
|
+
return descriptors;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function buildVersionBarrier({ inputs, compiled, predecessors, predecessorPlan }) {
|
|
871
|
+
const treatment = compiled.primary.treatment[0];
|
|
872
|
+
const affectedTodos = inputs.primary.planInput.todos.map(({ id }) => id);
|
|
873
|
+
const newPlanVersion = {
|
|
874
|
+
schema: 'lattice.rc2.plan_version.v1',
|
|
875
|
+
version: 'rc2-delivery-policy-v5',
|
|
876
|
+
predecessor_version: predecessorPlan.version,
|
|
877
|
+
plan: treatment.plan,
|
|
878
|
+
plan_digest: digestArtifact(treatment.plan),
|
|
879
|
+
bundle_digest: digestArtifact(treatment.bundle),
|
|
880
|
+
verdict_digest: digestArtifact(treatment.verdict),
|
|
881
|
+
affected_todos: affectedTodos,
|
|
882
|
+
causal_predecessors: predecessors,
|
|
883
|
+
};
|
|
884
|
+
const planDiff = {
|
|
885
|
+
schema: 'lattice.rc2.plan_diff.v1',
|
|
886
|
+
old_plan: {
|
|
887
|
+
version: predecessorPlan.version,
|
|
888
|
+
digest: digestArtifact(predecessorPlan),
|
|
889
|
+
},
|
|
890
|
+
new_plan: {
|
|
891
|
+
version: newPlanVersion.version,
|
|
892
|
+
digest: digestArtifact(newPlanVersion),
|
|
893
|
+
},
|
|
894
|
+
causal_predecessors: predecessors,
|
|
895
|
+
affected_todos: affectedTodos,
|
|
896
|
+
invalidated_contexts: [
|
|
897
|
+
{ kind: 'old_plan', ref: predecessorPlan.version,
|
|
898
|
+
reason: 'the v4 artifact semantics cannot receive v5 bindings by append' },
|
|
899
|
+
{ kind: 'agent_context', ref: `${predecessorPlan.version}-agent-context`,
|
|
900
|
+
reason: 'v4 agent context does not bind the version witness verifier identity' },
|
|
901
|
+
{ kind: 'partial_patch', ref: `${predecessorPlan.version}-partial-patch`,
|
|
902
|
+
reason: 'patches compiled before version witness binding cannot cross the barrier' },
|
|
903
|
+
{ kind: 'interface_assumption', ref: `${predecessorPlan.version}-interface-assumption`,
|
|
904
|
+
reason: 'v4 interface evidence does not bind the active witness epoch' },
|
|
905
|
+
{ kind: 'boundary_evidence', ref: `${predecessorPlan.version}-boundary-evidence`,
|
|
906
|
+
reason: 'only fresh version-bound treatment evidence is valid for plan v5' },
|
|
907
|
+
],
|
|
908
|
+
};
|
|
909
|
+
return { newPlanVersion, planDiff };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function distinctConflictPairs(record) {
|
|
913
|
+
return new Set(record.bundle.graph.conflicts.map(({ todo_ids: todoIds }) => (
|
|
914
|
+
[...todoIds].sort().join('\u0000')
|
|
915
|
+
))).size;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function conditionMetrics(record) {
|
|
919
|
+
return {
|
|
920
|
+
conflict_records: record.bundle.graph.conflicts.length,
|
|
921
|
+
distinct_conflict_pairs: distinctConflictPairs(record),
|
|
922
|
+
minimum_feasible_waves: record.plan.minimum_feasible_waves,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
function buildComparison({ baseSha, identity, transform, compiled, newPlanVersion, planDiff }) {
|
|
927
|
+
return {
|
|
928
|
+
schema: 'lattice.rc2.control_treatment_comparison.v1',
|
|
929
|
+
base_sha: baseSha,
|
|
930
|
+
identity_digest: digestArtifact(identity),
|
|
931
|
+
independent_variable: {
|
|
932
|
+
kind: 'accepted_registry_shard_patch',
|
|
933
|
+
transform_artifact_digest: digestArtifact(transform.accepted.artifact),
|
|
934
|
+
patch_digest: transform.accepted.artifact.patch.digest,
|
|
935
|
+
},
|
|
936
|
+
condition_artifact_digests: {
|
|
937
|
+
primary_control: compiled.primary.control.map(({ artifact_digest: digest }) => digest),
|
|
938
|
+
primary_treatment: compiled.primary.treatment.map(({ artifact_digest: digest }) => digest),
|
|
939
|
+
primary_partial_state: compiled.primary.partial_state.artifact_digest,
|
|
940
|
+
primary_capacity_2: compiled.primary.capacity_2.artifact_digest,
|
|
941
|
+
primary_unknown: digestArtifact(compiled.primary.third_only_unknown),
|
|
942
|
+
rc1_transfer_control_normal: compiled.rc1_transfer.control.normal.artifact_digest,
|
|
943
|
+
rc1_transfer_control_negative: compiled.rc1_transfer.control.negative.artifact_digest,
|
|
944
|
+
rc1_transfer_treatment_normal: compiled.rc1_transfer.treatment.normal.artifact_digest,
|
|
945
|
+
rc1_transfer_treatment_negative: compiled.rc1_transfer.treatment.negative.artifact_digest,
|
|
946
|
+
},
|
|
947
|
+
metrics: {
|
|
948
|
+
primary_control: conditionMetrics(compiled.primary.control[0]),
|
|
949
|
+
primary_treatment: conditionMetrics(compiled.primary.treatment[0]),
|
|
950
|
+
primary_partial_state: conditionMetrics(compiled.primary.partial_state),
|
|
951
|
+
primary_capacity_2: conditionMetrics(compiled.primary.capacity_2),
|
|
952
|
+
rc1_transfer_control_normal: conditionMetrics(compiled.rc1_transfer.control.normal),
|
|
953
|
+
rc1_transfer_control_negative: conditionMetrics(compiled.rc1_transfer.control.negative),
|
|
954
|
+
rc1_transfer_treatment_normal: conditionMetrics(compiled.rc1_transfer.treatment.normal),
|
|
955
|
+
rc1_transfer_treatment_negative: conditionMetrics(compiled.rc1_transfer.treatment.negative),
|
|
956
|
+
},
|
|
957
|
+
version_barrier: {
|
|
958
|
+
new_plan_version_digest: digestArtifact(newPlanVersion),
|
|
959
|
+
plan_diff_digest: digestArtifact(planDiff),
|
|
960
|
+
causal_predecessors_digest: digestArtifact(planDiff.causal_predecessors),
|
|
961
|
+
},
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function evaluateHypothesis(comparison, compiled, transform) {
|
|
966
|
+
const checks = [
|
|
967
|
+
{ id: 'accepted_transform_only', passed: transform.accepted.status === 'accepted' },
|
|
968
|
+
{
|
|
969
|
+
id: 'primary_repeat_reproducible',
|
|
970
|
+
passed: new Set(compiled.primary.control.map(({ artifact_digest: digest }) => digest)).size === 1
|
|
971
|
+
&& new Set(compiled.primary.treatment.map(({ artifact_digest: digest }) => digest)).size === 1,
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
id: 'primary_conflicts_removed',
|
|
975
|
+
passed: comparison.metrics.primary_control.conflict_records === 12
|
|
976
|
+
&& comparison.metrics.primary_control.distinct_conflict_pairs === 3
|
|
977
|
+
&& comparison.metrics.primary_treatment.conflict_records === 0,
|
|
978
|
+
},
|
|
979
|
+
{
|
|
980
|
+
id: 'primary_waves_reduced',
|
|
981
|
+
passed: comparison.metrics.primary_control.minimum_feasible_waves === 3
|
|
982
|
+
&& comparison.metrics.primary_treatment.minimum_feasible_waves === 1,
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
id: 'partial_state_preserved',
|
|
986
|
+
passed: comparison.metrics.primary_partial_state.conflict_records === 1
|
|
987
|
+
&& comparison.metrics.primary_partial_state.minimum_feasible_waves === 2,
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
id: 'capacity_control_preserved',
|
|
991
|
+
passed: comparison.metrics.primary_capacity_2.conflict_records === 0
|
|
992
|
+
&& comparison.metrics.primary_capacity_2.minimum_feasible_waves === 2,
|
|
993
|
+
},
|
|
994
|
+
{
|
|
995
|
+
id: 'third_only_unknown_closed',
|
|
996
|
+
passed: compiled.primary.third_only_unknown.outcome.outcome === 'unknown'
|
|
997
|
+
&& compiled.primary.third_only_unknown.verification.outcome === 'unknown',
|
|
998
|
+
},
|
|
999
|
+
{
|
|
1000
|
+
id: 'rc1_transfer_isomorphic',
|
|
1001
|
+
passed: comparison.metrics.rc1_transfer_control_normal.conflict_records === 3
|
|
1002
|
+
&& comparison.metrics.rc1_transfer_control_normal.minimum_feasible_waves === 2
|
|
1003
|
+
&& comparison.metrics.rc1_transfer_treatment_normal.conflict_records === 0
|
|
1004
|
+
&& comparison.metrics.rc1_transfer_treatment_normal.minimum_feasible_waves === 1
|
|
1005
|
+
&& comparison.metrics.rc1_transfer_control_negative.minimum_feasible_waves === 2
|
|
1006
|
+
&& comparison.metrics.rc1_transfer_treatment_negative.minimum_feasible_waves === 2,
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
id: 'all_minimum_verified',
|
|
1010
|
+
passed: [
|
|
1011
|
+
...compiled.primary.control,
|
|
1012
|
+
...compiled.primary.treatment,
|
|
1013
|
+
compiled.primary.partial_state,
|
|
1014
|
+
compiled.primary.capacity_2,
|
|
1015
|
+
compiled.rc1_transfer.control.normal,
|
|
1016
|
+
compiled.rc1_transfer.control.negative,
|
|
1017
|
+
compiled.rc1_transfer.treatment.normal,
|
|
1018
|
+
compiled.rc1_transfer.treatment.negative,
|
|
1019
|
+
].every(({ verification }) => verification.outcome === 'verified'),
|
|
1020
|
+
},
|
|
1021
|
+
];
|
|
1022
|
+
return {
|
|
1023
|
+
schema: 'lattice.rc2.hypothesis_evaluation.v1',
|
|
1024
|
+
checks,
|
|
1025
|
+
supported: checks.every(({ passed }) => passed),
|
|
1026
|
+
failed_conditions: checks.filter(({ passed }) => !passed).map(({ id }) => id),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function patchReviewLines(patch) {
|
|
1031
|
+
return patch.toString('utf8').split('\n').filter((line) => (
|
|
1032
|
+
(line.startsWith('+') && !line.startsWith('+++'))
|
|
1033
|
+
|| (line.startsWith('-') && !line.startsWith('---'))
|
|
1034
|
+
)).length;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function buildCost(costMeasurements, transform) {
|
|
1038
|
+
const elapsed = Math.round(costMeasurements
|
|
1039
|
+
.reduce((sum, measurement) => sum + measurement.elapsed_ms, 0) * 1_000) / 1_000;
|
|
1040
|
+
return {
|
|
1041
|
+
schema: 'lattice.rc2.stage_cost.v1',
|
|
1042
|
+
measurements: costMeasurements,
|
|
1043
|
+
aggregate: {
|
|
1044
|
+
measured_count: costMeasurements.length,
|
|
1045
|
+
not_measured_count: 0,
|
|
1046
|
+
elapsed_ms: elapsed,
|
|
1047
|
+
},
|
|
1048
|
+
intervention: {
|
|
1049
|
+
patch_bytes: transform.accepted.patch.byteLength,
|
|
1050
|
+
files: transform.accepted.artifact.scope.changed_paths.length,
|
|
1051
|
+
review_lines: patchReviewLines(transform.accepted.patch),
|
|
1052
|
+
},
|
|
1053
|
+
rework: { rejected_attempts: 2, retries: 0, rollbacks: 0 },
|
|
1054
|
+
unverified: [
|
|
1055
|
+
'actual multi-agent wall-clock improvement',
|
|
1056
|
+
'elapsed measurement attestation beyond saved arithmetic binding',
|
|
1057
|
+
'arbitrary-repository seam success rate',
|
|
1058
|
+
],
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
function allCompiledRecords(compiled) {
|
|
1063
|
+
return [
|
|
1064
|
+
...compiled.primary.control,
|
|
1065
|
+
...compiled.primary.treatment,
|
|
1066
|
+
compiled.primary.partial_state,
|
|
1067
|
+
compiled.primary.capacity_2,
|
|
1068
|
+
compiled.primary.third_only_unknown,
|
|
1069
|
+
compiled.rc1_transfer.control.normal,
|
|
1070
|
+
compiled.rc1_transfer.control.negative,
|
|
1071
|
+
compiled.rc1_transfer.treatment.normal,
|
|
1072
|
+
compiled.rc1_transfer.treatment.negative,
|
|
1073
|
+
];
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
function buildExecutionEvidence({
|
|
1077
|
+
baseSha,
|
|
1078
|
+
identity,
|
|
1079
|
+
transform,
|
|
1080
|
+
runs,
|
|
1081
|
+
compiled,
|
|
1082
|
+
newPlanVersion,
|
|
1083
|
+
planDiff,
|
|
1084
|
+
comparison,
|
|
1085
|
+
hypothesis,
|
|
1086
|
+
cost,
|
|
1087
|
+
}) {
|
|
1088
|
+
const runRecords = [
|
|
1089
|
+
...runs.primary.control,
|
|
1090
|
+
...runs.primary.treatment,
|
|
1091
|
+
...runs.rc1_transfer.control,
|
|
1092
|
+
...runs.rc1_transfer.treatment,
|
|
1093
|
+
];
|
|
1094
|
+
return {
|
|
1095
|
+
schema: 'lattice.rc2.campaign_execution_evidence.v1',
|
|
1096
|
+
base_sha: baseSha,
|
|
1097
|
+
identity_digest: digestArtifact(identity),
|
|
1098
|
+
transform_artifact_digest: digestArtifact(transform.accepted.artifact),
|
|
1099
|
+
run_digests: runRecords.map((runRecord) => digestArtifact(storedRunRecord(runRecord))),
|
|
1100
|
+
compiled_condition_digests: allCompiledRecords(compiled).map(digestArtifact),
|
|
1101
|
+
new_plan_version_digest: digestArtifact(newPlanVersion),
|
|
1102
|
+
plan_diff_digest: digestArtifact(planDiff),
|
|
1103
|
+
comparison_digest: digestArtifact(comparison),
|
|
1104
|
+
hypothesis_evaluation_digest: digestArtifact(hypothesis),
|
|
1105
|
+
cost_digest: digestArtifact(cost),
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async function timedAsync(stage, costMeasurements, execute) {
|
|
1110
|
+
const startedAt = performance.now();
|
|
1111
|
+
const value = await execute();
|
|
1112
|
+
costMeasurements.push({ stage, state: 'measured', elapsed_ms: roundedMilliseconds(startedAt) });
|
|
1113
|
+
return value;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
async function runTransforms({ root, baseSha, candidateSpec, costMeasurements }) {
|
|
1117
|
+
const accepted = await timedAsync('transform.accepted', costMeasurements, () => (
|
|
1118
|
+
runRc2DeliveryPolicySeamTransform({ repoRoot: root, baseRef: baseSha, candidateSpec })
|
|
1119
|
+
));
|
|
1120
|
+
if (accepted.status !== 'accepted' || !Buffer.isBuffer(accepted.patch)) {
|
|
1121
|
+
throw new TypeError('default RC2 transform was not accepted');
|
|
1122
|
+
}
|
|
1123
|
+
const incomplete = await timedAsync('transform.rejected.incomplete', costMeasurements, () => (
|
|
1124
|
+
runRc2DeliveryPolicySeamTransform({
|
|
1125
|
+
repoRoot: root,
|
|
1126
|
+
baseRef: baseSha,
|
|
1127
|
+
candidateSpec,
|
|
1128
|
+
transform: async ({ worktreePath }) => {
|
|
1129
|
+
await applyRc2DeliveryPolicyTransform({ worktreePath });
|
|
1130
|
+
await rm(path.join(worktreePath, 'test/rc2-delivery-policy-sms.test.mjs'));
|
|
1131
|
+
},
|
|
1132
|
+
})
|
|
1133
|
+
));
|
|
1134
|
+
const scope = await timedAsync('transform.rejected.scope', costMeasurements, () => (
|
|
1135
|
+
runRc2DeliveryPolicySeamTransform({
|
|
1136
|
+
repoRoot: root,
|
|
1137
|
+
baseRef: baseSha,
|
|
1138
|
+
candidateSpec,
|
|
1139
|
+
transform: async ({ worktreePath }) => {
|
|
1140
|
+
await applyRc2DeliveryPolicyTransform({ worktreePath });
|
|
1141
|
+
await writeFile(path.join(worktreePath, 'rc2-scope-violation.tmp'), 'forbidden\n');
|
|
1142
|
+
},
|
|
1143
|
+
})
|
|
1144
|
+
));
|
|
1145
|
+
if (incomplete.status !== 'rejected'
|
|
1146
|
+
|| incomplete.artifact.rejection.kind !== 'incomplete_transform'
|
|
1147
|
+
|| scope.status !== 'rejected'
|
|
1148
|
+
|| scope.artifact.rejection.kind !== 'scope_violation') {
|
|
1149
|
+
throw new TypeError('RC2 rejected transform controls did not preserve typed outcomes');
|
|
1150
|
+
}
|
|
1151
|
+
return { accepted, rejected: { incomplete, scope } };
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/** accepted seamを独立変数にfresh sensor→v2 compile→new planまで実行する。 */
|
|
1155
|
+
export async function runRc2Campaign(options = {}) {
|
|
1156
|
+
if (!exactRecord(options, ['repoRoot', 'baseRef', 'artifactVersion'])
|
|
1157
|
+
|| options.artifactVersion !== ARTIFACT_VERSION) {
|
|
1158
|
+
throw new TypeError('runRc2Campaign options must select exact artifactVersion v4');
|
|
1159
|
+
}
|
|
1160
|
+
const { root, baseSha } = await resolveRepository(options.repoRoot, options.baseRef);
|
|
1161
|
+
const [inputs, predecessors, identityBefore] = await Promise.all([
|
|
1162
|
+
loadInputs(root),
|
|
1163
|
+
loadPredecessors(root),
|
|
1164
|
+
captureExecutionIdentity(root),
|
|
1165
|
+
]);
|
|
1166
|
+
const predecessorPlan = loadV3PlanPredecessor(predecessors);
|
|
1167
|
+
const costMeasurements = [];
|
|
1168
|
+
const transform = await runTransforms({
|
|
1169
|
+
root,
|
|
1170
|
+
baseSha,
|
|
1171
|
+
candidateSpec: inputs.primary.candidateSpec,
|
|
1172
|
+
costMeasurements,
|
|
1173
|
+
});
|
|
1174
|
+
const primaryPaths = surfacePaths(inputs.primary.candidateSpec);
|
|
1175
|
+
const rc1Paths = surfacePaths(inputs.rc1.candidateSpec);
|
|
1176
|
+
if (primaryPaths.length !== 9 || rc1Paths.length !== 6) {
|
|
1177
|
+
throw new TypeError('campaign fixed surface cardinality is invalid');
|
|
1178
|
+
}
|
|
1179
|
+
const rc1TransformArtifact = JSON.parse(
|
|
1180
|
+
predecessors.get('predecessors/rc1-v6-transform-artifact.json').toString('utf8'),
|
|
1181
|
+
);
|
|
1182
|
+
const rc1Patch = predecessors.get('predecessors/rc1-v6-seam.patch');
|
|
1183
|
+
if (rc1TransformArtifact.patch.digest !== sha256(rc1Patch)) {
|
|
1184
|
+
throw new TypeError('RC1 v6 predecessor patch binding is invalid');
|
|
1185
|
+
}
|
|
1186
|
+
const common = {
|
|
1187
|
+
repoRoot: root,
|
|
1188
|
+
baseSha,
|
|
1189
|
+
sensorIdentity: identityBefore.snapshot.sensor_identity,
|
|
1190
|
+
costMeasurements,
|
|
1191
|
+
};
|
|
1192
|
+
const primaryControl = [];
|
|
1193
|
+
const primaryTreatment = [];
|
|
1194
|
+
for (let index = 1; index <= 2; index += 1) {
|
|
1195
|
+
primaryControl.push(await observeFreshIndex({
|
|
1196
|
+
...common,
|
|
1197
|
+
family: 'primary',
|
|
1198
|
+
condition: 'control',
|
|
1199
|
+
runId: `primary-control-${index}`,
|
|
1200
|
+
querySet: inputs.primary.querySet,
|
|
1201
|
+
paths: primaryPaths,
|
|
1202
|
+
patch: null,
|
|
1203
|
+
allowedPaths: [],
|
|
1204
|
+
oracle: null,
|
|
1205
|
+
rc1Inputs: null,
|
|
1206
|
+
}));
|
|
1207
|
+
}
|
|
1208
|
+
for (let index = 1; index <= 2; index += 1) {
|
|
1209
|
+
primaryTreatment.push(await observeFreshIndex({
|
|
1210
|
+
...common,
|
|
1211
|
+
family: 'primary',
|
|
1212
|
+
condition: 'treatment',
|
|
1213
|
+
runId: `primary-treatment-${index}`,
|
|
1214
|
+
querySet: inputs.primary.querySet,
|
|
1215
|
+
paths: primaryPaths,
|
|
1216
|
+
patch: transform.accepted.patch,
|
|
1217
|
+
allowedPaths: [...RC2_DELIVERY_POLICY_TRANSFORM_PATHS],
|
|
1218
|
+
oracle: null,
|
|
1219
|
+
rc1Inputs: null,
|
|
1220
|
+
}));
|
|
1221
|
+
}
|
|
1222
|
+
const transferControl = await observeFreshIndex({
|
|
1223
|
+
...common,
|
|
1224
|
+
family: 'rc1_transfer',
|
|
1225
|
+
condition: 'control',
|
|
1226
|
+
runId: 'rc1-transfer-control-1',
|
|
1227
|
+
querySet: inputs.rc1.querySet,
|
|
1228
|
+
paths: rc1Paths,
|
|
1229
|
+
patch: null,
|
|
1230
|
+
allowedPaths: [],
|
|
1231
|
+
oracle: inputs.rc1.oracle,
|
|
1232
|
+
rc1Inputs: inputs.rc1,
|
|
1233
|
+
});
|
|
1234
|
+
const transferTreatment = await observeFreshIndex({
|
|
1235
|
+
...common,
|
|
1236
|
+
family: 'rc1_transfer',
|
|
1237
|
+
condition: 'treatment',
|
|
1238
|
+
runId: 'rc1-transfer-treatment-1',
|
|
1239
|
+
querySet: inputs.rc1.querySet,
|
|
1240
|
+
paths: rc1Paths,
|
|
1241
|
+
patch: rc1Patch,
|
|
1242
|
+
allowedPaths: [...rc1TransformArtifact.scope.allowed_paths],
|
|
1243
|
+
oracle: inputs.rc1.oracle,
|
|
1244
|
+
rc1Inputs: inputs.rc1,
|
|
1245
|
+
});
|
|
1246
|
+
const runs = {
|
|
1247
|
+
primary: { control: primaryControl, treatment: primaryTreatment },
|
|
1248
|
+
rc1_transfer: { control: [transferControl], treatment: [transferTreatment] },
|
|
1249
|
+
};
|
|
1250
|
+
const compiled = compileAllConditions({ runs, inputs, costMeasurements });
|
|
1251
|
+
|
|
1252
|
+
const identityAfter = await captureExecutionIdentity(root);
|
|
1253
|
+
if (!isDeepStrictEqual(identityBefore.snapshot, identityAfter.snapshot)
|
|
1254
|
+
|| identityBefore.payloads.size !== identityAfter.payloads.size
|
|
1255
|
+
|| [...identityBefore.payloads].some(([relativePath, bytes]) => (
|
|
1256
|
+
!identityAfter.payloads.get(relativePath)?.equals(bytes)
|
|
1257
|
+
))) {
|
|
1258
|
+
throw new TypeError('RC2 execution identity drifted during campaign');
|
|
1259
|
+
}
|
|
1260
|
+
const identity = {
|
|
1261
|
+
schema: 'lattice.rc2.execution_identity.v4',
|
|
1262
|
+
sources: identityBefore.snapshot.sources,
|
|
1263
|
+
sensor_identity: identityBefore.snapshot.sensor_identity,
|
|
1264
|
+
sensor_identity_digest: digestArtifact(identityBefore.snapshot.sensor_identity),
|
|
1265
|
+
before_digest: identityBefore.digest,
|
|
1266
|
+
after_digest: identityAfter.digest,
|
|
1267
|
+
};
|
|
1268
|
+
|
|
1269
|
+
const preliminaryPayloads = mergePayloads(
|
|
1270
|
+
inputPayloads(inputs),
|
|
1271
|
+
identityBefore.payloads,
|
|
1272
|
+
predecessors,
|
|
1273
|
+
transformPayloads(transform),
|
|
1274
|
+
runPayloads(runs),
|
|
1275
|
+
new Map([['identity.json', jsonBytes(identity)]]),
|
|
1276
|
+
);
|
|
1277
|
+
const causalPredecessors = predecessorDescriptors(preliminaryPayloads);
|
|
1278
|
+
const { newPlanVersion, planDiff } = buildVersionBarrier({
|
|
1279
|
+
inputs,
|
|
1280
|
+
compiled,
|
|
1281
|
+
predecessors: causalPredecessors,
|
|
1282
|
+
predecessorPlan,
|
|
1283
|
+
});
|
|
1284
|
+
const comparison = buildComparison({
|
|
1285
|
+
baseSha,
|
|
1286
|
+
identity,
|
|
1287
|
+
transform,
|
|
1288
|
+
compiled,
|
|
1289
|
+
newPlanVersion,
|
|
1290
|
+
planDiff,
|
|
1291
|
+
});
|
|
1292
|
+
const hypothesisEvaluation = evaluateHypothesis(comparison, compiled, transform);
|
|
1293
|
+
if (!hypothesisEvaluation.supported) {
|
|
1294
|
+
throw new TypeError(
|
|
1295
|
+
`RC2 hypothesis is not supported: ${hypothesisEvaluation.failed_conditions.join(', ')}`,
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
const cost = buildCost(costMeasurements, transform);
|
|
1299
|
+
const executionEvidence = buildExecutionEvidence({
|
|
1300
|
+
baseSha,
|
|
1301
|
+
identity,
|
|
1302
|
+
transform,
|
|
1303
|
+
runs,
|
|
1304
|
+
compiled,
|
|
1305
|
+
newPlanVersion,
|
|
1306
|
+
planDiff,
|
|
1307
|
+
comparison,
|
|
1308
|
+
hypothesis: hypothesisEvaluation,
|
|
1309
|
+
cost,
|
|
1310
|
+
});
|
|
1311
|
+
return {
|
|
1312
|
+
schema: 'lattice.rc2.campaign_result.v4',
|
|
1313
|
+
artifact_version: ARTIFACT_VERSION,
|
|
1314
|
+
base_sha: baseSha,
|
|
1315
|
+
fixed_inputs: inputs,
|
|
1316
|
+
identity,
|
|
1317
|
+
identity_payloads: identityBefore.payloads,
|
|
1318
|
+
predecessor_payloads: predecessors,
|
|
1319
|
+
transform,
|
|
1320
|
+
runs,
|
|
1321
|
+
compiled,
|
|
1322
|
+
new_plan_version: newPlanVersion,
|
|
1323
|
+
plan_diff: planDiff,
|
|
1324
|
+
comparison,
|
|
1325
|
+
hypothesis_evaluation: hypothesisEvaluation,
|
|
1326
|
+
cost,
|
|
1327
|
+
execution_evidence: executionEvidence,
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
function artifactFiles(result) {
|
|
1332
|
+
const files = mergePayloads(
|
|
1333
|
+
inputPayloads(result.fixed_inputs),
|
|
1334
|
+
result.identity_payloads,
|
|
1335
|
+
result.predecessor_payloads,
|
|
1336
|
+
transformPayloads(result.transform),
|
|
1337
|
+
runPayloads(result.runs),
|
|
1338
|
+
compiledPayloads(result.compiled),
|
|
1339
|
+
new Map([
|
|
1340
|
+
['identity.json', jsonBytes(result.identity)],
|
|
1341
|
+
['new-plan-version.json', jsonBytes(result.new_plan_version)],
|
|
1342
|
+
['plan-diff.json', jsonBytes(result.plan_diff)],
|
|
1343
|
+
['comparison.json', jsonBytes(result.comparison)],
|
|
1344
|
+
['hypothesis-evaluation.json', jsonBytes(result.hypothesis_evaluation)],
|
|
1345
|
+
['cost.json', jsonBytes(result.cost)],
|
|
1346
|
+
['execution-evidence.json', jsonBytes(result.execution_evidence)],
|
|
1347
|
+
]),
|
|
1348
|
+
);
|
|
1349
|
+
if (!isDeepStrictEqual([...files.keys()].sort(), [...RC2_ARTIFACT_PATHS])) {
|
|
1350
|
+
throw new TypeError('RC2 artifact payload set differs from the exact contract');
|
|
1351
|
+
}
|
|
1352
|
+
return files;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function artifactMediaType(relativePath) {
|
|
1356
|
+
if (relativePath.endsWith('.json')) return 'application/json';
|
|
1357
|
+
if (relativePath.endsWith('.mjs')) return 'application/javascript';
|
|
1358
|
+
if (relativePath.endsWith('.patch')) return 'text/x-diff';
|
|
1359
|
+
if (relativePath.endsWith('.md')) return 'text/markdown';
|
|
1360
|
+
return 'application/octet-stream';
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
async function syncDirectory(directory) {
|
|
1364
|
+
const handle = await open(directory, 'r');
|
|
1365
|
+
// Windowsはdirectory handleのfsyncを許さず常にEPERM/EINVALを返す(Node仕様)。
|
|
1366
|
+
// win32のこの2値だけ許容し、他OS・他エラーは従来どおり失敗させる。
|
|
1367
|
+
try {
|
|
1368
|
+
await handle.sync();
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
if (process.platform !== 'win32' || !['EPERM', 'EINVAL'].includes(error?.code)) throw error;
|
|
1371
|
+
} finally {
|
|
1372
|
+
await handle.close();
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async function writeDurableFile(target, bytes) {
|
|
1377
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
1378
|
+
const handle = await open(target, 'wx');
|
|
1379
|
+
try {
|
|
1380
|
+
await handle.writeFile(bytes);
|
|
1381
|
+
await handle.sync();
|
|
1382
|
+
} finally {
|
|
1383
|
+
await handle.close();
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
async function syncArtifactDirectories(root, relativePaths) {
|
|
1388
|
+
const directories = new Set([root]);
|
|
1389
|
+
for (const relativePath of relativePaths) {
|
|
1390
|
+
let current = path.dirname(path.join(root, relativePath));
|
|
1391
|
+
while (current.startsWith(root)) {
|
|
1392
|
+
directories.add(current);
|
|
1393
|
+
if (current === root) break;
|
|
1394
|
+
current = path.dirname(current);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
const ordered = [...directories].sort((left, right) => right.length - left.length);
|
|
1398
|
+
for (const directory of ordered) await syncDirectory(directory);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/** verified exact artifact setを既存rootへ上書きせずatomicに発行する。 */
|
|
1402
|
+
export async function writeRc2CampaignArtifacts(options = {}) {
|
|
1403
|
+
if (!exactRecord(options, ['repoRoot', 'result'])
|
|
1404
|
+
|| typeof options.repoRoot !== 'string'
|
|
1405
|
+
|| options.repoRoot.length === 0
|
|
1406
|
+
|| options.result?.schema !== 'lattice.rc2.campaign_result.v4'
|
|
1407
|
+
|| options.result?.artifact_version !== ARTIFACT_VERSION) {
|
|
1408
|
+
throw new TypeError('writeRc2CampaignArtifacts options are invalid');
|
|
1409
|
+
}
|
|
1410
|
+
const root = await realpath(options.repoRoot);
|
|
1411
|
+
const artifactRoot = path.join(root, ARTIFACT_ROOTS.v4);
|
|
1412
|
+
try {
|
|
1413
|
+
await lstat(artifactRoot);
|
|
1414
|
+
throw new Error('RC2 artifact root already exists; immutable evidence is not overwritten');
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
1417
|
+
}
|
|
1418
|
+
const files = artifactFiles(options.result);
|
|
1419
|
+
const manifest = {
|
|
1420
|
+
schema: 'lattice.rc2.artifact_manifest.v4',
|
|
1421
|
+
base_sha: options.result.base_sha,
|
|
1422
|
+
result_digest: digestArtifact(options.result.hypothesis_evaluation),
|
|
1423
|
+
files: [...files].map(([relativePath, bytes]) => ({
|
|
1424
|
+
path: relativePath,
|
|
1425
|
+
media_type: artifactMediaType(relativePath),
|
|
1426
|
+
bytes: bytes.byteLength,
|
|
1427
|
+
sha256: sha256(bytes),
|
|
1428
|
+
})).sort((left, right) => left.path.localeCompare(right.path)),
|
|
1429
|
+
};
|
|
1430
|
+
const payloads = [...files].map(([relativePath, bytes]) => ({ path: relativePath, bytes }));
|
|
1431
|
+
const verification = verifyRc2CampaignArtifactSet({ manifest, payloads });
|
|
1432
|
+
if (!verification.valid) {
|
|
1433
|
+
throw new TypeError(
|
|
1434
|
+
`RC2 artifact set is invalid: ${verification.failed_conditions.join(', ')}`,
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
const parent = path.dirname(artifactRoot);
|
|
1438
|
+
await mkdir(parent, { recursive: true });
|
|
1439
|
+
const temporaryRoot = await mkdtemp(path.join(parent, '.v4-write-'));
|
|
1440
|
+
try {
|
|
1441
|
+
for (const [relativePath, bytes] of files) {
|
|
1442
|
+
await writeDurableFile(path.join(temporaryRoot, relativePath), bytes);
|
|
1443
|
+
}
|
|
1444
|
+
await writeDurableFile(path.join(temporaryRoot, 'artifact-manifest.json'), jsonBytes(manifest));
|
|
1445
|
+
await syncArtifactDirectories(temporaryRoot, [
|
|
1446
|
+
...RC2_ARTIFACT_PATHS,
|
|
1447
|
+
'artifact-manifest.json',
|
|
1448
|
+
]);
|
|
1449
|
+
await rename(temporaryRoot, artifactRoot);
|
|
1450
|
+
await syncDirectory(parent);
|
|
1451
|
+
} catch (error) {
|
|
1452
|
+
await rm(temporaryRoot, { recursive: true, force: true });
|
|
1453
|
+
throw error;
|
|
1454
|
+
}
|
|
1455
|
+
return manifest;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
function artifactPathsForVersion(artifactVersion) {
|
|
1459
|
+
if (artifactVersion === 'v1') return RC2_V1_ARTIFACT_PATHS;
|
|
1460
|
+
if (artifactVersion === 'v2') return RC2_V2_ARTIFACT_PATHS;
|
|
1461
|
+
if (artifactVersion === 'v3') return RC2_V3_ARTIFACT_PATHS;
|
|
1462
|
+
if (artifactVersion === 'v4') return RC2_ARTIFACT_PATHS;
|
|
1463
|
+
return null;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function invalidDiskReceipt(condition) {
|
|
1467
|
+
return {
|
|
1468
|
+
schema: 'lattice.rc2.artifact_set_verification.v1',
|
|
1469
|
+
valid: false,
|
|
1470
|
+
checks: [{ id: condition, passed: false }],
|
|
1471
|
+
failed_conditions: [condition],
|
|
1472
|
+
};
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/** immutable rootを再読し、in-memory resultなしでpure verifierへ渡す。 */
|
|
1476
|
+
export async function verifyRc2CampaignArtifactsOnDisk(options = {}) {
|
|
1477
|
+
if (!exactRecord(options, ['repoRoot', 'artifactVersion'])
|
|
1478
|
+
|| typeof options.repoRoot !== 'string'
|
|
1479
|
+
|| options.repoRoot.length === 0
|
|
1480
|
+
|| artifactPathsForVersion(options.artifactVersion) === null) {
|
|
1481
|
+
throw new TypeError('verifyRc2CampaignArtifactsOnDisk options are invalid');
|
|
1482
|
+
}
|
|
1483
|
+
const artifactRoot = path.join(options.repoRoot, ARTIFACT_ROOTS[options.artifactVersion]);
|
|
1484
|
+
const artifactPaths = artifactPathsForVersion(options.artifactVersion);
|
|
1485
|
+
let manifestBytes;
|
|
1486
|
+
let manifest;
|
|
1487
|
+
try {
|
|
1488
|
+
manifestBytes = await readFile(path.join(artifactRoot, 'artifact-manifest.json'));
|
|
1489
|
+
manifest = JSON.parse(manifestBytes.toString('utf8'));
|
|
1490
|
+
} catch {
|
|
1491
|
+
return invalidDiskReceipt('manifest_canonical');
|
|
1492
|
+
}
|
|
1493
|
+
if (!jsonBytes(manifest).equals(manifestBytes)) {
|
|
1494
|
+
return invalidDiskReceipt('manifest_canonical');
|
|
1495
|
+
}
|
|
1496
|
+
let payloads;
|
|
1497
|
+
try {
|
|
1498
|
+
payloads = await Promise.all(artifactPaths.map(async (relativePath) => ({
|
|
1499
|
+
path: relativePath,
|
|
1500
|
+
bytes: await readFile(path.join(artifactRoot, relativePath)),
|
|
1501
|
+
})));
|
|
1502
|
+
} catch {
|
|
1503
|
+
return invalidDiskReceipt('exact_artifact_set');
|
|
1504
|
+
}
|
|
1505
|
+
return verifyRc2CampaignArtifactSet({ manifest, payloads });
|
|
1506
|
+
}
|