@deksden-com/dd-flow-cli 0.9.0-beta.141 → 0.9.0-beta.143
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 +17 -0
- package/dist/build-info.json +7 -7
- package/dist/cli/command-inputs.js +1 -1
- package/dist/cli/command-option-descriptions.js +1 -0
- package/dist/cli/help.js +1 -0
- package/dist/cli/run-cli.js +9 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +8 -3
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +29 -8
- package/dist/harness-runtime/lib/dd-codex.mjs +184 -51
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +26 -7
- package/dist/schemas/code-review-decision.schema.json +9 -0
- package/dist/schemas/flow-run.schema.json +1 -0
- package/dist/schemas/plan-review-decision.schema.json +9 -0
- package/dist/services/accepted-intent.js +127 -0
- package/dist/services/check-runner-evidence.js +2 -0
- package/dist/services/code-checks.js +255 -65
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/run-control.js +36 -1
- package/dist/services/runtime-budget.js +16 -0
- package/dist/services/stage-pause.js +30 -0
- package/dist/services/verification-matrix.js +20 -2
- package/dist/services/vnext-code-review.js +14 -2
- package/dist/services/vnext-plan-review.js +6 -1
- package/dist/services/vnext-protocolize.js +2 -0
- package/dist/services/vnext-specify.js +4 -0
- package/dist/services/work-registry.js +4 -2
- package/dist/storage/database.js +4 -0
- package/package.json +1 -1
- package/tools/probe-codex-history.mjs +37 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
import { assertPathWithin } from "../storage/paths.js";
|
|
6
|
+
import { acceptStagePacket, prepareStagePacket, resolveStagePublicationPath } from "./stage-report-renderer.js";
|
|
7
|
+
export const acceptedIntentContract = "dd-flow/accepted-intent@1";
|
|
8
|
+
const digest = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
9
|
+
export function acceptedIntentSourceRole(role) { return role === "accepted_intake" || role.startsWith("accepted_hitl:"); }
|
|
10
|
+
export function acceptedIntentReviewFingerprint(sources) {
|
|
11
|
+
const inputs = sources?.filter(source => acceptedIntentSourceRole(source.role)).map(({ role, sha256 }) => ({ role, sha256 })).sort((a, b) => a.role < b.role ? -1 : a.role > b.role ? 1 : 0);
|
|
12
|
+
return inputs?.length ? digest(Buffer.from(JSON.stringify(inputs))) : null;
|
|
13
|
+
}
|
|
14
|
+
/** An independent review covers its retained inputs only. An own-stage user
|
|
15
|
+
* clarification may use the existing coordinator correction, explicitly named
|
|
16
|
+
* as such; it never retroactively expands the independent review's coverage. */
|
|
17
|
+
export function validateAcceptedIntentReview(context, scope, reviewedSources, contextFingerprint, acknowledgement) {
|
|
18
|
+
const reviewed = acceptedIntentReviewFingerprint(reviewedSources);
|
|
19
|
+
if (!reviewed)
|
|
20
|
+
return null; // Historical source contract has no new authority.
|
|
21
|
+
if (contextFingerprint !== reviewed)
|
|
22
|
+
throw new AppError("accepted_intent_review_stale", "Review context does not bind the retained accepted inputs", 1, { stage: scope.stage });
|
|
23
|
+
const currentSources = prepareAcceptedIntentSources(context, { ...scope, stageDirectory: "intake", marked: true }).sources;
|
|
24
|
+
const current = acceptedIntentReviewFingerprint(currentSources);
|
|
25
|
+
const reviewedEvent = Math.max(0, ...(reviewedSources ?? []).filter(source => source.role.startsWith("accepted_hitl:")).map(source => Number(source.role.split(":")[1])));
|
|
26
|
+
const rows = context.db.all("SELECT id, payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_resumed' AND json_extract(payload_json, '$.run_id') = ? AND id > ? ORDER BY id LIMIT 256", [scope.projectId, scope.runId, reviewedEvent]);
|
|
27
|
+
const additions = rows.map(row => ({ id: row.id, ...JSON.parse(row.payload_json) }));
|
|
28
|
+
const refs = additions.map(answer => answer.pause_id);
|
|
29
|
+
const evidence = { reviewed_fingerprint: reviewed, current_fingerprint: current, independent_coverage: "reviewed_sources_only", coordinator_clarification_refs: refs,
|
|
30
|
+
source_event_id: Math.max(reviewedEvent, ...additions.map(answer => answer.id)) };
|
|
31
|
+
if (current === reviewed) {
|
|
32
|
+
if (acknowledgement && (acknowledgement.reviewed_fingerprint !== reviewed || acknowledgement.current_fingerprint !== current || acknowledgement.clarification_refs.length))
|
|
33
|
+
throw new AppError("accepted_intent_review_ack_required", "Accepted-input acknowledgement differs from the retained review", 2, { ...evidence, phase: "prepare", effect: "no_effect", recoverable: true });
|
|
34
|
+
return evidence;
|
|
35
|
+
}
|
|
36
|
+
if (!additions.length || additions.some(answer => answer.stage !== scope.stage))
|
|
37
|
+
throw new AppError("accepted_intent_review_stale", "Independent review cannot cover newly accepted upstream decisions; use a supported upstream revision", 1, { ...evidence, stage: scope.stage });
|
|
38
|
+
if (!acknowledgement || acknowledgement.reviewed_fingerprint !== reviewed || acknowledgement.current_fingerprint !== current || JSON.stringify(acknowledgement.clarification_refs) !== JSON.stringify(refs)) {
|
|
39
|
+
if (scope.decisionFrozen)
|
|
40
|
+
throw new AppError("accepted_intent_review_stale", "New answer cannot alter an already frozen review decision; use a supported upstream revision", 1, evidence);
|
|
41
|
+
throw new AppError("accepted_intent_review_ack_required", "Own-stage clarification requires an explicit coordinator acknowledgement, not reuse of old independent coverage", 2, { ...evidence, phase: "prepare", effect: "no_effect", recoverable: true, accepted_intent_acknowledgement: { reviewed_fingerprint: reviewed, current_fingerprint: current, clarification_refs: refs }, instruction: "Apply/classify this clarification through the existing coordinator correction/repair route; include the exact acknowledgement in the decision. Independent review covers only the earlier retained inputs." });
|
|
42
|
+
}
|
|
43
|
+
return evidence;
|
|
44
|
+
}
|
|
45
|
+
export function assertAcceptedIntentReviewCurrent(context, scope, evidence) {
|
|
46
|
+
if (!evidence)
|
|
47
|
+
return;
|
|
48
|
+
const row = context.db.get("SELECT MAX(id) AS id FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_resumed' AND json_extract(payload_json, '$.run_id') = ?", [scope.projectId, scope.runId]);
|
|
49
|
+
if ((row?.id ?? 0) !== evidence.source_event_id)
|
|
50
|
+
throw new AppError("accepted_intent_review_stale", "Accepted answers changed during review settlement", 1);
|
|
51
|
+
}
|
|
52
|
+
export function assertDeliveredHitlReferences(context, scope, references) {
|
|
53
|
+
if (!references.length)
|
|
54
|
+
return;
|
|
55
|
+
const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [scope.projectId, scope.runId]);
|
|
56
|
+
if (JSON.parse(run.index_json).accepted_intent_contract !== acceptedIntentContract)
|
|
57
|
+
return;
|
|
58
|
+
for (const reference of new Set(references)) {
|
|
59
|
+
const answered = context.db.get("SELECT id FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_resumed' AND json_extract(payload_json, '$.run_id') = ? AND json_extract(payload_json, '$.pause_id') = ? LIMIT 1", [scope.projectId, scope.runId, reference]);
|
|
60
|
+
if (!answered)
|
|
61
|
+
throw new AppError("validation", "Obligation amendment must reference an actually delivered answer in this RUN", 2, { hitl_ref: reference });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** Retain only inputs actually delivered to this RUN, never agent recommendations
|
|
65
|
+
* or hidden evaluation expectations. Reuse the existing publication custody. */
|
|
66
|
+
export function retainAcceptedIntake(context, scope) {
|
|
67
|
+
if (!scope.bytes.length || scope.bytes.length > 1024 * 1024)
|
|
68
|
+
throw new AppError("accepted_intent_source_invalid", "Accepted intake exceeds the bounded source contract", 2);
|
|
69
|
+
const prepared = prepareStagePacket(scope.file, scope.bytes);
|
|
70
|
+
context.db.writeTransaction(() => {
|
|
71
|
+
acceptStagePacket(context, { ...scope, stage: "specify", prepared });
|
|
72
|
+
const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [scope.projectId, scope.runId]);
|
|
73
|
+
const index = JSON.parse(row.index_json);
|
|
74
|
+
index.accepted_intent_contract = acceptedIntentContract;
|
|
75
|
+
context.db.run("UPDATE runs SET index_json = ? WHERE project_id = ? AND id = ?", [JSON.stringify(index), scope.projectId, scope.runId]);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function prepareAcceptedIntentSources(context, input) {
|
|
79
|
+
if (!input.marked)
|
|
80
|
+
return { sources: [], packets: [] };
|
|
81
|
+
const fail = (reason) => { throw new AppError("accepted_intent_source_gap", "Original accepted input is unavailable; SPECIFY is not a substitute", 1, { reason, run_id: input.runId }); };
|
|
82
|
+
const rows = context.db.all("SELECT id, payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_resumed' AND json_extract(payload_json, '$.run_id') = ? ORDER BY id LIMIT 256", [input.projectId, input.runId]);
|
|
83
|
+
const entries = [{ role: "accepted_intake", file: path.join(input.home, "intake/discussion.md") }, ...rows.flatMap(row => {
|
|
84
|
+
const value = JSON.parse(row.payload_json);
|
|
85
|
+
const identity = `accepted_hitl:${String(row.id).padStart(12, "0")}:${value.pause_id}`;
|
|
86
|
+
return [{ role: `${identity}:0-question`, file: path.join(path.dirname(value.answer_path), "question.md") }, { role: `${identity}:1-answer`, file: value.answer_path }];
|
|
87
|
+
})];
|
|
88
|
+
if (entries.length > 256)
|
|
89
|
+
fail("source_count_exceeds_bound");
|
|
90
|
+
let total = 0;
|
|
91
|
+
const retained = entries.map(entry => {
|
|
92
|
+
const file = resolveStagePublicationPath(context, input.projectId, input.runId, entry.file);
|
|
93
|
+
const relative = path.relative(input.home, file);
|
|
94
|
+
if (path.isAbsolute(relative) || relative.startsWith(`..${path.sep}`) || relative === "..")
|
|
95
|
+
fail("source_outside_run");
|
|
96
|
+
const suffix = `/${relative.split(path.sep).join("/")}`;
|
|
97
|
+
const packetRows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_packet' AND json_extract(payload_json, '$.run_id') = ? AND substr(json_extract(payload_json, '$.file'), -length(?)) = ? LIMIT 33", [input.projectId, input.runId, suffix, suffix]);
|
|
98
|
+
if (packetRows.length > 32 || packetRows.some(row => Buffer.byteLength(row.payload_json) > 2 * 1024 * 1024))
|
|
99
|
+
fail(`unbounded_custody:${relative}`);
|
|
100
|
+
const candidates = packetRows.map(row => ({ value: JSON.parse(row.payload_json) })).filter(packet => resolveStagePublicationPath(context, input.projectId, input.runId, packet.value.file) === file);
|
|
101
|
+
if (!candidates.length)
|
|
102
|
+
fail(`missing_custody:${relative}`);
|
|
103
|
+
const bytes = Buffer.from(candidates[0].value.base64, "base64"), sha256 = digest(bytes);
|
|
104
|
+
total += bytes.length;
|
|
105
|
+
if (!bytes.length || bytes.length > 1024 * 1024 || total > 16 * 1024 * 1024 || candidates.some(packet => packet.value.sha256 !== sha256 || !Buffer.from(packet.value.base64, "base64").equals(bytes) || Buffer.from(packet.value.base64, "base64").toString("base64") !== packet.value.base64))
|
|
106
|
+
fail(`invalid_custody:${relative}`);
|
|
107
|
+
// An unavailable or replaced published source is an explicit evidence gap,
|
|
108
|
+
// rather than silent use of a later summary or arbitrary local file.
|
|
109
|
+
if (!fs.existsSync(file) || !fs.lstatSync(file).isFile() || fs.statSync(file).size !== bytes.length)
|
|
110
|
+
fail(`source_unavailable:${relative}`);
|
|
111
|
+
const actual = path.relative(fs.realpathSync(input.home), fs.realpathSync(file));
|
|
112
|
+
if (path.isAbsolute(actual) || actual === ".." || actual.startsWith(`..${path.sep}`) || !fs.readFileSync(file).equals(bytes))
|
|
113
|
+
fail(`source_unavailable:${relative}`);
|
|
114
|
+
const snapshot = `${input.stageDirectory}/verification/sources/${sha256}/accepted-input.md`;
|
|
115
|
+
const destination = path.join(input.home, snapshot);
|
|
116
|
+
try {
|
|
117
|
+
assertPathWithin(input.home, destination, "accepted_source_snapshot");
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (!(error instanceof AppError) || error.code !== "validation")
|
|
121
|
+
throw error;
|
|
122
|
+
throw new AppError("verification_matrix_path_invalid", "Accepted source snapshot escapes its RUN", 1, { path: snapshot });
|
|
123
|
+
}
|
|
124
|
+
return { source: { role: entry.role, root: "run", path: snapshot, sha256 }, packet: prepareStagePacket(destination, bytes) };
|
|
125
|
+
});
|
|
126
|
+
return { sources: retained.map(item => item.source), packets: [...new Map(retained.map(item => [item.packet.file, item.packet])).values()] };
|
|
127
|
+
}
|
|
@@ -224,6 +224,8 @@ export function readRunnerEvidence(root, contract, binding) {
|
|
|
224
224
|
}
|
|
225
225
|
export function runnerEvidencePass(evidence) { return Boolean(evidence?.completed && !evidence.observation_unknown && evidence.counts.failed === 0 && evidence.failures.length === 0); }
|
|
226
226
|
export function checkRepairDisposition(receipt) {
|
|
227
|
+
if (receipt.fingerprint_scope != null && receipt.fingerprint_scope !== "check_inputs")
|
|
228
|
+
return "tooling_blocker";
|
|
227
229
|
if (receipt.status === "running" || receipt.status === "aborted" || receipt.runner_evidence?.observation_unknown)
|
|
228
230
|
return "settlement_required";
|
|
229
231
|
if (receipt.status === "passed")
|