@quolu/lattice 0.63.7 → 0.63.9
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/todo-cli.mjs +23 -6
- package/src/todo-migration.mjs +1 -1
- package/src/todo-store.mjs +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quolu/lattice",
|
|
3
|
-
"version": "0.63.
|
|
3
|
+
"version": "0.63.9",
|
|
4
4
|
"description": "Schedulability compiler for multi-agent development: observe real code boundaries, refactor the conflicting seam, recompile the plan for parallel execution",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Quo / クオ at kitepon.dev",
|
package/src/todo-cli.mjs
CHANGED
|
@@ -437,24 +437,32 @@ function looksLikeEvidenceDescriptor(value) {
|
|
|
437
437
|
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
-
function writeEvidenceBlob(repoRoot, bytes) {
|
|
441
|
-
|
|
440
|
+
function writeEvidenceBlob(repoRoot, bytes, blobPath) {
|
|
441
|
+
// worktree pathから作る証拠は`git add`と同じclean filterを通す。生のCRLF bytesを
|
|
442
|
+
// そのままhashすると、commit先はLFへ正規化される一方、descriptorはdangling blobを
|
|
443
|
+
// 指すため、commit後もhard検証を通せない。
|
|
444
|
+
const oid = gitSync(['hash-object', '-w', `--path=${blobPath}`, '--stdin'], {
|
|
442
445
|
cwd: repoRoot, input: bytes, encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'],
|
|
443
446
|
}).trim();
|
|
447
|
+
const storedBytes = gitSync(['cat-file', 'blob', oid], {
|
|
448
|
+
cwd: repoRoot, stdio: ['ignore', 'pipe', 'ignore'],
|
|
449
|
+
});
|
|
444
450
|
return {
|
|
445
451
|
git_blob_oid: oid,
|
|
446
|
-
content_digest: createHash('sha256').update(
|
|
452
|
+
content_digest: createHash('sha256').update(storedBytes).digest('hex'),
|
|
447
453
|
};
|
|
448
454
|
}
|
|
449
455
|
|
|
450
456
|
async function resolveDoneEvidence({ repoRoot, evidenceRef, evidenceMessage, planKey, taskId }) {
|
|
451
457
|
if (typeof evidenceMessage === 'string') {
|
|
452
458
|
const bytes = Buffer.from(evidenceMessage, 'utf8');
|
|
459
|
+
const sourceDigest = createHash('sha256').update(bytes).digest('hex');
|
|
460
|
+
const evidencePath = `.lattice/todo/evidence/${planKey}/${taskId}/${sourceDigest}.md`;
|
|
453
461
|
const descriptor = {
|
|
454
462
|
evidence_id: evidenceIdFor(planKey, taskId),
|
|
455
463
|
repo_id: 'self',
|
|
456
|
-
path:
|
|
457
|
-
...writeEvidenceBlob(repoRoot, bytes),
|
|
464
|
+
path: evidencePath,
|
|
465
|
+
...writeEvidenceBlob(repoRoot, bytes, evidencePath),
|
|
458
466
|
media_type: 'text/markdown',
|
|
459
467
|
anchor_digest: null,
|
|
460
468
|
};
|
|
@@ -486,7 +494,7 @@ async function resolveDoneEvidence({ repoRoot, evidenceRef, evidenceMessage, pla
|
|
|
486
494
|
evidence_id: evidenceIdFor(planKey, taskId),
|
|
487
495
|
repo_id: 'self',
|
|
488
496
|
path: located.inputRef,
|
|
489
|
-
...writeEvidenceBlob(repoRoot, bytes),
|
|
497
|
+
...writeEvidenceBlob(repoRoot, bytes, located.inputRef),
|
|
490
498
|
media_type: mediaTypeFor(located.inputRef),
|
|
491
499
|
anchor_digest: null,
|
|
492
500
|
};
|
|
@@ -626,6 +634,14 @@ async function mutate({
|
|
|
626
634
|
repoRoot, evidenceRef, evidenceMessage, planKey, taskId,
|
|
627
635
|
});
|
|
628
636
|
const testResult = testResultRef === null ? null : await readTestResultInput(repoRoot, testResultRef);
|
|
637
|
+
const materializedEvidence = typeof evidenceMessage === 'string'
|
|
638
|
+
? {
|
|
639
|
+
ref: evidence.path,
|
|
640
|
+
bytes: gitSync(['cat-file', 'blob', evidence.git_blob_oid], {
|
|
641
|
+
cwd: repoRoot, stdio: ['ignore', 'pipe', 'ignore'],
|
|
642
|
+
}),
|
|
643
|
+
}
|
|
644
|
+
: null;
|
|
629
645
|
let eventPayload = payload;
|
|
630
646
|
if (kind === 'done' && payload === 'authored') {
|
|
631
647
|
eventPayload = { evidence, ...(testResult === null ? {} : { test_result: testResult }) };
|
|
@@ -641,6 +657,7 @@ async function mutate({
|
|
|
641
657
|
writer: createTodoStoreWriter({ caller: 'g5-authoring' }),
|
|
642
658
|
planKey,
|
|
643
659
|
event: { kind, task_id: taskId, actor, payload: eventPayload },
|
|
660
|
+
materializedEvidence,
|
|
644
661
|
});
|
|
645
662
|
const task = snapshot.tasks.find(({ task_id: current }) => current === event.task_id);
|
|
646
663
|
const authoredTask = plan.tasks.find(({ task_id: current }) => current === event.task_id);
|
package/src/todo-migration.mjs
CHANGED
|
@@ -128,7 +128,7 @@ function validateEdges(value, schema) {
|
|
|
128
128
|
const basic = exactRecord(edge, ['from', 'to']);
|
|
129
129
|
const refsValid = nodeRef(edge?.from) && nodeRef(edge?.to);
|
|
130
130
|
const crossPlan = refsValid && isCrossPlanEdge(edge);
|
|
131
|
-
const withReason = [TODO_EXTRACTION_SCHEMA_V3, TODO_EXTRACTION_SCHEMA_V4].includes(schema)
|
|
131
|
+
const withReason = [TODO_EXTRACTION_SCHEMA_V3, TODO_EXTRACTION_SCHEMA_V4].includes(schema)
|
|
132
132
|
&& exactRecord(edge, ['from', 'to', 'reason'])
|
|
133
133
|
&& boundedText(edge.reason);
|
|
134
134
|
return (basic || withReason) && refsValid
|
package/src/todo-store.mjs
CHANGED
|
@@ -2050,6 +2050,19 @@ export async function appendTodoEvent(options = {}) {
|
|
|
2050
2050
|
eventDigest: event.event_digest,
|
|
2051
2051
|
}),
|
|
2052
2052
|
});
|
|
2053
|
+
if (options.materializedEvidence !== null && options.materializedEvidence !== undefined) {
|
|
2054
|
+
const asset = options.materializedEvidence;
|
|
2055
|
+
const descriptor = event.kind === 'done' ? event.payload.evidence : null;
|
|
2056
|
+
if (!exactRecord(asset, ['ref', 'bytes']) || !Buffer.isBuffer(asset.bytes)
|
|
2057
|
+
|| descriptor === null || asset.ref !== descriptor.path
|
|
2058
|
+
|| !asset.ref.startsWith(`${STORE_ROOT_REF}/evidence/`)
|
|
2059
|
+
|| sha256Bytes(asset.bytes) !== descriptor.content_digest) {
|
|
2060
|
+
throw new TypeError('materialized evidence input invalid');
|
|
2061
|
+
}
|
|
2062
|
+
// prospective replayが成立した後、journalより先に実体を同じstore lock内へ置く。
|
|
2063
|
+
// content-addressed pathなので再試行と同文面の並行要求も同じbytesへ収束する。
|
|
2064
|
+
await atomicWrite(path.resolve(repoRoot, asset.ref), asset.bytes);
|
|
2065
|
+
}
|
|
2053
2066
|
const eventBytes = canonicalLine(event);
|
|
2054
2067
|
const activeRef = member.descriptor.journal_ref;
|
|
2055
2068
|
const activeAbsolute = path.resolve(repoRoot, activeRef);
|