@deksden-com/dd-flow-cli 0.9.0-beta.136 → 0.9.0-beta.138
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 +12 -0
- package/README.md +9 -0
- package/dist/build-info.json +6 -6
- package/dist/cli/argument-values.js +5 -5
- package/dist/cli/command-inputs.js +89 -20
- package/dist/cli/command-metadata.js +56 -3
- package/dist/cli/command-option-descriptions.js +5 -0
- package/dist/cli/executor-onboarding.js +8 -3
- package/dist/cli/flows.js +29 -0
- package/dist/cli/help.js +240 -6
- package/dist/cli/input-preparation.js +54 -5
- package/dist/cli/memory-flow-admission.js +44 -0
- package/dist/cli/memory-init-invocation.js +78 -0
- package/dist/cli/memory-init.js +240 -0
- package/dist/cli/memory-storage-admission.js +53 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/response-file.js +2 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/setup.js +9 -7
- package/dist/cli.js +54 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- package/dist/harness-runtime/DD-ZCODE.md +13 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -3
- package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +4 -3
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +11 -5
- package/dist/harness-runtime/lib/dd-codex.mjs +22 -10
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +2 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +20 -5
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +45 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +7 -3
- package/dist/harness-runtime/lib/delegation-instructions.mjs +5 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +1 -0
- package/dist/harness-runtime/lib/managed-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +50 -15
- package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +1 -1
- package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
- package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
- package/dist/harness-runtime/lib/opencode-plugin.mjs +5 -1
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/runtime/context.js +1 -1
- package/dist/schemas/discovery-result.schema.json +184 -0
- package/dist/schemas/execution-bundle.schema.json +205 -0
- package/dist/schemas/execution-settings.schema.json +162 -0
- package/dist/schemas/flow-descriptor.schema.json +54 -0
- package/dist/schemas/flow-run.schema.json +22 -0
- package/dist/schemas/harness-catalog.schema.json +80 -0
- package/dist/schemas/mb-init-compose-result.schema.json +457 -0
- package/dist/schemas/mb-init-discover-result.schema.json +500 -0
- package/dist/schemas/mb-init-finish-result.schema.json +422 -0
- package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
- package/dist/schemas/mb-init-review-result.schema.json +396 -0
- package/dist/schemas/mb-init-verify-result.schema.json +391 -0
- package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
- package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
- package/dist/schemas/memory-composition-work-result.schema.json +38 -0
- package/dist/schemas/memory-repair-work-result.schema.json +49 -0
- package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
- package/dist/schemas/memory-review-work-result.schema.json +43 -0
- package/dist/schemas/model-catalog.schema.json +153 -0
- package/dist/schemas/profile-settings.schema.json +122 -0
- package/dist/schemas/release-manifest.schema.json +291 -0
- package/dist/schemas/run-engine-binding.schema.json +1 -1
- package/dist/services/check-runner-evidence.js +51 -4
- package/dist/services/cli-operation-classifier.js +8 -47
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-observer.js +99 -0
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +79 -13
- package/dist/services/dashboard.js +8 -2
- package/dist/services/descriptor-coordinator-session.js +35 -0
- package/dist/services/descriptor-root-work.js +53 -0
- package/dist/services/descriptor-run-settlement.js +105 -0
- package/dist/services/descriptor-run-start.js +449 -0
- package/dist/services/descriptor-stage-coordinator.js +197 -0
- package/dist/services/descriptor-stage-settlement.js +222 -0
- package/dist/services/descriptor-stage-start.js +139 -0
- package/dist/services/engines.js +39 -22
- package/dist/services/eval-snapshots.js +83 -25
- package/dist/services/execution-bundles.js +263 -0
- package/dist/services/execution-policy.js +20 -16
- package/dist/services/external-work-launch.js +91 -11
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +242 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +31 -3
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +7 -4
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/memory-accepted-composition.js +77 -0
- package/dist/services/memory-baseline-preservation.js +65 -0
- package/dist/services/memory-completed-correction-wave.js +320 -0
- package/dist/services/memory-compose-handler.js +258 -0
- package/dist/services/memory-composition-acceptance.js +199 -0
- package/dist/services/memory-composition-graph.js +134 -0
- package/dist/services/memory-composition-registration.js +146 -0
- package/dist/services/memory-composition-work-custody.js +151 -0
- package/dist/services/memory-composition-work-results.js +96 -0
- package/dist/services/memory-corrected-review-acceptance.js +129 -0
- package/dist/services/memory-corrected-review-batch.js +30 -0
- package/dist/services/memory-corrected-review-findings.js +106 -0
- package/dist/services/memory-corrected-review-inputs.js +210 -0
- package/dist/services/memory-corrected-review-projection.js +78 -0
- package/dist/services/memory-corrected-review-receipt.js +170 -0
- package/dist/services/memory-corrected-review-registration.js +191 -0
- package/dist/services/memory-correction-check-receipt.js +219 -0
- package/dist/services/memory-correction-input-owner.js +6 -0
- package/dist/services/memory-discover-handler.js +104 -0
- package/dist/services/memory-finish-evidence.js +435 -0
- package/dist/services/memory-finish-handler.js +215 -0
- package/dist/services/memory-finish-integration.js +280 -0
- package/dist/services/memory-finish-readiness.js +122 -0
- package/dist/services/memory-git-integration.js +151 -0
- package/dist/services/memory-historical-corrected-review.js +206 -0
- package/dist/services/memory-historical-original-review.js +130 -0
- package/dist/services/memory-historical-repair-registration.js +99 -0
- package/dist/services/memory-historical-second-review.js +128 -0
- package/dist/services/memory-init-bootstrap-readiness.js +210 -0
- package/dist/services/memory-init-contexts.js +89 -0
- package/dist/services/memory-init-controller-start.js +84 -0
- package/dist/services/memory-init-engine-release.js +223 -0
- package/dist/services/memory-init-git-policy.js +136 -0
- package/dist/services/memory-init-lint-capability.js +185 -0
- package/dist/services/memory-init-native-capacity.js +241 -0
- package/dist/services/memory-init-preflight.js +312 -0
- package/dist/services/memory-init-project-bootstrap.js +599 -0
- package/dist/services/memory-init-request-candidate.js +157 -0
- package/dist/services/memory-init-request-input.js +66 -0
- package/dist/services/memory-init-resource-storage.js +125 -0
- package/dist/services/memory-init-run-allocation.js +81 -0
- package/dist/services/memory-init-run-bundle.js +98 -0
- package/dist/services/memory-init-start.js +175 -0
- package/dist/services/memory-init-startup-custody.js +80 -0
- package/dist/services/memory-init-startup-input.js +240 -0
- package/dist/services/memory-init-storage.js +81 -0
- package/dist/services/memory-init-stores.js +359 -0
- package/dist/services/memory-init-worktrunk-capability.js +172 -0
- package/dist/services/memory-layer-publication.js +611 -0
- package/dist/services/memory-layer.js +156 -0
- package/dist/services/memory-lint-config.js +85 -0
- package/dist/services/memory-native-review-copy.js +165 -0
- package/dist/services/memory-normal-flow-entry.js +75 -0
- package/dist/services/memory-ordinary-defaults.js +112 -0
- package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
- package/dist/services/memory-preflight-handler.js +259 -0
- package/dist/services/memory-repair-current-view.js +299 -0
- package/dist/services/memory-repair-document-delta.js +451 -0
- package/dist/services/memory-repair-history.js +491 -0
- package/dist/services/memory-repair-physical-binding.js +312 -0
- package/dist/services/memory-repair-publication-files.js +152 -0
- package/dist/services/memory-repair-publication-intent.js +27 -0
- package/dist/services/memory-repair-publication-plan.js +160 -0
- package/dist/services/memory-repair-publication-source.js +225 -0
- package/dist/services/memory-repair-publication.js +794 -0
- package/dist/services/memory-repair-registration.js +239 -0
- package/dist/services/memory-repair-work-baseline.js +413 -0
- package/dist/services/memory-repair-work-completion.js +97 -0
- package/dist/services/memory-repair-work-custody.js +73 -0
- package/dist/services/memory-repair-work-results.js +89 -0
- package/dist/services/memory-repair-workspace.js +252 -0
- package/dist/services/memory-repair-writer-copy.js +735 -0
- package/dist/services/memory-result-binding.js +173 -0
- package/dist/services/memory-retained-review-authority.js +129 -0
- package/dist/services/memory-retained-review-results.js +70 -0
- package/dist/services/memory-retained-second-baseline.js +102 -0
- package/dist/services/memory-retained-second-repairs.js +156 -0
- package/dist/services/memory-review-acceptance.js +214 -0
- package/dist/services/memory-review-correction-plan.js +171 -0
- package/dist/services/memory-review-handler.js +585 -0
- package/dist/services/memory-review-inputs.js +200 -0
- package/dist/services/memory-review-receipt.js +180 -0
- package/dist/services/memory-review-registration.js +108 -0
- package/dist/services/memory-review-work-custody.js +124 -0
- package/dist/services/memory-review-work-input.js +72 -0
- package/dist/services/memory-review-work-results.js +73 -0
- package/dist/services/memory-second-corrected-review-inputs.js +165 -0
- package/dist/services/memory-second-correction-plan.js +76 -0
- package/dist/services/memory-second-repair-baseline.js +91 -0
- package/dist/services/memory-second-repair-launch.js +614 -0
- package/dist/services/memory-second-repair-registration.js +88 -0
- package/dist/services/memory-second-repair-source.js +187 -0
- package/dist/services/memory-second-review-acceptance.js +115 -0
- package/dist/services/memory-source-observations.js +117 -0
- package/dist/services/memory-stage-dispatch.js +28 -0
- package/dist/services/memory-stage-results.js +199 -0
- package/dist/services/memory-verification-acceptance.js +135 -0
- package/dist/services/memory-verification-checks.js +252 -0
- package/dist/services/memory-verification-receipt.js +159 -0
- package/dist/services/memory-verify-handler.js +135 -0
- package/dist/services/memory-work-result-preparation.js +63 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/recovery-snapshot-database.js +416 -1
- package/dist/services/repair-intents.js +1 -1
- package/dist/services/review-copy.js +10 -2
- package/dist/services/run-control.js +80 -9
- package/dist/services/run-controller-capture.js +10 -3
- package/dist/services/run-controller-process.js +21 -0
- package/dist/services/run-controller-recovery.js +3 -1
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +184 -48
- package/dist/services/run-recovery-guard.js +2 -0
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +319 -25
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-lifecycle.js +2 -1
- package/dist/services/stage-report-renderer.js +76 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code.js +3 -3
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +136 -10
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +6 -4
- package/dist/services/vnext-protocolize.js +60 -2
- package/dist/services/work-registry.js +616 -71
- package/dist/services/workspace-files.js +1 -1
- package/dist/storage/database.js +84 -6
- package/dist/storage/writer-contract.js +11 -7
- package/package.json +13 -3
- package/tools/create-canon-release.mjs +33 -0
- package/tools/produce-hybrid-release.mjs +41 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { AppError } from "../shared/errors.js";
|
|
4
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
5
|
+
import { appendAudit } from "./audit.js";
|
|
6
|
+
import { PreparedMemoryInitTarget } from "./memory-init-preflight.js";
|
|
7
|
+
const event = "memory.init_git_policy", token = Symbol("memory-init-git-policy");
|
|
8
|
+
function refuse(reason) {
|
|
9
|
+
throw new AppError("memory_init_git_policy_invalid", "Init lacks explicit retained Git policy", 2, { reason, effect: "no_effect" });
|
|
10
|
+
}
|
|
11
|
+
function validScope(scope) {
|
|
12
|
+
if (!scope || typeof scope.requestId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(scope.requestId)
|
|
13
|
+
|| typeof scope.projectRoot !== "string" || !path.isAbsolute(scope.projectRoot)
|
|
14
|
+
|| typeof scope.workspaceRoot !== "string" || !path.isAbsolute(scope.workspaceRoot)
|
|
15
|
+
|| Object.keys(scope).sort().join() !== "projectRoot,requestId,workspaceRoot")
|
|
16
|
+
refuse("invalid_request_scope");
|
|
17
|
+
}
|
|
18
|
+
/** SQL preservation, not fresh policy inference, Git execution or permission. */
|
|
19
|
+
export function readMemoryInitGitPolicy(context, scope) {
|
|
20
|
+
validScope(scope);
|
|
21
|
+
const rows = context.db.all(`SELECT payload_json FROM audit_events WHERE event_type=?
|
|
22
|
+
AND json_extract(payload_json,'$.scope.requestId')=? AND json_extract(payload_json,'$.scope.projectRoot')=?
|
|
23
|
+
AND json_extract(payload_json,'$.scope.workspaceRoot')=? LIMIT 2`, [event, scope.requestId, scope.projectRoot, scope.workspaceRoot]);
|
|
24
|
+
if (!rows.length)
|
|
25
|
+
return null;
|
|
26
|
+
if (rows.length !== 1 || Buffer.byteLength(rows[0].payload_json) > 32 * 1024)
|
|
27
|
+
refuse("ambiguous_or_unbounded_policy");
|
|
28
|
+
let value;
|
|
29
|
+
try {
|
|
30
|
+
value = JSON.parse(rows[0].payload_json);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return refuse("invalid_policy_receipt");
|
|
34
|
+
}
|
|
35
|
+
if (!value || value.schema_id !== "dd-flow/memory-init-git-policy@1" || hybridDigest(value.scope) !== hybridDigest(scope)
|
|
36
|
+
|| Object.keys(value).sort().join() !== "initial_head,integration_branch,push_authorized,repository,route,schema_id,scope,source,target_sha256,workspace_branch"
|
|
37
|
+
|| value.source !== "explicit_request" || value.push_authorized !== false
|
|
38
|
+
|| !["integration_branch_direct", "feature_worktree"].includes(value.route)
|
|
39
|
+
|| typeof value.target_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(value.target_sha256)
|
|
40
|
+
|| typeof value.repository !== "string" || !path.isAbsolute(value.repository)
|
|
41
|
+
|| typeof value.workspace_branch !== "string" || !value.workspace_branch
|
|
42
|
+
|| typeof value.integration_branch !== "string" || !value.integration_branch
|
|
43
|
+
|| (value.initial_head !== null && (typeof value.initial_head !== "string" || !/^[a-f0-9]{40,64}$/.test(value.initial_head))))
|
|
44
|
+
refuse("policy_receipt_changed");
|
|
45
|
+
return structuredClone(value);
|
|
46
|
+
}
|
|
47
|
+
/** Explicit request choice frozen against genuine no-effect target preflight.
|
|
48
|
+
* Retaining this receipt must precede bootstrap publication. It grants no
|
|
49
|
+
* dispatch authority by itself or compatibility with a historical bootstrap.
|
|
50
|
+
* Memory finish integrates only after independently accepted current review;
|
|
51
|
+
* this receipt never authorizes push. */
|
|
52
|
+
export class PreparedMemoryInitGitPolicy {
|
|
53
|
+
#target;
|
|
54
|
+
#receipt;
|
|
55
|
+
constructor(key, target, receipt) {
|
|
56
|
+
if (key !== token)
|
|
57
|
+
refuse("policy_token_required");
|
|
58
|
+
this.#target = target;
|
|
59
|
+
this.#receipt = structuredClone(receipt);
|
|
60
|
+
Object.freeze(this);
|
|
61
|
+
}
|
|
62
|
+
static prepare(context, input) {
|
|
63
|
+
if (!(input.target instanceof PreparedMemoryInitTarget))
|
|
64
|
+
refuse("prepared_target_required");
|
|
65
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
66
|
+
refuse("preparation_outside_writer_required");
|
|
67
|
+
const prepared = this.prepareFresh(context.env, input);
|
|
68
|
+
const receipt = prepared.value(), retained = readMemoryInitGitPolicy(context, receipt.scope);
|
|
69
|
+
if (retained && hybridDigest(retained) !== hybridDigest(receipt))
|
|
70
|
+
refuse("policy_request_conflict");
|
|
71
|
+
return prepared;
|
|
72
|
+
}
|
|
73
|
+
/** Fresh explicit policy before opening runtime stores. This checks Git and
|
|
74
|
+
* scope, but grants no SQL custody, integration or provider authority. */
|
|
75
|
+
static prepareFresh(environment, input) {
|
|
76
|
+
if (!(input.target instanceof PreparedMemoryInitTarget))
|
|
77
|
+
refuse("prepared_target_required");
|
|
78
|
+
const selection = input.selection;
|
|
79
|
+
if (!selection || Object.keys(selection).sort().join() !== "integrationBranch,route"
|
|
80
|
+
|| !["integration_branch_direct", "feature_worktree"].includes(selection.route)
|
|
81
|
+
|| typeof selection.integrationBranch !== "string" || !selection.integrationBranch || selection.integrationBranch.startsWith("-") || selection.integrationBranch.length > 1024
|
|
82
|
+
|| [...selection.integrationBranch].some(character => character.charCodeAt(0) <= 32 || character.charCodeAt(0) === 127))
|
|
83
|
+
refuse("explicit_policy_required");
|
|
84
|
+
input.target.revalidate();
|
|
85
|
+
const facts = input.target.facts(), scope = { requestId: facts.request_id, projectRoot: facts.project_root, workspaceRoot: facts.workspace_root };
|
|
86
|
+
if (!facts.tool_probes)
|
|
87
|
+
refuse("fresh_target_required");
|
|
88
|
+
const env = Object.fromEntries(Object.entries(environment).filter(([key]) => !key.startsWith("GIT_")));
|
|
89
|
+
const result = spawnSync(facts.tools.git, ["check-ref-format", `refs/heads/${selection.integrationBranch}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
|
|
90
|
+
if (result.error || result.signal || result.status !== 0)
|
|
91
|
+
refuse("invalid_integration_branch");
|
|
92
|
+
if (selection.route === "integration_branch_direct"
|
|
93
|
+
? scope.projectRoot !== scope.workspaceRoot || facts.branch !== selection.integrationBranch
|
|
94
|
+
: scope.projectRoot === scope.workspaceRoot || facts.branch === selection.integrationBranch)
|
|
95
|
+
refuse("route_workspace_mismatch");
|
|
96
|
+
if (selection.route === "feature_worktree") {
|
|
97
|
+
const branch = spawnSync(facts.tools.git, ["-C", scope.projectRoot, "rev-parse", "--verify", `refs/heads/${selection.integrationBranch}^{commit}`], { env: { ...env, GIT_OPTIONAL_LOCKS: "0" }, encoding: "utf8", timeout: 5000, maxBuffer: 64 * 1024, shell: false, windowsHide: true });
|
|
98
|
+
if (branch.error || branch.signal || branch.status !== 0 || !/^[a-f0-9]{40,64}$/.test(branch.stdout.trim()))
|
|
99
|
+
refuse("integration_branch_unavailable");
|
|
100
|
+
}
|
|
101
|
+
const receipt = { schema_id: "dd-flow/memory-init-git-policy@1", scope, target_sha256: input.target.sha256,
|
|
102
|
+
repository: facts.repository, workspace_branch: facts.branch, initial_head: facts.head, source: "explicit_request",
|
|
103
|
+
route: selection.route, integration_branch: selection.integrationBranch, push_authorized: false };
|
|
104
|
+
return new PreparedMemoryInitGitPolicy(token, input.target, receipt);
|
|
105
|
+
}
|
|
106
|
+
value() { return structuredClone(this.#receipt); }
|
|
107
|
+
retain(context) {
|
|
108
|
+
if (context.db.isOwnedWriteTransaction?.())
|
|
109
|
+
refuse("preparation_outside_writer_required");
|
|
110
|
+
this.#target.revalidate();
|
|
111
|
+
return context.db.writeTransaction(() => this.retainPreparedSql(context), "memoryInit.retainGitPolicy");
|
|
112
|
+
}
|
|
113
|
+
/** Metadata-only publication for an already prepared aggregate owner. */
|
|
114
|
+
retainPreparedSql(context) {
|
|
115
|
+
if (!context.db.isOwnedWriteTransaction?.())
|
|
116
|
+
refuse("owned_policy_writer_required");
|
|
117
|
+
const receipt = readMemoryInitGitPolicy(context, this.#receipt.scope);
|
|
118
|
+
if (receipt) {
|
|
119
|
+
if (hybridDigest(receipt) !== hybridDigest(this.#receipt))
|
|
120
|
+
refuse("policy_request_conflict");
|
|
121
|
+
return receipt;
|
|
122
|
+
}
|
|
123
|
+
const scope = this.#receipt.scope;
|
|
124
|
+
if (context.db.get(`SELECT 1 FROM audit_events WHERE event_type IN ('memory.init_project_bootstrap_intent','memory.init_project_bootstrap')
|
|
125
|
+
AND json_extract(payload_json,'$.scope.requestId')=? AND json_extract(payload_json,'$.scope.projectRoot')=?
|
|
126
|
+
AND json_extract(payload_json,'$.scope.workspaceRoot')=? LIMIT 1`, [scope.requestId, scope.projectRoot, scope.workspaceRoot]))
|
|
127
|
+
refuse("policy_must_precede_bootstrap");
|
|
128
|
+
appendAudit(context, { eventType: event, payload: this.#receipt });
|
|
129
|
+
this.assertCustody(context);
|
|
130
|
+
return this.value();
|
|
131
|
+
}
|
|
132
|
+
assertCustody(context) {
|
|
133
|
+
if (hybridDigest(readMemoryInitGitPolicy(context, this.#receipt.scope)) !== hybridDigest(this.#receipt))
|
|
134
|
+
refuse("policy_receipt_changed");
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { AppError } from "../shared/errors.js";
|
|
7
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
8
|
+
import { PreparedMemoryInitTarget } from "./memory-init-preflight.js";
|
|
9
|
+
import { readBoundedReleaseFile } from "./release-source.js";
|
|
10
|
+
function refuse(reason) {
|
|
11
|
+
throw new AppError("memory_init_lint_capability_failed", "Selected mb-lint cannot validate Memory Bank documents", 1, { reason, effect: "no_effect", next_action: "repair_prerequisite_then_retry" });
|
|
12
|
+
}
|
|
13
|
+
const hash = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
14
|
+
const logical = ".memory-bank/index.md";
|
|
15
|
+
const validDocument = `---
|
|
16
|
+
file: '.memory-bank/index.md'
|
|
17
|
+
description: 'Isolated deterministic capability probe.'
|
|
18
|
+
purpose: 'Validate the selected linter without reading project documents.'
|
|
19
|
+
version: '0.1.0'
|
|
20
|
+
date: '2026-01-01'
|
|
21
|
+
status: 'DRAFT'
|
|
22
|
+
c4_level: 'documentation'
|
|
23
|
+
parent: null
|
|
24
|
+
tags: [capability-probe]
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Isolated capability probe
|
|
28
|
+
`;
|
|
29
|
+
/** Shape validation for historical SQL custody, never executable qualification. */
|
|
30
|
+
export function isMemoryInitLintCapabilityFacts(value) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
32
|
+
return false;
|
|
33
|
+
const facts = value, hashes = ["executable_sha256", "valid_document_sha256", "invalid_document_sha256",
|
|
34
|
+
"positive_report_sha256", "negative_report_sha256", "required_fields_report_sha256", "missing_link_report_sha256"];
|
|
35
|
+
return facts.schema_id === "dd-flow/memory-init-lint-capability@1" && typeof facts.executable === "string" && path.isAbsolute(facts.executable)
|
|
36
|
+
&& typeof facts.version === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(facts.version)
|
|
37
|
+
&& Object.keys(facts).length === hashes.length + 3
|
|
38
|
+
&& hashes.every(key => typeof facts[key] === "string" && /^[a-f0-9]{64}$/.test(facts[key]));
|
|
39
|
+
}
|
|
40
|
+
/** Tests deterministic JSON lint behavior, not semantic grounding or provider readiness.
|
|
41
|
+
* CLI writes only its own temporary files containing fixed probe data.
|
|
42
|
+
*/
|
|
43
|
+
export class PreparedMemoryInitLintCapability {
|
|
44
|
+
sha256;
|
|
45
|
+
#facts;
|
|
46
|
+
constructor(facts) {
|
|
47
|
+
this.#facts = facts;
|
|
48
|
+
this.sha256 = hybridDigest(facts);
|
|
49
|
+
Object.freeze(this);
|
|
50
|
+
}
|
|
51
|
+
static prepare(target, env) {
|
|
52
|
+
if (!(target instanceof PreparedMemoryInitTarget))
|
|
53
|
+
refuse("unprepared_target");
|
|
54
|
+
const selected = target.facts(), executable = selected.tools["mb-lint"], probe = selected.tool_probes?.["mb-lint"];
|
|
55
|
+
if (!probe)
|
|
56
|
+
refuse("missing_selected_tool_probe");
|
|
57
|
+
const checksum = () => {
|
|
58
|
+
try {
|
|
59
|
+
return hash(readBoundedReleaseFile(executable, 64 * 1024 * 1024));
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return refuse("selected_executable_unavailable");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
if (checksum() !== probe.executable_sha256)
|
|
66
|
+
refuse("selected_executable_changed");
|
|
67
|
+
let scratch, rootOwner, primary, prepared;
|
|
68
|
+
try {
|
|
69
|
+
scratch = fs.mkdtempSync(path.join(os.tmpdir(), "dd-flow-init-lint-"));
|
|
70
|
+
rootOwner = fs.lstatSync(scratch);
|
|
71
|
+
const bank = path.join(scratch, ".memory-bank");
|
|
72
|
+
fs.mkdirSync(bank);
|
|
73
|
+
const bankOwner = fs.lstatSync(bank);
|
|
74
|
+
const file = path.join(scratch, logical);
|
|
75
|
+
fs.writeFileSync(file, validDocument, { flag: "wx", mode: 0o600 });
|
|
76
|
+
const fileOwner = fs.lstatSync(file);
|
|
77
|
+
let expectedDocument = validDocument;
|
|
78
|
+
const same = (actual, selected) => actual.dev === selected.dev && actual.ino === selected.ino;
|
|
79
|
+
const ownDirectories = () => {
|
|
80
|
+
const root = fs.lstatSync(scratch), directory = fs.lstatSync(bank);
|
|
81
|
+
if (!root.isDirectory() || !directory.isDirectory() || !same(root, rootOwner) || !same(directory, bankOwner))
|
|
82
|
+
refuse("probe_directory_owner_changed");
|
|
83
|
+
};
|
|
84
|
+
const writeProbe = (document) => {
|
|
85
|
+
ownDirectories();
|
|
86
|
+
const fd = fs.openSync(file, fs.constants.O_WRONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
87
|
+
try {
|
|
88
|
+
const actual = fs.fstatSync(fd);
|
|
89
|
+
if (!actual.isFile() || actual.nlink !== 1 || !same(actual, fileOwner))
|
|
90
|
+
refuse("probe_file_owner_changed");
|
|
91
|
+
fs.ftruncateSync(fd, 0);
|
|
92
|
+
fs.writeFileSync(fd, document);
|
|
93
|
+
expectedDocument = document;
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
fs.closeSync(fd);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const probeEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("GIT_")));
|
|
100
|
+
const run = (status, rule) => {
|
|
101
|
+
ownDirectories();
|
|
102
|
+
const result = spawnSync(executable, ["--root", scratch, "--files", logical, "--format", "json"], { cwd: scratch, env: { ...probeEnv, GIT_OPTIONAL_LOCKS: "0" }, shell: false, windowsHide: true, timeout: 5000, maxBuffer: 256 * 1024 });
|
|
103
|
+
if (result.error || result.signal || result.status !== status)
|
|
104
|
+
refuse("lint_probe_execution_failed");
|
|
105
|
+
ownDirectories();
|
|
106
|
+
const actual = fs.lstatSync(file);
|
|
107
|
+
if (!actual.isFile() || actual.nlink !== 1 || !same(actual, fileOwner)
|
|
108
|
+
|| hash(readBoundedReleaseFile(file, 64 * 1024)) !== hash(Buffer.from(expectedDocument)))
|
|
109
|
+
refuse("probe_input_changed");
|
|
110
|
+
let report;
|
|
111
|
+
try {
|
|
112
|
+
report = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(result.stdout));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return refuse("lint_probe_invalid_json");
|
|
116
|
+
}
|
|
117
|
+
if (!report || report.tool !== "mb-lint" || report.version !== probe.version || report.root !== "."
|
|
118
|
+
|| report.memoryBank !== ".memory-bank" || !Array.isArray(report.findings)
|
|
119
|
+
|| !report.summary || typeof report.summary !== "object")
|
|
120
|
+
refuse("lint_probe_invalid_report");
|
|
121
|
+
const summary = report.summary;
|
|
122
|
+
if (Object.keys(summary).length !== 4 || ["errors", "warnings", "info", "suggestions"].some(key => !Number.isSafeInteger(summary[key]) || Number(summary[key]) < 0)
|
|
123
|
+
|| summary.errors !== report.findings.filter(value => value?.severity === "error").length)
|
|
124
|
+
refuse("lint_probe_invalid_summary");
|
|
125
|
+
if (status === 0 && (Object.values(summary).some(value => value !== 0) || report.findings.length !== 0))
|
|
126
|
+
refuse("valid_document_rejected");
|
|
127
|
+
if (status === 1 && (!(Number(summary.errors) > 0) || !report.findings.some(value => value && typeof value === "object"
|
|
128
|
+
&& value.ruleId === rule && value.severity === "error" && value.file === logical)))
|
|
129
|
+
refuse(`lint_rule_not_detected:${rule}`);
|
|
130
|
+
return hybridDigest(report);
|
|
131
|
+
};
|
|
132
|
+
const positive = run(0), invalid = "---\nfile: [\n---\n\n# Invalid YAML probe\n";
|
|
133
|
+
writeProbe(invalid);
|
|
134
|
+
const negative = run(1, "frontmatter/parseable");
|
|
135
|
+
writeProbe("---\nfile: '.memory-bank/index.md'\n---\n\n# Missing frontmatter fields\n");
|
|
136
|
+
const fields = run(1, "frontmatter/required-fields");
|
|
137
|
+
writeProbe(`${validDocument}\n[Missing target](missing-probe-target.md)\n`);
|
|
138
|
+
const links = run(1, "links/markdown-target-exists");
|
|
139
|
+
if (checksum() !== probe.executable_sha256)
|
|
140
|
+
refuse("selected_executable_changed");
|
|
141
|
+
prepared = new PreparedMemoryInitLintCapability({ schema_id: "dd-flow/memory-init-lint-capability@1",
|
|
142
|
+
executable, executable_sha256: probe.executable_sha256, version: probe.version,
|
|
143
|
+
valid_document_sha256: hash(Buffer.from(validDocument)), invalid_document_sha256: hash(Buffer.from(invalid)),
|
|
144
|
+
positive_report_sha256: positive, negative_report_sha256: negative,
|
|
145
|
+
required_fields_report_sha256: fields, missing_link_report_sha256: links });
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
primary = error instanceof AppError ? error : new AppError("memory_init_lint_capability_failed", "Lint probe observation failed", 1, { reason: "lint_probe_observation_failed", effect: "no_effect", next_action: "repair_prerequisite_then_retry" });
|
|
149
|
+
}
|
|
150
|
+
if (scratch)
|
|
151
|
+
try {
|
|
152
|
+
const actual = fs.lstatSync(scratch);
|
|
153
|
+
if (!rootOwner || !actual.isDirectory() || actual.dev !== rootOwner.dev || actual.ino !== rootOwner.ino)
|
|
154
|
+
refuse("probe_root_owner_changed");
|
|
155
|
+
fs.rmSync(scratch, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
throw new AppError("memory_init_lint_probe_cleanup_failed", "Isolated lint probe requires cleanup", 1, { reason: primary instanceof AppError ? primary.details.reason : primary ? "probe_failed" : "cleanup_failed",
|
|
159
|
+
effect: "partial", owned_probe_path: scratch, next_action: "reconcile_owned_probe" });
|
|
160
|
+
}
|
|
161
|
+
if (primary)
|
|
162
|
+
throw primary;
|
|
163
|
+
return prepared;
|
|
164
|
+
}
|
|
165
|
+
facts() { return { ...this.#facts }; }
|
|
166
|
+
revalidate(target, _env) {
|
|
167
|
+
if (!(target instanceof PreparedMemoryInitTarget))
|
|
168
|
+
refuse("unprepared_target");
|
|
169
|
+
const selected = target.facts(), probe = selected.tool_probes?.["mb-lint"];
|
|
170
|
+
if (!probe || selected.tools["mb-lint"] !== this.#facts.executable
|
|
171
|
+
|| probe.version !== this.#facts.version || probe.executable_sha256 !== this.#facts.executable_sha256)
|
|
172
|
+
refuse("lint_capability_changed");
|
|
173
|
+
// This instance already passed the functional probes. Publication checks
|
|
174
|
+
// preserve its selected executable, not repeat qualification after effects.
|
|
175
|
+
let checksum;
|
|
176
|
+
try {
|
|
177
|
+
checksum = hash(readBoundedReleaseFile(this.#facts.executable, 64 * 1024 * 1024));
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return refuse("selected_executable_unavailable");
|
|
181
|
+
}
|
|
182
|
+
if (checksum !== this.#facts.executable_sha256)
|
|
183
|
+
refuse("selected_executable_changed");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { AppError, errorRecord } from "../shared/errors.js";
|
|
6
|
+
import { hybridDigest } from "./hybrid-contracts.js";
|
|
7
|
+
import { resolveHarnessCommand, harnessRuntimeArguments } from "./harness-config.js";
|
|
8
|
+
import { runHarnessAdapter, adapterSessionId } from "./harness-adapter.js";
|
|
9
|
+
import { renderCapacityInstructions } from "../harness-runtime/lib/delegation-instructions.mjs";
|
|
10
|
+
import { normalizeNativeChildren } from "../harness-runtime/lib/native-children.mjs";
|
|
11
|
+
import { readBoundedReleaseFile } from "./release-source.js";
|
|
12
|
+
import { acquireInstallationLock } from "./installation-lock.js";
|
|
13
|
+
import { inspectDaemonOperation } from "../harness-runtime/lib/daemon-operations.mjs";
|
|
14
|
+
import { nativeCommandFingerprint } from "../harness-runtime/lib/native-request-proof.mjs";
|
|
15
|
+
const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
16
|
+
function refuse(reason, effect = "no_effect") {
|
|
17
|
+
throw new AppError("memory_init_native_capacity_required", "Native startup requires its exact qualified probe", 1, { reason, effect, next_action: "reconcile_exact_native_probe" });
|
|
18
|
+
}
|
|
19
|
+
function read(file) {
|
|
20
|
+
try {
|
|
21
|
+
const stat = fs.lstatSync(file);
|
|
22
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
23
|
+
refuse("linked_probe_receipt", "unknown");
|
|
24
|
+
return JSON.parse(readBoundedReleaseFile(file, 1024 * 1024).toString("utf8"));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error.code === "ENOENT")
|
|
28
|
+
return null;
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function retain(file, value) {
|
|
33
|
+
fs.writeFileSync(file, JSON.stringify(value), { flag: "wx", mode: 0o600 });
|
|
34
|
+
}
|
|
35
|
+
function publicReply(result) {
|
|
36
|
+
const reply = { ok: true, ...result };
|
|
37
|
+
// The daemon retains this transport-only shutdown marker, but its public
|
|
38
|
+
// response deliberately omits it (dd-codex-daemon response projection).
|
|
39
|
+
delete reply._shutdown;
|
|
40
|
+
return reply;
|
|
41
|
+
}
|
|
42
|
+
/** Technical child probe: no RUN, project registration, lifecycle hooks or
|
|
43
|
+
* operator profile writes. An unresolved intent never authorizes redispatch. */
|
|
44
|
+
export async function produceMemoryInitNativeCapacity(input) {
|
|
45
|
+
if (!input.allowLiveProbe || !Number.isInteger(input.maximum) || input.maximum < 1 || input.maximum > 15
|
|
46
|
+
|| !/^[a-f0-9]{64}$/.test(input.requestSha256)
|
|
47
|
+
|| input.nativeConfigHome !== undefined && !path.isAbsolute(input.nativeConfigHome))
|
|
48
|
+
refuse("explicit_probe_authorization_required");
|
|
49
|
+
const nativeConfigHome = input.nativeConfigHome ?? input.env.CODEX_HOME ?? path.join(input.env.HOME ?? os.homedir(), ".codex");
|
|
50
|
+
const executable = resolveHarnessCommand(input.home, input.profile.harness, "adapter");
|
|
51
|
+
const runtime = resolveHarnessCommand(input.home, input.profile.harness, "runtime");
|
|
52
|
+
const identity = { schema_id: "dd-flow/memory-native-probe-input@1", scope: input.scope,
|
|
53
|
+
profile: input.profile, maximum: input.maximum, native_config_home: nativeConfigHome,
|
|
54
|
+
request_sha256: input.requestSha256, adapter: { path: executable, sha256: sha(readBoundedReleaseFile(executable, 16 * 1024 * 1024)) },
|
|
55
|
+
runtime: { path: runtime, sha256: sha(readBoundedReleaseFile(runtime, 256 * 1024 * 1024)) } };
|
|
56
|
+
const root = path.join(input.home, "memory-init-native-probes", hybridDigest(input.scope));
|
|
57
|
+
fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
58
|
+
if (fs.realpathSync(root) !== root)
|
|
59
|
+
refuse("linked_probe_root", "unknown");
|
|
60
|
+
const lease = acquireInstallationLock(path.join(root, "probe.lock"), hybridDigest(identity));
|
|
61
|
+
try {
|
|
62
|
+
const prior = read(path.join(root, "input.json"));
|
|
63
|
+
if (prior && hybridDigest(prior) !== hybridDigest(identity))
|
|
64
|
+
refuse("probe_selection_changed", "unknown");
|
|
65
|
+
if (!prior)
|
|
66
|
+
retain(path.join(root, "input.json"), identity);
|
|
67
|
+
const result = read(path.join(root, "capacity.json"));
|
|
68
|
+
const publish = async (proof) => {
|
|
69
|
+
const cleanup = proof.cleanup;
|
|
70
|
+
const start = read(path.join(root, "daemon-start.json"));
|
|
71
|
+
const rootSessionId = adapterSessionId(read(path.join(root, "session-create.json")));
|
|
72
|
+
const inspected = read(path.join(root, "session-inspect.json"));
|
|
73
|
+
const children = rootSessionId && inspected ? normalizeNativeChildren(inspected, rootSessionId) : [];
|
|
74
|
+
for (const phase of ["daemon-start", "session-create", "session-prompt", "daemon-stop"]) {
|
|
75
|
+
const operationId = `memory-native:${hybridDigest(identity)}:${phase}`;
|
|
76
|
+
const observed = await inspectDaemonOperation(path.join(root, "daemon"), operationId).catch(() => null);
|
|
77
|
+
const fingerprint = read(path.join(root, `${phase}.request-proof.json`));
|
|
78
|
+
const intent = read(path.join(root, `${phase}.intent.json`));
|
|
79
|
+
const expected = read(path.join(root, `${phase}.json`));
|
|
80
|
+
if (!observed || !fingerprint || fingerprint.command_sha256 !== intent?.command_sha256
|
|
81
|
+
|| observed.invocation_sha256 !== fingerprint.command_sha256 || observed.params_sha256 !== fingerprint.params_sha256
|
|
82
|
+
|| observed.operation_id !== operationId || observed.state !== "completed"
|
|
83
|
+
|| phase !== "daemon-start" && observed.daemon_id !== start?.daemon_id
|
|
84
|
+
|| hybridDigest(publicReply(observed.result)) !== hybridDigest(expected))
|
|
85
|
+
refuse("probe_provider_custody_changed", "unknown");
|
|
86
|
+
}
|
|
87
|
+
if (hybridDigest(proof.input) !== hybridDigest(identity) || proof.failure || !rootSessionId
|
|
88
|
+
|| proof.root_session_id !== rootSessionId || hybridDigest(proof.inspected) !== hybridDigest(inspected)
|
|
89
|
+
|| hybridDigest(proof.children) !== hybridDigest(children) || proof.capacity !== children.length
|
|
90
|
+
|| children.length < input.maximum || children.length > input.maximum || new Set(children.map(child => child.session_id)).size !== children.length
|
|
91
|
+
|| children.some(child => child.session_id === rootSessionId || child.parent_session_id !== rootSessionId || child.provenance === "parent_mismatch")
|
|
92
|
+
|| cleanup?.stopped !== true || cleanup.settled !== true || cleanup.shutdown_contract !== "dd-flow/daemon-shutdown@1"
|
|
93
|
+
|| typeof start?.daemon_id !== "string" || cleanup.daemon_id !== start.daemon_id
|
|
94
|
+
|| hybridDigest(cleanup) !== hybridDigest(read(path.join(root, "daemon-stop.json"))))
|
|
95
|
+
refuse("probe_not_qualified", "partial");
|
|
96
|
+
const bytes = Buffer.from(JSON.stringify(proof)), checksum = sha(bytes);
|
|
97
|
+
const reference = { path: "execution/native-capacity.json", sha256: checksum };
|
|
98
|
+
const receipts = new Map([[checksum, { kind: "native-capacity", requestId: input.scope.requestId,
|
|
99
|
+
projectRoot: input.scope.projectRoot, workspace: input.scope.workspaceRoot,
|
|
100
|
+
harness: input.profile.harness, profileRevision: hybridDigest(input.profile), capacity: Number(proof.capacity) }]]);
|
|
101
|
+
return { reference, receipts, receiptBytes: new Map([[checksum, bytes]]) };
|
|
102
|
+
};
|
|
103
|
+
if (result)
|
|
104
|
+
return await publish(result);
|
|
105
|
+
// An independent bounded technical workspace cannot modify the target.
|
|
106
|
+
const workspace = path.join(root, "project"), stateDir = path.join(root, "daemon");
|
|
107
|
+
fs.mkdirSync(workspace, { recursive: true, mode: 0o700 });
|
|
108
|
+
const common = ["--state-dir", stateDir, "--cwd", workspace, "--journal", path.join(root, "events.jsonl"),
|
|
109
|
+
"--no-flow", ...harnessRuntimeArguments(input.home, input.profile.harness)];
|
|
110
|
+
const env = { ...input.env, ...(input.nativeConfigHome !== undefined ? { CODEX_HOME: input.nativeConfigHome } : {}),
|
|
111
|
+
DD_FLOW_CONFIG_HOME: input.home, DD_FLOW_RESOURCE_HOME: path.join(root, "resources") };
|
|
112
|
+
delete env.DD_FLOW_HOME;
|
|
113
|
+
delete env.DD_FLOW_BIN;
|
|
114
|
+
delete env.DD_FLOW_ENGINE_HOME;
|
|
115
|
+
const call = async (phase, args, timeoutMs = 120_000) => {
|
|
116
|
+
lease.assertOwnership();
|
|
117
|
+
const operationId = `memory-native:${hybridDigest(identity)}:${phase}`;
|
|
118
|
+
const command = nativeCommandFingerprint({ executable, args: [...args, ...common], cwd: workspace, env, operationId });
|
|
119
|
+
const received = read(path.join(root, `${phase}.json`));
|
|
120
|
+
const intent = read(path.join(root, `${phase}.intent.json`));
|
|
121
|
+
if (intent && (intent.operation_id !== operationId || hybridDigest(intent.args) !== hybridDigest(args)
|
|
122
|
+
|| intent.command_sha256 !== command))
|
|
123
|
+
refuse("phase_request_changed", "unknown");
|
|
124
|
+
const durable = phase !== "session-inspect";
|
|
125
|
+
if (received || intent) {
|
|
126
|
+
if (!durable && received && intent)
|
|
127
|
+
return received;
|
|
128
|
+
// The shared adapter builder retained this before RPC/provider dispatch,
|
|
129
|
+
// independently of both the eventual reply and the local ACK receipt.
|
|
130
|
+
const witness = read(path.join(root, `${phase}.request-proof.json`));
|
|
131
|
+
if (!witness)
|
|
132
|
+
refuse(`unresolved_${phase}`, "unknown");
|
|
133
|
+
const observed = await inspectDaemonOperation(stateDir, operationId).catch(() => null);
|
|
134
|
+
const start = read(path.join(root, "daemon-start.json"));
|
|
135
|
+
if (observed?.operation_id !== operationId || observed.operation !== args.slice(0, 2).join(".")
|
|
136
|
+
|| witness.command_sha256 !== command || observed.invocation_sha256 !== command
|
|
137
|
+
|| observed.params_sha256 !== witness.params_sha256 || !/^[a-f0-9]{64}$/.test(String(observed.params_sha256))
|
|
138
|
+
|| phase !== "daemon-start" && observed.daemon_id !== start?.daemon_id
|
|
139
|
+
|| observed.state !== "completed" || !observed.result || typeof observed.result !== "object"
|
|
140
|
+
|| observed.settlement && !["not_required", "settled"].includes(String(observed.settlement.state)))
|
|
141
|
+
refuse(`unresolved_${phase}`, "unknown");
|
|
142
|
+
const recovered = publicReply(observed.result);
|
|
143
|
+
if (received) {
|
|
144
|
+
if (hybridDigest(received) !== hybridDigest(recovered))
|
|
145
|
+
refuse("phase_receipt_changed", "unknown");
|
|
146
|
+
return received;
|
|
147
|
+
}
|
|
148
|
+
retain(path.join(root, `${phase}.json`), recovered);
|
|
149
|
+
return recovered;
|
|
150
|
+
}
|
|
151
|
+
retain(path.join(root, `${phase}.intent.json`), { operation_id: operationId, args, command_sha256: command });
|
|
152
|
+
let reply;
|
|
153
|
+
try {
|
|
154
|
+
const parent = fs.lstatSync(root);
|
|
155
|
+
reply = await runHarnessAdapter({ executable, args: [...args, ...common], env: { ...env,
|
|
156
|
+
DD_FLOW_NATIVE_REQUEST_PROOF_FILE: path.join(root, `${phase}.request-proof.json`), DD_FLOW_NATIVE_COMMAND_SHA256: command,
|
|
157
|
+
DD_FLOW_NATIVE_REQUEST_OPERATION: args.slice(0, 2).join("."), DD_FLOW_NATIVE_REQUEST_PROOF_OWNER: JSON.stringify({ dev: parent.dev, ino: parent.ino }) }, operationId,
|
|
158
|
+
harness: input.profile.harness, cwd: workspace, timeoutMs, signal: AbortSignal.timeout(timeoutMs) });
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (!durable)
|
|
162
|
+
throw error;
|
|
163
|
+
// Lost first ACK is observation loss, not permission to launch again.
|
|
164
|
+
const witness = read(path.join(root, `${phase}.request-proof.json`));
|
|
165
|
+
const observed = await inspectDaemonOperation(stateDir, operationId).catch(() => null);
|
|
166
|
+
const start = read(path.join(root, "daemon-start.json"));
|
|
167
|
+
if (!witness || witness.operation_id !== operationId || witness.operation !== args.slice(0, 2).join(".")
|
|
168
|
+
|| observed?.operation_id !== operationId || observed.operation !== witness.operation
|
|
169
|
+
|| witness.command_sha256 !== command || observed.invocation_sha256 !== command
|
|
170
|
+
|| observed.params_sha256 !== witness.params_sha256 || observed.state !== "completed"
|
|
171
|
+
|| phase !== "daemon-start" && observed.daemon_id !== start?.daemon_id
|
|
172
|
+
|| observed.settlement && observed.settlement.state !== "not_required"
|
|
173
|
+
&& observed.settlement.state !== "settled"
|
|
174
|
+
|| !observed.result || typeof observed.result !== "object")
|
|
175
|
+
throw error;
|
|
176
|
+
reply = publicReply(observed.result);
|
|
177
|
+
}
|
|
178
|
+
if (durable) {
|
|
179
|
+
const observed = await inspectDaemonOperation(stateDir, operationId);
|
|
180
|
+
const witness = read(path.join(root, `${phase}.request-proof.json`));
|
|
181
|
+
if (observed.operation_id !== operationId || observed.state !== "completed" || !witness
|
|
182
|
+
|| witness.command_sha256 !== command || observed.invocation_sha256 !== command
|
|
183
|
+
|| observed.params_sha256 !== witness.params_sha256)
|
|
184
|
+
refuse(`unresolved_${phase}`, "unknown");
|
|
185
|
+
}
|
|
186
|
+
retain(path.join(root, `${phase}.json`), reply);
|
|
187
|
+
return reply;
|
|
188
|
+
};
|
|
189
|
+
let inspected, rootSessionId = null;
|
|
190
|
+
let failure, cleanup;
|
|
191
|
+
try {
|
|
192
|
+
await call("daemon-start", ["daemon", "start"]);
|
|
193
|
+
const created = await call("session-create", ["session", "create", "--model", input.profile.model, "--reasoning", input.profile.reasoning]);
|
|
194
|
+
rootSessionId = adapterSessionId(created);
|
|
195
|
+
if (!rootSessionId)
|
|
196
|
+
refuse("root_session_identity_missing", "unknown");
|
|
197
|
+
await call("session-prompt", ["session", "prompt", "--session-id", rootSessionId, "--model", input.profile.model,
|
|
198
|
+
"--reasoning", input.profile.reasoning, "--prompt", renderCapacityInstructions({ harness: input.profile.harness, maximum: input.maximum })], 180_000);
|
|
199
|
+
inspected = await call("session-inspect", ["session", "inspect", "--session-id", rootSessionId]);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
failure = errorRecord(error);
|
|
203
|
+
// Retain a physical tree observation before owned shutdown, even when the
|
|
204
|
+
// prompt ACK is unknown. A later exact terminal receipt can then reconcile
|
|
205
|
+
// without needing to restart the stopped daemon or resend productive work.
|
|
206
|
+
if (rootSessionId && !read(path.join(root, "session-inspect.intent.json"))) {
|
|
207
|
+
try {
|
|
208
|
+
inspected = await call("session-inspect", ["session", "inspect", "--session-id", rootSessionId]);
|
|
209
|
+
}
|
|
210
|
+
catch { /* The unresolved observation remains pending, never fabricated. */ }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
try {
|
|
215
|
+
const start = read(path.join(root, "daemon-start.json")), daemon = read(path.join(stateDir, "daemon.json"));
|
|
216
|
+
if (!start || typeof start.daemon_id !== "string" || daemon?.daemon_id !== start.daemon_id)
|
|
217
|
+
refuse("cleanup_daemon_incarnation_unknown", "unknown");
|
|
218
|
+
cleanup = await call("daemon-stop", ["daemon", "stop", "--cancel-tree"]);
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
failure ??= errorRecord(error);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const children = rootSessionId && inspected ? normalizeNativeChildren(inspected, rootSessionId) : [];
|
|
225
|
+
const qualified = !failure && cleanup?.stopped === true && cleanup.settled === true
|
|
226
|
+
&& cleanup.shutdown_contract === "dd-flow/daemon-shutdown@1" && typeof cleanup.daemon_id === "string"
|
|
227
|
+
&& children.length === input.maximum && children.length <= 15
|
|
228
|
+
&& children.every(child => child.parent_session_id === rootSessionId && child.session_id !== rootSessionId && child.provenance !== "parent_mismatch");
|
|
229
|
+
const proof = { schema_id: "dd-flow/memory-native-capacity@1", input: identity, root_session_id: rootSessionId,
|
|
230
|
+
inspected, cleanup, children, capacity: children.length, qualified, ...(failure ? { failure } : {}) };
|
|
231
|
+
if (failure) {
|
|
232
|
+
fs.appendFileSync(path.join(root, "observations.jsonl"), `${JSON.stringify(proof)}\n`, { mode: 0o600 });
|
|
233
|
+
refuse("probe_operation_unresolved", "unknown");
|
|
234
|
+
}
|
|
235
|
+
retain(path.join(root, "capacity.json"), proof);
|
|
236
|
+
return await publish(proof);
|
|
237
|
+
}
|
|
238
|
+
finally {
|
|
239
|
+
lease.release();
|
|
240
|
+
}
|
|
241
|
+
}
|