@deksden-com/dd-flow-cli 0.9.0-beta.120 → 0.9.0-beta.122
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/CHANGELOG.md +9 -0
- package/dist/build-info.json +5 -5
- package/dist/cli/hook-ingress.js +4 -3
- package/dist/cli/native-hook-ingress.js +34 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +6 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +16 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +12 -2
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +60 -8
- package/dist/harness-runtime/lib/dd-zcode.mjs +28 -13
- package/dist/harness-runtime/lib/delegation-instructions.mjs +3 -3
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
- package/dist/harness-runtime/lib/lifecycle-command.mjs +218 -0
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +2 -2
- package/dist/harness-runtime/lib/native-hook-command.mjs +36 -5
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/services/code-checks.js +40 -0
- package/dist/services/codex-hook-delivery.js +6 -2
- package/dist/services/hooks.js +51 -59
- package/dist/services/lifecycle-command.js +1 -383
- package/dist/services/lifecycle-contract.js +1 -30
- package/dist/services/lifecycle-invocations.js +34 -13
- package/dist/services/managed-processes.js +42 -6
- package/dist/services/run-control.js +7 -1
- package/dist/services/runs.js +60 -47
- package/dist/services/runtime-scope-stop.js +23 -2
- package/dist/services/stage-report-renderer.js +19 -0
- package/dist/services/verification-matrix.js +325 -0
- package/dist/services/vnext-code-review.js +25 -8
- package/dist/services/vnext-code.js +38 -11
- package/dist/services/vnext-merge.js +30 -7
- package/dist/services/vnext-plan-review.js +22 -6
- package/dist/services/vnext-plan.js +45 -17
- package/dist/services/work-registry.js +13 -1
- package/dist/storage/database.js +43 -9
- package/package.json +4 -1
|
@@ -25,6 +25,7 @@ import { prepareError, prepareJsonFile } from "../cli/input-preparation.js";
|
|
|
25
25
|
import { lifecycleRetryInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
26
26
|
import { acceptedDecisionPrecedence } from "./stage-context.js";
|
|
27
27
|
import { reviewEnvironmentAllowed, reviewExecutionCopyHash, reviewSourceHash, reviewSourcePaths } from "./review-copy.js";
|
|
28
|
+
import { latestRunVerificationMatrix, verificationMatrixInstruction, verificationMatrixContract } from "./verification-matrix.js";
|
|
28
29
|
const workColumns = "work_id, project_id, run_id, parent_work_id, task, launch_policy, result_schema, payload_json, depends_on_json, status, result, created_at, started_at, updated_at, completed_at";
|
|
29
30
|
export function ensureWorkRegistry(context) { context.db.exec("SELECT 1 FROM works LIMIT 1"); context.db.exec("SELECT 1 FROM work_sessions LIMIT 1"); }
|
|
30
31
|
function ownsPhysicalWorkSession(context, projectId, owner, identity) {
|
|
@@ -986,6 +987,14 @@ function assertSharedReviewInput(context, work, run, generation = recoveryGuard(
|
|
|
986
987
|
if (value.cycle !== payload?.review_cycle)
|
|
987
988
|
throw new AppError("review_input_invalid", "CODE-REVIEW cycle differs from its Work", 1);
|
|
988
989
|
}
|
|
990
|
+
const matrix = latestRunVerificationMatrix(context, work.project_id, work.run_id, String(kind), "input");
|
|
991
|
+
const marked = JSON.parse(context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [work.project_id, work.run_id]).index_json).verification_matrix_contract === verificationMatrixContract;
|
|
992
|
+
if (marked && !matrix)
|
|
993
|
+
throw new AppError("review_input_contract_missing", "Marked reviewer Stage lacks its immutable matrix baseline", 1, { stage: kind });
|
|
994
|
+
if (matrix) {
|
|
995
|
+
add("run", matrix.metadata.json.path, matrix.metadata.json.sha256);
|
|
996
|
+
add("run", matrix.metadata.markdown.path, matrix.metadata.markdown.sha256);
|
|
997
|
+
}
|
|
989
998
|
const runInputs = [...expected].map(([key, sha]) => { const [root, ...parts] = key.split(":"); return { root: root, path: parts.join(":"), sha256: sha }; }).sort((a, b) => `${a.root}:${a.path}`.localeCompare(`${b.root}:${b.path}`));
|
|
990
999
|
for (const item of runInputs) {
|
|
991
1000
|
const target = reviewInputPath(item.root === "run" ? home : run.workspace_root, item.path);
|
|
@@ -1236,7 +1245,10 @@ export function validateReviewResolutions(workId, assigned, resolutions) {
|
|
|
1236
1245
|
}
|
|
1237
1246
|
function renderWorkerPrompt(context, work, run, dependencies, commands) {
|
|
1238
1247
|
const body = renderWorkerPromptBody(context, work, run, dependencies, commands);
|
|
1239
|
-
|
|
1248
|
+
const kind = parsePayload(work)?.kind;
|
|
1249
|
+
const matrixStage = kind === "plan-review" || kind === "code-review" ? kind : codePacket(work) ? "code" : null;
|
|
1250
|
+
const matrix = matrixStage ? latestRunVerificationMatrix(context, work.project_id, work.run_id, matrixStage, kind === "plan-review" || kind === "code-review" ? "input" : undefined) : null;
|
|
1251
|
+
return ["<accepted_decision_precedence>", `Read ${path.join(requireRunHome(run), "01-specify", "specify.json")} and later accepted RUN decisions relevant to this Work.`, acceptedDecisionPrecedence, "</accepted_decision_precedence>", "", body, verificationMatrixInstruction(matrix?.metadata ?? null, requireRunHome(run), `assigned Work ${work.work_id}`)].join("\n");
|
|
1240
1252
|
}
|
|
1241
1253
|
function workerLifecycleCommands(context, work, run) {
|
|
1242
1254
|
const resultInputPath = path.join(requireRunHome(run), "works", work.work_id, "result-input.json");
|
package/dist/storage/database.js
CHANGED
|
@@ -331,7 +331,16 @@ export function ensureWorkLaunchStorage(db) {
|
|
|
331
331
|
CREATE INDEX IF NOT EXISTS idx_work_launches_status ON work_launches(status, updated_at DESC);
|
|
332
332
|
`);
|
|
333
333
|
}
|
|
334
|
-
export function getDatabase(ddFlowHome, mode = "initialize", importPermitId) {
|
|
334
|
+
export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hookDeadlineAt) {
|
|
335
|
+
if (hookDeadlineAt !== undefined && (!Number.isSafeInteger(hookDeadlineAt) || mode !== "hook"))
|
|
336
|
+
throw new AppError("hook_deadline_invalid", "Hook deadline must be a finite absolute millisecond deadline", 1, { effect: "unknown" });
|
|
337
|
+
const deadline = hookDeadlineAt === undefined ? undefined : (phase) => {
|
|
338
|
+
const remaining = hookDeadlineAt - Date.now();
|
|
339
|
+
if (remaining <= 0)
|
|
340
|
+
throw new AppError("native_hook_timeout", "Native hook deadline expired before durable admission", 1, { phase, deadline_at: hookDeadlineAt, effect: "unknown" });
|
|
341
|
+
return Math.max(1, Math.min(4000, remaining));
|
|
342
|
+
};
|
|
343
|
+
deadline?.("store_open");
|
|
335
344
|
if (mode !== "read_existing")
|
|
336
345
|
assertImportAdmission(ddFlowHome, importPermitId);
|
|
337
346
|
const dbPath = path.join(ddFlowHome, "db.sqlite");
|
|
@@ -344,7 +353,12 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId) {
|
|
|
344
353
|
if (!exists || mode === "migrate")
|
|
345
354
|
ensureDir(ddFlowHome);
|
|
346
355
|
const db = mode === "read_existing" ? new DatabaseSync(dbPath, { readOnly: true }) : new DatabaseSync(dbPath);
|
|
347
|
-
|
|
356
|
+
try {
|
|
357
|
+
db.exec(`PRAGMA busy_timeout = ${deadline?.("store_open") ?? 4000}`);
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
return closeAfterOpenFailure(db, error);
|
|
361
|
+
}
|
|
348
362
|
if (mode !== "read_existing") {
|
|
349
363
|
try {
|
|
350
364
|
registerStoreWriter(db);
|
|
@@ -354,7 +368,7 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId) {
|
|
|
354
368
|
}
|
|
355
369
|
}
|
|
356
370
|
try {
|
|
357
|
-
configureDatabase(db, mode);
|
|
371
|
+
configureDatabase(db, mode, deadline);
|
|
358
372
|
}
|
|
359
373
|
catch (error) {
|
|
360
374
|
return closeAfterOpenFailure(db, error);
|
|
@@ -374,6 +388,7 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId) {
|
|
|
374
388
|
migrateSessionStorageKeys(db, dbPath);
|
|
375
389
|
}
|
|
376
390
|
else {
|
|
391
|
+
deadline?.("store_validation");
|
|
377
392
|
assertHookStoreReady(db);
|
|
378
393
|
}
|
|
379
394
|
}
|
|
@@ -384,7 +399,7 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId) {
|
|
|
384
399
|
if (mode === "read_existing")
|
|
385
400
|
db.exec("PRAGMA query_only = ON");
|
|
386
401
|
db.exec("PRAGMA foreign_keys = ON");
|
|
387
|
-
return databaseFacade(dbPath, mode !== "read_existing", db);
|
|
402
|
+
return databaseFacade(dbPath, mode !== "read_existing", db, undefined, deadline);
|
|
388
403
|
}
|
|
389
404
|
/**
|
|
390
405
|
* Hook ingress is a hot, synchronous path. It may write receipts, but it
|
|
@@ -554,7 +569,7 @@ function emptyReadOnlyDatabase(dbPath) {
|
|
|
554
569
|
all: () => []
|
|
555
570
|
};
|
|
556
571
|
}
|
|
557
|
-
function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
572
|
+
function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadline) {
|
|
558
573
|
let reserved = false;
|
|
559
574
|
let poisoned = false;
|
|
560
575
|
let closed = false;
|
|
@@ -567,6 +582,14 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
|
567
582
|
let manualFrames = [];
|
|
568
583
|
let readDepth = 0;
|
|
569
584
|
const writerTiming = channel("dd-flow.sqlite.writer");
|
|
585
|
+
const ingressPhase = channel("dd-flow.sqlite.ingress");
|
|
586
|
+
const bound = (phase) => {
|
|
587
|
+
const remaining = deadline?.(phase);
|
|
588
|
+
if (remaining !== undefined)
|
|
589
|
+
db.exec(`PRAGMA busy_timeout = ${remaining}`);
|
|
590
|
+
if (ingressPhase.hasSubscribers)
|
|
591
|
+
ingressPhase.publish({ phase, ...(remaining !== undefined ? { remaining_ms: remaining } : {}) });
|
|
592
|
+
};
|
|
570
593
|
const publishCommitted = () => {
|
|
571
594
|
const pending = afterCommit;
|
|
572
595
|
afterCommit = [];
|
|
@@ -584,8 +607,11 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
|
584
607
|
throw new AppError("read_snapshot_write", "Cannot reserve a writer inside a read snapshot", 1);
|
|
585
608
|
if (!writable)
|
|
586
609
|
throw new AppError("read_only_storage", "Read-only access cannot start a write transaction", 1, { path: dbPath });
|
|
610
|
+
bound("writer_reservation");
|
|
587
611
|
db.exec("BEGIN IMMEDIATE");
|
|
588
612
|
reserved = true;
|
|
613
|
+
if (ingressPhase.hasSubscribers)
|
|
614
|
+
ingressPhase.publish({ phase: "writer_reserved" });
|
|
589
615
|
};
|
|
590
616
|
const rollback = (sql, original) => {
|
|
591
617
|
try {
|
|
@@ -626,6 +652,8 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
|
626
652
|
beginWriteTransaction();
|
|
627
653
|
}
|
|
628
654
|
catch (error) {
|
|
655
|
+
if (error instanceof AppError)
|
|
656
|
+
throw error;
|
|
629
657
|
throw new AppError("storage_write_failed", "Cannot reserve SQLite writer", 1, { cause: errorDetails(error), transaction_phase: "begin", transaction_rolled_back: true });
|
|
630
658
|
}
|
|
631
659
|
let phase = "body";
|
|
@@ -636,11 +664,14 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
|
636
664
|
throw new AppError("write_transaction_async", "Write transaction callback must be synchronous", 1);
|
|
637
665
|
}
|
|
638
666
|
phase = "commit";
|
|
667
|
+
bound("commit");
|
|
639
668
|
if (nested)
|
|
640
669
|
db.exec(`RELEASE ${marker}`);
|
|
641
670
|
else
|
|
642
671
|
db.exec("COMMIT");
|
|
643
672
|
phase = "committed";
|
|
673
|
+
if (ingressPhase.hasSubscribers)
|
|
674
|
+
ingressPhase.publish({ phase: "committed" });
|
|
644
675
|
if (!nested && writerTiming.hasSubscribers)
|
|
645
676
|
writerTiming.publish({ operation: operationName, wall_time_ms: performance.now() - startedAt, path: dbPath });
|
|
646
677
|
if (!nested)
|
|
@@ -786,22 +817,25 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.()) {
|
|
|
786
817
|
executePublications([operation]);
|
|
787
818
|
} },
|
|
788
819
|
run: (sql, params = []) => { assertUsable(); if (readDepth)
|
|
789
|
-
throw new AppError("read_snapshot_write", "Cannot write inside a read snapshot", 1); return db.prepare(sql).run(...params); },
|
|
790
|
-
get: (sql, params = []) => { assertUsable(); return db.prepare(sql).get(...params); },
|
|
791
|
-
all: (sql, params = []) => { assertUsable(); return db.prepare(sql).all(...params); }
|
|
820
|
+
throw new AppError("read_snapshot_write", "Cannot write inside a read snapshot", 1); bound("write"); return db.prepare(sql).run(...params); },
|
|
821
|
+
get: (sql, params = []) => { assertUsable(); bound("lookup"); return db.prepare(sql).get(...params); },
|
|
822
|
+
all: (sql, params = []) => { assertUsable(); bound("lookup"); return db.prepare(sql).all(...params); }
|
|
792
823
|
};
|
|
793
824
|
}
|
|
794
825
|
function errorDetails(error) {
|
|
795
826
|
const value = error;
|
|
796
827
|
return { message: String(value?.message ?? error), ...(typeof value?.code === "string" ? { code: value.code } : {}), ...(typeof value?.errcode === "number" ? { sqlite_extended_code: value.errcode } : {}) };
|
|
797
828
|
}
|
|
798
|
-
function configureDatabase(db, mode) {
|
|
829
|
+
function configureDatabase(db, mode, ingressDeadline) {
|
|
799
830
|
if (mode !== "read_existing") {
|
|
800
831
|
// Concurrent first openers can collide while changing journal mode, even
|
|
801
832
|
// with busy_timeout set. Retrying this configuration does not replay writes.
|
|
802
833
|
const deadline = performance.now() + 4000, pause = new Int32Array(new SharedArrayBuffer(4));
|
|
803
834
|
for (;;) {
|
|
804
835
|
try {
|
|
836
|
+
const remaining = ingressDeadline?.("store_configuration");
|
|
837
|
+
if (remaining !== undefined)
|
|
838
|
+
db.exec(`PRAGMA busy_timeout = ${remaining}`);
|
|
805
839
|
const current = db.prepare("PRAGMA journal_mode").get();
|
|
806
840
|
if (current.journal_mode !== "wal")
|
|
807
841
|
db.exec("PRAGMA journal_mode = WAL");
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.122",
|
|
4
4
|
"description": "Mechanical runtime CLI for dd-flow workflows.",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.23.0",
|
|
6
7
|
"bin": {
|
|
7
8
|
"dd-flow": "dist/cli.js",
|
|
8
9
|
"dd-codex": "dist/harness-runtime/bin/dd-codex.mjs",
|
|
@@ -49,6 +50,8 @@
|
|
|
49
50
|
"ajv": "^8.20.0",
|
|
50
51
|
"shell-quote": "^1.10.0",
|
|
51
52
|
"smol-toml": "^1.6.1",
|
|
53
|
+
"tree-sitter": "0.25.1",
|
|
54
|
+
"tree-sitter-bash": "0.25.1",
|
|
52
55
|
"yaml": "^2.9.0"
|
|
53
56
|
},
|
|
54
57
|
"publishConfig": {
|