@quolu/lattice 0.16.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"precheck": "node --check src/bridge-launch-agent.mjs",
|
|
63
63
|
"check": "node --check bin/lattice.mjs && node --check bin/lattice-mcp.mjs && node --check bin/lattice-dashboard.mjs && node --check bin/lattice-bridge.mjs && node --check bin/lattice-scripted-adapter.mjs && node --check src/cli-stdio.mjs && node --check src/bridge-address.mjs && node --check src/bridge-registrar.mjs && node --check src/todo-independence-guidance.mjs && node --check src/todo-independence.mjs && node --check src/todo-independence-contracts.mjs && node --check src/seam-proposal-contracts.mjs && node --check src/seam-proposal-queries.mjs && node --check src/seam-proposal.mjs && node --check src/todo-gantt-layout.mjs && node --check src/todo-gantt-scope.mjs && node --check src/bridge-config.mjs && node --check src/bridge-server.mjs && node --check src/bridge-daemon.mjs && node --check src/bridge-cli.mjs && node --check src/project-cli.mjs && node --check src/sensor-cli.mjs && node --check src/sensor-runtime.mjs && node --check src/sensor-adapter.mjs && node --check src/factory-diagnostics.mjs && node --check src/runtime-errors.mjs && node --check src/runtime-contracts.mjs && node --check src/runtime-event-store.mjs && node --check src/runtime-adapter-registry.mjs && node --check src/runtime-scripted-adapter-controller.mjs && node --check src/hash-chain.mjs && node --check src/dag-chain.mjs && node --check src/todo-contracts.mjs && node --check src/todo-chain.mjs && node --check src/todo-store.mjs && node --check src/todo-migration.mjs && node --check src/todo-revision.mjs && node --check src/todo-status.mjs && node --check src/todo-cli.mjs && node --check src/todo-dashboard-registry.mjs && node --check src/todo-gantt-presentation.mjs && node --check src/todo-gantt-live.mjs && node --check src/bounded-seam.mjs && node --check src/todo-narrative-anchor.mjs && node --check src/todo-markdown-renderer.mjs && node --check src/todo-gantt-svg.mjs && node --check src/todo-gantt-html.mjs && node --check src/runtime-projection.mjs && node --check src/runtime-decision-verifier.mjs && node --check src/runtime-front-end.mjs && node --check src/runtime-cli.mjs && node --check src/rc3-dogfood-scaffold.mjs && node --check src/runtime-engine.mjs && node --check src/runtime-scripted-executor.mjs && node --check src/runtime-diff-observer.mjs && node --check src/runtime-worktree-executor.mjs && node --check src/runtime-hold-recompile.mjs && node --check src/rc3-scripted-campaign.mjs && node --check src/rc3-actual-dogfood.mjs && node --check src/rc4-stage1-dogfood.mjs && node --check research/fixtures/dispatch-record/src/dispatch-record.mjs && node --check test/research-dispatch-record.test.mjs",
|
|
64
64
|
"check:project-identity": "node --check src/project-identity.mjs",
|
|
65
|
-
"ci": "npm run test && npm run test:sensor && npm run check && npm run check:project-identity"
|
|
65
|
+
"ci": "npm run test && npm run test:sensor && npm run check && npm run check:project-identity && npm run verify:todo-store",
|
|
66
|
+
"verify:todo-store": "node bin/lattice.mjs todo verify --json"
|
|
66
67
|
}
|
|
67
68
|
}
|
package/src/bridge-daemon.mjs
CHANGED
|
@@ -17,6 +17,9 @@ const STOP_REQUEST_SCHEMA = 'lattice.bridge_stop_request.v1';
|
|
|
17
17
|
const STOP_RECEIPT_SCHEMA = 'lattice.bridge_stop_receipt.v1';
|
|
18
18
|
const ACTIVE_MARKER_SCHEMA = 'lattice.bridge_daemon_active.v1';
|
|
19
19
|
const CONTROL_MAX_BYTES = 65_536;
|
|
20
|
+
/** control fileの差し替え競合だけを吸収する再読の上限と間隔(`readStrictJson`が唯一の使用者)。 */
|
|
21
|
+
const CONTROL_READ_RETRY_LIMIT = 5;
|
|
22
|
+
const CONTROL_READ_RETRY_DELAY_MS = 20;
|
|
20
23
|
|
|
21
24
|
export function bridgeDaemonDescriptorPath(env = process.env) {
|
|
22
25
|
return path.join(bridgeConfigPaths(env).root, 'bridge-daemon.json');
|
|
@@ -66,7 +69,16 @@ function isIsoTimestamp(value) {
|
|
|
66
69
|
try { return new Date(value).toISOString() === value; } catch { return false; }
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
/** 読み取り中の差し替え・消失につけるmarker。内容の異常と同じ顔をさせない。 */
|
|
73
|
+
const CONTROL_READ_RACE = Symbol('bridge_control_read_race');
|
|
74
|
+
|
|
75
|
+
function controlReadRace(message) {
|
|
76
|
+
const error = new Error(message);
|
|
77
|
+
error[CONTROL_READ_RACE] = true;
|
|
78
|
+
return error;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readStrictJsonOnce(ref, label) {
|
|
70
82
|
let before;
|
|
71
83
|
let handle;
|
|
72
84
|
try {
|
|
@@ -77,12 +89,12 @@ async function readStrictJson(ref, code, label) {
|
|
|
77
89
|
const opened = await handle.stat();
|
|
78
90
|
if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino
|
|
79
91
|
|| opened.size !== before.size || opened.size > CONTROL_MAX_BYTES) {
|
|
80
|
-
throw
|
|
92
|
+
throw controlReadRace(`${label} changed during validation`);
|
|
81
93
|
}
|
|
82
94
|
const text = await handle.readFile('utf8');
|
|
83
95
|
const after = await lstat(ref);
|
|
84
96
|
if (after.dev !== opened.dev || after.ino !== opened.ino || after.size !== opened.size) {
|
|
85
|
-
throw
|
|
97
|
+
throw controlReadRace(`${label} changed during read`);
|
|
86
98
|
}
|
|
87
99
|
const errors = [];
|
|
88
100
|
const tree = parseTree(text, errors, { allowTrailingComma: false, disallowComments: true });
|
|
@@ -91,11 +103,37 @@ async function readStrictJson(ref, code, label) {
|
|
|
91
103
|
}
|
|
92
104
|
return JSON.parse(text);
|
|
93
105
|
} catch (error) {
|
|
94
|
-
|
|
95
|
-
|
|
106
|
+
// 最初のlstatで無い=未起動。読み始めた後に消えたのは停止との競合で、再読でnullへ落ち着く。
|
|
107
|
+
if (error?.code === 'ENOENT') {
|
|
108
|
+
if (before === undefined) return null;
|
|
109
|
+
throw controlReadRace(`${label} removed during read`);
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
96
112
|
} finally { await handle?.close(); }
|
|
97
113
|
}
|
|
98
114
|
|
|
115
|
+
/**
|
|
116
|
+
* 公開はatomic renameなので、読み手はinodeの差し替えに必ず出会う。差し替えを跨いだ読みは
|
|
117
|
+
* 内容の異常ではなく再読で解ける競合であり、壊れたcontrol fileと同じerrorにしてはならない。
|
|
118
|
+
*
|
|
119
|
+
* 再試行するのは`CONTROL_READ_RACE`が付いた3条件だけである——検証中のinode/size変化、
|
|
120
|
+
* 読み取り中のinode/size変化、最初のlstat後の消失。最大`CONTROL_READ_RETRY_LIMIT`回、
|
|
121
|
+
* 間隔`CONTROL_READ_RETRY_DELAY_MS`で再読し、超えたら他と同じtyped errorで落とす。
|
|
122
|
+
* JSON不正・schema不正・mode不正は競合ではないので一度も再試行せず即fail closedにする。
|
|
123
|
+
*/
|
|
124
|
+
async function readStrictJson(ref, code, label) {
|
|
125
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
126
|
+
try {
|
|
127
|
+
return await readStrictJsonOnce(ref, label);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (error?.[CONTROL_READ_RACE] !== true || attempt >= CONTROL_READ_RETRY_LIMIT) {
|
|
130
|
+
throw new BridgeConfigError(code, `${label} invalid`, undefined, error);
|
|
131
|
+
}
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_READ_RETRY_DELAY_MS));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
99
137
|
async function readStrictControl(ref, schema, keys) {
|
|
100
138
|
const value = await readStrictJson(ref, 'BRIDGE_STOP_CONTROL_INVALID', 'bridge stop control');
|
|
101
139
|
if (value === null) return null;
|
|
@@ -15,9 +15,12 @@ import {
|
|
|
15
15
|
isGitSha,
|
|
16
16
|
severabilityOfConflictKind,
|
|
17
17
|
} from './todo-independence-contracts.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
SEAM_PROPOSAL_GUIDANCE_CODES,
|
|
20
|
+
seamProposalGuidanceCode,
|
|
21
|
+
} from './todo-independence-guidance.mjs';
|
|
19
22
|
|
|
20
|
-
export const SEAM_PROPOSAL_SCHEMA = 'lattice.seam_proposal.
|
|
23
|
+
export const SEAM_PROPOSAL_SCHEMA = 'lattice.seam_proposal.v2';
|
|
21
24
|
export const SEAM_PROPOSAL_PROJECTION_SCHEMA = 'lattice.seam_proposal_projection.v1';
|
|
22
25
|
export const SEAM_PROPOSAL_VERDICTS = Object.freeze([
|
|
23
26
|
'seam_candidate',
|
|
@@ -139,10 +142,18 @@ function surfaceEntry(value, taskIds, { proposed }) {
|
|
|
139
142
|
: value.owner_task_ids.length === 1;
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
/**
|
|
146
|
+
* `candidate_paths`は、同名symbolが複数fileに居た時だけ埋まる(ADR 0134)。
|
|
147
|
+
*
|
|
148
|
+
* 単数の`resolved_path`と併存させるのは、両者が別の事実だからである。前者は「一意に決まった」、
|
|
149
|
+
* 後者は「決まらず、候補はこれだけあった」。候補を持つ受け皿が無かった頃、後者は`unknown`へ
|
|
150
|
+
* 潰れ、宣言の`within`で絞れる情報が記録に残らなかった。一意に決まった時に候補を並べない
|
|
151
|
+
* (空配列に限る)のは、決まった事実と決まらなかった事実を同じ形にしないためである。
|
|
152
|
+
*/
|
|
142
153
|
function queryEntry(value) {
|
|
143
154
|
return exactRecord(value, [
|
|
144
155
|
'query_id', 'operation', 'target', 'outcome', 'resolved_name', 'resolved_path',
|
|
145
|
-
'result_digest',
|
|
156
|
+
'candidate_paths', 'result_digest',
|
|
146
157
|
])
|
|
147
158
|
&& isTodoIdentifier(value.query_id)
|
|
148
159
|
&& isTodoIdentifier(value.operation)
|
|
@@ -150,6 +161,12 @@ function queryEntry(value) {
|
|
|
150
161
|
&& isTodoIdentifier(value.outcome)
|
|
151
162
|
&& (value.resolved_name === null || boundedText(value.resolved_name))
|
|
152
163
|
&& (value.resolved_path === null || repoRelativePath(value.resolved_path))
|
|
164
|
+
&& boundedList(value.candidate_paths, repoRelativePath)
|
|
165
|
+
&& strictlySorted(value.candidate_paths)
|
|
166
|
+
&& (value.outcome === 'ambiguous'
|
|
167
|
+
? value.candidate_paths.length >= 2 && value.resolved_path === null
|
|
168
|
+
&& boundedText(value.resolved_name)
|
|
169
|
+
: value.candidate_paths.length === 0)
|
|
153
170
|
&& isTodoDigest(value.result_digest);
|
|
154
171
|
}
|
|
155
172
|
|
|
@@ -281,7 +298,7 @@ function decisionEntry(value) {
|
|
|
281
298
|
}
|
|
282
299
|
|
|
283
300
|
/**
|
|
284
|
-
* Validate the immutable public `lattice.seam_proposal.
|
|
301
|
+
* Validate the immutable public `lattice.seam_proposal.v2` artifact.
|
|
285
302
|
*
|
|
286
303
|
* This validates the artifact's closed runtime shape and canonical relations. Binding the
|
|
287
304
|
* recorded conflicts back to the referenced independence bytes is a consumer responsibility.
|
|
@@ -309,19 +326,21 @@ export function validateSeamProposal(value) {
|
|
|
309
326
|
}
|
|
310
327
|
}
|
|
311
328
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
329
|
+
/**
|
|
330
|
+
* 案内は、鮮度と載っているunknownから一意に決まる。生成側の文言をそのまま載せる規約なので
|
|
331
|
+
* shapeだけを見ると、投影が状況と噛み合わない案内を載せても通ってしまう。codeは規則正本へ
|
|
332
|
+
* 引き直して照合する(ADR 0130 Decision 1・2)。
|
|
333
|
+
*/
|
|
334
|
+
function projectionGuidance(value, coverage, components) {
|
|
335
|
+
if (!exactRecord(value, ['code', 'message', 'next_action'])
|
|
336
|
+
|| !SEAM_PROPOSAL_GUIDANCE_CODES.includes(value.code)
|
|
337
|
+
|| !boundedText(value.message)
|
|
338
|
+
|| !isTodoIdentifier(value.next_action)) return false;
|
|
339
|
+
const unknownKinds = Array.isArray(components)
|
|
340
|
+
? components.flatMap((component) => (Array.isArray(component?.unknowns)
|
|
341
|
+
? component.unknowns.map((unknown) => unknown?.kind) : []))
|
|
342
|
+
: [];
|
|
343
|
+
return value.code === seamProposalGuidanceCode({ coverage, unknownKinds });
|
|
325
344
|
}
|
|
326
345
|
|
|
327
346
|
function projectionComponent(value) {
|
|
@@ -383,7 +402,7 @@ export function validateSeamProposalProjection(value) {
|
|
|
383
402
|
|| !isTodoIdentifier(value.plan_key)
|
|
384
403
|
|| !TODO_INDEPENDENCE_COVERAGE.includes(value.coverage)
|
|
385
404
|
|| !isGitSha(value.current_base_sha)
|
|
386
|
-
|| !projectionGuidance(value.guidance, value.coverage)
|
|
405
|
+
|| !projectionGuidance(value.guidance, value.coverage, value.components)
|
|
387
406
|
|| !isTodoDigest(value.result_digest)) return false;
|
|
388
407
|
|
|
389
408
|
if (value.coverage === 'missing') {
|
|
@@ -249,12 +249,24 @@ function exactSymbolResolution(outcome, target, operation) {
|
|
|
249
249
|
return { outcome: 'unknown' };
|
|
250
250
|
}
|
|
251
251
|
const paths = [...new Set(exact.map(({ node }) => node.filePath))].sort(compareText);
|
|
252
|
-
if (paths.length
|
|
253
|
-
|
|
252
|
+
if (paths.length === 1) {
|
|
253
|
+
return { outcome: 'resolved', resolved_name: target, resolved_path: paths[0] };
|
|
254
|
+
}
|
|
255
|
+
// 同名が複数fileにある。`query`は「その名前はどこに居るか」を問う操作なので、候補を
|
|
256
|
+
// 持ったまま返す——宣言の`within`が指す資源で絞れば一意に決まる場合がある(ADR 0134)。
|
|
257
|
+
// graph操作(callers/callees/impact)は逆に、展開の起点が一意でなければ意味を持たない。
|
|
258
|
+
// 同じ曖昧さでも問いが違うので、そちらはunknownのまま潰す。
|
|
259
|
+
if (operation === 'query') {
|
|
260
|
+
return { outcome: 'ambiguous', resolved_name: target, candidate_paths: paths };
|
|
261
|
+
}
|
|
262
|
+
return { outcome: 'unknown' };
|
|
254
263
|
}
|
|
255
264
|
|
|
256
265
|
function symbolResolutionForEvidence(local, canonical) {
|
|
257
|
-
|
|
266
|
+
// 起点が一意でない名前についてのgraph観測は、どのsymbolについての観測か確定しない。
|
|
267
|
+
if (canonical.outcome === 'unknown' || canonical.outcome === 'ambiguous') {
|
|
268
|
+
return { outcome: 'unknown' };
|
|
269
|
+
}
|
|
258
270
|
if (canonical.outcome === 'absent') {
|
|
259
271
|
return local.outcome === 'absent' ? local : { outcome: 'unknown' };
|
|
260
272
|
}
|
|
@@ -294,6 +306,7 @@ function evidenceQuery({ query, outcome, resolution }) {
|
|
|
294
306
|
outcome: resolution.outcome,
|
|
295
307
|
resolved_name: resolution.resolved_name ?? null,
|
|
296
308
|
resolved_path: resolution.resolved_path ?? null,
|
|
309
|
+
candidate_paths: resolution.candidate_paths ?? [],
|
|
297
310
|
result_digest: digestArtifact(portableSensorOutcome(outcome)),
|
|
298
311
|
};
|
|
299
312
|
}
|
|
@@ -472,7 +485,7 @@ async function collectCalleeClosure({
|
|
|
472
485
|
/**
|
|
473
486
|
* Collect through the bundled sensor adapter. The normalized evidence remains the only
|
|
474
487
|
* contract-shaped artifact; raw outcomes are returned on a separate, in-memory channel for
|
|
475
|
-
* structural cut enumeration and must not be embedded in lattice.seam_proposal.
|
|
488
|
+
* structural cut enumeration and must not be embedded in lattice.seam_proposal.v2.
|
|
476
489
|
*/
|
|
477
490
|
export async function collectSeamProposalEvidenceBundle({
|
|
478
491
|
cwd,
|
package/src/seam-proposal.mjs
CHANGED
|
@@ -774,13 +774,42 @@ function pathContains(resourcePath, symbolPath) {
|
|
|
774
774
|
: symbolPath === resourcePath;
|
|
775
775
|
}
|
|
776
776
|
|
|
777
|
+
/**
|
|
778
|
+
* 宣言symbolのpathを、宣言した資源の内側で解く(ADR 0134)。
|
|
779
|
+
*
|
|
780
|
+
* 一意に解決した名前はそのまま返す。同名が複数fileに居た場合だけ、receiptが持つ候補を
|
|
781
|
+
* 資源で絞る——絞って1つに決まる時だけ束縛根拠になる。0個なら宣言した資源の中には無く、
|
|
782
|
+
* 2つ以上なら資源の中でも決まらない。どちらも「解けなかった」であって、片方を勝たせない。
|
|
783
|
+
*
|
|
784
|
+
* 絞るのは宣言された資源であって、宣言そのものではない。sensorが返した候補集合の外から
|
|
785
|
+
* pathを持ち込むことはないので、宣言が誤っていても存在しないsymbolへは束縛されない。
|
|
786
|
+
*/
|
|
787
|
+
function resolveConcernSymbolPath(queries, name, resourcePath) {
|
|
788
|
+
const receipt = queries.find((query) => (
|
|
789
|
+
query.operation === 'query' && query.target === name && query.resolved_name === name
|
|
790
|
+
&& (query.outcome === 'resolved' || query.outcome === 'ambiguous')
|
|
791
|
+
));
|
|
792
|
+
if (receipt === undefined) return null;
|
|
793
|
+
if (receipt.outcome === 'resolved') {
|
|
794
|
+
return typeof receipt.resolved_path === 'string' && receipt.resolved_path.length > 0
|
|
795
|
+
? receipt.resolved_path : null;
|
|
796
|
+
}
|
|
797
|
+
const inside = (receipt.candidate_paths ?? [])
|
|
798
|
+
.filter((candidate) => pathContains(resourcePath, candidate));
|
|
799
|
+
return inside.length === 1 ? inside[0] : null;
|
|
800
|
+
}
|
|
801
|
+
|
|
777
802
|
/**
|
|
778
803
|
* Resolve declared concern anchors against fresh sensor evidence.
|
|
779
804
|
*
|
|
780
|
-
* A declaration only becomes a binding anchor when the sensor resolves the exact name
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
805
|
+
* A declaration only becomes a binding anchor when the sensor resolves the exact name inside the
|
|
806
|
+
* declared resource to exactly one path. Fuzzy resolution to a neighbouring symbol, an absent
|
|
807
|
+
* name, or a symbol living outside the contested resource yields a typed unknown instead — a
|
|
808
|
+
* wrong declaration must never widen what the binder believes it knows.
|
|
809
|
+
*
|
|
810
|
+
* A name living in several files is resolved against the declared resource rather than being
|
|
811
|
+
* dropped (ADR 0134). The resource is one the ToDo already claims under `owns`, so this narrows
|
|
812
|
+
* the sensor's own candidate set; it never introduces a path the sensor did not report.
|
|
784
813
|
*
|
|
785
814
|
* Two ToDos claiming the same symbol is a contradiction in the declarations themselves, not a cut
|
|
786
815
|
* to be discovered: the anchor is dropped from both and reported, so neither side can be bound by
|
|
@@ -808,7 +837,7 @@ export function resolveConcernAnchors({ manualWitness, taskIds, evidence } = {})
|
|
|
808
837
|
continue;
|
|
809
838
|
}
|
|
810
839
|
for (const symbol of entry.symbols) {
|
|
811
|
-
const symbolPath =
|
|
840
|
+
const symbolPath = resolveConcernSymbolPath(queries, symbol, resourcePath);
|
|
812
841
|
if (symbolPath === null) {
|
|
813
842
|
unknowns.push({ kind: 'concern_anchor_unresolved', ref: `${taskId}:${symbol}` });
|
|
814
843
|
continue;
|
|
@@ -1894,7 +1923,7 @@ function conflictComponents(independenceArtifact) {
|
|
|
1894
1923
|
}
|
|
1895
1924
|
|
|
1896
1925
|
/**
|
|
1897
|
-
* Build the immutable lattice.seam_proposal.
|
|
1926
|
+
* Build the immutable lattice.seam_proposal.v2 artifact from one complete independence record.
|
|
1898
1927
|
* Sensor collection stays outside this producer; callers pass the original witness evidence and
|
|
1899
1928
|
* the seam-specific normalized/raw evidence collected for the same clean HEAD.
|
|
1900
1929
|
*/
|
package/src/todo-cli.mjs
CHANGED
|
@@ -1013,7 +1013,10 @@ async function seamProposal({ repoRoot, requestedPlanKey }) {
|
|
|
1013
1013
|
topology_digest: artifact?.source_binding.topology_digest ?? null,
|
|
1014
1014
|
independence_result_digest: artifact?.source_binding.independence_result_digest ?? null,
|
|
1015
1015
|
compiled_at: artifact?.compiled_at ?? null,
|
|
1016
|
-
guidance: selectSeamProposalGuidance({
|
|
1016
|
+
guidance: selectSeamProposalGuidance({
|
|
1017
|
+
coverage,
|
|
1018
|
+
unknownKinds: components.flatMap(({ unknowns }) => unknowns.map(({ kind }) => kind)),
|
|
1019
|
+
}),
|
|
1017
1020
|
component_count: artifact === null ? null : components.length,
|
|
1018
1021
|
conflict_resource_count: artifact === null ? null : components
|
|
1019
1022
|
.reduce((count, component) => count + component.conflicts.length, 0),
|
|
@@ -24,9 +24,39 @@ export const SEAM_PROPOSAL_GUIDANCE_CODES = Object.freeze([
|
|
|
24
24
|
'seam_proposal_unrecorded',
|
|
25
25
|
'seam_proposal_superseded',
|
|
26
26
|
'seam_proposal_stale',
|
|
27
|
+
'seam_proposal_binding_overlap',
|
|
28
|
+
'seam_proposal_binding_outside_resource',
|
|
29
|
+
'seam_proposal_binding_symbol_unresolved',
|
|
30
|
+
'seam_proposal_binding_resource_unresolved',
|
|
31
|
+
'seam_proposal_binding_ambiguous',
|
|
32
|
+
'seam_proposal_binding_missing',
|
|
27
33
|
'seam_proposal_verified',
|
|
28
34
|
]);
|
|
29
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 束縛失敗のunknown種別から案内codeを引く表。並びは優先順で、先頭ほど次の一歩が具体的である。
|
|
38
|
+
*
|
|
39
|
+
* 宣言が壊れている状況を、宣言が無い状況より上に置く。壊れている方は直す対象が
|
|
40
|
+
* 一意に決まるのに対し、無い方は何をどう宣言するかから決めることになるからである。
|
|
41
|
+
*/
|
|
42
|
+
const SEAM_PROPOSAL_BINDING_GUIDANCE = Object.freeze([
|
|
43
|
+
Object.freeze(['concern_anchor_overlap', 'seam_proposal_binding_overlap']),
|
|
44
|
+
Object.freeze(['concern_anchor_outside_resource', 'seam_proposal_binding_outside_resource']),
|
|
45
|
+
Object.freeze(['concern_anchor_unresolved', 'seam_proposal_binding_symbol_unresolved']),
|
|
46
|
+
Object.freeze(['concern_anchor_resource_unresolved', 'seam_proposal_binding_resource_unresolved']),
|
|
47
|
+
Object.freeze(['semantic_owner_binding_ambiguous', 'seam_proposal_binding_ambiguous']),
|
|
48
|
+
Object.freeze(['semantic_owner_binding_missing', 'seam_proposal_binding_missing']),
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 束縛の案内へ共通で添える、記録が更新される条件。
|
|
53
|
+
*
|
|
54
|
+
* concern_anchorsはwitness setにあり、seam提案はindependence記録のwitness_set_digestと
|
|
55
|
+
* 一致する宣言しか読まない。宣言を直しただけでは提案は変わらないので、そこまで述べないと
|
|
56
|
+
* 「直したのに同じunknownが出る」で止まる。
|
|
57
|
+
*/
|
|
58
|
+
const BINDING_RECOMPILE_HINT = '宣言はwitness setにあり、independence compileとseam-proposal compileを通し直すまで提案へ写らない。';
|
|
59
|
+
|
|
30
60
|
const CATALOG = Object.freeze({
|
|
31
61
|
independence_no_ready_frontier: Object.freeze({
|
|
32
62
|
message: '着手候補が無いため、並列可否を述べる対象が無い。',
|
|
@@ -76,6 +106,30 @@ const CATALOG = Object.freeze({
|
|
|
76
106
|
message: 'seam提案の生成後にHEADが進み、記録時点の構造証拠は現在のcodeを指していない。',
|
|
77
107
|
next_action: 'compile_seam_proposal',
|
|
78
108
|
}),
|
|
109
|
+
seam_proposal_binding_overlap: Object.freeze({
|
|
110
|
+
message: `同じ資源について、複数のToDoが同じsymbolを自分の担当として宣言している。どちら側へ切るか決まらないため、切断候補を束縛できない。${BINDING_RECOMPILE_HINT}`,
|
|
111
|
+
next_action: 'split_overlapping_concern_anchors_then_recompile',
|
|
112
|
+
}),
|
|
113
|
+
seam_proposal_binding_outside_resource: Object.freeze({
|
|
114
|
+
message: `concern_anchorsが挙げたsymbolが、withinで指した資源の外にある。その資源の分割を説明しないので束縛へ使えない。${BINDING_RECOMPILE_HINT}`,
|
|
115
|
+
next_action: 'correct_concern_anchors_then_recompile',
|
|
116
|
+
}),
|
|
117
|
+
seam_proposal_binding_symbol_unresolved: Object.freeze({
|
|
118
|
+
message: `concern_anchorsが挙げたsymbolを、sensorが同名同pathで解決できなかった。近い別symbolへは寄せないため、束縛は成立していない。${BINDING_RECOMPILE_HINT}`,
|
|
119
|
+
next_action: 'correct_concern_anchors_then_recompile',
|
|
120
|
+
}),
|
|
121
|
+
seam_proposal_binding_resource_unresolved: Object.freeze({
|
|
122
|
+
message: `concern_anchorsのwithinが指す資源をsensorで解決できなかった。宣言がどの資源についてのものか確定しない。${BINDING_RECOMPILE_HINT}`,
|
|
123
|
+
next_action: 'correct_concern_anchors_then_recompile',
|
|
124
|
+
}),
|
|
125
|
+
seam_proposal_binding_ambiguous: Object.freeze({
|
|
126
|
+
message: `宣言したsymbolが切断候補の複数側へ当たるため、どのToDoがどちらを所有するか一意に決まらない。${BINDING_RECOMPILE_HINT}`,
|
|
127
|
+
next_action: 'narrow_concern_anchors_then_recompile',
|
|
128
|
+
}),
|
|
129
|
+
seam_proposal_binding_missing: Object.freeze({
|
|
130
|
+
message: `係争資源の中でどのToDoが何を触るかが宣言されていないため、切断候補を所有者へ束縛できない。依存線でも呼び出し辺でも所有は決まらない。${BINDING_RECOMPILE_HINT}`,
|
|
131
|
+
next_action: 'declare_concern_anchors_then_recompile',
|
|
132
|
+
}),
|
|
79
133
|
seam_proposal_verified: Object.freeze({
|
|
80
134
|
message: 'seam提案の記録は現在のplan、並列可否記録、HEADと一致している。',
|
|
81
135
|
next_action: 'none',
|
|
@@ -135,21 +189,37 @@ export function selectIndependenceGuidance({
|
|
|
135
189
|
return todoIndependenceGuidance('independence_verified');
|
|
136
190
|
}
|
|
137
191
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
/**
|
|
193
|
+
* seam提案の状況から案内codeを引く。投影の生成側と契約の検査側が同じ規則を読むための正本。
|
|
194
|
+
*
|
|
195
|
+
* 鮮度(coverage)を束縛失敗より先に見る。記録が古い・別topologyについてのものである時、
|
|
196
|
+
* そこに載っているunknownは現在のcodeについての事実ではないため、先に再compileが要る。
|
|
197
|
+
* 記録が現在と一致している時だけ、束縛失敗が「今直せる状況」になる。
|
|
198
|
+
*/
|
|
199
|
+
export function seamProposalGuidanceCode({ coverage, unknownKinds = [] }) {
|
|
200
|
+
if (coverage === 'missing') return 'seam_proposal_unrecorded';
|
|
201
|
+
if (coverage === 'superseded') return 'seam_proposal_superseded';
|
|
202
|
+
if (coverage === 'stale') return 'seam_proposal_stale';
|
|
203
|
+
if (coverage !== 'verified') throw new TypeError(`unknown seam proposal coverage: ${coverage}`);
|
|
204
|
+
const kinds = new Set(unknownKinds);
|
|
205
|
+
const binding = SEAM_PROPOSAL_BINDING_GUIDANCE.find(([kind]) => kinds.has(kind));
|
|
206
|
+
return binding === undefined ? 'seam_proposal_verified' : binding[1];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function selectSeamProposalGuidance({ coverage, unknownKinds = [] }) {
|
|
210
|
+
return todoIndependenceGuidance(seamProposalGuidanceCode({ coverage, unknownKinds }));
|
|
145
211
|
}
|
|
146
212
|
|
|
147
213
|
/**
|
|
148
214
|
* 宣言からcompileを経て読むまでの順序。helpとMCP instructionsが同じ手順を語るための正本。
|
|
149
215
|
* 面ごとに手順を書き直すと、片方だけが古くなる。
|
|
216
|
+
*
|
|
217
|
+
* 宣言できる欄を挙げる面はここだけなので、witness契約へ欄を足したらここへも足す。
|
|
218
|
+
* 足さないと、能力はあるのに機械が黙る面が残る(ADR 0130)。
|
|
150
219
|
*/
|
|
151
220
|
export const TODO_INDEPENDENCE_WORKFLOW = Object.freeze([
|
|
152
221
|
'1. 宣言する: .lattice/todo/witness/<plan_key>.json へ、ToDoごとのowns/reads/writes/affected_testsを書く',
|
|
222
|
+
' 係争資源しか所有していないToDoは、その資源の中で自分が触るsymbolをconcern_anchorsへ宣言できる(witness set v2)。並列可否の判定には写らず、切断候補の束縛だけに効く',
|
|
153
223
|
'2. 判定する: lattice todo independence compile --plan <key> --input <ref>(実sensorを引き、clean worktreeが要る)',
|
|
154
224
|
'3. 読む: lattice todo independence --plan <key> --json(sensorを引かず、記録とHEAD照合だけで返る)',
|
|
155
225
|
'4. 追従する: plan改訂後は lattice todo independence witness migrate --plan <key> で宣言を写してから再compileする',
|