@deksden-com/dd-flow-cli 0.9.0-beta.142 → 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 +10 -0
- package/dist/build-info.json +7 -7
- 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 +23 -6
- 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 +213 -47
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/run-control.js +36 -1
- 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
|
@@ -892,7 +892,42 @@ export function nativeInventoryVersion(directory, operations = retainedDaemonOpe
|
|
|
892
892
|
const keys = ["schema_id", "daemon_id", "config", "pid", "provider_pid", "shutdown_state", "shutdown", "active_operation", "active_tree", "sessions", "descendants", "root_session_id", "resource_process", "provider_process", "owned_processes"];
|
|
893
893
|
// Only these native verbs are read-only. Unknown verbs remain guarded.
|
|
894
894
|
const productive = operations.filter(operation => !isObservationOperation(operation.operation));
|
|
895
|
-
|
|
895
|
+
const knownSchema = ["dd-opencode/daemon-state@2", "dd-codex/daemon-state@1", "dd-grok/daemon-state@1", "dd-zcode/daemon-state@1", "dd-agy/daemon-state@1", "dd-droid/daemon-state@1"].includes(String(state.schema_id));
|
|
896
|
+
const inventory = knownSchema ? Object.fromEntries(keys.map(key => [key, state[key]])) : { ...state };
|
|
897
|
+
const canonical = (value) => Array.isArray(value) ? value.map(canonical)
|
|
898
|
+
: value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, canonical(item)])) : value;
|
|
899
|
+
// Sets have no observation order. Unknown schemas/fields remain guarded;
|
|
900
|
+
// only the proven derived OpenCode adapter alias is not ownership authority.
|
|
901
|
+
for (const key of ["sessions", "descendants", "owned_processes"]) {
|
|
902
|
+
const values = inventory[key];
|
|
903
|
+
if (!Array.isArray(values))
|
|
904
|
+
continue;
|
|
905
|
+
const seen = new Set();
|
|
906
|
+
inventory[key] = values.map(value => {
|
|
907
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
908
|
+
if (state.schema_id === "dd-opencode/daemon-state@2")
|
|
909
|
+
throw new AppError("native_inventory_invalid", `Invalid native ${key} record`, 1);
|
|
910
|
+
return value;
|
|
911
|
+
}
|
|
912
|
+
const record = value;
|
|
913
|
+
const identity = record.provider_session_id ?? record.process_id ?? record.pid;
|
|
914
|
+
if (identity === undefined && state.schema_id !== "dd-opencode/daemon-state@2")
|
|
915
|
+
return record;
|
|
916
|
+
if (!(typeof identity === "string" && identity.length > 0 || typeof identity === "number" && Number.isSafeInteger(identity) && identity > 0) || seen.has(String(identity)))
|
|
917
|
+
throw new AppError("native_inventory_invalid", `Invalid or duplicate native ${key} identity`, 1);
|
|
918
|
+
seen.add(String(identity));
|
|
919
|
+
if (state.schema_id === "dd-opencode/daemon-state@2" && key === "sessions") {
|
|
920
|
+
if (record.adapter_session_id !== undefined && record.adapter_session_id !== record.provider_session_id)
|
|
921
|
+
throw new AppError("native_inventory_invalid", "Native Session adapter identity differs", 1);
|
|
922
|
+
const authority = { ...record };
|
|
923
|
+
delete authority.adapter_session_id;
|
|
924
|
+
return authority;
|
|
925
|
+
}
|
|
926
|
+
return record;
|
|
927
|
+
}).map(canonical).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
928
|
+
}
|
|
929
|
+
const productiveInventory = productive.map(canonical).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
|
|
930
|
+
return crypto.createHash("sha256").update(JSON.stringify(canonical([inventory, productiveInventory]))).digest("hex");
|
|
896
931
|
}
|
|
897
932
|
export function isObservationOperation(operation) {
|
|
898
933
|
return ["session.inspect", "daemon.status"].includes(String(operation));
|
|
@@ -7,6 +7,7 @@ import { AppError } from "../shared/errors.js";
|
|
|
7
7
|
import { entityReferenceVariants, shortEntityReference } from "../shared/entity-references.js";
|
|
8
8
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
9
9
|
import { publishImmutableInput } from "../storage/immutable-input.js";
|
|
10
|
+
import { acceptStagePacket, prepareStagePacket } from "./stage-report-renderer.js";
|
|
10
11
|
import { resolveWorkReference } from "../storage/work-references.js";
|
|
11
12
|
import { findRecentMatchingHookEvent, claimStageResumeHookEvent, stageResumeMatchKey } from "./hooks.js";
|
|
12
13
|
import { requireProjectByRoot } from "./projects.js";
|
|
@@ -15,6 +16,8 @@ import { bindSessionForResume, refreshRunWorkProjection } from "./work-registry.
|
|
|
15
16
|
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
16
17
|
import { runtimeCommand } from "./runtime-command.js";
|
|
17
18
|
import { prepareAgentOutputFile } from "../storage/agent-output.js";
|
|
19
|
+
import { latestRunVerificationMatrix } from "./verification-matrix.js";
|
|
20
|
+
import { validateAcceptedIntentReview } from "./accepted-intent.js";
|
|
18
21
|
export { flowCommand } from "./runtime-command.js";
|
|
19
22
|
export function prepareStagePause(context, input) {
|
|
20
23
|
if (typeof input.question !== "string" || !input.question.trim())
|
|
@@ -28,6 +31,8 @@ export function prepareStagePause(context, input) {
|
|
|
28
31
|
if (work.status !== "running")
|
|
29
32
|
throw new AppError("invalid_work_state", "Only a running Work can pause", 1, { work_id: work.work_id, status: work.status });
|
|
30
33
|
const question = input.question;
|
|
34
|
+
if (Buffer.byteLength(question) > 1024 * 1024)
|
|
35
|
+
throw new AppError("accepted_intent_source_invalid", "HITL question exceeds the bounded source contract", 2);
|
|
31
36
|
const stage = requireStage(run, input.stage, "running");
|
|
32
37
|
requireRunHome(run);
|
|
33
38
|
return { projectRoot, project, run, work, question, stage };
|
|
@@ -39,6 +44,7 @@ export function pauseStageForUser(context, input) {
|
|
|
39
44
|
const questionPath = path.join(pauseRoot, "question.md");
|
|
40
45
|
fs.mkdirSync(pauseRoot, { recursive: true });
|
|
41
46
|
publishImmutableInput(questionPath, Buffer.from(question), "hitl_question_conflict");
|
|
47
|
+
const questionPacket = prepareStagePacket(questionPath, Buffer.from(question));
|
|
42
48
|
const answerInputPath = prepareAgentOutputFile(path.join(pauseRoot, "answer-input.md"));
|
|
43
49
|
const now = context.now();
|
|
44
50
|
context.db.writeTransaction(() => {
|
|
@@ -46,6 +52,7 @@ export function pauseStageForUser(context, input) {
|
|
|
46
52
|
if (paused.changes !== 1)
|
|
47
53
|
throw new AppError("invalid_work_state", "Work changed before pause could be accepted", 1, { work_id: work.work_id });
|
|
48
54
|
pauseFlowRunStage(context, { projectRoot, runId: run.id, stage: stage.stage, workId: work.work_id, pauseId, questionPath });
|
|
55
|
+
acceptStagePacket(context, { projectId: project.id, runId: run.id, stage: stage.stage, prepared: questionPacket });
|
|
49
56
|
context.db.afterCommit(() => refreshRunWorkProjection(context, project.id, run.id));
|
|
50
57
|
});
|
|
51
58
|
const resume = stageResumeCommand(context, { runId: run.id, stage: stage.stage, workId: work.work_id, projectRoot, answerInputPath });
|
|
@@ -103,9 +110,12 @@ export function resumeStageAfterUser(context, input) {
|
|
|
103
110
|
}).eventKey;
|
|
104
111
|
const now = context.now();
|
|
105
112
|
const answerPath = path.join(path.dirname(pause.question_path), `answer-${crypto.createHash("sha256").update(`${pause.id}\0${input.answer}`).digest("hex")}.md`);
|
|
113
|
+
if (Buffer.byteLength(input.answer) > 1024 * 1024)
|
|
114
|
+
throw new AppError("accepted_intent_source_invalid", "Delivered user answer exceeds the bounded source contract", 2);
|
|
106
115
|
// The answer is an immutable, content-addressed input. Prepare it before
|
|
107
116
|
// reserving SQLite's writer; an unreferenced candidate has no flow authority.
|
|
108
117
|
publishImmutableInput(answerPath, Buffer.from(input.answer), "hitl_answer_conflict");
|
|
118
|
+
const answerPacket = prepareStagePacket(answerPath, Buffer.from(input.answer));
|
|
109
119
|
context.db.writeTransaction(() => {
|
|
110
120
|
const identity = claimStageResumeHookEvent(context, { projectId: project.id, runId: run.id, stage: stage.stage, workId: work.work_id, projectRoot, eventKey: hookEventId });
|
|
111
121
|
if (workSession.session_id !== identity.sessionId) {
|
|
@@ -117,11 +127,30 @@ export function resumeStageAfterUser(context, input) {
|
|
|
117
127
|
if (resumed.changes !== 1)
|
|
118
128
|
throw new AppError("invalid_work_state", "Work changed before resume could be accepted", 1, { work_id: work.work_id });
|
|
119
129
|
resumeFlowRunStage(context, { projectRoot, runId: run.id, stage: stage.stage, workId: work.work_id, answerPath });
|
|
130
|
+
acceptStagePacket(context, { projectId: project.id, runId: run.id, stage: stage.stage, prepared: answerPacket });
|
|
120
131
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "stage_resume_session_bound", stage: stage.stage, work_id: work.work_id, session_id: identity.sessionId });
|
|
121
132
|
context.db.afterCommit(() => refreshRunWorkProjection(context, project.id, run.id));
|
|
122
133
|
});
|
|
123
134
|
const prompt = evidence.prompt;
|
|
124
135
|
const retryCommands = lifecycleRetryCommands(context, stage.stage);
|
|
136
|
+
const intentInstructions = [];
|
|
137
|
+
if (["plan-review", "code-review"].includes(stage.stage)) {
|
|
138
|
+
try {
|
|
139
|
+
const home = requireRunHome(run), source = latestRunVerificationMatrix(context, project.id, run.id, stage.stage, "input");
|
|
140
|
+
const reviewContext = path.join(home, stage.stage === "plan-review" ? "04-plan-review/work-context.json" : "06-code-review/review-context.json");
|
|
141
|
+
const retainedContext = fs.existsSync(reviewContext) ? JSON.parse(fs.readFileSync(reviewContext, "utf8")) : null;
|
|
142
|
+
validateAcceptedIntentReview(context, { projectId: project.id, runId: run.id, home, stage: stage.stage, decisionFrozen: Boolean(retainedContext?.accepted_decision) }, source?.metadata.accepted_sources, retainedContext?.accepted_intent_review_fingerprint);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (!(error instanceof AppError))
|
|
146
|
+
throw error;
|
|
147
|
+
intentInstructions.push(`Accepted-input review boundary: ${error.code}: ${error.message}`);
|
|
148
|
+
if (error.code === "accepted_intent_review_ack_required")
|
|
149
|
+
intentInstructions.push(`Apply/classify this own-stage clarification through the existing coordinator correction/repair route. Include this exact field in the decision: ${JSON.stringify({ accepted_intent_acknowledgement: error.details.accepted_intent_acknowledgement })}. Independent reviewer results prove only the earlier retained inputs; do not claim they reviewed the new answer.`);
|
|
150
|
+
else
|
|
151
|
+
intentInstructions.push("Do not accept stale independent review as coverage of this answer; report the actionable upstream revision/evidence blocker.");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
125
154
|
const continuation = [
|
|
126
155
|
prompt.trimEnd(),
|
|
127
156
|
"",
|
|
@@ -130,6 +159,7 @@ export function resumeStageAfterUser(context, input) {
|
|
|
130
159
|
`- Question source: ${pause.question_path}`,
|
|
131
160
|
`- Answer source: ${answerPath}`,
|
|
132
161
|
"- Continue the same stage, Work and attempt. Do not call stage start or repeat completed preparation.",
|
|
162
|
+
...intentInstructions,
|
|
133
163
|
...(retryCommands.length ? ["- A prior no-effect lifecycle rejection now has these authoritative replacement commands; correct the rejected input and use only these replacements:", ...retryCommands.map(command => ` ${command}`)] : []),
|
|
134
164
|
"",
|
|
135
165
|
"<user_question>",
|
|
@@ -6,6 +6,7 @@ import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
|
6
6
|
import { checkReceiptEvidenceValid, checkReceipts, effectiveCheckDeclarations } from "./code-checks.js";
|
|
7
7
|
import { acceptStagePacket, prepareStagePacket, publishStagePacket, resolveStagePublicationPath } from "./stage-report-renderer.js";
|
|
8
8
|
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
9
|
+
import { acceptedIntentContract, acceptedIntentSourceRole, prepareAcceptedIntentSources } from "./accepted-intent.js";
|
|
9
10
|
export const verificationMatrixContract = "dd-flow/verification-matrix@1";
|
|
10
11
|
export const finalCheckCoverageContract = "dd-flow/final-check-coverage@1";
|
|
11
12
|
/** A view of already validated facts: never a gate or a receipt selector. */
|
|
@@ -55,7 +56,9 @@ export function verificationMatrixInstruction(metadata, runHome, task) {
|
|
|
55
56
|
const contract = `<verification_evidence_contract>\nCheck prerequisites must be executable in the check command or supported project policy; purpose prose and list order do not prepare an environment. A readiness gate is final verification, not implicit setup before the first CODE Work. PLAN and PLAN-REVIEW must compare claimed prerequisites with the actual launch path: a description saying migrate-before-check cannot repair a command that checks before migration. Prefer the existing self-contained project verification alias.\nBrowser checks must prove the expected surface exists and the measured set is nonempty before asserting layout geometry. An empty DOM is not evidence of no overlap. Derive navigation from the actual UI and accepted specification. Review whether the test can fail for the intended defect; a green named contour does not prove complete product semantics. Keep native test outcome separate from observation/cleanup: passed assertions with unknown cleanup are neither PASS nor a product assertion failure. Use retained native diagnostics and proof limits; generic timeouts or infrastructure errors do not authorize source repair. Do not patch the candidate product outside the assigned flow Work.\n</verification_evidence_contract>\n`;
|
|
56
57
|
if (!metadata)
|
|
57
58
|
return contract;
|
|
58
|
-
|
|
59
|
+
const originals = metadata.accepted_sources?.length ? ["Original accepted inputs (ordered intake, then question/answer pairs):", "A question is retained only to interpret its delivered user answer. Agent recommendations and options are not accepted decisions unless that answer explicitly adopts them.", ...metadata.accepted_sources.map(source => `- ${source.role}: ${safePath(runHome, source.path, false)} (sha256 ${source.sha256})`)].join("\n") : "Original accepted-input coverage is unavailable for this historical RUN. Do not treat SPECIFY as the original user input or claim that semantic preservation was verified.";
|
|
60
|
+
const originalContract = `<accepted_intent_sources>\n${originals}\nCompare original accepted inputs with SPECIFY, effective amendments, PLAN and observable implementation. Preserve material actors, operations, UI/API surfaces, state transitions and exceptions when applicable. API evidence does not prove UI behavior. Later text supersedes an earlier accepted decision only when explicitly accepted as such; unresolved conflicts are material questions, not last-text-wins. Report lost obligations with source and R/AC references. Never edit immutable accepted SPECIFY to conceal a finding. Use the supported upstream correction route; if unavailable or a wholly missing R/AC needs a new accepted revision, report an actionable blocker rather than repairing only PLAN. Do not fabricate HITL references. A structurally complete matrix is not semantic acceptance.\n</accepted_intent_sources>\n`;
|
|
61
|
+
return `${contract}${originalContract}<verification_matrix>\nRuntime generated the technical verification matrix: ${safePath(runHome, metadata.markdown.path, false)}; structured data: ${safePath(runHome, metadata.json.path, false)}. Source fingerprint: ${metadata.source_fingerprint}; completeness: ${metadata.completeness}. Read it before ${task}. This is a derived view of the named accepted requirements, PLAN and receipts, not semantic acceptance. Do not edit these generated files or manually duplicate receipt IDs/hashes/results. Record insufficient proof or violated criteria in the ordinary Stage/Work result with evidence references; modify only your assigned source/test/result documents. Review inputs remain read-only. A passed check proves only its named contour and limits, not all business semantics or production readiness.\n</verification_matrix>\n`;
|
|
59
62
|
}
|
|
60
63
|
/** Attachment and packet authority share a short transaction, then both files
|
|
61
64
|
* must be visible before the prompt claims they are ready. */
|
|
@@ -86,6 +89,12 @@ export function prepareRunVerificationMatrix(context, input) {
|
|
|
86
89
|
const owning = index.stage_runs?.find(item => item.stage === input.stage);
|
|
87
90
|
const attempt = input.stageAttempt ?? owning?.attempt ?? "try-001";
|
|
88
91
|
const sources = [], packets = [];
|
|
92
|
+
const intentContract = index.accepted_intent_contract;
|
|
93
|
+
if (intentContract !== undefined && intentContract !== acceptedIntentContract)
|
|
94
|
+
throw new AppError("accepted_intent_contract_unsupported", "Unknown accepted-input custody contract", 1, { contract: intentContract });
|
|
95
|
+
const intent = prepareAcceptedIntentSources(context, { projectId: input.projectId, runId: input.runId, home, stageDirectory: vnextStageDirectory(input.stage), marked: index.accepted_intent_contract === acceptedIntentContract });
|
|
96
|
+
sources.push(...intent.sources);
|
|
97
|
+
packets.push(...intent.packets);
|
|
89
98
|
const readSource = (role, root, relative, expected) => {
|
|
90
99
|
const file = safePath(root === "run" ? home : workspace, relative), bytes = fs.readFileSync(file), sha = digest(bytes);
|
|
91
100
|
if (expected && sha !== expected)
|
|
@@ -258,6 +267,8 @@ export function prepareRunVerificationMatrix(context, input) {
|
|
|
258
267
|
const markdown = prepareStagePacket(safePath(home, `${root}/verification-matrix.md`), Buffer.from(renderVerificationMatrix(matrix)));
|
|
259
268
|
packets.push(json, markdown);
|
|
260
269
|
const metadata = { contract: verificationMatrixContract, role: header.role, completeness: header.completeness, source_fingerprint: matrix.header.source_fingerprint, json: { path: slash(path.relative(home, json.file)), sha256: json.sha256 }, markdown: { path: slash(path.relative(home, markdown.file)), sha256: markdown.sha256 } };
|
|
270
|
+
if (intent.sources.length)
|
|
271
|
+
metadata.accepted_sources = intent.sources;
|
|
261
272
|
if (index.final_check_coverage_contract === finalCheckCoverageContract)
|
|
262
273
|
metadata.coverage_contract = finalCheckCoverageContract;
|
|
263
274
|
const predecessors = (index.stage_runs ?? []).filter(item => item.stage !== input.stage && item.status === "done").map(item => ({ stage: item.stage, ...(item.attempt ? { attempt: item.attempt } : {}), ...(item.data_sha256 ? { data_sha256: item.data_sha256 } : {}) }));
|
|
@@ -294,6 +305,8 @@ export function latestRunVerificationMatrix(context, projectId, runId, stage, ro
|
|
|
294
305
|
if (!run?.run_root)
|
|
295
306
|
return null;
|
|
296
307
|
const index = JSON.parse(run.index_json);
|
|
308
|
+
if (index.accepted_intent_contract !== undefined && index.accepted_intent_contract !== acceptedIntentContract)
|
|
309
|
+
throw new AppError("accepted_intent_contract_unsupported", "Unknown accepted-input custody contract", 1, { contract: index.accepted_intent_contract });
|
|
297
310
|
const attempt = index.stage_runs?.find(item => item.stage === stage)?.attempt;
|
|
298
311
|
const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_packet' ORDER BY id DESC", [projectId]);
|
|
299
312
|
for (const row of rows) {
|
|
@@ -319,9 +332,14 @@ export function latestRunVerificationMatrix(context, projectId, runId, stage, ro
|
|
|
319
332
|
const md = { file: mdFile, bytes: mdBytes, sha256: paired.sha256 };
|
|
320
333
|
const json = { file: packet.file, bytes, sha256: packet.sha256 };
|
|
321
334
|
const metadata = { contract: verificationMatrixContract, role: matrix.header.role, completeness: matrix.header.completeness, source_fingerprint: matrix.header.source_fingerprint, json: { path: slash(path.relative(run.run_root, packet.file)), sha256: packet.sha256 }, markdown: { path: slash(path.relative(run.run_root, mdFile)), sha256: md.sha256 } };
|
|
335
|
+
const acceptedSources = matrix.sources.filter(source => acceptedIntentSourceRole(source.role)).sort((a, b) => a.role === "accepted_intake" ? -1 : b.role === "accepted_intake" ? 1 : compare(a.role, b.role));
|
|
336
|
+
if (index.accepted_intent_contract === acceptedIntentContract && !acceptedSources.some(source => source.role === "accepted_intake"))
|
|
337
|
+
throw new AppError("accepted_intent_source_gap", "Marked matrix is missing its original accepted intake", 1, { stage, run_id: runId });
|
|
338
|
+
if (acceptedSources.length)
|
|
339
|
+
metadata.accepted_sources = acceptedSources;
|
|
322
340
|
if (index.final_check_coverage_contract === finalCheckCoverageContract)
|
|
323
341
|
metadata.coverage_contract = finalCheckCoverageContract;
|
|
324
|
-
const sourcePackets = matrix.sources.filter(source => (source.role.startsWith("plan:") || source.role === "code_work_batch") && source.root === "run").map(source => {
|
|
342
|
+
const sourcePackets = matrix.sources.filter(source => (source.role.startsWith("plan:") || source.role === "code_work_batch" || acceptedIntentSourceRole(source.role)) && source.root === "run").map(source => {
|
|
325
343
|
const file = safePath(run.run_root, source.path, false);
|
|
326
344
|
const retained = rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.stage === stage && item.attempt === attempt && resolveStagePublicationPath(context, projectId, runId, item.file) === file && item.sha256 === source.sha256);
|
|
327
345
|
if (!retained)
|
|
@@ -28,6 +28,7 @@ import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
|
28
28
|
import { prepareJsonFile, prepareOutputFile } from "../cli/input-preparation.js";
|
|
29
29
|
import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
|
|
30
30
|
import { reviewDefEvidence } from "./review-deferral.js";
|
|
31
|
+
import { acceptedIntentReviewFingerprint, validateAcceptedIntentReview, assertAcceptedIntentReviewCurrent } from "./accepted-intent.js";
|
|
31
32
|
import { stageSuccessor } from "./vnext-contracts.js";
|
|
32
33
|
const stage = "code-review";
|
|
33
34
|
const stageDir = vnextStageDirectory(stage);
|
|
@@ -93,7 +94,7 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
|
|
|
93
94
|
freezeFlowRunReviewMode(context, { projectRoot, runId: run.id, review: "code", mode: mode.mode, source: mode.source, reason: mode.reason });
|
|
94
95
|
const promptPath = path.join(root, "stage-prompt.md");
|
|
95
96
|
const reviewContextPath = path.join(root, "review-context.json");
|
|
96
|
-
fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", reviewer_inputs: prepared.reviewerInputs, cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
|
|
97
|
+
fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", accepted_intent_review_fingerprint: acceptedIntentReviewFingerprint(prepared.matrix?.metadata.accepted_sources), reviewer_inputs: prepared.reviewerInputs, cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
|
|
97
98
|
if (mode.mode === "off") {
|
|
98
99
|
fs.writeFileSync(path.join(root, "decision.json"), `${JSON.stringify(disabledReviewDecision(), null, 2)}\n`);
|
|
99
100
|
const prompt = `<stage_identity>\n- RUN: ${run.id}\n- stage: code-review\n- mode: off\n</stage_identity>\n\nCODE-REVIEW is disabled by the frozen RUN configuration. Finish with: ${finishCommand(context, run.id, projectRoot, path.join(root, "decision.json"))}\n${stageLifecycleInstruction}\n`;
|
|
@@ -197,6 +198,7 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
197
198
|
? disabledDecision
|
|
198
199
|
: readCodeReviewDecision(decisionFile, reviewContext.accepted_decision, Boolean(input.decisionFile), input.decision);
|
|
199
200
|
validateSchema({ schemaName: "code-review-decision", file: decisionFile, data: decision, projectRoot: run.workspace_root, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
|
|
201
|
+
const intentEvidence = mode === "off" ? null : validateAcceptedIntentReview(context, { projectId: project.id, runId: run.id, home, stage, decisionFrozen: Boolean(reviewContext.accepted_decision) }, latestRunVerificationMatrix(context, project.id, run.id, stage, "input")?.metadata.accepted_sources, reviewContext.accepted_intent_review_fingerprint, decision.accepted_intent_acknowledgement);
|
|
200
202
|
const cycle = reviewContext.cycle ?? "initial";
|
|
201
203
|
const reviewers = reviewerWorks(context, project.id, run.id, cycle);
|
|
202
204
|
const defEvidence = decision.findings.filter(item => item.disposition === "defer").map(item => reviewDefEvidence(run.workspace_root, item.def_id));
|
|
@@ -218,13 +220,20 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
218
220
|
throw new AppError("review_decision_changed", "The accepted CODE-REVIEW decision cannot change during repair", 2, { decision_file: decisionFile });
|
|
219
221
|
const fixIds = canonical.fix_ids;
|
|
220
222
|
if (fixIds.length && repairs.length === 0) {
|
|
223
|
+
// The fresh repair sees the coordinator's newly accepted clarification,
|
|
224
|
+
// without pretending that the earlier independent review covered it.
|
|
225
|
+
if (intentEvidence?.coordinator_clarification_refs.length) {
|
|
226
|
+
const correctionMatrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", semanticFile: decisionFile });
|
|
227
|
+
context.db.writeTransaction(() => { assertAcceptedIntentReviewCurrent(context, { projectId: project.id, runId: run.id }, intentEvidence); acceptVerificationMatrix(context, correctionMatrix); });
|
|
228
|
+
publishVerificationMatrix(context, correctionMatrix);
|
|
229
|
+
}
|
|
221
230
|
// Accept the value once. Repairs consume this decision, not a mutable
|
|
222
231
|
// operator file. Reuse the existing context artifact and review cycle.
|
|
223
232
|
const contextFile = path.join(root, "review-context.json"), temporary = `${contextFile}.tmp-${crypto.randomUUID()}`;
|
|
224
233
|
fs.writeFileSync(temporary, `${JSON.stringify({ ...reviewContext, accepted_decision: decision, def_evidence: defEvidence }, null, 2)}\n`);
|
|
225
234
|
fs.renameSync(temporary, contextFile);
|
|
226
235
|
const repair = addVnextCodeReviewRepair(context, { projectRoot, runId: run.id, findingIds: fixIds, checkRefsByFinding, decisionReasonsByFinding: Object.fromEntries(canonical.decision.findings.map((item) => [item.finding_ref, item.reason])), objective: `Resolve accepted CODE-REVIEW findings: ${fixIds.join(", ")}` });
|
|
227
|
-
const retained = latestRunVerificationMatrix(context, project.id, run.id, stage, "input");
|
|
236
|
+
const retained = latestRunVerificationMatrix(context, project.id, run.id, stage, intentEvidence?.coordinator_clarification_refs.length ? "output" : "input");
|
|
228
237
|
return { ok: true, run_id: run.id, stage, outcome: "repair_required", repair, ...(retained ? { verification_matrix: retained.metadata } : {}), instruction: `The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. When it completes, invoke next.finish_command; this review cycle uses the decision already accepted by the runtime.\n${verificationMatrixInstruction(retained?.metadata ?? null, home, "the accepted review decision and repair")}`, next: { finish_command: finishCommand(context, run.id, projectRoot, decisionFile) } };
|
|
229
238
|
}
|
|
230
239
|
repairs = reviewRepairWorks(context, project.id, run.id, root, cycle);
|
|
@@ -273,9 +282,12 @@ export async function finishVnextCodeReview(context, input) {
|
|
|
273
282
|
if (JSON.stringify(defEvidence) !== JSON.stringify(decision.findings.filter(item => item.disposition === "defer").map(item => reviewDefEvidence(run.workspace_root, item.def_id))))
|
|
274
283
|
throw new AppError("deferral_invalid", "DEF evidence changed while final checks ran", 2);
|
|
275
284
|
Object.assign(report.semantic, { def_evidence: defEvidence });
|
|
285
|
+
if (intentEvidence)
|
|
286
|
+
Object.assign(report.semantic, { accepted_intent_review: intentEvidence });
|
|
276
287
|
const preparedReport = prepareStageReport(report);
|
|
277
288
|
const completionInput = { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html", dataSha256: preparedReport.sha256, git: report.mechanical.git };
|
|
278
289
|
withStageSettlement(context, () => {
|
|
290
|
+
assertAcceptedIntentReviewCurrent(context, { projectId: project.id, runId: run.id }, intentEvidence);
|
|
279
291
|
assertRunMutationAllowed(context, project.id, run.id, generation);
|
|
280
292
|
const currentReviewers = reviewerWorks(context, project.id, run.id, cycle);
|
|
281
293
|
const currentRepairs = reviewRepairWorks(context, project.id, run.id, root, cycle);
|
|
@@ -25,6 +25,7 @@ import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-ins
|
|
|
25
25
|
import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix } from "./verification-matrix.js";
|
|
26
26
|
import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
|
|
27
27
|
import { reviewDefEvidence } from "./review-deferral.js";
|
|
28
|
+
import { acceptedIntentReviewFingerprint, validateAcceptedIntentReview, assertAcceptedIntentReviewCurrent } from "./accepted-intent.js";
|
|
28
29
|
import { stageSuccessor } from "./vnext-contracts.js";
|
|
29
30
|
const stage = "plan-review";
|
|
30
31
|
const stageDir = vnextStageDirectory(stage);
|
|
@@ -171,7 +172,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
|
|
|
171
172
|
const workSessionId = String(binding.work_session_id);
|
|
172
173
|
const sessionId = String(binding.session_id);
|
|
173
174
|
const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: workId, dispatch: "plan-review", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
|
|
174
|
-
writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", reviewer_inputs: prepared.reviewerInputs, read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_sources: readJson(prepared.batch).sources.map(source => ({ ...source, content_sha256: planContentHash(readJson(path.join(run.workspace_root, ".memory-bank", "protocol", source.protocol_id, "plan.json"))) })), plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
|
|
175
|
+
writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", accepted_intent_review_fingerprint: acceptedIntentReviewFingerprint(prepared.matrix?.metadata.accepted_sources), reviewer_inputs: prepared.reviewerInputs, read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_sources: readJson(prepared.batch).sources.map(source => ({ ...source, content_sha256: planContentHash(readJson(path.join(run.workspace_root, ".memory-bank", "protocol", source.protocol_id, "plan.json"))) })), plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
|
|
175
176
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
176
177
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "work_session_started", stage, work_id: workId, id: workSessionId, session_id: sessionId });
|
|
177
178
|
return { ok: true, run_id: run.id, stage, outcome: "review_required", work_id: workId, id: workSessionId, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}), orchestration, plan_review: { requested_mode: requested, effective_mode: effective, policy_source: policySource(run), groups }, next: { dispatch_command: dispatchCommand(context, run.id, projectRoot), finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
|
|
@@ -280,6 +281,7 @@ function finishPlanReviewOwned(context, input) {
|
|
|
280
281
|
return withStageSettlement(context, () => finishTerminalReview(context, { projectRoot, projectId: project.id, run, root, parentWorkId: parent.work_id, decision, now, decisionReceipt, preparedReport, completionInput, preparedCompletion }));
|
|
281
282
|
}
|
|
282
283
|
const latestChildren = latestReviewChildren(children);
|
|
284
|
+
const intentEvidence = validateAcceptedIntentReview(context, { projectId: project.id, runId: run.id, home, stage }, latestRunVerificationMatrix(context, project.id, run.id, stage, "input")?.metadata.accepted_sources, contextFile.accepted_intent_review_fingerprint, decision.accepted_intent_acknowledgement);
|
|
283
285
|
const evidenceFailures = reviewerEvidenceFailures(context, { projectId: project.id, parentWorkId: parent.work_id, children: latestChildren, groups, planRevision: reviewedPlanRevision, workspaceRoot: run.workspace_root, runHome: home, runId: run.id });
|
|
284
286
|
if (evidenceFailures.length)
|
|
285
287
|
throw new AppError("review_evidence_invalid", "PLAN-REVIEW reviewer evidence is incomplete, stale, or not isolated", 2, { errors: evidenceFailures });
|
|
@@ -332,11 +334,14 @@ function finishPlanReviewOwned(context, input) {
|
|
|
332
334
|
const matrix = prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "output", semanticFile: decisionPath });
|
|
333
335
|
const completionBundle = readFlowRunExecutionBundle(context, { projectRoot, runId: run.id });
|
|
334
336
|
return withStageSettlement(context, () => {
|
|
337
|
+
assertAcceptedIntentReviewCurrent(context, { projectId: project.id, runId: run.id }, intentEvidence);
|
|
335
338
|
const registered = addWorkBatch(context, { parentWorkId: codeParentId, prepared: codeItems, stage: "code" });
|
|
336
339
|
const now = context.now();
|
|
337
340
|
settleReviewParent(context, { projectRoot, projectId: project.id, runId: run.id, parentWorkId: parent.work_id, status: "completed", result: decision.summary, reason: "plan_review_completed", resultPath: path.join(root, "stage-report.json") });
|
|
338
341
|
const report = reportFor(context, { run, mode: contextFile.system?.effective_mode ?? "standard", requested: contextFile.system?.requested_mode ?? "auto", outcome: "accepted", groups, batchChecksum: contextFile.system?.batch_checksum ?? finalBatchChecksum, planChecksum: contextFile.system?.plan_checksum ?? finalPlanChecksum, finalBatchChecksum, finalPlanChecksum, initialPlanRevision: reviewedPlanRevision, finalPlanRevision: planRevision, code: registered, now, projectRoot, decision, children: latestChildren, flow: flowCommand(context), git: reportGit, ...(next.command ? { nextCommand: next.command } : {}) });
|
|
339
342
|
Object.assign(report.semantic, { def_evidence: defEvidence });
|
|
343
|
+
if (intentEvidence)
|
|
344
|
+
Object.assign(report.semantic, { accepted_intent_review: intentEvidence });
|
|
340
345
|
applyReviewResults(home, latestChildren, decision);
|
|
341
346
|
if (matrix)
|
|
342
347
|
Object.assign(report.semantic, { verification_matrix: matrix.metadata });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isControllerManagedInvocation, managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
2
2
|
import { stageSuccessor } from "./vnext-contracts.js";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
+
import { assertDeliveredHitlReferences } from "./accepted-intent.js";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
@@ -220,6 +221,7 @@ export async function finishVnextProtocolize(context, input) {
|
|
|
220
221
|
try {
|
|
221
222
|
if (result.outcome !== "protocolized")
|
|
222
223
|
throw new AppError("validation", "PROTOCOLIZE may finish only as protocolized; use stage pause for every user question", 2);
|
|
224
|
+
assertDeliveredHitlReferences(context, { projectId: project.id, runId: run.id }, (result.obligation_amendments ?? []).map(amendment => amendment.hitl_ref));
|
|
223
225
|
obligations = effectiveObligations(result, acceptedObligations);
|
|
224
226
|
validateDelivery(projectRoot, result, obligations);
|
|
225
227
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
2
2
|
import { stageSuccessor } from "./vnext-contracts.js";
|
|
3
3
|
import crypto from "node:crypto";
|
|
4
|
+
import { retainAcceptedIntake } from "./accepted-intent.js";
|
|
4
5
|
import fs from "node:fs";
|
|
5
6
|
import path from "node:path";
|
|
6
7
|
import { spawnSync } from "node:child_process";
|
|
@@ -40,6 +41,8 @@ export function prepareVnextSpecifyStart(context, input) {
|
|
|
40
41
|
const discussion = intake.markdown.trim();
|
|
41
42
|
if (!discussion)
|
|
42
43
|
throw new AppError("validation", "SPECIFY intake must not be empty", 2);
|
|
44
|
+
if (Buffer.byteLength(intake.markdown) > 1024 * 1024)
|
|
45
|
+
throw new AppError("accepted_intent_source_invalid", "Accepted intake exceeds the bounded source contract", 2);
|
|
43
46
|
const flow = assertFlowDefinition(projectRoot);
|
|
44
47
|
const template = readRequiredFile(path.join(projectRoot, ".memory-bank", "dd-flow", "vnext", "specify.md"), "vNext SPECIFY prompt template");
|
|
45
48
|
const startInput = { projectRoot, flowKind: flow.id === protocolizeFlowId ? "vnext_protocolize" : "vnext_specify", subjectType: "discussion", subjectId: "SPECIFY", slug: input.slug ?? "specify", nextAction: "await_work_session" };
|
|
@@ -128,6 +131,7 @@ export function launchVnextSpecify(context, input, prepared = prepareVnextSpecif
|
|
|
128
131
|
status: "running",
|
|
129
132
|
dataSchemaId: "dd-flow/specify@1"
|
|
130
133
|
});
|
|
134
|
+
retainAcceptedIntake(context, { projectId: run.project_id, runId: run.id, file: discussionPath, bytes: Buffer.from(intake.markdown) });
|
|
131
135
|
refreshRunWorkProjection(context, run.project_id, run.id);
|
|
132
136
|
appendFlowRunTimelineEvent(context, run.project_id, run.id, {
|
|
133
137
|
type: "work_waiting_for_agent",
|
|
@@ -39,6 +39,7 @@ import { lifecycleRetryInstruction } from "../harness-runtime/lib/delegation-ins
|
|
|
39
39
|
import { acceptedDecisionPrecedence, loadExternalStageContext, renderRetainedStageContext, renderRunOperationalContext, runContextRelocations } from "./stage-context.js";
|
|
40
40
|
import { reviewEnvironmentAllowed, reviewExecutionCopyHash, reviewSourceHash, reviewSourcePaths } from "./review-copy.js";
|
|
41
41
|
import { latestRunVerificationMatrix, verificationMatrixInstruction, verificationMatrixContract } from "./verification-matrix.js";
|
|
42
|
+
import { acceptedIntentSourceRole } from "./accepted-intent.js";
|
|
42
43
|
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";
|
|
43
44
|
export function ensureWorkRegistry(context) { context.db.get("SELECT 1 FROM works LIMIT 1"); context.db.get("SELECT 1 FROM work_sessions LIMIT 1"); }
|
|
44
45
|
function ownsPhysicalWorkSession(context, projectId, owner, identity) {
|
|
@@ -1418,7 +1419,7 @@ function assertSharedReviewInput(context, work, run, generation = recoveryGuard(
|
|
|
1418
1419
|
if (matrix) {
|
|
1419
1420
|
add("run", matrix.metadata.json.path, matrix.metadata.json.sha256);
|
|
1420
1421
|
add("run", matrix.metadata.markdown.path, matrix.metadata.markdown.sha256);
|
|
1421
|
-
for (const source of matrix.matrix.sources.filter(source => ["specify", "protocolize", "protocolize_report"].includes(source.role)))
|
|
1422
|
+
for (const source of matrix.matrix.sources.filter(source => ["specify", "protocolize", "protocolize_report"].includes(source.role) || acceptedIntentSourceRole(source.role)))
|
|
1422
1423
|
add(source.root, source.path, source.sha256);
|
|
1423
1424
|
}
|
|
1424
1425
|
for (const item of value.reviewer_inputs ?? [])
|
|
@@ -1823,7 +1824,8 @@ function renderWorkerPrompt(context, work, run, dependencies, commands, workspac
|
|
|
1823
1824
|
const retainedContext = contextFile ? renderRetainedStageContext({ loaded: loadExternalStageContext({ file: contextFile, expectedSha256: contextInput.sha256, stage: contextStage }), receipts: relocationReceipts, ...(workspaceView ? { workspaceView } : {}) }) : "";
|
|
1824
1825
|
const body = `${operational}\n\n${retainedContext}${renderWorkerPromptBody(context, work, run, dependencies, commands)}`;
|
|
1825
1826
|
const matrixStage = kind === "plan-review" || kind === "code-review" ? kind : codePacket(work) ? "code" : null;
|
|
1826
|
-
const
|
|
1827
|
+
const reviewRepairMatrix = codePacket(work)?.repair?.review_findings?.length ? latestRunVerificationMatrix(context, work.project_id, work.run_id, "code-review", "output") : null;
|
|
1828
|
+
const matrix = reviewRepairMatrix ?? (matrixStage ? latestRunVerificationMatrix(context, work.project_id, work.run_id, matrixStage, kind === "plan-review" || kind === "code-review" ? "input" : undefined) : null);
|
|
1827
1829
|
const sources = matrix?.matrix.sources.filter(source => ["specify", "protocolize", "protocolize_report"].includes(source.role)) ?? [];
|
|
1828
1830
|
return ["<accepted_decision_precedence>", `Read ${path.join(requireRunHome(run), "01-specify", "specify.json")} and later accepted RUN decisions relevant to this Work.`, ...sources.map(source => `Accepted ${source.role}: ${path.join(source.root === "run" ? requireRunHome(run) : run.workspace_root, source.path)} (sha256 ${source.sha256}). PROTOCOLIZE includes the effective amendments; preserve SPECIFY non-goals.`), acceptedDecisionPrecedence, "</accepted_decision_precedence>", "", body, verificationMatrixInstruction(matrix?.metadata ?? null, requireRunHome(run), `assigned Work ${work.work_id}`)].join("\n");
|
|
1829
1831
|
}
|
package/dist/storage/database.js
CHANGED
|
@@ -1683,6 +1683,10 @@ function migrate(db) {
|
|
|
1683
1683
|
ensureColumn(db, "check_receipts", "after_fingerprint", "ALTER TABLE check_receipts ADD COLUMN after_fingerprint TEXT NOT NULL DEFAULT ''");
|
|
1684
1684
|
ensureColumn(db, "check_receipts", "profile_hash", "ALTER TABLE check_receipts ADD COLUMN profile_hash TEXT");
|
|
1685
1685
|
ensureColumn(db, "check_receipts", "mutation_paths_json", "ALTER TABLE check_receipts ADD COLUMN mutation_paths_json TEXT NOT NULL DEFAULT '[]'");
|
|
1686
|
+
ensureColumn(db, "check_receipts", "fingerprint_scope", "ALTER TABLE check_receipts ADD COLUMN fingerprint_scope TEXT");
|
|
1687
|
+
ensureColumn(db, "check_receipts", "input_manifest_json", "ALTER TABLE check_receipts ADD COLUMN input_manifest_json TEXT");
|
|
1688
|
+
ensureColumn(db, "check_receipts", "execution_failures_json", "ALTER TABLE check_receipts ADD COLUMN execution_failures_json TEXT");
|
|
1689
|
+
ensureColumn(db, "check_receipts", "execution_contract_hash", "ALTER TABLE check_receipts ADD COLUMN execution_contract_hash TEXT");
|
|
1686
1690
|
ensureColumn(db, "hook_events", "event_key", "ALTER TABLE hook_events ADD COLUMN event_key TEXT");
|
|
1687
1691
|
ensureColumn(db, "hook_events", "match_key", "ALTER TABLE hook_events ADD COLUMN match_key TEXT");
|
|
1688
1692
|
ensureColumn(db, "hook_events", "claimed_at", "ALTER TABLE hook_events ADD COLUMN claimed_at TEXT");
|
package/package.json
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* global console */
|
|
2
|
+
// Explicit non-generative contract probe. Never uses the operator's Codex home.
|
|
3
|
+
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { CodexBridge, codexReadPhase, readCodexHistory, readCodexRpc } from "../dist/harness-runtime/lib/dd-codex.mjs";
|
|
8
|
+
import { errorRecord } from "../dist/harness-runtime/lib/operation-errors.mjs";
|
|
9
|
+
|
|
10
|
+
const bin = process.argv[2];
|
|
11
|
+
if (!bin || !path.isAbsolute(bin)) throw new Error("Usage: node tools/probe-codex-history.mjs /absolute/native/codex");
|
|
12
|
+
const home = await mkdtemp(path.join(os.tmpdir(), "dd-codex-history-"));
|
|
13
|
+
await writeFile(path.join(home, "config.toml"), "[features]\nplugins = false\nremote_models = false\n");
|
|
14
|
+
const bridge = new CodexBridge({ bin, cwd: home, journal: path.join(home, "native.jsonl"),
|
|
15
|
+
env: { ...Object.fromEntries(Object.keys(process.env).map(key => [key, undefined])),
|
|
16
|
+
PATH: process.env.PATH, HOME: home, CODEX_HOME: home, CODEX_SQLITE_HOME: home, CODEX_LOG_DIR: home } });
|
|
17
|
+
const report = { home, non_generative: true, reads: [] };
|
|
18
|
+
try {
|
|
19
|
+
await bridge.start();
|
|
20
|
+
const created = await bridge.request("thread/start", { cwd: home, ephemeral: false, approvalPolicy: "never", sandbox: "read-only" });
|
|
21
|
+
report.session_id = created.thread.id;
|
|
22
|
+
report.history_mode = created.thread.historyMode;
|
|
23
|
+
const phase = codexReadPhase(bridge);
|
|
24
|
+
const compact = await readCodexRpc(bridge, "thread/read", { threadId: report.session_id, includeTurns: false }, phase);
|
|
25
|
+
report.compact = { id: compact.thread.id, status: compact.thread.status };
|
|
26
|
+
for (const method of ["thread/turns/list", "thread/read"]) {
|
|
27
|
+
const read = await readCodexHistory(bridge, method, { threadId: report.session_id,
|
|
28
|
+
...(method === "thread/read" ? { includeTurns: true } : { limit: 20, itemsView: "summary" }) }, phase);
|
|
29
|
+
report.reads.push({ method, available: read.available, ...(read.error ? { error: read.error } : {}) });
|
|
30
|
+
}
|
|
31
|
+
} catch (error) { report.error = errorRecord(error); process.exitCode = 1; }
|
|
32
|
+
finally {
|
|
33
|
+
try { await bridge.close(); }
|
|
34
|
+
catch (error) { report.cleanup_error = errorRecord(error); process.exitCode = 1; }
|
|
35
|
+
await writeFile(path.join(home, "result.json"), JSON.stringify(report, null, 2) + "\n");
|
|
36
|
+
}
|
|
37
|
+
console.log(JSON.stringify(report, null, 2));
|