@deksden-com/dd-flow-cli 0.9.0-beta.137 → 0.9.0-beta.139
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 +19 -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 +197 -0
- package/dist/cli/command-option-descriptions.js +191 -0
- package/dist/cli/completion-cli.js +70 -0
- package/dist/cli/completion.js +57 -0
- package/dist/cli/error-output.js +52 -0
- package/dist/cli/executor-onboarding.js +76 -0
- package/dist/cli/flows.js +29 -0
- package/dist/cli/harnesses.js +84 -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/models.js +39 -0
- package/dist/cli/native-hook-ingress.js +1 -1
- package/dist/cli/operator-profiles.js +61 -0
- package/dist/cli/output-options.js +35 -0
- package/dist/cli/response-file.js +71 -0
- package/dist/cli/run-cli.js +283 -160
- package/dist/cli/runtime-process.js +1 -1
- package/dist/cli/setup.js +149 -0
- package/dist/cli.js +56 -2
- package/dist/domain/stage-handler-identities.js +20 -0
- 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 +53 -22
- package/dist/harness-runtime/lib/dd-agy.mjs +16 -5
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +12 -6
- package/dist/harness-runtime/lib/dd-codex.mjs +24 -12
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +2 -1
- package/dist/harness-runtime/lib/dd-droid.mjs +32 -9
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +11 -3
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +100 -15
- package/dist/harness-runtime/lib/dd-opencode.mjs +51 -20
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +22 -10
- package/dist/harness-runtime/lib/dd-zcode.mjs +17 -4
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +16 -3
- package/dist/harness-runtime/lib/lease-renewal.mjs +2 -1
- 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-children.d.mts +2 -0
- package/dist/harness-runtime/lib/native-children.mjs +20 -1
- 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 +32 -9
- package/dist/harness-runtime/lib/operation-progress.mjs +3 -1
- package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
- package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
- 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 +26 -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/cli-upgrade-allowlist.js +27 -0
- package/dist/services/code-checks.js +387 -34
- package/dist/services/controller-child-capacity.js +13 -0
- package/dist/services/controller-child-observer.js +18 -2
- package/dist/services/controller-execution.js +88 -0
- package/dist/services/controller-fanout.js +71 -12
- 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/discovery/agy-discovery.js +2 -0
- package/dist/services/discovery/codex-discovery.js +2 -0
- package/dist/services/discovery/droid-discovery.js +2 -0
- package/dist/services/discovery/grok-discovery.js +2 -0
- package/dist/services/discovery/opencode-discovery.js +2 -0
- package/dist/services/discovery/static.js +31 -0
- package/dist/services/discovery/zcode-discovery.js +2 -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/executor-onboarding.js +56 -0
- package/dist/services/external-work-launch.js +93 -15
- package/dist/services/flow-catalog.js +21 -0
- package/dist/services/flow-descriptors.js +82 -0
- package/dist/services/frozen-engine.js +250 -0
- package/dist/services/generic-execution.js +164 -0
- package/dist/services/harness-adapter.js +28 -2
- package/dist/services/harness-discovery.js +29 -0
- package/dist/services/harness-doctor.js +75 -0
- package/dist/services/harness-probe-effects.js +51 -0
- package/dist/services/harness-version-probe.js +100 -0
- package/dist/services/hooks.js +24 -6
- package/dist/services/hybrid-contracts.js +483 -0
- package/dist/services/ids.js +29 -1
- package/dist/services/installation-lock.js +95 -0
- package/dist/services/installed-flow-source.js +183 -0
- package/dist/services/lifecycle-invocations.js +257 -78
- package/dist/services/managed-processes.js +31 -10
- 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 +296 -0
- package/dist/services/memory-init-preflight.js +330 -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 +181 -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 +178 -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/merge-server.js +2 -4
- package/dist/services/model-catalog.js +109 -0
- package/dist/services/model-discovery.js +46 -0
- package/dist/services/operator-profiles.js +236 -0
- package/dist/services/projects.js +17 -2
- package/dist/services/{codex-capacity.js → provider-capacity.js} +34 -16
- package/dist/services/recovery-snapshot-database.js +417 -2
- package/dist/services/release-archive-producer.js +52 -0
- package/dist/services/release-archive.js +143 -0
- package/dist/services/release-cache.js +142 -0
- package/dist/services/release-compatibility.js +18 -0
- package/dist/services/release-download.js +67 -0
- package/dist/services/release-envelope.js +104 -0
- package/dist/services/release-manifest.js +45 -0
- package/dist/services/release-payload.js +114 -0
- package/dist/services/release-producer.js +63 -0
- package/dist/services/release-provenance.js +160 -0
- package/dist/services/release-registry.js +66 -0
- package/dist/services/release-source.js +194 -0
- 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-adapter.js +1 -1
- 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 +5 -5
- package/dist/services/run-controller-state.js +1 -1
- package/dist/services/run-controller.js +158 -64
- package/dist/services/run-recovery-guard.js +5 -3
- package/dist/services/run-recovery-runtime.js +3 -3
- package/dist/services/run-recovery.js +145 -5
- package/dist/services/run-start-custody.js +177 -0
- package/dist/services/runs.js +413 -30
- package/dist/services/setup.js +377 -0
- package/dist/services/stage-context.js +9 -1
- package/dist/services/stage-handlers.js +74 -0
- package/dist/services/stage-report-renderer.js +63 -0
- package/dist/services/stage-work-graph.js +17 -0
- package/dist/services/vnext-code-review.js +1 -1
- package/dist/services/vnext-code.js +6 -6
- package/dist/services/vnext-execution-profile.js +2 -2
- package/dist/services/vnext-fanout.js +73 -11
- package/dist/services/vnext-merge.js +8 -3
- package/dist/services/vnext-plan-review.js +7 -5
- package/dist/services/vnext-plan.js +3 -3
- package/dist/services/vnext-protocolize.js +20 -6
- package/dist/services/vnext-workspace-policy.js +7 -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
- package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +0 -10
- package/dist/harness-runtime/lib/codex-capacity-policy.mjs +0 -102
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { AppError } from "../shared/errors.js";
|
|
5
|
+
import { hybridDigest, validateHybridContract } from "./hybrid-contracts.js";
|
|
6
|
+
import { acquireInstallationLock } from "./installation-lock.js";
|
|
7
|
+
import { readBoundedReleaseFile } from "./release-source.js";
|
|
8
|
+
export function operatorSettingsPath(home) { return path.join(home, "profile-settings.json"); }
|
|
9
|
+
function profileError(code, message, exitCode = 2) {
|
|
10
|
+
return new AppError(code, `${message}; use dd-flow profiles --help`, exitCode, { phase: "profiles", effect: "no_effect", recoverable: true });
|
|
11
|
+
}
|
|
12
|
+
function completeSettings(input) {
|
|
13
|
+
const value = { ...input, profiles: input.profiles.map(entry => ({ ...entry, revision: hybridDigest(entry.profile) })) };
|
|
14
|
+
return { ...value, revision: hybridDigest(value) };
|
|
15
|
+
}
|
|
16
|
+
function admittedSettings(input) {
|
|
17
|
+
const issues = validateHybridContract("profile-settings", input);
|
|
18
|
+
if (issues.length)
|
|
19
|
+
throw profileError("profile_settings_invalid", "Profile settings violate the schema or aliases/revision contract");
|
|
20
|
+
const settings = input;
|
|
21
|
+
for (const { profile } of settings.profiles) {
|
|
22
|
+
if ([profile.provider, profile.model, profile.reasoning, profile.mode].some(value => !value.trim())) {
|
|
23
|
+
throw profileError("profile_settings_invalid", "Profile execution values must not be blank");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return settings;
|
|
27
|
+
}
|
|
28
|
+
function readRegularJson(file) {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Parser errors can contain submitted secrets. Never retain input text or cause.
|
|
34
|
+
throw profileError("profile_settings_invalid", "Profile input is unreadable or malformed JSON");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** First successful write imports valid legacy IDs; thereafter the settings file is the sole owner. */
|
|
38
|
+
export function readOperatorProfiles(home) {
|
|
39
|
+
const file = operatorSettingsPath(home);
|
|
40
|
+
if (fs.existsSync(file))
|
|
41
|
+
return admittedSettings(readRegularJson(file));
|
|
42
|
+
const profiles = [];
|
|
43
|
+
const legacy = path.join(home, "agent-profiles");
|
|
44
|
+
if (fs.existsSync(legacy)) {
|
|
45
|
+
for (const entry of fs.readdirSync(legacy).sort()) {
|
|
46
|
+
if (!entry.endsWith(".json"))
|
|
47
|
+
continue;
|
|
48
|
+
const profile = readRegularJson(path.join(legacy, entry));
|
|
49
|
+
const id = entry.slice(0, -5);
|
|
50
|
+
profiles.push({ id, revision: hybridDigest(profile), profile });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return admittedSettings(completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles,
|
|
54
|
+
aliases: {}, default_profile: null }));
|
|
55
|
+
}
|
|
56
|
+
export function resolveOperatorProfile(settings, id) {
|
|
57
|
+
const selected = id ?? settings.default_profile;
|
|
58
|
+
if (!selected)
|
|
59
|
+
throw profileError("agent_profile_missing", "Select an explicit profile or configure a default", 1);
|
|
60
|
+
const target = Object.hasOwn(settings.aliases, selected) ? settings.aliases[selected] : selected;
|
|
61
|
+
const entry = settings.profiles.find(item => item.id === target);
|
|
62
|
+
if (!entry)
|
|
63
|
+
throw profileError("agent_profile_missing", "Selected profile is unavailable", 1);
|
|
64
|
+
return structuredClone(entry.profile);
|
|
65
|
+
}
|
|
66
|
+
/** Used by explicit files and onboarding alike; extra credential fields fail before any write. */
|
|
67
|
+
export function prepareOperatorProfile(input) {
|
|
68
|
+
if (!input || typeof input !== "object" || Array.isArray(input))
|
|
69
|
+
throw profileError("profile_settings_invalid", "Profile must be an object");
|
|
70
|
+
const sample = input;
|
|
71
|
+
const value = completeSettings({ schema_id: "dd-flow/profile-settings@1", profiles: [
|
|
72
|
+
{ id: sample?.id, revision: hybridDigest(input), profile: sample }
|
|
73
|
+
], aliases: {}, default_profile: null });
|
|
74
|
+
return admittedSettings(value).profiles[0].profile;
|
|
75
|
+
}
|
|
76
|
+
export function readOperatorProfileInput(file) {
|
|
77
|
+
return prepareOperatorProfile(readRegularJson(file));
|
|
78
|
+
}
|
|
79
|
+
function changedSettings(current, mutation) {
|
|
80
|
+
const next = structuredClone(current);
|
|
81
|
+
if (mutation.action === "create" || mutation.action === "edit") {
|
|
82
|
+
if (mutation.action === "create" && mutation.setDefault !== undefined && typeof mutation.setDefault !== "boolean") {
|
|
83
|
+
throw profileError("profile_settings_invalid", "Default selection must be an explicit boolean");
|
|
84
|
+
}
|
|
85
|
+
const profile = prepareOperatorProfile(mutation.profile);
|
|
86
|
+
const index = next.profiles.findIndex(entry => entry.id === profile.id);
|
|
87
|
+
if (mutation.action === "create" && index >= 0)
|
|
88
|
+
throw profileError("profile_conflict", "Profile ID already exists", 1);
|
|
89
|
+
if (mutation.action === "edit" && index < 0)
|
|
90
|
+
throw profileError("agent_profile_missing", "Cannot edit a missing profile", 1);
|
|
91
|
+
const entry = { id: profile.id, revision: hybridDigest(profile), profile };
|
|
92
|
+
if (index >= 0)
|
|
93
|
+
next.profiles[index] = entry;
|
|
94
|
+
else
|
|
95
|
+
next.profiles.push(entry);
|
|
96
|
+
if (mutation.action === "create" && mutation.setDefault === true)
|
|
97
|
+
next.default_profile = profile.id;
|
|
98
|
+
}
|
|
99
|
+
else if (mutation.action === "remove") {
|
|
100
|
+
if (!next.profiles.some(entry => entry.id === mutation.id))
|
|
101
|
+
throw profileError("agent_profile_missing", "Cannot remove a missing profile", 1);
|
|
102
|
+
if (Object.values(next.aliases).includes(mutation.id) || next.default_profile === mutation.id
|
|
103
|
+
|| next.default_profile !== null && next.aliases[next.default_profile] === mutation.id) {
|
|
104
|
+
throw profileError("profile_in_use", "Remove aliases and change the default before removing this profile", 1);
|
|
105
|
+
}
|
|
106
|
+
next.profiles = next.profiles.filter(entry => entry.id !== mutation.id);
|
|
107
|
+
}
|
|
108
|
+
else if (mutation.action === "alias") {
|
|
109
|
+
if (mutation.target === null) {
|
|
110
|
+
if (!Object.hasOwn(next.aliases, mutation.alias))
|
|
111
|
+
throw profileError("profile_alias_missing", "Alias is unavailable", 1);
|
|
112
|
+
if (next.default_profile === mutation.alias)
|
|
113
|
+
throw profileError("profile_in_use", "Change the default before removing its alias", 1);
|
|
114
|
+
delete next.aliases[mutation.alias];
|
|
115
|
+
}
|
|
116
|
+
else
|
|
117
|
+
Object.defineProperty(next.aliases, mutation.alias, { value: mutation.target, enumerable: true, writable: true, configurable: true });
|
|
118
|
+
}
|
|
119
|
+
else if (mutation.action === "set-default")
|
|
120
|
+
next.default_profile = mutation.id;
|
|
121
|
+
else
|
|
122
|
+
throw profileError("profile_settings_invalid", "Unknown profile mutation");
|
|
123
|
+
const { revision: _revision, ...content } = next;
|
|
124
|
+
void _revision;
|
|
125
|
+
return admittedSettings(completeSettings(content));
|
|
126
|
+
}
|
|
127
|
+
/** Pure preflight suitable for freezing into an installation request before any effects. */
|
|
128
|
+
export function prepareProfileCreation(settings, profile, setDefault) {
|
|
129
|
+
if (typeof setDefault !== "boolean")
|
|
130
|
+
throw profileError("profile_settings_invalid", "Default selection requires explicit consent");
|
|
131
|
+
const before = admittedSettings(structuredClone(settings)), admitted = prepareOperatorProfile(profile);
|
|
132
|
+
const after = changedSettings(before, { action: "create", profile: admitted, setDefault });
|
|
133
|
+
return { schema_id: "dd-flow/profile-creation-intent@1", before, after, profile: structuredClone(admitted), set_default: setDefault };
|
|
134
|
+
}
|
|
135
|
+
/** Exact frozen intent reconciliation: an already-applied write is not applied a second time.
|
|
136
|
+
* A later edit/default change is a conflict, never a reason to restore old settings.
|
|
137
|
+
*/
|
|
138
|
+
export function validateProfileCreationIntent(input) {
|
|
139
|
+
if (!input || typeof input !== "object" || Array.isArray(input)
|
|
140
|
+
|| Object.keys(input).some(key => !["schema_id", "before", "after", "profile", "set_default"].includes(key))
|
|
141
|
+
|| ["schema_id", "before", "after", "profile", "set_default"].some(key => !Object.hasOwn(input, key))
|
|
142
|
+
|| input.schema_id !== "dd-flow/profile-creation-intent@1")
|
|
143
|
+
throw profileError("profile_settings_invalid", "Invalid profile creation intent");
|
|
144
|
+
const expected = prepareProfileCreation(input.before, input.profile, input.set_default);
|
|
145
|
+
admittedSettings(input.after);
|
|
146
|
+
if (hybridDigest(expected) !== hybridDigest(input))
|
|
147
|
+
throw profileError("profile_settings_invalid", "Profile creation intent changed");
|
|
148
|
+
}
|
|
149
|
+
export function readProfileCreationIntent(file) {
|
|
150
|
+
try {
|
|
151
|
+
const input = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readBoundedReleaseFile(file, 4 * 1024 * 1024)));
|
|
152
|
+
validateProfileCreationIntent(input);
|
|
153
|
+
return input;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
throw profileError("profile_settings_invalid", "Profile creation intent must be a bounded valid regular JSON file");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function commitProfileCreation(home, input) {
|
|
160
|
+
validateProfileCreationIntent(input);
|
|
161
|
+
const current = readOperatorProfiles(home);
|
|
162
|
+
if (current.revision === input.after.revision)
|
|
163
|
+
return { replayed: true, settings: current };
|
|
164
|
+
return { replayed: false, settings: mutateOperatorProfiles(home, input.before.revision, { action: "create", profile: input.profile, setDefault: input.set_default }) };
|
|
165
|
+
}
|
|
166
|
+
/** Compare-and-swap under an exclusive owned lock; no dual writer for legacy profile files. */
|
|
167
|
+
export function mutateOperatorProfiles(home, expectedRevision, mutation) {
|
|
168
|
+
const before = readOperatorProfiles(home);
|
|
169
|
+
if (before.revision !== expectedRevision)
|
|
170
|
+
throw profileError("profile_revision_conflict", "Profile settings changed; reread before retrying", 1);
|
|
171
|
+
const prepared = changedSettings(before, mutation); // Fully validate before creating the home/lock.
|
|
172
|
+
const serialized = JSON.stringify(prepared, null, 2) + "\n";
|
|
173
|
+
if (Buffer.byteLength(serialized) > 4 * 1024 * 1024)
|
|
174
|
+
throw profileError("profile_settings_invalid", "Profile settings exceed the bounded reader limit");
|
|
175
|
+
fs.mkdirSync(home, { recursive: true, mode: 0o700 });
|
|
176
|
+
const lock = path.join(home, "profile-settings.lock");
|
|
177
|
+
// Profiles use storage-owner identity, not a setup request ledger. A dead
|
|
178
|
+
// writer may be recovered by a later CAS mutation, including after commit.
|
|
179
|
+
let lease;
|
|
180
|
+
try {
|
|
181
|
+
lease = acquireInstallationLock(lock, hybridDigest({ resource: "profile-settings", home: path.resolve(home) }));
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
throw profileError("profile_write_conflict", "Another profile writer or unreconciled lock exists", 1);
|
|
185
|
+
}
|
|
186
|
+
const temporary = path.join(home, `.profile-settings-${randomUUID()}.tmp`);
|
|
187
|
+
let ownsTemporary = false;
|
|
188
|
+
let renameAttempted = false;
|
|
189
|
+
let failure;
|
|
190
|
+
let result;
|
|
191
|
+
try {
|
|
192
|
+
if (readOperatorProfiles(home).revision !== expectedRevision)
|
|
193
|
+
throw profileError("profile_revision_conflict", "Profile settings changed while acquiring the lock", 1);
|
|
194
|
+
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
195
|
+
ownsTemporary = true;
|
|
196
|
+
try {
|
|
197
|
+
fs.writeFileSync(fd, serialized);
|
|
198
|
+
fs.fsyncSync(fd);
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
fs.closeSync(fd);
|
|
202
|
+
}
|
|
203
|
+
renameAttempted = true;
|
|
204
|
+
fs.renameSync(temporary, operatorSettingsPath(home));
|
|
205
|
+
result = readOperatorProfiles(home);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
failure = error instanceof AppError && !renameAttempted ? error : new AppError("profile_write_interrupted", "Profile write requires reconciliation; reread settings before retrying", 1, { phase: "profiles", effect: "unknown", recoverable: true,
|
|
209
|
+
next_action: "dd-flow profiles list --json" });
|
|
210
|
+
}
|
|
211
|
+
finally {
|
|
212
|
+
let cleanupFailed = false;
|
|
213
|
+
try {
|
|
214
|
+
if (ownsTemporary && fs.existsSync(temporary))
|
|
215
|
+
fs.unlinkSync(temporary);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
cleanupFailed = true;
|
|
219
|
+
}
|
|
220
|
+
try {
|
|
221
|
+
lease.release();
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
cleanupFailed = true;
|
|
225
|
+
}
|
|
226
|
+
if (cleanupFailed) {
|
|
227
|
+
if (failure)
|
|
228
|
+
failure.details.cleanup = "not_confirmed";
|
|
229
|
+
else
|
|
230
|
+
failure = new AppError("profile_cleanup_failed", "Profile writer cleanup requires reconciliation", 1, { phase: "profiles", effect: "unknown", cleanup: "not_confirmed", next_action: "dd-flow profiles list --json" });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (failure)
|
|
234
|
+
throw failure;
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
@@ -11,9 +11,24 @@ import { loadProjectFlowContract } from "../domain/flow-contract.js";
|
|
|
11
11
|
import { buildStaticFlowGuidance } from "./flow-guidance.js";
|
|
12
12
|
import { normalizeProtocolLifecycle } from "./protocol-lifecycle.js";
|
|
13
13
|
import { buildProjectSummary, projectRegistrySummary, readPublishedProjectSummary } from "./project-summary.js";
|
|
14
|
+
/** Resolve filesystem identity before entering a composite SQL writer. */
|
|
15
|
+
export class PreparedProjectRegistration {
|
|
16
|
+
#root;
|
|
17
|
+
constructor(root) { this.#root = root; Object.freeze(this); }
|
|
18
|
+
static prepare(input) {
|
|
19
|
+
return new PreparedProjectRegistration(resolveProjectRoot(input.root));
|
|
20
|
+
}
|
|
21
|
+
root() { return this.#root; }
|
|
22
|
+
}
|
|
14
23
|
export function registerProject(context, input) {
|
|
15
|
-
|
|
16
|
-
|
|
24
|
+
return registerPreparedProject(context, PreparedProjectRegistration.prepare(input));
|
|
25
|
+
}
|
|
26
|
+
/** Metadata-only registration; the composite caller owns its SQL transaction. */
|
|
27
|
+
export function registerPreparedProject(context, prepared) {
|
|
28
|
+
if (!(prepared instanceof PreparedProjectRegistration))
|
|
29
|
+
throw new AppError("validation", "Project registration must be prepared", 2);
|
|
30
|
+
const root = prepared.root();
|
|
31
|
+
const existing = context.db.get("SELECT * FROM projects WHERE root = ?", [root]);
|
|
17
32
|
const now = context.now();
|
|
18
33
|
const project = existing ?? newProjectRecord(context, root, now);
|
|
19
34
|
if (existing) {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
2
|
import crypto from "node:crypto";
|
|
3
|
-
import { CAPACITY_POLICY, capacityBackoff, nativeItemsSettled,
|
|
3
|
+
import { CAPACITY_POLICY, MAX_OVERLOAD_RETRIES, capacityBackoff, nativeItemsSettled, normalizeProviderFailure, overloadExhausted, parseCapacityOperation, refusalObservation, terminalProviderOverload } from "../harness-runtime/lib/provider-overload-policy.mjs";
|
|
4
4
|
import { AppError } from "../shared/errors.js";
|
|
5
5
|
import { isObservationLoss } from "../harness-runtime/lib/operation-errors.mjs";
|
|
6
|
-
export function
|
|
6
|
+
export function providerChildFailure(child) {
|
|
7
|
+
return child.provider_failure ?? { code: child.native_source ? "provider_capacity_transient" : "turn_interrupted", details: { native_source: child.native_source, provider_session_id: child.session_id, turn_id: child.terminal_turn_id, native_turn_id: child.terminal_turn_id, terminal_status: child.status, observed_at: child.terminal_observed_at ?? null, provider_error: child.provider_error, native_turn_items: child.native_turn_items } };
|
|
8
|
+
}
|
|
9
|
+
export function eligibleProviderChildOverload(child, parentSessionId) {
|
|
7
10
|
return typeof child.session_id === "string" && child.session_id !== parentSessionId && child.parent_session_id === parentSessionId && child.provenance !== "parent_mismatch" && child.status === "failed" && typeof child.terminal_turn_id === "string" && nativeItemsSettled(child.native_turn_items)
|
|
8
|
-
&&
|
|
11
|
+
&& terminalProviderOverload(providerChildFailure(child), child.session_id, child.terminal_turn_id);
|
|
9
12
|
}
|
|
10
13
|
/** A terminal root is not a settled tree. Only the daemon's exact owned tree
|
|
11
14
|
* with a settled root and positively live descendants permits waiting. */
|
|
@@ -37,7 +40,7 @@ export function assertCapacityOwnerAdmission(context, record, operationId, bindi
|
|
|
37
40
|
return;
|
|
38
41
|
const root = parseCapacityOperation(operationId).root;
|
|
39
42
|
const childOrdinal = /:child:[a-f0-9]+:followup:(\d+)$/.exec(root)?.[1];
|
|
40
|
-
if (childOrdinal && (!Number.isSafeInteger(Number(childOrdinal)) || Number(childOrdinal) < 1 || String(Number(childOrdinal)) !== childOrdinal))
|
|
43
|
+
if (childOrdinal && (!Number.isSafeInteger(Number(childOrdinal)) || Number(childOrdinal) < 1 || Number(childOrdinal) > MAX_OVERLOAD_RETRIES || String(Number(childOrdinal)) !== childOrdinal))
|
|
41
44
|
throw new AppError("capacity_continuation_conflict", "Invalid native child continuation ordinal", 1);
|
|
42
45
|
if (root === operationId && !childOrdinal)
|
|
43
46
|
return;
|
|
@@ -71,7 +74,9 @@ export function assertCapacityOwnerAdmission(context, record, operationId, bindi
|
|
|
71
74
|
if (intent)
|
|
72
75
|
break;
|
|
73
76
|
}
|
|
74
|
-
if (
|
|
77
|
+
if (intent?.policy && intent.policy !== CAPACITY_POLICY)
|
|
78
|
+
throw new AppError("capacity_policy_mismatch", "Continuation is pinned to another retry policy", 1);
|
|
79
|
+
if (!intent || parsed.ordinal > MAX_OVERLOAD_RETRIES || intent.policy !== CAPACITY_POLICY || intent.root_operation_id !== root || intent.ordinal !== parsed.ordinal
|
|
75
80
|
|| intent.predecessor !== predecessor || intent.operation_id !== operationId || intent.refusal?.turn_id !== intent.turn_id
|
|
76
81
|
|| intent.refusal?.ordinal !== parsed.ordinal - 1 || intent.refusal?.session_id !== intent.session_id
|
|
77
82
|
|| !Number.isFinite(Date.parse(intent.not_before)) || Date.parse(intent.not_before) > Date.parse(context.now())
|
|
@@ -115,9 +120,13 @@ export function capacityDelay(error, ordinal, now = Date.now()) {
|
|
|
115
120
|
return capacityBackoff(error, ordinal, now);
|
|
116
121
|
}
|
|
117
122
|
/** Continue a confirmed terminal Turn, never replay its completed effects. */
|
|
118
|
-
export async function
|
|
123
|
+
export async function continueProviderCapacity(input) {
|
|
119
124
|
let { error, operationId } = input;
|
|
120
125
|
const history = [...(input.retainedIntents?.() ?? [])];
|
|
126
|
+
if (history.some(intent => intent.policy !== CAPACITY_POLICY) || input.retainedFailures?.().some(refusal => refusal.policy !== CAPACITY_POLICY))
|
|
127
|
+
throw new AppError("capacity_policy_mismatch", "Retained continuation uses another retry policy", 1);
|
|
128
|
+
if (history.length > MAX_OVERLOAD_RETRIES)
|
|
129
|
+
throw new AppError("capacity_continuation_conflict", "Retained episode exceeds its retry budget", 1);
|
|
121
130
|
const parsed = parseCapacityOperation(input.operationId);
|
|
122
131
|
const rootId = parsed.root;
|
|
123
132
|
const text = input.continuationText ?? "Continue the same assigned task in this Session. Check current durable RUN, Work and Stage state first. Never repeat an observed lifecycle command, accepted HITL answer, check or merge apply; use only an exact issued successor. Stop at the assigned boundary.";
|
|
@@ -217,9 +226,9 @@ export async function continueCodexCapacity(input) {
|
|
|
217
226
|
throw new AppError("capacity_continuation_blocked", "Retained completion cannot be reconciled with its owner boundary", 1);
|
|
218
227
|
return boundary === "paused" || boundary === "completed" ? { provider_session_id: input.sessionId, capacity_decision: boundary } : observed.result;
|
|
219
228
|
}
|
|
220
|
-
if (observed.state === "failed" && observed.error && !
|
|
229
|
+
if (observed.state === "failed" && observed.error && !terminalProviderOverload(observed.error, input.sessionId))
|
|
221
230
|
throw observed.error;
|
|
222
|
-
if (observed.state !== "failed" || !
|
|
231
|
+
if (observed.state !== "failed" || !terminalProviderOverload(observed.error, input.sessionId))
|
|
223
232
|
throw new AppError("operation_observation_lost", "Retained successor outcome is not proven; do not resend", 1, { operation_id: intent.operation_id, primary_error: error, observed });
|
|
224
233
|
operationId = intent.operation_id;
|
|
225
234
|
error = observed.error;
|
|
@@ -240,7 +249,7 @@ export async function continueCodexCapacity(input) {
|
|
|
240
249
|
for (;; ordinal += 1) {
|
|
241
250
|
if (!Number.isSafeInteger(ordinal))
|
|
242
251
|
conflict("Continuation ordinal overflow");
|
|
243
|
-
if (!
|
|
252
|
+
if (!terminalProviderOverload(error, input.sessionId)) {
|
|
244
253
|
if (ordinal === 1)
|
|
245
254
|
return null;
|
|
246
255
|
throw error;
|
|
@@ -250,14 +259,14 @@ export async function continueCodexCapacity(input) {
|
|
|
250
259
|
return completed;
|
|
251
260
|
const prior = await observe(() => input.inspectOperation(operationId));
|
|
252
261
|
const failure = prior.error;
|
|
253
|
-
const confirmed =
|
|
262
|
+
const confirmed = normalizeProviderFailure(failure, input.sessionId);
|
|
254
263
|
const turnId = confirmed?.turn_id;
|
|
255
264
|
input.signal?.throwIfAborted();
|
|
256
265
|
input.assertCurrent();
|
|
257
266
|
if (prior.primary_error)
|
|
258
267
|
throw prior.primary_error;
|
|
259
268
|
if (prior.operation_id !== operationId || prior.operation !== "session.prompt" || prior.session_id !== input.sessionId
|
|
260
|
-
|| prior.state !== "failed" || prior.primary_error || !turnId || !
|
|
269
|
+
|| prior.state !== "failed" || prior.primary_error || !turnId || !terminalProviderOverload(failure, input.sessionId, turnId)) {
|
|
261
270
|
throw blocked("Retained predecessor does not prove the exact terminal overloaded Turn");
|
|
262
271
|
}
|
|
263
272
|
const readiness = async () => {
|
|
@@ -272,7 +281,7 @@ export async function continueCodexCapacity(input) {
|
|
|
272
281
|
const ready = await readiness();
|
|
273
282
|
if (ready === "unproven")
|
|
274
283
|
throw blocked("Native predecessor readiness is unproven; do not cancel or replay to manufacture proof");
|
|
275
|
-
const native =
|
|
284
|
+
const native = normalizeProviderFailure(failure, input.sessionId, turnId);
|
|
276
285
|
let refusal = refusals.find(item => item.ordinal === ordinal - 1);
|
|
277
286
|
if (refusal && (refusal.turn_id !== turnId || refusal.session_id !== input.sessionId || refusal.policy !== CAPACITY_POLICY))
|
|
278
287
|
conflict("Failure ordinal identifies a different native Turn");
|
|
@@ -293,17 +302,26 @@ export async function continueCodexCapacity(input) {
|
|
|
293
302
|
input.retainFailure?.(operationId, refusal);
|
|
294
303
|
refusals.push(refusal);
|
|
295
304
|
}
|
|
296
|
-
const
|
|
297
|
-
if (
|
|
305
|
+
const exhausted = overloadExhausted(refusals);
|
|
306
|
+
if (exhausted) {
|
|
298
307
|
if (input.atomicIntent)
|
|
299
308
|
input.retainFailure?.(operationId, refusal);
|
|
300
|
-
throw new AppError("
|
|
309
|
+
throw new AppError("provider_unavailable", "Provider overload retry episode exhausted", 1, { ...exhausted, root_operation_id: rootId, provider_session_id: input.sessionId, continuation_count: ordinal - 1, primary_error: refusals.length ? input.error : failure, last_error: failure });
|
|
301
310
|
}
|
|
302
311
|
const nextId = `${rootId}:capacity:${ordinal}:session.prompt`;
|
|
303
312
|
const existing = history.find(item => item.operation_id === nextId);
|
|
304
313
|
if (existing && (existing.predecessor !== operationId || existing.turn_id !== turnId || existing.prompt_sha256 !== promptSha || existing.ordinal !== ordinal))
|
|
305
314
|
throw error;
|
|
306
|
-
|
|
315
|
+
let wait;
|
|
316
|
+
try {
|
|
317
|
+
wait = existing ? Math.max(0, Date.parse(existing.not_before) - Date.now()) : capacityDelay(failure, ordinal);
|
|
318
|
+
}
|
|
319
|
+
catch (cause) {
|
|
320
|
+
if (input.atomicIntent)
|
|
321
|
+
input.retainFailure?.(operationId, refusal);
|
|
322
|
+
const metadata = cause;
|
|
323
|
+
throw new AppError(metadata.code ?? "capacity_retry_metadata_invalid", metadata.message ?? "Invalid provider retry metadata", 1, { ...metadata.details, policy: CAPACITY_POLICY, attempts: refusals.length, root_operation_id: rootId, primary_error: input.error, last_error: failure });
|
|
324
|
+
}
|
|
307
325
|
if (!Number.isFinite(wait) || Date.now() + wait > 8.64e15 || input.deadline !== undefined && Date.now() + wait >= input.deadline) {
|
|
308
326
|
if (input.atomicIntent)
|
|
309
327
|
input.retainFailure?.(operationId, refusal);
|