@quolu/lattice 0.52.0 → 0.52.2
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/LICENSE +147 -147
- package/README.ja.md +355 -355
- package/README.md +258 -258
- package/bin/lattice-mcp.mjs +0 -0
- package/bin/lattice-scripted-adapter.mjs +0 -0
- package/bin/lattice-scripted-worker.mjs +0 -0
- package/bin/lattice-work-order-adapter.mjs +0 -0
- package/bin/lattice.mjs +0 -0
- package/docs/bridge-setup.md +132 -132
- package/docs/schemas/lattice.executor_packet.v1.schema.json +57 -57
- package/docs/schemas/lattice.executor_receipt.v1.schema.json +66 -66
- package/docs/schemas/lattice.phase_todo_revision.v3.schema.json +360 -360
- package/docs/schemas/lattice.plan_create_input.v1.schema.json +56 -56
- package/docs/schemas/lattice.plan_create_input.v2.schema.json +72 -72
- package/docs/schemas/lattice.plan_create_input.v3.schema.json +81 -81
- package/docs/schemas/lattice.plan_create_input.v4.schema.json +85 -85
- package/docs/schemas/lattice.run_request.v1.schema.json +238 -238
- package/docs/schemas/lattice.runtime_adapter_capabilities.v2.schema.json +55 -55
- package/docs/schemas/lattice.runtime_adapter_registration_input.v1.schema.json +78 -78
- package/docs/schemas/lattice.runtime_adapter_registration_input.v2.schema.json +86 -86
- package/docs/schemas/lattice.todo_extraction.v2.schema.json +298 -298
- package/docs/schemas/lattice.todo_extraction.v3.schema.json +146 -146
- package/docs/schemas/lattice.todo_revision.v2.schema.json +260 -260
- package/docs/schemas/lattice.todo_revision_set.v3.schema.json +363 -363
- package/package.json +103 -103
- package/sensor/LICENSE +21 -21
- package/sensor/NOTICE +19 -19
- package/sensor/dist/bin/lattice-sensor.js +9 -9
- package/sensor/dist/db/index.js +24 -24
- package/sensor/dist/db/migrations.js +41 -41
- package/sensor/dist/db/queries.js +164 -164
- package/sensor/dist/db/schema.sql +205 -205
- package/sensor/dist/directory.js +5 -5
- package/sensor/dist/extraction/wasm/tree-sitter-c_sharp.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfml.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfquery.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cfscript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-cobol.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-erlang.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-go.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-java.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-javascript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-nix.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-python.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-tsx.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-typescript.wasm +0 -0
- package/sensor/dist/extraction/wasm/tree-sitter-vbnet.wasm +0 -0
- package/sensor/dist/mcp/liveness-watchdog.js +53 -53
- package/sensor/dist/mcp/server-instructions.js +95 -95
- package/sensor/package.json +56 -56
- 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-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/hash-chain.mjs +76 -76
- package/src/hooks-cli.mjs +1057 -1053
- package/src/isolation-runner.mjs +409 -409
- package/src/node-version-guard.mjs +44 -44
- package/src/project-cli.mjs +697 -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 -1502
- 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 -520
- package/src/runtime-cli.mjs +4588 -4584
- package/src/runtime-contracts.mjs +824 -824
- package/src/runtime-control-store.mjs +604 -600
- 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 -162
- 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 -477
- package/src/runtime-hold-recompile.mjs +916 -916
- package/src/runtime-io-sentinel.mjs +391 -391
- package/src/runtime-lifecycle-lock.mjs +294 -290
- package/src/runtime-managed-supervisor.mjs +1490 -1490
- package/src/runtime-multi-epoch-store.mjs +838 -834
- package/src/runtime-projection.mjs +269 -269
- package/src/runtime-pull-intake.mjs +1192 -1188
- package/src/runtime-scripted-adapter-controller.mjs +1160 -1156
- 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 -1167
- 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 +3141 -3126
- 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 -625
- 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 +4322 -4314
- package/src/treatment-compiler.mjs +728 -728
- package/src/treatment-runner.mjs +656 -656
- package/src/witness-scaffold.mjs +180 -180
package/src/control-compiler.mjs
CHANGED
|
@@ -1,532 +1,532 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canonicalizeArtifact,
|
|
3
|
-
digestArtifact,
|
|
4
|
-
validateBoundaryManifest,
|
|
5
|
-
validateBoundaryVerdict,
|
|
6
|
-
validatePlanGraph,
|
|
7
|
-
validatePlanInput,
|
|
8
|
-
} from './artifact-contracts.mjs';
|
|
9
|
-
import { portableSensorOutcome } from './sensor-adapter.mjs';
|
|
10
|
-
|
|
11
|
-
const SHA256 = /^[0-9a-f]{64}$/;
|
|
12
|
-
const IDENTIFIER = /^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$/;
|
|
13
|
-
const OPERATIONS = new Set(['status', 'query', 'callers', 'callees', 'impact', 'affected']);
|
|
14
|
-
const SEAM_BY_CONCERN = new Map([
|
|
15
|
-
['dispatch.channel_policy', {
|
|
16
|
-
symbol: 'selectDispatchChannel',
|
|
17
|
-
path: 'research/fixtures/dispatch-record/src/dispatch-channel.mjs',
|
|
18
|
-
}],
|
|
19
|
-
['dispatch.label_policy', {
|
|
20
|
-
symbol: 'formatDispatchLabel',
|
|
21
|
-
path: 'research/fixtures/dispatch-record/src/dispatch-label.mjs',
|
|
22
|
-
}],
|
|
23
|
-
]);
|
|
24
|
-
|
|
25
|
-
function fail(reason) {
|
|
26
|
-
throw new TypeError(`control compile契約違反: ${reason}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function exactRecord(value, keys) {
|
|
30
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
31
|
-
|| Object.getPrototypeOf(value) !== Object.prototype) {
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
const actual = Object.keys(value).sort();
|
|
35
|
-
const expected = [...keys].sort();
|
|
36
|
-
return actual.length === expected.length
|
|
37
|
-
&& actual.every((key, index) => key === expected[index]);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function nonEmptyText(value) {
|
|
41
|
-
return typeof value === 'string'
|
|
42
|
-
&& value.length > 0
|
|
43
|
-
&& value === value.trim()
|
|
44
|
-
&& !/[\u0000-\u001f\u007f]/.test(value)
|
|
45
|
-
&& Buffer.byteLength(value, 'utf8') <= 4_096;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function identifier(value) {
|
|
49
|
-
return typeof value === 'string' && IDENTIFIER.test(value);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function repoPath(value) {
|
|
53
|
-
return nonEmptyText(value)
|
|
54
|
-
&& Buffer.byteLength(value, 'utf8') <= 1_024
|
|
55
|
-
&& !value.startsWith('/')
|
|
56
|
-
&& !value.endsWith('/')
|
|
57
|
-
&& !value.includes('\\')
|
|
58
|
-
&& !/^[A-Za-z]:/.test(value)
|
|
59
|
-
&& value.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function uniqueTextArray(value) {
|
|
63
|
-
return Array.isArray(value)
|
|
64
|
-
&& value.length <= 256
|
|
65
|
-
&& value.every(nonEmptyText)
|
|
66
|
-
&& new Set(value).size === value.length;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function assertManualEvidence(planInput, manualEvidence) {
|
|
70
|
-
if (!exactRecord(manualEvidence, ['schema', 'evidence'])
|
|
71
|
-
|| manualEvidence.schema !== 'lattice.manual_boundary_evidence.v1'
|
|
72
|
-
|| !Array.isArray(manualEvidence.evidence)
|
|
73
|
-
|| manualEvidence.evidence.length !== planInput.todos.length) {
|
|
74
|
-
fail('manual evidence rootが不正');
|
|
75
|
-
}
|
|
76
|
-
const todoIds = new Set(planInput.todos.map(({ id }) => id));
|
|
77
|
-
const observed = new Set();
|
|
78
|
-
for (const entry of manualEvidence.evidence) {
|
|
79
|
-
if (!exactRecord(entry, [
|
|
80
|
-
'todo_id',
|
|
81
|
-
'state_reads',
|
|
82
|
-
'state_writes',
|
|
83
|
-
'effects',
|
|
84
|
-
'unknowns',
|
|
85
|
-
])
|
|
86
|
-
|| !identifier(entry.todo_id)
|
|
87
|
-
|| !todoIds.has(entry.todo_id)
|
|
88
|
-
|| observed.has(entry.todo_id)
|
|
89
|
-
|| !uniqueTextArray(entry.state_reads)
|
|
90
|
-
|| !uniqueTextArray(entry.state_writes)
|
|
91
|
-
|| !uniqueTextArray(entry.effects)
|
|
92
|
-
|| !uniqueTextArray(entry.unknowns)) {
|
|
93
|
-
fail('manual evidence recordがplan TODOと一致しない');
|
|
94
|
-
}
|
|
95
|
-
observed.add(entry.todo_id);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function assertQuerySet(querySet) {
|
|
100
|
-
if (!exactRecord(querySet, ['schema', 'queries'])
|
|
101
|
-
|| querySet.schema !== 'lattice.sensor_query_set.v1'
|
|
102
|
-
|| !Array.isArray(querySet.queries)
|
|
103
|
-
|| querySet.queries.length === 0
|
|
104
|
-
|| querySet.queries.length > 256) {
|
|
105
|
-
fail('query set rootが不正');
|
|
106
|
-
}
|
|
107
|
-
const ids = new Set();
|
|
108
|
-
for (const query of querySet.queries) {
|
|
109
|
-
if (!query || typeof query !== 'object' || Array.isArray(query)
|
|
110
|
-
|| !identifier(query.id) || ids.has(query.id) || !OPERATIONS.has(query.operation)) {
|
|
111
|
-
fail('query set recordが不正');
|
|
112
|
-
}
|
|
113
|
-
ids.add(query.id);
|
|
114
|
-
if (query.operation === 'status') {
|
|
115
|
-
if (!exactRecord(query, ['id', 'operation'])) fail('status query shapeが不正');
|
|
116
|
-
} else if (query.operation === 'affected') {
|
|
117
|
-
if (!exactRecord(query, ['id', 'operation', 'targets'])
|
|
118
|
-
|| !Array.isArray(query.targets)
|
|
119
|
-
|| query.targets.length === 0
|
|
120
|
-
|| !query.targets.every(repoPath)
|
|
121
|
-
|| new Set(query.targets).size !== query.targets.length) {
|
|
122
|
-
fail('affected query shapeが不正');
|
|
123
|
-
}
|
|
124
|
-
} else if (!exactRecord(query, ['id', 'operation', 'target'])
|
|
125
|
-
|| !nonEmptyText(query.target)) {
|
|
126
|
-
fail('symbol query shapeが不正');
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function assertLatticeSensorEvidence(querySet, sensorEvidence) {
|
|
132
|
-
if (!exactRecord(sensorEvidence, ['cwd', 'outcomes'])
|
|
133
|
-
|| !nonEmptyText(sensorEvidence.cwd)
|
|
134
|
-
|| !Array.isArray(sensorEvidence.outcomes)
|
|
135
|
-
|| sensorEvidence.outcomes.length !== querySet.queries.length) {
|
|
136
|
-
fail('LatticeSensor evidence rootがquery setと一致しない');
|
|
137
|
-
}
|
|
138
|
-
for (let index = 0; index < querySet.queries.length; index += 1) {
|
|
139
|
-
const query = querySet.queries[index];
|
|
140
|
-
const outcome = sensorEvidence.outcomes[index];
|
|
141
|
-
if (!outcome || typeof outcome !== 'object' || Array.isArray(outcome)
|
|
142
|
-
|| outcome.id !== query.id
|
|
143
|
-
|| outcome.operation !== query.operation
|
|
144
|
-
|| !nonEmptyText(outcome.outcome)) {
|
|
145
|
-
fail(`LatticeSensor evidence ${query.id}がID/operation/順序と一致しない`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function findSingleQuery(querySet, operation, target) {
|
|
151
|
-
const matches = querySet.queries.filter((query) => query.operation === operation
|
|
152
|
-
&& (target === undefined || query.target === target));
|
|
153
|
-
if (matches.length !== 1) fail(`query setに${operation}:${target ?? ''}がexactly one存在しない`);
|
|
154
|
-
return matches[0];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function rc1Seams(planInput) {
|
|
158
|
-
if (planInput.todos.length !== 2
|
|
159
|
-
|| planInput.plan_version !== 'rc1-control-v1'
|
|
160
|
-
|| planInput.capacity.writers !== 2
|
|
161
|
-
|| new Set(planInput.todos.map(({ anchor }) => anchor.symbol)).size !== 1
|
|
162
|
-
|| new Set(planInput.todos.map(({ anchor }) => anchor.path)).size !== 1
|
|
163
|
-
|| planInput.todos[0].anchor.path !== planInput.project.fixture_entry) {
|
|
164
|
-
fail('RC1 control planはcapacity 2で同じmonolithic anchorを持つ2 TODOでなければならない');
|
|
165
|
-
}
|
|
166
|
-
return planInput.todos.map((todo) => {
|
|
167
|
-
const seam = SEAM_BY_CONCERN.get(todo.concern);
|
|
168
|
-
if (!seam) fail(`query setへ接続できないconcern: ${todo.concern}`);
|
|
169
|
-
return { todo_id: todo.id, ...seam };
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function assertRc1QueryCoverage(planInput, querySet, seams) {
|
|
174
|
-
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
175
|
-
const anchorPath = planInput.todos[0].anchor.path;
|
|
176
|
-
findSingleQuery(querySet, 'status');
|
|
177
|
-
for (const operation of ['query', 'callers', 'callees', 'impact']) {
|
|
178
|
-
findSingleQuery(querySet, operation, anchorSymbol);
|
|
179
|
-
}
|
|
180
|
-
for (const seam of seams) {
|
|
181
|
-
findSingleQuery(querySet, 'query', seam.symbol);
|
|
182
|
-
findSingleQuery(querySet, 'impact', seam.symbol);
|
|
183
|
-
}
|
|
184
|
-
const affected = querySet.queries.filter(({ operation }) => operation === 'affected');
|
|
185
|
-
const expectedTargets = [anchorPath, ...seams.map(({ path }) => path)];
|
|
186
|
-
if (affected.length !== 1
|
|
187
|
-
|| JSON.stringify(affected[0].targets) !== JSON.stringify(expectedTargets)
|
|
188
|
-
|| querySet.queries.length !== 10) {
|
|
189
|
-
fail('query setがRC1 controlの固定coverageからdriftした');
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function outcomeById(querySet, sensorEvidence) {
|
|
194
|
-
return new Map(querySet.queries.map((query, index) => [
|
|
195
|
-
query.id,
|
|
196
|
-
sensorEvidence.outcomes[index],
|
|
197
|
-
]));
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function assertOutcome(outcomes, query, expected) {
|
|
201
|
-
const outcome = outcomes.get(query.id);
|
|
202
|
-
if (outcome.outcome !== expected) {
|
|
203
|
-
fail(`LatticeSensor evidence ${query.id}は${expected}でなく${outcome.outcome}`);
|
|
204
|
-
}
|
|
205
|
-
return outcome;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function controlGraphFacts(planInput, querySet, sensorEvidence, seams) {
|
|
209
|
-
const outcomes = outcomeById(querySet, sensorEvidence);
|
|
210
|
-
const statusQuery = findSingleQuery(querySet, 'status');
|
|
211
|
-
const status = assertOutcome(outcomes, statusQuery, 'ready');
|
|
212
|
-
if (!status.data || !nonEmptyText(status.data.version)) {
|
|
213
|
-
fail('LatticeSensor statusにversionがない');
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
217
|
-
for (const operation of ['query', 'callers', 'callees', 'impact']) {
|
|
218
|
-
assertOutcome(outcomes, findSingleQuery(querySet, operation, anchorSymbol), 'ready');
|
|
219
|
-
}
|
|
220
|
-
for (const seam of seams) {
|
|
221
|
-
assertOutcome(outcomes, findSingleQuery(querySet, 'query', seam.symbol), 'symbol_absent');
|
|
222
|
-
assertOutcome(outcomes, findSingleQuery(querySet, 'impact', seam.symbol), 'symbol_absent');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const affectedQuery = querySet.queries.find(({ operation }) => operation === 'affected');
|
|
226
|
-
const affected = assertOutcome(outcomes, affectedQuery, 'unresolved');
|
|
227
|
-
if (!Array.isArray(affected.targets)
|
|
228
|
-
|| affected.targets.length !== affectedQuery.targets.length
|
|
229
|
-
|| affected.targets.some((entry, index) => entry.target !== affectedQuery.targets[index])) {
|
|
230
|
-
fail('affected LatticeSensor evidenceがquery targetsと一致しない');
|
|
231
|
-
}
|
|
232
|
-
const anchorEvidence = affected.targets[0];
|
|
233
|
-
if (anchorEvidence.outcome !== 'ready'
|
|
234
|
-
|| !anchorEvidence.data
|
|
235
|
-
|| !Array.isArray(anchorEvidence.data.affectedTests)
|
|
236
|
-
|| anchorEvidence.data.affectedTests.length === 0
|
|
237
|
-
|| !anchorEvidence.data.affectedTests.every(repoPath)) {
|
|
238
|
-
fail('anchorのaffected testを取得できない');
|
|
239
|
-
}
|
|
240
|
-
for (const proposed of affected.targets.slice(1)) {
|
|
241
|
-
if (proposed.outcome !== 'empty'
|
|
242
|
-
|| !proposed.data
|
|
243
|
-
|| !Array.isArray(proposed.data.affectedTests)
|
|
244
|
-
|| proposed.data.affectedTests.length !== 0) {
|
|
245
|
-
fail('未作成seam pathのaffected evidenceがemptyでない');
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
sensorVersion: status.data.version,
|
|
250
|
-
affectedTests: [...new Set(anchorEvidence.data.affectedTests)].sort(),
|
|
251
|
-
outcomes,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function manualByTodo(manualEvidence) {
|
|
256
|
-
return new Map(manualEvidence.evidence.map((entry) => [entry.todo_id, entry]));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function manualRecordId(todoId) {
|
|
260
|
-
return `manual-${todoId}`;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function resourcesForTodo(todo, manual) {
|
|
264
|
-
const owns = [
|
|
265
|
-
{ kind: 'symbol', target: todo.anchor.symbol },
|
|
266
|
-
{ kind: 'path', target: todo.anchor.path },
|
|
267
|
-
];
|
|
268
|
-
return {
|
|
269
|
-
owns,
|
|
270
|
-
reads: manual.state_reads.map((target) => ({ kind: 'state', target })),
|
|
271
|
-
writes: [
|
|
272
|
-
...owns.map((entry) => ({ ...entry })),
|
|
273
|
-
...manual.state_writes.map((target) => ({ kind: 'state', target })),
|
|
274
|
-
...manual.effects.map((target) => ({ kind: 'effect', target })),
|
|
275
|
-
],
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function sharedManualConflicts(planInput, manualRecords) {
|
|
280
|
-
const [leftTodo, rightTodo] = planInput.todos;
|
|
281
|
-
const left = manualRecords.get(leftTodo.id);
|
|
282
|
-
const right = manualRecords.get(rightTodo.id);
|
|
283
|
-
const conflicts = [];
|
|
284
|
-
const stateTargets = [...new Set([
|
|
285
|
-
...left.state_reads,
|
|
286
|
-
...left.state_writes,
|
|
287
|
-
].filter((target) => (right.state_reads.includes(target) || right.state_writes.includes(target))
|
|
288
|
-
&& (left.state_writes.includes(target) || right.state_writes.includes(target))))].sort();
|
|
289
|
-
for (const target of stateTargets) {
|
|
290
|
-
conflicts.push({
|
|
291
|
-
id: `shared-state-${target}`,
|
|
292
|
-
kind: 'state',
|
|
293
|
-
todo_ids: [leftTodo.id, rightTodo.id],
|
|
294
|
-
resource: { kind: 'state', target },
|
|
295
|
-
evidence_refs: [manualRecordId(leftTodo.id), manualRecordId(rightTodo.id)],
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
const effectTargets = left.effects.filter((target) => right.effects.includes(target)).sort();
|
|
299
|
-
for (const target of effectTargets) {
|
|
300
|
-
conflicts.push({
|
|
301
|
-
id: `shared-effect-${target}`,
|
|
302
|
-
kind: 'effect',
|
|
303
|
-
todo_ids: [leftTodo.id, rightTodo.id],
|
|
304
|
-
resource: { kind: 'effect', target },
|
|
305
|
-
evidence_refs: [manualRecordId(leftTodo.id), manualRecordId(rightTodo.id)],
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
if (conflicts.some(({ id }) => !identifier(id))) {
|
|
309
|
-
fail('manual conflict targetからbounded IDを作れない');
|
|
310
|
-
}
|
|
311
|
-
return conflicts;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function graphRecords(querySet, sensorEvidence) {
|
|
315
|
-
return querySet.queries.map((query, index) => ({
|
|
316
|
-
id: query.id,
|
|
317
|
-
operation: query.operation,
|
|
318
|
-
status: sensorEvidence.outcomes[index].outcome,
|
|
319
|
-
result_digest: digestArtifact(portableSensorOutcome(sensorEvidence.outcomes[index])),
|
|
320
|
-
}));
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function compileManifest({
|
|
324
|
-
planInput,
|
|
325
|
-
manualEvidence,
|
|
326
|
-
querySet,
|
|
327
|
-
sensorEvidence,
|
|
328
|
-
codeSnapshotDigest,
|
|
329
|
-
seams,
|
|
330
|
-
facts,
|
|
331
|
-
}) {
|
|
332
|
-
const manuals = manualByTodo(manualEvidence);
|
|
333
|
-
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
334
|
-
const anchorQuery = findSingleQuery(querySet, 'query', anchorSymbol);
|
|
335
|
-
const anchorImpact = findSingleQuery(querySet, 'impact', anchorSymbol);
|
|
336
|
-
const affectedQuery = querySet.queries.find(({ operation }) => operation === 'affected');
|
|
337
|
-
const graphRefs = [anchorQuery.id, anchorImpact.id, affectedQuery.id];
|
|
338
|
-
const manualEvidenceRecords = planInput.todos.map((todo) => ({
|
|
339
|
-
id: manualRecordId(todo.id),
|
|
340
|
-
todo_id: todo.id,
|
|
341
|
-
result_digest: digestArtifact(manuals.get(todo.id)),
|
|
342
|
-
}));
|
|
343
|
-
const expectedUnknowns = seams.flatMap((seam) => [
|
|
344
|
-
`new_surface_unknown:${seam.symbol}`,
|
|
345
|
-
`path_not_indexed_unknown:${seam.path}`,
|
|
346
|
-
]);
|
|
347
|
-
const manualUnknowns = planInput.todos.flatMap((todo) => manuals.get(todo.id).unknowns
|
|
348
|
-
.map((unknown) => `manual_unknown:${todo.id}:${unknown}`));
|
|
349
|
-
const unknowns = [...new Set([...expectedUnknowns, ...manualUnknowns])];
|
|
350
|
-
const writeConflict = {
|
|
351
|
-
id: 'shared-dispatch-boundary',
|
|
352
|
-
kind: 'write_boundary',
|
|
353
|
-
todo_ids: planInput.todos.map(({ id }) => id),
|
|
354
|
-
resource: { kind: 'symbol', target: anchorSymbol },
|
|
355
|
-
evidence_refs: [anchorQuery.id, anchorImpact.id],
|
|
356
|
-
};
|
|
357
|
-
const manualConflicts = sharedManualConflicts(planInput, manuals);
|
|
358
|
-
const conflicts = [writeConflict, ...manualConflicts];
|
|
359
|
-
const conflictIds = conflicts.map(({ id }) => id);
|
|
360
|
-
const todos = planInput.todos.map((todo, index) => {
|
|
361
|
-
const resources = resourcesForTodo(todo, manuals.get(todo.id));
|
|
362
|
-
return {
|
|
363
|
-
id: todo.id,
|
|
364
|
-
owns: resources.owns,
|
|
365
|
-
reads: resources.reads,
|
|
366
|
-
writes: resources.writes,
|
|
367
|
-
hard_needs: [],
|
|
368
|
-
conflict_ids: conflictIds,
|
|
369
|
-
unknowns: [
|
|
370
|
-
`new_surface_unknown:${seams[index].symbol}`,
|
|
371
|
-
`path_not_indexed_unknown:${seams[index].path}`,
|
|
372
|
-
...manuals.get(todo.id).unknowns.map((unknown) => `manual_unknown:${todo.id}:${unknown}`),
|
|
373
|
-
],
|
|
374
|
-
tests: facts.affectedTests,
|
|
375
|
-
evidence_refs: [...graphRefs, manualRecordId(todo.id)],
|
|
376
|
-
};
|
|
377
|
-
});
|
|
378
|
-
const manifest = {
|
|
379
|
-
schema: 'lattice.boundary_manifest.v1',
|
|
380
|
-
plan_input_digest: digestArtifact(planInput),
|
|
381
|
-
source: {
|
|
382
|
-
code_snapshot_digest: codeSnapshotDigest,
|
|
383
|
-
query_set_digest: digestArtifact(querySet),
|
|
384
|
-
manual_evidence_digest: digestArtifact(manualEvidence),
|
|
385
|
-
sensor_version: facts.sensorVersion,
|
|
386
|
-
},
|
|
387
|
-
graph_evidence: graphRecords(querySet, sensorEvidence),
|
|
388
|
-
manual_evidence: manualEvidenceRecords,
|
|
389
|
-
todos,
|
|
390
|
-
conflicts,
|
|
391
|
-
unknowns,
|
|
392
|
-
};
|
|
393
|
-
if (!validateBoundaryManifest(manifest)) {
|
|
394
|
-
fail('生成したboundary manifestがpublic contractを満たさない');
|
|
395
|
-
}
|
|
396
|
-
return manifest;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
function compileVerdict(manifest, seams, manualEvidence) {
|
|
400
|
-
const manualBlocking = manualEvidence.evidence.some(({ unknowns }) => unknowns.length > 0);
|
|
401
|
-
const nonCodeConflict = manifest.conflicts.some(({ kind }) => kind === 'state' || kind === 'effect');
|
|
402
|
-
let verdict;
|
|
403
|
-
let seamCandidate = null;
|
|
404
|
-
let reasons;
|
|
405
|
-
if (manualBlocking) {
|
|
406
|
-
verdict = 'unknown_requires_evidence';
|
|
407
|
-
reasons = ['manual unknownが残るためindependenceまたはseam safetyを確定できない'];
|
|
408
|
-
} else if (nonCodeConflict) {
|
|
409
|
-
verdict = 'intentional_serial';
|
|
410
|
-
reasons = ['shared manual state/effect conflictはcode seamだけでは切断できない'];
|
|
411
|
-
} else {
|
|
412
|
-
verdict = 'seam_candidate';
|
|
413
|
-
reasons = ['shared code write boundaryは非重複ownershipへ抽出できる'];
|
|
414
|
-
seamCandidate = {
|
|
415
|
-
id: 'extract-dispatch-policies',
|
|
416
|
-
proposed_owns: seams.map((seam) => ({
|
|
417
|
-
todo_id: seam.todo_id,
|
|
418
|
-
resources: [
|
|
419
|
-
{ kind: 'symbol', target: seam.symbol },
|
|
420
|
-
{ kind: 'path', target: seam.path },
|
|
421
|
-
],
|
|
422
|
-
})),
|
|
423
|
-
preconditions: [
|
|
424
|
-
'characterization verifier is green',
|
|
425
|
-
'post-transform LatticeSensor query set resolves proposed surfaces',
|
|
426
|
-
],
|
|
427
|
-
};
|
|
428
|
-
}
|
|
429
|
-
const artifact = {
|
|
430
|
-
schema: 'lattice.boundary_verdict.v1',
|
|
431
|
-
boundary_manifest_digest: digestArtifact(manifest),
|
|
432
|
-
verdicts: [{
|
|
433
|
-
id: 'channel-label-verdict',
|
|
434
|
-
todo_ids: manifest.todos.map(({ id }) => id),
|
|
435
|
-
verdict,
|
|
436
|
-
conflict_ids: manifest.conflicts.map(({ id }) => id),
|
|
437
|
-
seam_candidate: seamCandidate,
|
|
438
|
-
reasons,
|
|
439
|
-
unknowns: manifest.unknowns,
|
|
440
|
-
}],
|
|
441
|
-
};
|
|
442
|
-
if (!validateBoundaryVerdict(artifact)) {
|
|
443
|
-
fail('生成したboundary verdictがpublic contractを満たさない');
|
|
444
|
-
}
|
|
445
|
-
return artifact;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function compilePlan(planInput, manifest) {
|
|
449
|
-
const edgeKinds = new Map([
|
|
450
|
-
['write_boundary', 'write_conflict'],
|
|
451
|
-
['state', 'state_conflict'],
|
|
452
|
-
['effect', 'effect_conflict'],
|
|
453
|
-
['semantic', 'semantic_conflict'],
|
|
454
|
-
['dynamic_unknown', 'dynamic_unknown'],
|
|
455
|
-
]);
|
|
456
|
-
const edges = manifest.conflicts.map((conflict) => ({
|
|
457
|
-
id: conflict.id,
|
|
458
|
-
from: conflict.todo_ids[0],
|
|
459
|
-
to: conflict.todo_ids[1],
|
|
460
|
-
kind: edgeKinds.get(conflict.kind),
|
|
461
|
-
evidence_refs: conflict.evidence_refs,
|
|
462
|
-
}));
|
|
463
|
-
const serialized = edges.length > 0;
|
|
464
|
-
const waves = serialized
|
|
465
|
-
? planInput.todos.map((todo, index) => ({ index, todo_ids: [todo.id] }))
|
|
466
|
-
: [{ index: 0, todo_ids: planInput.todos.map(({ id }) => id) }];
|
|
467
|
-
const graph = {
|
|
468
|
-
schema: 'lattice.plan_graph.v1',
|
|
469
|
-
plan_version: planInput.plan_version,
|
|
470
|
-
source_manifest_digest: digestArtifact(manifest),
|
|
471
|
-
capacity: { writers: planInput.capacity.writers },
|
|
472
|
-
nodes: planInput.todos.map((todo) => ({
|
|
473
|
-
id: todo.id,
|
|
474
|
-
outcome: todo.outcome,
|
|
475
|
-
owned_boundaries: manifest.todos.find(({ id }) => id === todo.id).owns,
|
|
476
|
-
})),
|
|
477
|
-
edges,
|
|
478
|
-
joins: [],
|
|
479
|
-
waves,
|
|
480
|
-
minimum_feasible_waves: waves.length,
|
|
481
|
-
};
|
|
482
|
-
if (!validatePlanGraph(graph)) {
|
|
483
|
-
fail('生成したcontrol plan graphがpublic contractを満たさない');
|
|
484
|
-
}
|
|
485
|
-
return graph;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* RC1 original fixtureをboundary manifest、typed verdict、control planへcompileする。
|
|
490
|
-
* @param {object} options
|
|
491
|
-
* @returns {object}
|
|
492
|
-
*/
|
|
493
|
-
export function compileControlArtifacts({
|
|
494
|
-
planInput,
|
|
495
|
-
manualEvidence,
|
|
496
|
-
querySet,
|
|
497
|
-
sensorEvidence,
|
|
498
|
-
codeSnapshotDigest,
|
|
499
|
-
} = {}) {
|
|
500
|
-
for (const value of [planInput, manualEvidence, querySet, sensorEvidence]) {
|
|
501
|
-
canonicalizeArtifact(value);
|
|
502
|
-
}
|
|
503
|
-
if (!validatePlanInput(planInput)) fail('plan inputがpublic contractを満たさない');
|
|
504
|
-
if (typeof codeSnapshotDigest !== 'string' || !SHA256.test(codeSnapshotDigest)) {
|
|
505
|
-
fail('code snapshot digestがSHA-256でない');
|
|
506
|
-
}
|
|
507
|
-
assertManualEvidence(planInput, manualEvidence);
|
|
508
|
-
assertQuerySet(querySet);
|
|
509
|
-
assertLatticeSensorEvidence(querySet, sensorEvidence);
|
|
510
|
-
const seams = rc1Seams(planInput);
|
|
511
|
-
assertRc1QueryCoverage(planInput, querySet, seams);
|
|
512
|
-
const facts = controlGraphFacts(planInput, querySet, sensorEvidence, seams);
|
|
513
|
-
const boundaryManifest = compileManifest({
|
|
514
|
-
planInput,
|
|
515
|
-
manualEvidence,
|
|
516
|
-
querySet,
|
|
517
|
-
sensorEvidence,
|
|
518
|
-
codeSnapshotDigest,
|
|
519
|
-
seams,
|
|
520
|
-
facts,
|
|
521
|
-
});
|
|
522
|
-
const boundaryVerdict = compileVerdict(boundaryManifest, seams, manualEvidence);
|
|
523
|
-
const planGraph = compilePlan(planInput, boundaryManifest);
|
|
524
|
-
return {
|
|
525
|
-
boundary_manifest: boundaryManifest,
|
|
526
|
-
boundary_manifest_digest: digestArtifact(boundaryManifest),
|
|
527
|
-
boundary_verdict: boundaryVerdict,
|
|
528
|
-
boundary_verdict_digest: digestArtifact(boundaryVerdict),
|
|
529
|
-
plan_graph: planGraph,
|
|
530
|
-
plan_graph_digest: digestArtifact(planGraph),
|
|
531
|
-
};
|
|
532
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
canonicalizeArtifact,
|
|
3
|
+
digestArtifact,
|
|
4
|
+
validateBoundaryManifest,
|
|
5
|
+
validateBoundaryVerdict,
|
|
6
|
+
validatePlanGraph,
|
|
7
|
+
validatePlanInput,
|
|
8
|
+
} from './artifact-contracts.mjs';
|
|
9
|
+
import { portableSensorOutcome } from './sensor-adapter.mjs';
|
|
10
|
+
|
|
11
|
+
const SHA256 = /^[0-9a-f]{64}$/;
|
|
12
|
+
const IDENTIFIER = /^[0-9A-Za-z](?:[0-9A-Za-z._-]{0,127})$/;
|
|
13
|
+
const OPERATIONS = new Set(['status', 'query', 'callers', 'callees', 'impact', 'affected']);
|
|
14
|
+
const SEAM_BY_CONCERN = new Map([
|
|
15
|
+
['dispatch.channel_policy', {
|
|
16
|
+
symbol: 'selectDispatchChannel',
|
|
17
|
+
path: 'research/fixtures/dispatch-record/src/dispatch-channel.mjs',
|
|
18
|
+
}],
|
|
19
|
+
['dispatch.label_policy', {
|
|
20
|
+
symbol: 'formatDispatchLabel',
|
|
21
|
+
path: 'research/fixtures/dispatch-record/src/dispatch-label.mjs',
|
|
22
|
+
}],
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
function fail(reason) {
|
|
26
|
+
throw new TypeError(`control compile契約違反: ${reason}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function exactRecord(value, keys) {
|
|
30
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)
|
|
31
|
+
|| Object.getPrototypeOf(value) !== Object.prototype) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const actual = Object.keys(value).sort();
|
|
35
|
+
const expected = [...keys].sort();
|
|
36
|
+
return actual.length === expected.length
|
|
37
|
+
&& actual.every((key, index) => key === expected[index]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function nonEmptyText(value) {
|
|
41
|
+
return typeof value === 'string'
|
|
42
|
+
&& value.length > 0
|
|
43
|
+
&& value === value.trim()
|
|
44
|
+
&& !/[\u0000-\u001f\u007f]/.test(value)
|
|
45
|
+
&& Buffer.byteLength(value, 'utf8') <= 4_096;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function identifier(value) {
|
|
49
|
+
return typeof value === 'string' && IDENTIFIER.test(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function repoPath(value) {
|
|
53
|
+
return nonEmptyText(value)
|
|
54
|
+
&& Buffer.byteLength(value, 'utf8') <= 1_024
|
|
55
|
+
&& !value.startsWith('/')
|
|
56
|
+
&& !value.endsWith('/')
|
|
57
|
+
&& !value.includes('\\')
|
|
58
|
+
&& !/^[A-Za-z]:/.test(value)
|
|
59
|
+
&& value.split('/').every((segment) => segment !== '' && segment !== '.' && segment !== '..');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function uniqueTextArray(value) {
|
|
63
|
+
return Array.isArray(value)
|
|
64
|
+
&& value.length <= 256
|
|
65
|
+
&& value.every(nonEmptyText)
|
|
66
|
+
&& new Set(value).size === value.length;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function assertManualEvidence(planInput, manualEvidence) {
|
|
70
|
+
if (!exactRecord(manualEvidence, ['schema', 'evidence'])
|
|
71
|
+
|| manualEvidence.schema !== 'lattice.manual_boundary_evidence.v1'
|
|
72
|
+
|| !Array.isArray(manualEvidence.evidence)
|
|
73
|
+
|| manualEvidence.evidence.length !== planInput.todos.length) {
|
|
74
|
+
fail('manual evidence rootが不正');
|
|
75
|
+
}
|
|
76
|
+
const todoIds = new Set(planInput.todos.map(({ id }) => id));
|
|
77
|
+
const observed = new Set();
|
|
78
|
+
for (const entry of manualEvidence.evidence) {
|
|
79
|
+
if (!exactRecord(entry, [
|
|
80
|
+
'todo_id',
|
|
81
|
+
'state_reads',
|
|
82
|
+
'state_writes',
|
|
83
|
+
'effects',
|
|
84
|
+
'unknowns',
|
|
85
|
+
])
|
|
86
|
+
|| !identifier(entry.todo_id)
|
|
87
|
+
|| !todoIds.has(entry.todo_id)
|
|
88
|
+
|| observed.has(entry.todo_id)
|
|
89
|
+
|| !uniqueTextArray(entry.state_reads)
|
|
90
|
+
|| !uniqueTextArray(entry.state_writes)
|
|
91
|
+
|| !uniqueTextArray(entry.effects)
|
|
92
|
+
|| !uniqueTextArray(entry.unknowns)) {
|
|
93
|
+
fail('manual evidence recordがplan TODOと一致しない');
|
|
94
|
+
}
|
|
95
|
+
observed.add(entry.todo_id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function assertQuerySet(querySet) {
|
|
100
|
+
if (!exactRecord(querySet, ['schema', 'queries'])
|
|
101
|
+
|| querySet.schema !== 'lattice.sensor_query_set.v1'
|
|
102
|
+
|| !Array.isArray(querySet.queries)
|
|
103
|
+
|| querySet.queries.length === 0
|
|
104
|
+
|| querySet.queries.length > 256) {
|
|
105
|
+
fail('query set rootが不正');
|
|
106
|
+
}
|
|
107
|
+
const ids = new Set();
|
|
108
|
+
for (const query of querySet.queries) {
|
|
109
|
+
if (!query || typeof query !== 'object' || Array.isArray(query)
|
|
110
|
+
|| !identifier(query.id) || ids.has(query.id) || !OPERATIONS.has(query.operation)) {
|
|
111
|
+
fail('query set recordが不正');
|
|
112
|
+
}
|
|
113
|
+
ids.add(query.id);
|
|
114
|
+
if (query.operation === 'status') {
|
|
115
|
+
if (!exactRecord(query, ['id', 'operation'])) fail('status query shapeが不正');
|
|
116
|
+
} else if (query.operation === 'affected') {
|
|
117
|
+
if (!exactRecord(query, ['id', 'operation', 'targets'])
|
|
118
|
+
|| !Array.isArray(query.targets)
|
|
119
|
+
|| query.targets.length === 0
|
|
120
|
+
|| !query.targets.every(repoPath)
|
|
121
|
+
|| new Set(query.targets).size !== query.targets.length) {
|
|
122
|
+
fail('affected query shapeが不正');
|
|
123
|
+
}
|
|
124
|
+
} else if (!exactRecord(query, ['id', 'operation', 'target'])
|
|
125
|
+
|| !nonEmptyText(query.target)) {
|
|
126
|
+
fail('symbol query shapeが不正');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function assertLatticeSensorEvidence(querySet, sensorEvidence) {
|
|
132
|
+
if (!exactRecord(sensorEvidence, ['cwd', 'outcomes'])
|
|
133
|
+
|| !nonEmptyText(sensorEvidence.cwd)
|
|
134
|
+
|| !Array.isArray(sensorEvidence.outcomes)
|
|
135
|
+
|| sensorEvidence.outcomes.length !== querySet.queries.length) {
|
|
136
|
+
fail('LatticeSensor evidence rootがquery setと一致しない');
|
|
137
|
+
}
|
|
138
|
+
for (let index = 0; index < querySet.queries.length; index += 1) {
|
|
139
|
+
const query = querySet.queries[index];
|
|
140
|
+
const outcome = sensorEvidence.outcomes[index];
|
|
141
|
+
if (!outcome || typeof outcome !== 'object' || Array.isArray(outcome)
|
|
142
|
+
|| outcome.id !== query.id
|
|
143
|
+
|| outcome.operation !== query.operation
|
|
144
|
+
|| !nonEmptyText(outcome.outcome)) {
|
|
145
|
+
fail(`LatticeSensor evidence ${query.id}がID/operation/順序と一致しない`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findSingleQuery(querySet, operation, target) {
|
|
151
|
+
const matches = querySet.queries.filter((query) => query.operation === operation
|
|
152
|
+
&& (target === undefined || query.target === target));
|
|
153
|
+
if (matches.length !== 1) fail(`query setに${operation}:${target ?? ''}がexactly one存在しない`);
|
|
154
|
+
return matches[0];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rc1Seams(planInput) {
|
|
158
|
+
if (planInput.todos.length !== 2
|
|
159
|
+
|| planInput.plan_version !== 'rc1-control-v1'
|
|
160
|
+
|| planInput.capacity.writers !== 2
|
|
161
|
+
|| new Set(planInput.todos.map(({ anchor }) => anchor.symbol)).size !== 1
|
|
162
|
+
|| new Set(planInput.todos.map(({ anchor }) => anchor.path)).size !== 1
|
|
163
|
+
|| planInput.todos[0].anchor.path !== planInput.project.fixture_entry) {
|
|
164
|
+
fail('RC1 control planはcapacity 2で同じmonolithic anchorを持つ2 TODOでなければならない');
|
|
165
|
+
}
|
|
166
|
+
return planInput.todos.map((todo) => {
|
|
167
|
+
const seam = SEAM_BY_CONCERN.get(todo.concern);
|
|
168
|
+
if (!seam) fail(`query setへ接続できないconcern: ${todo.concern}`);
|
|
169
|
+
return { todo_id: todo.id, ...seam };
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function assertRc1QueryCoverage(planInput, querySet, seams) {
|
|
174
|
+
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
175
|
+
const anchorPath = planInput.todos[0].anchor.path;
|
|
176
|
+
findSingleQuery(querySet, 'status');
|
|
177
|
+
for (const operation of ['query', 'callers', 'callees', 'impact']) {
|
|
178
|
+
findSingleQuery(querySet, operation, anchorSymbol);
|
|
179
|
+
}
|
|
180
|
+
for (const seam of seams) {
|
|
181
|
+
findSingleQuery(querySet, 'query', seam.symbol);
|
|
182
|
+
findSingleQuery(querySet, 'impact', seam.symbol);
|
|
183
|
+
}
|
|
184
|
+
const affected = querySet.queries.filter(({ operation }) => operation === 'affected');
|
|
185
|
+
const expectedTargets = [anchorPath, ...seams.map(({ path }) => path)];
|
|
186
|
+
if (affected.length !== 1
|
|
187
|
+
|| JSON.stringify(affected[0].targets) !== JSON.stringify(expectedTargets)
|
|
188
|
+
|| querySet.queries.length !== 10) {
|
|
189
|
+
fail('query setがRC1 controlの固定coverageからdriftした');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function outcomeById(querySet, sensorEvidence) {
|
|
194
|
+
return new Map(querySet.queries.map((query, index) => [
|
|
195
|
+
query.id,
|
|
196
|
+
sensorEvidence.outcomes[index],
|
|
197
|
+
]));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function assertOutcome(outcomes, query, expected) {
|
|
201
|
+
const outcome = outcomes.get(query.id);
|
|
202
|
+
if (outcome.outcome !== expected) {
|
|
203
|
+
fail(`LatticeSensor evidence ${query.id}は${expected}でなく${outcome.outcome}`);
|
|
204
|
+
}
|
|
205
|
+
return outcome;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function controlGraphFacts(planInput, querySet, sensorEvidence, seams) {
|
|
209
|
+
const outcomes = outcomeById(querySet, sensorEvidence);
|
|
210
|
+
const statusQuery = findSingleQuery(querySet, 'status');
|
|
211
|
+
const status = assertOutcome(outcomes, statusQuery, 'ready');
|
|
212
|
+
if (!status.data || !nonEmptyText(status.data.version)) {
|
|
213
|
+
fail('LatticeSensor statusにversionがない');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
217
|
+
for (const operation of ['query', 'callers', 'callees', 'impact']) {
|
|
218
|
+
assertOutcome(outcomes, findSingleQuery(querySet, operation, anchorSymbol), 'ready');
|
|
219
|
+
}
|
|
220
|
+
for (const seam of seams) {
|
|
221
|
+
assertOutcome(outcomes, findSingleQuery(querySet, 'query', seam.symbol), 'symbol_absent');
|
|
222
|
+
assertOutcome(outcomes, findSingleQuery(querySet, 'impact', seam.symbol), 'symbol_absent');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const affectedQuery = querySet.queries.find(({ operation }) => operation === 'affected');
|
|
226
|
+
const affected = assertOutcome(outcomes, affectedQuery, 'unresolved');
|
|
227
|
+
if (!Array.isArray(affected.targets)
|
|
228
|
+
|| affected.targets.length !== affectedQuery.targets.length
|
|
229
|
+
|| affected.targets.some((entry, index) => entry.target !== affectedQuery.targets[index])) {
|
|
230
|
+
fail('affected LatticeSensor evidenceがquery targetsと一致しない');
|
|
231
|
+
}
|
|
232
|
+
const anchorEvidence = affected.targets[0];
|
|
233
|
+
if (anchorEvidence.outcome !== 'ready'
|
|
234
|
+
|| !anchorEvidence.data
|
|
235
|
+
|| !Array.isArray(anchorEvidence.data.affectedTests)
|
|
236
|
+
|| anchorEvidence.data.affectedTests.length === 0
|
|
237
|
+
|| !anchorEvidence.data.affectedTests.every(repoPath)) {
|
|
238
|
+
fail('anchorのaffected testを取得できない');
|
|
239
|
+
}
|
|
240
|
+
for (const proposed of affected.targets.slice(1)) {
|
|
241
|
+
if (proposed.outcome !== 'empty'
|
|
242
|
+
|| !proposed.data
|
|
243
|
+
|| !Array.isArray(proposed.data.affectedTests)
|
|
244
|
+
|| proposed.data.affectedTests.length !== 0) {
|
|
245
|
+
fail('未作成seam pathのaffected evidenceがemptyでない');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
sensorVersion: status.data.version,
|
|
250
|
+
affectedTests: [...new Set(anchorEvidence.data.affectedTests)].sort(),
|
|
251
|
+
outcomes,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function manualByTodo(manualEvidence) {
|
|
256
|
+
return new Map(manualEvidence.evidence.map((entry) => [entry.todo_id, entry]));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function manualRecordId(todoId) {
|
|
260
|
+
return `manual-${todoId}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resourcesForTodo(todo, manual) {
|
|
264
|
+
const owns = [
|
|
265
|
+
{ kind: 'symbol', target: todo.anchor.symbol },
|
|
266
|
+
{ kind: 'path', target: todo.anchor.path },
|
|
267
|
+
];
|
|
268
|
+
return {
|
|
269
|
+
owns,
|
|
270
|
+
reads: manual.state_reads.map((target) => ({ kind: 'state', target })),
|
|
271
|
+
writes: [
|
|
272
|
+
...owns.map((entry) => ({ ...entry })),
|
|
273
|
+
...manual.state_writes.map((target) => ({ kind: 'state', target })),
|
|
274
|
+
...manual.effects.map((target) => ({ kind: 'effect', target })),
|
|
275
|
+
],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function sharedManualConflicts(planInput, manualRecords) {
|
|
280
|
+
const [leftTodo, rightTodo] = planInput.todos;
|
|
281
|
+
const left = manualRecords.get(leftTodo.id);
|
|
282
|
+
const right = manualRecords.get(rightTodo.id);
|
|
283
|
+
const conflicts = [];
|
|
284
|
+
const stateTargets = [...new Set([
|
|
285
|
+
...left.state_reads,
|
|
286
|
+
...left.state_writes,
|
|
287
|
+
].filter((target) => (right.state_reads.includes(target) || right.state_writes.includes(target))
|
|
288
|
+
&& (left.state_writes.includes(target) || right.state_writes.includes(target))))].sort();
|
|
289
|
+
for (const target of stateTargets) {
|
|
290
|
+
conflicts.push({
|
|
291
|
+
id: `shared-state-${target}`,
|
|
292
|
+
kind: 'state',
|
|
293
|
+
todo_ids: [leftTodo.id, rightTodo.id],
|
|
294
|
+
resource: { kind: 'state', target },
|
|
295
|
+
evidence_refs: [manualRecordId(leftTodo.id), manualRecordId(rightTodo.id)],
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const effectTargets = left.effects.filter((target) => right.effects.includes(target)).sort();
|
|
299
|
+
for (const target of effectTargets) {
|
|
300
|
+
conflicts.push({
|
|
301
|
+
id: `shared-effect-${target}`,
|
|
302
|
+
kind: 'effect',
|
|
303
|
+
todo_ids: [leftTodo.id, rightTodo.id],
|
|
304
|
+
resource: { kind: 'effect', target },
|
|
305
|
+
evidence_refs: [manualRecordId(leftTodo.id), manualRecordId(rightTodo.id)],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (conflicts.some(({ id }) => !identifier(id))) {
|
|
309
|
+
fail('manual conflict targetからbounded IDを作れない');
|
|
310
|
+
}
|
|
311
|
+
return conflicts;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function graphRecords(querySet, sensorEvidence) {
|
|
315
|
+
return querySet.queries.map((query, index) => ({
|
|
316
|
+
id: query.id,
|
|
317
|
+
operation: query.operation,
|
|
318
|
+
status: sensorEvidence.outcomes[index].outcome,
|
|
319
|
+
result_digest: digestArtifact(portableSensorOutcome(sensorEvidence.outcomes[index])),
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function compileManifest({
|
|
324
|
+
planInput,
|
|
325
|
+
manualEvidence,
|
|
326
|
+
querySet,
|
|
327
|
+
sensorEvidence,
|
|
328
|
+
codeSnapshotDigest,
|
|
329
|
+
seams,
|
|
330
|
+
facts,
|
|
331
|
+
}) {
|
|
332
|
+
const manuals = manualByTodo(manualEvidence);
|
|
333
|
+
const anchorSymbol = planInput.todos[0].anchor.symbol;
|
|
334
|
+
const anchorQuery = findSingleQuery(querySet, 'query', anchorSymbol);
|
|
335
|
+
const anchorImpact = findSingleQuery(querySet, 'impact', anchorSymbol);
|
|
336
|
+
const affectedQuery = querySet.queries.find(({ operation }) => operation === 'affected');
|
|
337
|
+
const graphRefs = [anchorQuery.id, anchorImpact.id, affectedQuery.id];
|
|
338
|
+
const manualEvidenceRecords = planInput.todos.map((todo) => ({
|
|
339
|
+
id: manualRecordId(todo.id),
|
|
340
|
+
todo_id: todo.id,
|
|
341
|
+
result_digest: digestArtifact(manuals.get(todo.id)),
|
|
342
|
+
}));
|
|
343
|
+
const expectedUnknowns = seams.flatMap((seam) => [
|
|
344
|
+
`new_surface_unknown:${seam.symbol}`,
|
|
345
|
+
`path_not_indexed_unknown:${seam.path}`,
|
|
346
|
+
]);
|
|
347
|
+
const manualUnknowns = planInput.todos.flatMap((todo) => manuals.get(todo.id).unknowns
|
|
348
|
+
.map((unknown) => `manual_unknown:${todo.id}:${unknown}`));
|
|
349
|
+
const unknowns = [...new Set([...expectedUnknowns, ...manualUnknowns])];
|
|
350
|
+
const writeConflict = {
|
|
351
|
+
id: 'shared-dispatch-boundary',
|
|
352
|
+
kind: 'write_boundary',
|
|
353
|
+
todo_ids: planInput.todos.map(({ id }) => id),
|
|
354
|
+
resource: { kind: 'symbol', target: anchorSymbol },
|
|
355
|
+
evidence_refs: [anchorQuery.id, anchorImpact.id],
|
|
356
|
+
};
|
|
357
|
+
const manualConflicts = sharedManualConflicts(planInput, manuals);
|
|
358
|
+
const conflicts = [writeConflict, ...manualConflicts];
|
|
359
|
+
const conflictIds = conflicts.map(({ id }) => id);
|
|
360
|
+
const todos = planInput.todos.map((todo, index) => {
|
|
361
|
+
const resources = resourcesForTodo(todo, manuals.get(todo.id));
|
|
362
|
+
return {
|
|
363
|
+
id: todo.id,
|
|
364
|
+
owns: resources.owns,
|
|
365
|
+
reads: resources.reads,
|
|
366
|
+
writes: resources.writes,
|
|
367
|
+
hard_needs: [],
|
|
368
|
+
conflict_ids: conflictIds,
|
|
369
|
+
unknowns: [
|
|
370
|
+
`new_surface_unknown:${seams[index].symbol}`,
|
|
371
|
+
`path_not_indexed_unknown:${seams[index].path}`,
|
|
372
|
+
...manuals.get(todo.id).unknowns.map((unknown) => `manual_unknown:${todo.id}:${unknown}`),
|
|
373
|
+
],
|
|
374
|
+
tests: facts.affectedTests,
|
|
375
|
+
evidence_refs: [...graphRefs, manualRecordId(todo.id)],
|
|
376
|
+
};
|
|
377
|
+
});
|
|
378
|
+
const manifest = {
|
|
379
|
+
schema: 'lattice.boundary_manifest.v1',
|
|
380
|
+
plan_input_digest: digestArtifact(planInput),
|
|
381
|
+
source: {
|
|
382
|
+
code_snapshot_digest: codeSnapshotDigest,
|
|
383
|
+
query_set_digest: digestArtifact(querySet),
|
|
384
|
+
manual_evidence_digest: digestArtifact(manualEvidence),
|
|
385
|
+
sensor_version: facts.sensorVersion,
|
|
386
|
+
},
|
|
387
|
+
graph_evidence: graphRecords(querySet, sensorEvidence),
|
|
388
|
+
manual_evidence: manualEvidenceRecords,
|
|
389
|
+
todos,
|
|
390
|
+
conflicts,
|
|
391
|
+
unknowns,
|
|
392
|
+
};
|
|
393
|
+
if (!validateBoundaryManifest(manifest)) {
|
|
394
|
+
fail('生成したboundary manifestがpublic contractを満たさない');
|
|
395
|
+
}
|
|
396
|
+
return manifest;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function compileVerdict(manifest, seams, manualEvidence) {
|
|
400
|
+
const manualBlocking = manualEvidence.evidence.some(({ unknowns }) => unknowns.length > 0);
|
|
401
|
+
const nonCodeConflict = manifest.conflicts.some(({ kind }) => kind === 'state' || kind === 'effect');
|
|
402
|
+
let verdict;
|
|
403
|
+
let seamCandidate = null;
|
|
404
|
+
let reasons;
|
|
405
|
+
if (manualBlocking) {
|
|
406
|
+
verdict = 'unknown_requires_evidence';
|
|
407
|
+
reasons = ['manual unknownが残るためindependenceまたはseam safetyを確定できない'];
|
|
408
|
+
} else if (nonCodeConflict) {
|
|
409
|
+
verdict = 'intentional_serial';
|
|
410
|
+
reasons = ['shared manual state/effect conflictはcode seamだけでは切断できない'];
|
|
411
|
+
} else {
|
|
412
|
+
verdict = 'seam_candidate';
|
|
413
|
+
reasons = ['shared code write boundaryは非重複ownershipへ抽出できる'];
|
|
414
|
+
seamCandidate = {
|
|
415
|
+
id: 'extract-dispatch-policies',
|
|
416
|
+
proposed_owns: seams.map((seam) => ({
|
|
417
|
+
todo_id: seam.todo_id,
|
|
418
|
+
resources: [
|
|
419
|
+
{ kind: 'symbol', target: seam.symbol },
|
|
420
|
+
{ kind: 'path', target: seam.path },
|
|
421
|
+
],
|
|
422
|
+
})),
|
|
423
|
+
preconditions: [
|
|
424
|
+
'characterization verifier is green',
|
|
425
|
+
'post-transform LatticeSensor query set resolves proposed surfaces',
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const artifact = {
|
|
430
|
+
schema: 'lattice.boundary_verdict.v1',
|
|
431
|
+
boundary_manifest_digest: digestArtifact(manifest),
|
|
432
|
+
verdicts: [{
|
|
433
|
+
id: 'channel-label-verdict',
|
|
434
|
+
todo_ids: manifest.todos.map(({ id }) => id),
|
|
435
|
+
verdict,
|
|
436
|
+
conflict_ids: manifest.conflicts.map(({ id }) => id),
|
|
437
|
+
seam_candidate: seamCandidate,
|
|
438
|
+
reasons,
|
|
439
|
+
unknowns: manifest.unknowns,
|
|
440
|
+
}],
|
|
441
|
+
};
|
|
442
|
+
if (!validateBoundaryVerdict(artifact)) {
|
|
443
|
+
fail('生成したboundary verdictがpublic contractを満たさない');
|
|
444
|
+
}
|
|
445
|
+
return artifact;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function compilePlan(planInput, manifest) {
|
|
449
|
+
const edgeKinds = new Map([
|
|
450
|
+
['write_boundary', 'write_conflict'],
|
|
451
|
+
['state', 'state_conflict'],
|
|
452
|
+
['effect', 'effect_conflict'],
|
|
453
|
+
['semantic', 'semantic_conflict'],
|
|
454
|
+
['dynamic_unknown', 'dynamic_unknown'],
|
|
455
|
+
]);
|
|
456
|
+
const edges = manifest.conflicts.map((conflict) => ({
|
|
457
|
+
id: conflict.id,
|
|
458
|
+
from: conflict.todo_ids[0],
|
|
459
|
+
to: conflict.todo_ids[1],
|
|
460
|
+
kind: edgeKinds.get(conflict.kind),
|
|
461
|
+
evidence_refs: conflict.evidence_refs,
|
|
462
|
+
}));
|
|
463
|
+
const serialized = edges.length > 0;
|
|
464
|
+
const waves = serialized
|
|
465
|
+
? planInput.todos.map((todo, index) => ({ index, todo_ids: [todo.id] }))
|
|
466
|
+
: [{ index: 0, todo_ids: planInput.todos.map(({ id }) => id) }];
|
|
467
|
+
const graph = {
|
|
468
|
+
schema: 'lattice.plan_graph.v1',
|
|
469
|
+
plan_version: planInput.plan_version,
|
|
470
|
+
source_manifest_digest: digestArtifact(manifest),
|
|
471
|
+
capacity: { writers: planInput.capacity.writers },
|
|
472
|
+
nodes: planInput.todos.map((todo) => ({
|
|
473
|
+
id: todo.id,
|
|
474
|
+
outcome: todo.outcome,
|
|
475
|
+
owned_boundaries: manifest.todos.find(({ id }) => id === todo.id).owns,
|
|
476
|
+
})),
|
|
477
|
+
edges,
|
|
478
|
+
joins: [],
|
|
479
|
+
waves,
|
|
480
|
+
minimum_feasible_waves: waves.length,
|
|
481
|
+
};
|
|
482
|
+
if (!validatePlanGraph(graph)) {
|
|
483
|
+
fail('生成したcontrol plan graphがpublic contractを満たさない');
|
|
484
|
+
}
|
|
485
|
+
return graph;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* RC1 original fixtureをboundary manifest、typed verdict、control planへcompileする。
|
|
490
|
+
* @param {object} options
|
|
491
|
+
* @returns {object}
|
|
492
|
+
*/
|
|
493
|
+
export function compileControlArtifacts({
|
|
494
|
+
planInput,
|
|
495
|
+
manualEvidence,
|
|
496
|
+
querySet,
|
|
497
|
+
sensorEvidence,
|
|
498
|
+
codeSnapshotDigest,
|
|
499
|
+
} = {}) {
|
|
500
|
+
for (const value of [planInput, manualEvidence, querySet, sensorEvidence]) {
|
|
501
|
+
canonicalizeArtifact(value);
|
|
502
|
+
}
|
|
503
|
+
if (!validatePlanInput(planInput)) fail('plan inputがpublic contractを満たさない');
|
|
504
|
+
if (typeof codeSnapshotDigest !== 'string' || !SHA256.test(codeSnapshotDigest)) {
|
|
505
|
+
fail('code snapshot digestがSHA-256でない');
|
|
506
|
+
}
|
|
507
|
+
assertManualEvidence(planInput, manualEvidence);
|
|
508
|
+
assertQuerySet(querySet);
|
|
509
|
+
assertLatticeSensorEvidence(querySet, sensorEvidence);
|
|
510
|
+
const seams = rc1Seams(planInput);
|
|
511
|
+
assertRc1QueryCoverage(planInput, querySet, seams);
|
|
512
|
+
const facts = controlGraphFacts(planInput, querySet, sensorEvidence, seams);
|
|
513
|
+
const boundaryManifest = compileManifest({
|
|
514
|
+
planInput,
|
|
515
|
+
manualEvidence,
|
|
516
|
+
querySet,
|
|
517
|
+
sensorEvidence,
|
|
518
|
+
codeSnapshotDigest,
|
|
519
|
+
seams,
|
|
520
|
+
facts,
|
|
521
|
+
});
|
|
522
|
+
const boundaryVerdict = compileVerdict(boundaryManifest, seams, manualEvidence);
|
|
523
|
+
const planGraph = compilePlan(planInput, boundaryManifest);
|
|
524
|
+
return {
|
|
525
|
+
boundary_manifest: boundaryManifest,
|
|
526
|
+
boundary_manifest_digest: digestArtifact(boundaryManifest),
|
|
527
|
+
boundary_verdict: boundaryVerdict,
|
|
528
|
+
boundary_verdict_digest: digestArtifact(boundaryVerdict),
|
|
529
|
+
plan_graph: planGraph,
|
|
530
|
+
plan_graph_digest: digestArtifact(planGraph),
|
|
531
|
+
};
|
|
532
|
+
}
|