@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
|
@@ -6,7 +6,7 @@ import { AppError } from "../shared/errors.js";
|
|
|
6
6
|
import { agyWorkspaceHooks } from "../harness-runtime/lib/agy-hooks.mjs";
|
|
7
7
|
/** Local environment/credential files are never an exported reviewer or EVAL input. */
|
|
8
8
|
export function localEnvironmentAllowed(source) {
|
|
9
|
-
const name = path.basename(source);
|
|
9
|
+
const name = path.basename(source).normalize("NFC").toLowerCase();
|
|
10
10
|
return name === ".env.example" || (name !== ".env" && !name.startsWith(".env."));
|
|
11
11
|
}
|
|
12
12
|
/** A damaged/inaccessible repository is not an intentional gitless workspace. */
|
package/dist/storage/database.js
CHANGED
|
@@ -27,6 +27,18 @@ function closeAfterOpenFailure(db, error) {
|
|
|
27
27
|
const require = createRequire(import.meta.url);
|
|
28
28
|
const { DatabaseSync } = require("node:sqlite");
|
|
29
29
|
const resourceDatabases = new Map();
|
|
30
|
+
const resourceDatabaseIdentities = new WeakMap();
|
|
31
|
+
function assertResourceDatabaseIdentity(database) {
|
|
32
|
+
const expected = resourceDatabaseIdentities.get(database);
|
|
33
|
+
let actual;
|
|
34
|
+
try {
|
|
35
|
+
actual = fs.lstatSync(database.path);
|
|
36
|
+
}
|
|
37
|
+
catch { /* Refuse missing targets without reopening or initializing. */ }
|
|
38
|
+
if (!expected || !actual?.isFile() || actual.dev !== expected.dev || actual.ino !== expected.ino) {
|
|
39
|
+
throw new AppError("resource_storage_changed", "The cached resource registry no longer matches its physical database; close the owned connection before retrying", 1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
30
42
|
const publicationScopes = [];
|
|
31
43
|
/** Defer committed cross-store projections until the surrounding writer exits. */
|
|
32
44
|
export function withDeferredPublications(operation) {
|
|
@@ -334,6 +346,31 @@ export function ensureWorkLaunchStorage(db) {
|
|
|
334
346
|
CREATE INDEX IF NOT EXISTS idx_work_launches_status ON work_launches(status, updated_at DESC);
|
|
335
347
|
`);
|
|
336
348
|
}
|
|
349
|
+
/** Exclusive fresh creation; never reinterpret an empty/old database as fresh. */
|
|
350
|
+
function claimNewStore(file) {
|
|
351
|
+
for (const candidate of [file, `${file}-wal`, `${file}-shm`, `${file}-journal`]) {
|
|
352
|
+
try {
|
|
353
|
+
fs.lstatSync(candidate);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
if (error.code === "ENOENT")
|
|
357
|
+
continue;
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
throw new AppError("store_already_exists", "Fresh store creation refuses existing database or sidecar files", 1, { effect: "no_effect", path: candidate });
|
|
361
|
+
}
|
|
362
|
+
ensureDir(path.dirname(file));
|
|
363
|
+
let fd;
|
|
364
|
+
try {
|
|
365
|
+
fd = fs.openSync(file, "wx", 0o600);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
if (error.code === "EEXIST")
|
|
369
|
+
throw new AppError("store_already_exists", "Another owner already created this store", 1, { effect: "no_effect", path: file });
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
fs.closeSync(fd);
|
|
373
|
+
}
|
|
337
374
|
export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hookDeadlineAt) {
|
|
338
375
|
channel("dd-flow.sqlite.ingress").publish({ phase: "store_open", store_kind: "run" });
|
|
339
376
|
if (hookDeadlineAt !== undefined && !Number.isSafeInteger(hookDeadlineAt))
|
|
@@ -348,7 +385,11 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hoo
|
|
|
348
385
|
if (mode !== "read_existing")
|
|
349
386
|
assertImportAdmission(ddFlowHome, importPermitId);
|
|
350
387
|
const dbPath = path.join(ddFlowHome, "db.sqlite");
|
|
388
|
+
if (mode === "create_new")
|
|
389
|
+
claimNewStore(dbPath);
|
|
351
390
|
const exists = fs.existsSync(dbPath);
|
|
391
|
+
if (mode === "prepared_existing" && (!exists || !fs.lstatSync(dbPath).isFile()))
|
|
392
|
+
throw new AppError("run_storage_unprepared", "Prepare a regular existing RUN store before execution admission", 1);
|
|
352
393
|
if (mode === "read_existing" && !exists) {
|
|
353
394
|
return emptyReadOnlyDatabase(dbPath);
|
|
354
395
|
}
|
|
@@ -387,7 +428,12 @@ export function getDatabase(ddFlowHome, mode = "initialize", importPermitId, hoo
|
|
|
387
428
|
if (mode !== "read_existing" && mode !== "reconcile") {
|
|
388
429
|
try {
|
|
389
430
|
const initialize = () => { migrate(db); ensureRecoveryStorage(db); ensureLifecycleInvocationStorage(db); };
|
|
390
|
-
if (mode
|
|
431
|
+
if (mode === "prepared_existing") {
|
|
432
|
+
const required = readStoreWriterContract(db);
|
|
433
|
+
if (required !== STORE_WRITER_VERSION)
|
|
434
|
+
throw new AppError("store_writer_incompatible", "Existing RUN store has no compatible writer contract", 1, { required, supported: STORE_WRITER_VERSION });
|
|
435
|
+
}
|
|
436
|
+
else if (mode !== "hook") {
|
|
391
437
|
const fresh = !db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get();
|
|
392
438
|
if (fresh || readStoreWriterContract(db) !== null)
|
|
393
439
|
executeStoreSchema(db, initialize, fresh);
|
|
@@ -486,6 +532,11 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
486
532
|
deadline?.("resource_open");
|
|
487
533
|
const dbPath = path.join(resourceHome, "runtime.sqlite");
|
|
488
534
|
const cached = resourceDatabases.get(dbPath);
|
|
535
|
+
if (mode === "create_new") {
|
|
536
|
+
if (cached)
|
|
537
|
+
throw new AppError("store_already_exists", "Fresh store creation refuses a cached resource owner", 1, { effect: "no_effect", path: dbPath });
|
|
538
|
+
claimNewStore(dbPath);
|
|
539
|
+
}
|
|
489
540
|
if (mode === "read_existing" || mode === "read_isolated") {
|
|
490
541
|
// Reuse a current transaction's view without exposing its write capability.
|
|
491
542
|
if (cached && mode === "read_existing") {
|
|
@@ -494,6 +545,7 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
494
545
|
}
|
|
495
546
|
if (!fs.existsSync(dbPath))
|
|
496
547
|
return emptyReadOnlyDatabase(dbPath);
|
|
548
|
+
channel("dd-flow.sqlite.ingress").publish({ phase: "store_connection", store_kind: "resource" });
|
|
497
549
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
498
550
|
try {
|
|
499
551
|
db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}; PRAGMA query_only = ON`);
|
|
@@ -510,6 +562,8 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
510
562
|
if (mode === "prepared_existing" && !fs.existsSync(dbPath))
|
|
511
563
|
throw new AppError("resource_storage_unprepared", "Prepare the resource registry before maintenance", 1);
|
|
512
564
|
if (cached) {
|
|
565
|
+
if (mode === "prepared_existing")
|
|
566
|
+
assertResourceDatabaseIdentity(cached);
|
|
513
567
|
const bounded = deadlineAt === undefined ? cached : cached.withDeadline(deadlineAt);
|
|
514
568
|
if (mode === "prepared_existing")
|
|
515
569
|
assertResourceStoreReady({ prepare: sql => ({ get: (...params) => bounded.get(sql, params), all: (...params) => bounded.all(sql, params), run: (...params) => bounded.run(sql, params) }) });
|
|
@@ -517,9 +571,11 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
517
571
|
}
|
|
518
572
|
if (mode !== "prepared_existing")
|
|
519
573
|
ensureDir(resourceHome);
|
|
574
|
+
channel("dd-flow.sqlite.ingress").publish({ phase: "store_connection", store_kind: "resource" });
|
|
520
575
|
const db = new DatabaseSync(dbPath);
|
|
521
|
-
db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}`);
|
|
522
576
|
try {
|
|
577
|
+
channel("dd-flow.sqlite.ingress").publish({ phase: "store_writer_registration", store_kind: "resource" });
|
|
578
|
+
db.exec(`PRAGMA busy_timeout = ${deadline?.("resource_open") ?? 4000}`);
|
|
523
579
|
registerStoreWriter(db);
|
|
524
580
|
}
|
|
525
581
|
catch (error) {
|
|
@@ -535,8 +591,14 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
535
591
|
catch (error) {
|
|
536
592
|
return closeAfterOpenFailure(db, error);
|
|
537
593
|
}
|
|
538
|
-
|
|
539
|
-
|
|
594
|
+
let fresh;
|
|
595
|
+
try {
|
|
596
|
+
db.exec("PRAGMA foreign_keys = ON");
|
|
597
|
+
fresh = !db.prepare("SELECT 1 FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%' LIMIT 1").get();
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
return closeAfterOpenFailure(db, error);
|
|
601
|
+
}
|
|
540
602
|
try {
|
|
541
603
|
if (mode !== "prepared_existing")
|
|
542
604
|
executeStoreSchema(db, `
|
|
@@ -587,6 +649,15 @@ export function getResourceDatabase(resourceHome, mode = "initialize", deadlineA
|
|
|
587
649
|
resourceDatabases.delete(dbPath);
|
|
588
650
|
db.close?.();
|
|
589
651
|
});
|
|
652
|
+
try {
|
|
653
|
+
const identity = fs.lstatSync(dbPath);
|
|
654
|
+
if (!identity.isFile())
|
|
655
|
+
throw new AppError("resource_storage_changed", "Resource registry target is not a regular file", 1);
|
|
656
|
+
resourceDatabaseIdentities.set(database, { dev: identity.dev, ino: identity.ino });
|
|
657
|
+
}
|
|
658
|
+
catch (error) {
|
|
659
|
+
return closeAfterOpenFailure(db, error);
|
|
660
|
+
}
|
|
590
661
|
resourceDatabases.set(dbPath, database);
|
|
591
662
|
return deadlineAt === undefined ? database : database.withDeadline(deadlineAt);
|
|
592
663
|
}
|
|
@@ -644,6 +715,7 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
|
|
|
644
715
|
let afterCommit = [];
|
|
645
716
|
let manualFrames = [];
|
|
646
717
|
let readDepth = 0;
|
|
718
|
+
let ownedWriterToken = null;
|
|
647
719
|
const writerTiming = channel("dd-flow.sqlite.writer");
|
|
648
720
|
const ingressPhase = channel("dd-flow.sqlite.ingress");
|
|
649
721
|
const storeKind = path.basename(dbPath) === "runtime.sqlite" ? "resource" : "run";
|
|
@@ -674,6 +746,7 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
|
|
|
674
746
|
bound("writer_reservation");
|
|
675
747
|
db.exec("BEGIN IMMEDIATE");
|
|
676
748
|
reserved = true;
|
|
749
|
+
ownedWriterToken = Object.freeze({});
|
|
677
750
|
if (ingressPhase.hasSubscribers)
|
|
678
751
|
ingressPhase.publish({ phase: "writer_reserved", store_kind: storeKind });
|
|
679
752
|
};
|
|
@@ -841,6 +914,9 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
|
|
|
841
914
|
assertUsable();
|
|
842
915
|
if (readDepth)
|
|
843
916
|
throw new AppError("read_snapshot_write", "Cannot execute SQL inside a read snapshot", 1);
|
|
917
|
+
// Raw SQL may replace the transaction (including behind comments).
|
|
918
|
+
// Do not infer continuity by parsing SQL or its final isTransaction.
|
|
919
|
+
ownedWriterToken = null;
|
|
844
920
|
try {
|
|
845
921
|
db.exec(sql);
|
|
846
922
|
applyManualBoundary(sql);
|
|
@@ -866,9 +942,11 @@ function databaseFacade(dbPath, writable, db, close = () => db.close?.(), deadli
|
|
|
866
942
|
}
|
|
867
943
|
},
|
|
868
944
|
execSchema: (sql) => { assertUsable(); if (db.isTransaction && !reserved)
|
|
869
|
-
throw new AppError("write_transaction_unowned", "Cannot change schema inside an unowned transaction", 1); executeStoreSchema(db, sql); },
|
|
945
|
+
throw new AppError("write_transaction_unowned", "Cannot change schema inside an unowned transaction", 1); ownedWriterToken = null; executeStoreSchema(db, sql); },
|
|
870
946
|
beginWriteTransaction,
|
|
871
947
|
writeTransaction,
|
|
948
|
+
isOwnedWriteTransaction: () => { assertUsable(); return writable && reserved && ownedWriterToken !== null && db.isTransaction && readDepth === 0; },
|
|
949
|
+
ownedWriteTransactionToken: () => { assertUsable(); return writable && reserved && db.isTransaction && readDepth === 0 ? ownedWriterToken : null; },
|
|
872
950
|
readSnapshot,
|
|
873
951
|
afterCommit: (operation) => { assertUsable(); if (readDepth)
|
|
874
952
|
throw new AppError("read_snapshot_write", "Cannot queue publication inside a read snapshot", 1); if (db.isTransaction)
|
|
@@ -904,7 +982,7 @@ function errorDetails(error) {
|
|
|
904
982
|
return { message: String(value?.message ?? error), ...(typeof value?.code === "string" ? { code: value.code } : {}), ...(typeof value?.errcode === "number" ? { sqlite_extended_code: value.errcode } : {}) };
|
|
905
983
|
}
|
|
906
984
|
function configureDatabase(db, mode, ingressDeadline) {
|
|
907
|
-
if (mode !== "read_existing") {
|
|
985
|
+
if (mode !== "read_existing" && mode !== "prepared_existing") {
|
|
908
986
|
// Concurrent first openers can collide while changing journal mode, even
|
|
909
987
|
// with busy_timeout set. Retrying this configuration does not replay writes.
|
|
910
988
|
const deadline = performance.now() + 4000, pause = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -5,6 +5,15 @@ export const STORE_WRITER_VERSION = 2;
|
|
|
5
5
|
const version = STORE_WRITER_VERSION;
|
|
6
6
|
const table = "dd_flow_writer_contract";
|
|
7
7
|
const quote = (name) => `"${name.replaceAll('"', '""')}"`;
|
|
8
|
+
/** The executing writer's fence definition, also used for readonly admission. */
|
|
9
|
+
export function storeWriterTriggerSql(name, action) {
|
|
10
|
+
const key = crypto.createHash("sha256").update(name).digest("hex").slice(0, 24);
|
|
11
|
+
const trigger = `dd_flow_writer_${key}_${action.toLowerCase()}`;
|
|
12
|
+
return { name: trigger, sql: `CREATE TRIGGER ${quote(trigger)} BEFORE ${action} ON ${quote(name)} BEGIN
|
|
13
|
+
SELECT CASE WHEN dd_flow_writer_version() <> (SELECT version FROM ${table} WHERE id = 1)
|
|
14
|
+
OR (SELECT COUNT(*) FROM ${table}) <> 1 THEN RAISE(ABORT, 'store_writer_incompatible') END;
|
|
15
|
+
END;` };
|
|
16
|
+
}
|
|
8
17
|
/** Publish additive schema and its writer fences in the same transaction. */
|
|
9
18
|
export function executeStoreSchema(db, sql, initialize = false) {
|
|
10
19
|
const ownsTransaction = !db.isTransaction;
|
|
@@ -64,14 +73,9 @@ export function installStoreWriterContract(db) {
|
|
|
64
73
|
throw new AppError("store_writer_incompatible", "Store writer contract changed before migration", 1);
|
|
65
74
|
const tables = db.prepare("SELECT name FROM sqlite_schema WHERE type = 'table' AND name NOT LIKE 'sqlite_%'").all();
|
|
66
75
|
for (const target of tables) {
|
|
67
|
-
const key = crypto.createHash("sha256").update(target.name).digest("hex").slice(0, 24);
|
|
68
76
|
for (const action of ["INSERT", "UPDATE", "DELETE"]) {
|
|
69
|
-
const trigger =
|
|
70
|
-
db.exec(`DROP TRIGGER IF EXISTS ${trigger};
|
|
71
|
-
CREATE TRIGGER ${trigger} BEFORE ${action} ON ${quote(target.name)} BEGIN
|
|
72
|
-
SELECT CASE WHEN dd_flow_writer_version() <> (SELECT version FROM ${table} WHERE id = 1)
|
|
73
|
-
OR (SELECT COUNT(*) FROM ${table}) <> 1 THEN RAISE(ABORT, 'store_writer_incompatible') END;
|
|
74
|
-
END;`);
|
|
77
|
+
const trigger = storeWriterTriggerSql(target.name, action);
|
|
78
|
+
db.exec(`DROP TRIGGER IF EXISTS ${quote(trigger.name)}; ${trigger.sql}`);
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
if (ownsTransaction)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"version": "0.9.0-beta.
|
|
3
|
+
"version": "0.9.0-beta.138",
|
|
4
4
|
"description": "Mechanical runtime CLI for dd-flow workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@10.23.0",
|
|
@@ -27,7 +27,13 @@
|
|
|
27
27
|
"build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"lint": "eslint . --max-warnings=0",
|
|
30
|
-
"test": "pnpm test:release && pnpm test:integration && pnpm test:runtime-sensitive",
|
|
30
|
+
"test": "pnpm test:hybrid && pnpm test:e2 && pnpm test:e3 && pnpm test:release && pnpm test:integration && pnpm test:runtime-sensitive",
|
|
31
|
+
"test:hybrid": "node --test scripts/hybrid-contracts.test.mjs",
|
|
32
|
+
"test:hybrid:canon": "node --import ./scripts/source-test-loader.mjs --test scripts/hybrid-canon.test.mjs scripts/hybrid-inventory.test.mjs scripts/hybrid-consumers.test.mjs scripts/hybrid-schema-copies.test.mjs",
|
|
33
|
+
"test:e2": "node --import ./scripts/source-test-loader.mjs --test --test-concurrency=1 scripts/e2-*.test.mjs",
|
|
34
|
+
"test:e2:installed": "node --test scripts/verify-e2-built-cli.test.mjs scripts/verify-e2-installed-cli.test.mjs",
|
|
35
|
+
"test:e3": "node --import ./scripts/source-test-loader.mjs --test --test-concurrency=1 scripts/e3-*.test.mjs",
|
|
36
|
+
"test:e3:canon": "node --import ./scripts/source-test-loader.mjs --test scripts/verify-e3-canon.test.mjs",
|
|
31
37
|
"test:release": "node --test scripts/verify-release-build.test.mjs && vitest run --project=release",
|
|
32
38
|
"test:integration": "vitest run --project='integration-*'",
|
|
33
39
|
"test:fast": "vitest run --project=integration-fast",
|
|
@@ -38,7 +44,9 @@
|
|
|
38
44
|
"version-packages": "changeset version",
|
|
39
45
|
"release:candidate:pack": "node scripts/release-candidate.mjs pack",
|
|
40
46
|
"release:candidate:accept": "node scripts/release-candidate.mjs accept",
|
|
41
|
-
"release:publish": "node scripts/publish-release.mjs"
|
|
47
|
+
"release:publish": "node scripts/publish-release.mjs",
|
|
48
|
+
"release:hybrid:produce": "node tools/produce-hybrid-release.mjs",
|
|
49
|
+
"release:canon:archive": "node tools/create-canon-release.mjs"
|
|
42
50
|
},
|
|
43
51
|
"devDependencies": {
|
|
44
52
|
"@opencode/client": "2.0.28",
|
|
@@ -46,6 +54,7 @@
|
|
|
46
54
|
"@changesets/cli": "^2.31.0",
|
|
47
55
|
"@eslint/js": "^9.39.1",
|
|
48
56
|
"@types/node": "^24.10.1",
|
|
57
|
+
"@types/semver": "7.8.0",
|
|
49
58
|
"eslint": "^9.39.1",
|
|
50
59
|
"typescript": "^5.9.3",
|
|
51
60
|
"typescript-eslint": "^8.47.0",
|
|
@@ -54,6 +63,7 @@
|
|
|
54
63
|
"dependencies": {
|
|
55
64
|
"ajv": "^8.20.0",
|
|
56
65
|
"jsonc-parser": "3.3.1",
|
|
66
|
+
"semver": "7.8.1",
|
|
57
67
|
"shell-quote": "^1.10.0",
|
|
58
68
|
"smol-toml": "^1.6.1",
|
|
59
69
|
"tree-sitter": "0.25.1",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { produceCanonArchive } from "../dist/services/release-provenance.js";
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2), names = ["canon-root", "commit", "output"];
|
|
8
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
9
|
+
process.stdout.write("Usage: node tools/create-canon-release.mjs --canon-root <checkout> --commit <40-hex-commit> --output <new-archive.tgz>\nCreates an immutable Git-object archive with reviewed aliases materialized. Does not publish or qualify a release.\n");
|
|
10
|
+
} else {
|
|
11
|
+
let temporary;
|
|
12
|
+
let ownsTemporary = false;
|
|
13
|
+
try {
|
|
14
|
+
const options = new Map();
|
|
15
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
16
|
+
const name = args[index]?.slice(2), value = args[index + 1];
|
|
17
|
+
if (!args[index]?.startsWith("--") || !names.includes(name) || options.has(name) || !value || value.startsWith("--")) throw new Error("invalid arguments");
|
|
18
|
+
options.set(name, value);
|
|
19
|
+
}
|
|
20
|
+
if (names.some(name => !options.has(name))) throw new Error("missing arguments");
|
|
21
|
+
const bytes = produceCanonArchive(options.get("canon-root"), options.get("commit"));
|
|
22
|
+
const output = path.resolve(options.get("output"));
|
|
23
|
+
temporary = path.join(path.dirname(output), `.canon-archive-${randomUUID()}.tmp`);
|
|
24
|
+
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
25
|
+
ownsTemporary = true;
|
|
26
|
+
try { fs.writeFileSync(fd, bytes); fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
|
|
27
|
+
fs.linkSync(temporary, output); // Atomic no-replace publication, including dangling symlink targets.
|
|
28
|
+
process.stdout.write(`${JSON.stringify({ ok: true, commit: options.get("commit"), output, bytes: bytes.length, sha256: createHash("sha256").update(bytes).digest("hex") })}\n`);
|
|
29
|
+
} catch {
|
|
30
|
+
process.stderr.write("Canonical archive production refused; check committed source and a new output path.\n");
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
} finally { if (temporary && ownsTemporary) fs.rmSync(temporary, { force: true }); }
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createPrivateKey } from "node:crypto";
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { publishReleaseBundle } from "../dist/services/release-producer.js";
|
|
5
|
+
import { readBoundedReleaseFile } from "../dist/services/release-source.js";
|
|
6
|
+
import { createReleaseCompatibility, installedCatalogVersions } from "../dist/services/release-compatibility.js";
|
|
7
|
+
import { getCliVersionReport } from "../dist/services/build-info.js";
|
|
8
|
+
import { verifyProducerSource, verifyCanonArchiveSource } from "../dist/services/release-provenance.js";
|
|
9
|
+
|
|
10
|
+
const names = ["payload-file", "canon-root", "canon-archive", "engine-archive", "key-file", "output", "channel"];
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
13
|
+
process.stdout.write(`Produce an immutable local signed release bundle from existing qualification evidence.\nUsage: node tools/produce-hybrid-release.mjs ${names.map(name => `--${name} <value>`).join(" ")}\nThis does not publish remotely or qualify a route. Private keys never enter the output.\n`);
|
|
14
|
+
} else {
|
|
15
|
+
try {
|
|
16
|
+
const options = new Map();
|
|
17
|
+
for (let index = 0; index < args.length; index += 2) {
|
|
18
|
+
const name = args[index]?.slice(2), value = args[index + 1];
|
|
19
|
+
if (!args[index]?.startsWith("--") || !names.includes(name) || options.has(name) || !value || value.startsWith("--")) throw new Error("invalid arguments");
|
|
20
|
+
options.set(name, value);
|
|
21
|
+
}
|
|
22
|
+
if (names.some(name => !options.has(name))) throw new Error("missing arguments");
|
|
23
|
+
const compatibility = createReleaseCompatibility({ channel: options.get("channel"), catalogVersions: installedCatalogVersions, routerVersion: getCliVersionReport().cli.version });
|
|
24
|
+
const payload = JSON.parse(readBoundedReleaseFile(options.get("payload-file"), 16 * 1024 * 1024).toString("utf8"));
|
|
25
|
+
const manifest = JSON.parse(Buffer.from(payload.manifest_base64, "base64").toString("utf8"));
|
|
26
|
+
verifyProducerSource(options.get("canon-root"), payload.source, manifest.canon.version);
|
|
27
|
+
const canonArchive = readBoundedReleaseFile(options.get("canon-archive"), 256 * 1024 * 1024);
|
|
28
|
+
verifyCanonArchiveSource(options.get("canon-root"), payload.source.commit, manifest.canon.archive, canonArchive);
|
|
29
|
+
const result = publishReleaseBundle(options.get("output"), {
|
|
30
|
+
payload,
|
|
31
|
+
privateKey: createPrivateKey(readBoundedReleaseFile(options.get("key-file"), 64 * 1024)), compatibility,
|
|
32
|
+
canonArchive,
|
|
33
|
+
engineArchive: readBoundedReleaseFile(options.get("engine-archive"), 256 * 1024 * 1024)
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`);
|
|
36
|
+
} catch {
|
|
37
|
+
// Key/parser/network excerpts must never appear in producer diagnostics.
|
|
38
|
+
process.stderr.write("Release production refused; check explicit inputs and qualification evidence.\n");
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
}
|