clinkx 1.0.0 → 1.1.1
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 +95 -0
- package/README.md +184 -112
- package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
- package/clinkx-workflows/dist/chairman-knobs.js +27 -0
- package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +10 -0
- package/clinkx-workflows/dist/clink-client.js +39 -1
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +29 -1
- package/clinkx-workflows/dist/config.js +35 -2
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -1
- package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
- package/clinkx-workflows/dist/council-execution-policy.js +110 -0
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +9 -8
- package/clinkx-workflows/dist/council-expander.js +18 -9
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
- package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
- package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
- package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/index.js +4 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
- package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
- package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
- package/clinkx-workflows/dist/delegate-signature.js +14 -0
- package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
- package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
- package/clinkx-workflows/dist/delegate-validate.js +51 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
- package/clinkx-workflows/dist/engine.d.ts +113 -3
- package/clinkx-workflows/dist/engine.js +476 -80
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +22 -2
- package/clinkx-workflows/dist/errors.js +30 -2
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fidelity.d.ts +47 -1
- package/clinkx-workflows/dist/fidelity.js +54 -1
- package/clinkx-workflows/dist/fidelity.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
- package/clinkx-workflows/dist/fingerprint.js +32 -9
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.js +53 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/logger.d.ts +1 -1
- package/clinkx-workflows/dist/logger.js +17 -3
- package/clinkx-workflows/dist/logger.js.map +1 -1
- package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
- package/clinkx-workflows/dist/monotonic-clock.js +22 -0
- package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
- package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
- package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
- package/clinkx-workflows/dist/roster-contract.js +180 -0
- package/clinkx-workflows/dist/roster-contract.js.map +1 -0
- package/clinkx-workflows/dist/rosters.d.ts +14 -0
- package/clinkx-workflows/dist/rosters.js +169 -0
- package/clinkx-workflows/dist/rosters.js.map +1 -0
- package/clinkx-workflows/dist/run-health.d.ts +7 -0
- package/clinkx-workflows/dist/run-health.js +25 -0
- package/clinkx-workflows/dist/run-health.js.map +1 -0
- package/clinkx-workflows/dist/schema.d.ts +64 -0
- package/clinkx-workflows/dist/schema.js +24 -0
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/server.js +2 -0
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +15 -2
- package/clinkx-workflows/dist/shutdown.js +60 -16
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +23 -0
- package/clinkx-workflows/dist/state.d.ts +5 -0
- package/clinkx-workflows/dist/state.js +128 -21
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
- package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
- package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
- package/clinkx-workflows/dist/types.d.ts +6 -0
- package/clinkx-workflows/dist/validators/council.d.ts +50 -50
- package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
- package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
- package/clinkx-workflows/dist/workflow-tools.js +760 -168
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
- package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
- package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/member.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/workflows/council-answer.yaml +3 -0
- package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
- package/clinkx-workflows/workflows/council-debug.yaml +3 -0
- package/clinkx-workflows/workflows/council-default.yaml +7 -0
- package/clinkx-workflows/workflows/council-discover.yaml +3 -0
- package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
- package/conf/adapters/agy.json +9 -2
- package/conf/adapters/claude.json +7 -0
- package/conf/adapters/codex.json +7 -0
- package/conf/adapters/glm.json +19 -3
- package/conf/adapters/grok.json +9 -2
- package/conf/adapters/hapi/agy.json +9 -2
- package/conf/adapters/hapi/claude.json +7 -0
- package/conf/adapters/hapi/codex.json +7 -0
- package/conf/adapters/hapi/glm.json +20 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +17 -3
- package/dist/activity-journal.js +404 -79
- package/dist/activity-journal.js.map +1 -1
- package/dist/codex/agent-tools.js +36 -0
- package/dist/codex/agent-tools.js.map +1 -1
- package/dist/codex/app-server-client.d.ts +3 -0
- package/dist/codex/app-server-client.js +11 -0
- package/dist/codex/app-server-client.js.map +1 -1
- package/dist/codex/app-server-registry.d.ts +18 -0
- package/dist/codex/app-server-registry.js +88 -2
- package/dist/codex/app-server-registry.js.map +1 -1
- package/dist/codex/client-types.d.ts +7 -1
- package/dist/codex/protocol.d.ts +4 -4
- package/dist/config.d.ts +220 -23
- package/dist/config.js +104 -1
- package/dist/config.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +13 -0
- package/dist/context-builder/bundle.js +46 -0
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
- package/dist/context-builder/delegate-finalizer-effective.js +82 -0
- package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +18 -0
- package/dist/context-builder/delegate-packet.js +73 -9
- package/dist/context-builder/delegate-packet.js.map +1 -1
- package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
- package/dist/context-builder/delegate-reconstruction.js +110 -41
- package/dist/context-builder/delegate-reconstruction.js.map +1 -1
- package/dist/context-builder/delegate-validation.d.ts +31 -0
- package/dist/context-builder/delegate-validation.js +87 -10
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +9 -0
- package/dist/context-builder/delegate.js +210 -59
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +1 -1
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery-progress.d.ts +25 -0
- package/dist/context-builder/discovery-progress.js +64 -0
- package/dist/context-builder/discovery-progress.js.map +1 -0
- package/dist/context-builder/discovery-watchdog.d.ts +15 -0
- package/dist/context-builder/discovery-watchdog.js +39 -0
- package/dist/context-builder/discovery-watchdog.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +7 -0
- package/dist/context-builder/discovery.js +119 -7
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/external-gaps.d.ts +73 -0
- package/dist/context-builder/external-gaps.js +248 -0
- package/dist/context-builder/external-gaps.js.map +1 -0
- package/dist/context-builder/globs.d.ts +1 -0
- package/dist/context-builder/globs.js +15 -0
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +42 -1
- package/dist/context-builder/handler.js +690 -62
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/invocation-sidecar.d.ts +41 -0
- package/dist/context-builder/invocation-sidecar.js +189 -0
- package/dist/context-builder/invocation-sidecar.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +51 -51
- package/dist/context-builder/mutation-lock.d.ts +32 -0
- package/dist/context-builder/mutation-lock.js +321 -0
- package/dist/context-builder/mutation-lock.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +32 -0
- package/dist/context-builder/review-coverage.js +130 -23
- package/dist/context-builder/review-coverage.js.map +1 -1
- package/dist/context-builder/schema.d.ts +76 -1
- package/dist/context-builder/schema.js +21 -1
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +31 -0
- package/dist/context-builder/secrets.js +871 -84
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/status.d.ts +1 -1
- package/dist/context-builder/status.js +1 -0
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +29 -5
- package/dist/context-builder/storage.js +311 -236
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/versions.d.ts +19 -4
- package/dist/context-builder/versions.js +19 -4
- package/dist/context-builder/versions.js.map +1 -1
- package/dist/context-builder/workflow-runner.d.ts +6 -0
- package/dist/context-builder/workflow-runner.js +363 -173
- package/dist/context-builder/workflow-runner.js.map +1 -1
- package/dist/dashboard-adapter-catalog.js +7 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -1
- package/dist/dashboard-cli.d.ts +2 -0
- package/dist/dashboard-cli.js +40 -2
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-companion/client.d.ts +1 -0
- package/dist/dashboard-companion/client.js +22 -3
- package/dist/dashboard-companion/client.js.map +1 -1
- package/dist/dashboard-companion/protocol.js +4 -1
- package/dist/dashboard-companion/protocol.js.map +1 -1
- package/dist/dashboard-gateway/index.d.ts +1 -0
- package/dist/dashboard-gateway/index.js +33 -2
- package/dist/dashboard-gateway/index.js.map +1 -1
- package/dist/handler.js +5 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +145 -7
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +18 -3
- package/dist/logger.js.map +1 -1
- package/dist/parsers/agy-stream-json.d.ts +14 -0
- package/dist/parsers/agy-stream-json.js +172 -0
- package/dist/parsers/agy-stream-json.js.map +1 -0
- package/dist/parsers/extract.js +5 -1
- package/dist/parsers/extract.js.map +1 -1
- package/dist/parsers/observed-invocation.d.ts +20 -0
- package/dist/parsers/observed-invocation.js +42 -0
- package/dist/parsers/observed-invocation.js.map +1 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.js +60 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/progress-report/lock.d.ts +74 -0
- package/dist/progress-report/lock.js +343 -0
- package/dist/progress-report/lock.js.map +1 -0
- package/dist/progress-report/schema.d.ts +808 -0
- package/dist/progress-report/schema.js +119 -0
- package/dist/progress-report/schema.js.map +1 -0
- package/dist/progress-report/store.d.ts +66 -0
- package/dist/progress-report/store.js +766 -0
- package/dist/progress-report/store.js.map +1 -0
- package/dist/progress-report/tools.d.ts +20 -0
- package/dist/progress-report/tools.js +141 -0
- package/dist/progress-report/tools.js.map +1 -0
- package/dist/prompt-mode.d.ts +1 -1
- package/dist/prompt-mode.js +7 -2
- package/dist/prompt-mode.js.map +1 -1
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +10 -3
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +50 -0
- package/dist/safety.js +182 -1
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +94 -8
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +1 -1
- package/dist/session-log-stream.js +280 -33
- package/dist/session-log-stream.js.map +1 -1
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/docs/configuration.md +426 -0
- package/docs/security.md +293 -0
- package/package.json +5 -5
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { link as fsLink, open as fsOpen, mkdir as fsMkdir, readdir as fsReaddir, readFile as fsReadFile, rename as fsRename, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
1
|
+
import { link as fsLink, lstat as fsLstat, open as fsOpen, mkdir as fsMkdir, readdir as fsReaddir, readFile as fsReadFile, rename as fsRename, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
3
|
import { hostname } from "node:os";
|
|
4
4
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
@@ -9,6 +9,8 @@ import { boundContextBuilderOmissions, boundContextBuilderWarnings, } from "./di
|
|
|
9
9
|
import { cloneContextBuilderDelegateReceiptMetadata, CONTEXT_BUILDER_DELEGATE_SCHEMA_VERSION, CONTEXT_BUILDER_LEGACY_ARTIFACT_KEYS, CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS, CONTEXT_BUILDER_NO_DELEGATE_SCHEMA_VERSION, CONTEXT_BUILDER_SCHEMA_VERSION, isContextBuilderDelegateReceiptMetadata, } from "./receipt.js";
|
|
10
10
|
import { validateCallerRunId } from "./run-id.js";
|
|
11
11
|
import { scanSecretPath } from "./secrets.js";
|
|
12
|
+
import { CONTEXT_BUILDER_INVOCATION_AUDIT_FILENAME, readContextBuilderInvocationAuditSidecar, } from "./invocation-sidecar.js";
|
|
13
|
+
import { withContextBuilderMutationLock, } from "./mutation-lock.js";
|
|
12
14
|
export const CONTEXT_BUILDER_ARTIFACT_FILE_NAMES = Object.freeze({
|
|
13
15
|
manifest_json: "manifest.json",
|
|
14
16
|
bundle_md: "bundle.md",
|
|
@@ -26,6 +28,7 @@ const DEFAULT_STORAGE_OPS = {
|
|
|
26
28
|
rename: fsRename,
|
|
27
29
|
rm: fsRm,
|
|
28
30
|
open: fsOpen,
|
|
31
|
+
lstat: fsLstat,
|
|
29
32
|
link: fsLink,
|
|
30
33
|
stat: fsStat,
|
|
31
34
|
readdir: fsReaddir,
|
|
@@ -46,15 +49,38 @@ const CONTEXT_BUILDER_METRIC_TYPES = Object.freeze({
|
|
|
46
49
|
truncated: "boolean",
|
|
47
50
|
});
|
|
48
51
|
const CONTEXT_BUILDER_INTERNAL_CAP_KEYS = Object.keys(CONTEXT_BUILDER_INTERNAL_CAP_DEFAULTS);
|
|
49
|
-
const CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX = ".mutation.lock";
|
|
50
|
-
const CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS = 1_000;
|
|
51
|
-
const CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS = 10;
|
|
52
|
-
const CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS = 10 * 60 * 1_000;
|
|
53
52
|
export const WORKFLOW_CONTEXT_CACHE_MAX_ENTRIES_ENV = "CLINKX_WORKFLOWS_CONTEXT_CACHE_MAX_ENTRIES";
|
|
54
53
|
export const WORKFLOW_CONTEXT_CACHE_TTL_SECONDS_ENV = "CLINKX_WORKFLOWS_CONTEXT_CACHE_TTL_SECONDS";
|
|
55
54
|
export const DEFAULT_WORKFLOW_CONTEXT_CACHE_MAX_ENTRIES = 50;
|
|
56
55
|
export const DEFAULT_WORKFLOW_CONTEXT_CACHE_TTL_SECONDS = 86_400;
|
|
57
56
|
const DEFAULT_WORKFLOW_CONTEXT_TEMP_TTL_SECONDS = 3_600;
|
|
57
|
+
export class ContextBuilderAncillaryPromotionError extends ExecutionError {
|
|
58
|
+
reasonCode;
|
|
59
|
+
constructor(reasonCode) {
|
|
60
|
+
super("context_builder invocation audit ancillary promotion failed");
|
|
61
|
+
this.name = "ContextBuilderAncillaryPromotionError";
|
|
62
|
+
this.reasonCode = reasonCode;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export class ContextBuilderRunClaimConflictError extends InvalidParamsError {
|
|
66
|
+
constructor(runId) {
|
|
67
|
+
super(`context_builder run_id already exists: ${runId}`);
|
|
68
|
+
this.name = "ContextBuilderRunClaimConflictError";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export class ContextBuilderActiveRunClaimConflictError extends ExecutionError {
|
|
72
|
+
constructor(runId) {
|
|
73
|
+
super(`context_builder run_id conflicts with an incomplete or active run: ${runId}`);
|
|
74
|
+
this.name = "ContextBuilderActiveRunClaimConflictError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class ContextBuilderCommittedRunChangedError extends ExecutionError {
|
|
78
|
+
constructor(runId) {
|
|
79
|
+
super(`context_builder committed run changed before lease admission: ${runId}`);
|
|
80
|
+
this.name = "ContextBuilderCommittedRunChangedError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const HELD_CONTEXT_BUILDER_MUTATION_LOCKS = Symbol("heldContextBuilderMutationLocks");
|
|
58
84
|
export function resolveContextBuilderRunPaths(runsDir, runId) {
|
|
59
85
|
validateCallerRunId(runId);
|
|
60
86
|
const resolvedRunsDir = resolve(runsDir);
|
|
@@ -119,7 +145,7 @@ export async function createContextBuilderRun(input, ops = DEFAULT_STORAGE_OPS)
|
|
|
119
145
|
}
|
|
120
146
|
export async function writeContextBuilderArtifact(storage, key, content, ops = DEFAULT_STORAGE_OPS) {
|
|
121
147
|
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
122
|
-
await
|
|
148
|
+
await withContextBuilderStorageMutationLock(canonicalStorage, ops, async () => {
|
|
123
149
|
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
124
150
|
await writeAtomicText(canonicalStorage.artifacts[key], content, ops, {
|
|
125
151
|
mustNotExistPath: canonicalStorage.manifestJson,
|
|
@@ -130,7 +156,7 @@ export async function commitContextBuilderRun(storage, manifest, optionsOrOps =
|
|
|
130
156
|
const { options, ops } = normalizeCommitArguments(optionsOrOps, maybeOps);
|
|
131
157
|
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
132
158
|
const ownerNonce = options.ownerNonce ?? canonicalStorage.ownerNonce;
|
|
133
|
-
await
|
|
159
|
+
await withContextBuilderStorageMutationLock(canonicalStorage, ops, async () => {
|
|
134
160
|
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
135
161
|
await assertRequiredArtifactsReadable(canonicalStorage, ops);
|
|
136
162
|
throwIfCancelledBeforeManifest(options);
|
|
@@ -154,7 +180,7 @@ export async function writeContextBuilderArtifactsAndCommit(storage, contents, m
|
|
|
154
180
|
const ops = options.ops ?? DEFAULT_STORAGE_OPS;
|
|
155
181
|
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
156
182
|
const ownerNonce = options.ownerNonce ?? canonicalStorage.ownerNonce;
|
|
157
|
-
await
|
|
183
|
+
await withContextBuilderStorageMutationLock(canonicalStorage, ops, async () => {
|
|
158
184
|
await assertManifestAbsentForMutation(canonicalStorage, ops);
|
|
159
185
|
for (const [key, content] of Object.entries(contents)) {
|
|
160
186
|
if (options.signal?.aborted === true) {
|
|
@@ -182,66 +208,92 @@ export async function writeContextBuilderArtifactsAndCommit(storage, contents, m
|
|
|
182
208
|
}, { ownerNonce });
|
|
183
209
|
}
|
|
184
210
|
export async function promoteContextBuilderRun(tempRunDir, targetRunId, ops = DEFAULT_STORAGE_OPS) {
|
|
211
|
+
return await withPromotedContextBuilderRun(tempRunDir, targetRunId, async (storage) => storage, ops);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Acquire source before target, then retain the target lease through its
|
|
215
|
+
* caller's manifest re-read and materialization callback.
|
|
216
|
+
*/
|
|
217
|
+
export async function withPromotedContextBuilderRun(tempRunDir, targetRunId, callback, ops = DEFAULT_STORAGE_OPS) {
|
|
185
218
|
const resolvedTempRunDir = resolve(tempRunDir);
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
219
|
+
const tempStorage = resolveContextBuilderRunPaths(dirname(resolvedTempRunDir), basename(resolvedTempRunDir));
|
|
220
|
+
return await withCommittedContextBuilderRunMutationLease(tempStorage, async (sourceOps) => {
|
|
221
|
+
const tempManifest = await readContextBuilderManifest(tempStorage.manifestJson, sourceOps);
|
|
222
|
+
if (tempStorage.runDir !== resolvedTempRunDir
|
|
223
|
+
|| tempManifest.run_id !== tempStorage.runId) {
|
|
224
|
+
throw new ExecutionError("context_builder cannot promote an uncommitted or mismatched temp run");
|
|
225
|
+
}
|
|
226
|
+
let invocationAudit;
|
|
227
|
+
try {
|
|
228
|
+
invocationAudit = await readContextBuilderInvocationAuditSidecar(tempStorage.runDir);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
throw new ContextBuilderAncillaryPromotionError("invalid_source");
|
|
232
|
+
}
|
|
233
|
+
const targetStorage = resolveContextBuilderRunPaths(tempStorage.runsDir, targetRunId);
|
|
234
|
+
await claimContextBuilderRunDir(targetStorage, sourceOps);
|
|
235
|
+
let targetCommitted = false;
|
|
236
|
+
try {
|
|
237
|
+
await sourceOps.mkdir(targetStorage.artifactDir, { recursive: false, mode: 0o700 });
|
|
238
|
+
return await withContextBuilderStorageMutationLock(targetStorage, sourceOps, async (targetOps) => {
|
|
239
|
+
await assertManifestAbsentForMutation(targetStorage, targetOps);
|
|
240
|
+
const artifactEntries = Object.entries(tempManifest.artifacts);
|
|
241
|
+
for (const [key, sourcePath] of artifactEntries) {
|
|
242
|
+
if (key === "manifest_json") {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const targetPath = targetStorage.artifacts[key];
|
|
246
|
+
const sourceContent = await targetOps.readFile(sourcePath, "utf8");
|
|
247
|
+
const targetContent = key === "summary_json"
|
|
248
|
+
? rewriteContextBuilderSummaryRunId(sourceContent, targetRunId)
|
|
249
|
+
: sourceContent;
|
|
250
|
+
await writeAtomicText(targetPath, targetContent, targetOps, {
|
|
251
|
+
mustNotExistPath: targetStorage.manifestJson,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (invocationAudit !== undefined) {
|
|
255
|
+
try {
|
|
256
|
+
await writeAtomicText(join(targetStorage.runDir, CONTEXT_BUILDER_INVOCATION_AUDIT_FILENAME), invocationAudit.serialized, targetOps, { mustNotExistPath: targetStorage.manifestJson });
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
throw new ContextBuilderAncillaryPromotionError("copy_failed");
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const targetArtifacts = Object.fromEntries(Object.keys(tempManifest.artifacts).map((key) => [
|
|
263
|
+
key,
|
|
264
|
+
targetStorage.artifacts[key],
|
|
265
|
+
]));
|
|
266
|
+
const targetManifest = {
|
|
267
|
+
...tempManifest,
|
|
268
|
+
run_id: targetRunId,
|
|
269
|
+
artifacts: targetArtifacts,
|
|
270
|
+
};
|
|
271
|
+
await writeExclusiveText(targetStorage.manifestJson, `${JSON.stringify(targetManifest)}\n`, targetOps);
|
|
272
|
+
if (!await isContextBuilderRunCommitted(targetStorage.runDir, targetOps)) {
|
|
273
|
+
throw new ExecutionError(`context_builder promoted run failed validation: ${targetRunId}`);
|
|
202
274
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
275
|
+
targetCommitted = true;
|
|
276
|
+
await (targetOps.rm ?? fsRm)(resolvedTempRunDir, { recursive: true, force: true }).catch(() => { });
|
|
277
|
+
await syncDirectoryBestEffort(tempStorage.runsDir, targetOps);
|
|
278
|
+
return await callback(targetStorage, targetOps);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
if (!targetCommitted) {
|
|
283
|
+
// This call claimed the uncommitted target, so it owns cleanup.
|
|
284
|
+
await (sourceOps.rm ?? fsRm)(targetStorage.runDir, {
|
|
285
|
+
recursive: true,
|
|
286
|
+
force: true,
|
|
287
|
+
}).catch(() => { });
|
|
288
|
+
await syncDirectoryBestEffort(targetStorage.runsDir, sourceOps);
|
|
211
289
|
}
|
|
212
|
-
|
|
213
|
-
key,
|
|
214
|
-
targetStorage.artifacts[key],
|
|
215
|
-
]));
|
|
216
|
-
const targetManifest = {
|
|
217
|
-
...tempManifest,
|
|
218
|
-
run_id: targetRunId,
|
|
219
|
-
artifacts: targetArtifacts,
|
|
220
|
-
};
|
|
221
|
-
await writeExclusiveText(targetStorage.manifestJson, `${JSON.stringify(targetManifest)}\n`, ops);
|
|
222
|
-
});
|
|
223
|
-
if (!await isContextBuilderRunCommitted(targetStorage.runDir, ops)) {
|
|
224
|
-
throw new ExecutionError(`context_builder promoted run failed validation: ${targetRunId}`);
|
|
290
|
+
throw error;
|
|
225
291
|
}
|
|
226
|
-
}
|
|
227
|
-
catch (error) {
|
|
228
|
-
// The target dir was claimed above, so always clean it up on failure.
|
|
229
|
-
await (ops.rm ?? fsRm)(targetStorage.runDir, {
|
|
230
|
-
recursive: true,
|
|
231
|
-
force: true,
|
|
232
|
-
}).catch(() => { });
|
|
233
|
-
await syncDirectoryBestEffort(targetStorage.runsDir, ops);
|
|
234
|
-
throw error;
|
|
235
|
-
}
|
|
236
|
-
await (ops.rm ?? fsRm)(resolvedTempRunDir, { recursive: true, force: true })
|
|
237
|
-
.catch(() => { });
|
|
238
|
-
await syncDirectoryBestEffort(tempStorage.runsDir, ops);
|
|
239
|
-
return targetStorage;
|
|
292
|
+
}, ops);
|
|
240
293
|
}
|
|
241
294
|
export async function evictWorkflowContextRuns(runsDir, options = {}) {
|
|
242
295
|
const ops = options.ops ?? DEFAULT_STORAGE_OPS;
|
|
243
296
|
const readdir = ops.readdir ?? fsReaddir;
|
|
244
|
-
const stat = ops.stat ?? fsStat;
|
|
245
297
|
const resolvedRunsDir = await resolveContextBuilderRunsDir(runsDir);
|
|
246
298
|
const bounds = resolveWorkflowContextEvictionBounds(options);
|
|
247
299
|
const nowMs = options.nowMs ?? Date.now();
|
|
@@ -259,43 +311,52 @@ export async function evictWorkflowContextRuns(runsDir, options = {}) {
|
|
|
259
311
|
scanned += 1;
|
|
260
312
|
try {
|
|
261
313
|
const storage = resolveContextBuilderRunPaths(resolvedRunsDir, entry.name);
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
return undefined;
|
|
265
|
-
}
|
|
266
|
-
throw error;
|
|
267
|
-
});
|
|
268
|
-
if (runStat === undefined) {
|
|
314
|
+
const directoryAdmission = await admitContextBuilderRunDirectory(storage, ops);
|
|
315
|
+
if (directoryAdmission === undefined) {
|
|
269
316
|
skipped += 1;
|
|
270
317
|
continue;
|
|
271
318
|
}
|
|
272
|
-
|
|
319
|
+
const admission = await admitCommittedContextBuilderRun(storage, ops);
|
|
320
|
+
if (admission === undefined) {
|
|
273
321
|
skipped += 1;
|
|
274
322
|
continue;
|
|
275
323
|
}
|
|
324
|
+
const currentDirectoryAdmission = await admitContextBuilderRunDirectory(storage, ops);
|
|
325
|
+
if (currentDirectoryAdmission === undefined
|
|
326
|
+
|| !contextBuilderRunDirectoryAdmissionsEqual(directoryAdmission, currentDirectoryAdmission)) {
|
|
327
|
+
skipped += 1;
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const mtimeMs = Number(directoryAdmission.mtimeNs) / 1_000_000;
|
|
331
|
+
const candidate = {
|
|
332
|
+
storage,
|
|
333
|
+
mtimeMs,
|
|
334
|
+
admission,
|
|
335
|
+
directoryAdmission,
|
|
336
|
+
};
|
|
276
337
|
const isProtected = protectedRunIds.has(storage.runId);
|
|
277
338
|
if (entry.name.startsWith("wfctx-")) {
|
|
278
339
|
// Protected cache entries are excluded from LRU survivor accounting so
|
|
279
340
|
// they can never be selected for the max-entries trim below either.
|
|
280
341
|
if (!isProtected) {
|
|
281
|
-
committedCacheEntries.push(
|
|
342
|
+
committedCacheEntries.push(candidate);
|
|
282
343
|
}
|
|
283
|
-
if (nowMs -
|
|
344
|
+
if (nowMs - mtimeMs > bounds.cacheTtlMs) {
|
|
284
345
|
if (isProtected) {
|
|
285
346
|
skipped += 1;
|
|
286
347
|
}
|
|
287
348
|
else {
|
|
288
|
-
removeSet.set(storage.runId,
|
|
349
|
+
removeSet.set(storage.runId, candidate);
|
|
289
350
|
}
|
|
290
351
|
}
|
|
291
352
|
continue;
|
|
292
353
|
}
|
|
293
|
-
if (nowMs -
|
|
354
|
+
if (nowMs - mtimeMs > bounds.tempTtlMs) {
|
|
294
355
|
if (isProtected) {
|
|
295
356
|
skipped += 1;
|
|
296
357
|
}
|
|
297
358
|
else {
|
|
298
|
-
removeSet.set(storage.runId,
|
|
359
|
+
removeSet.set(storage.runId, candidate);
|
|
299
360
|
}
|
|
300
361
|
}
|
|
301
362
|
}
|
|
@@ -305,15 +366,16 @@ export async function evictWorkflowContextRuns(runsDir, options = {}) {
|
|
|
305
366
|
}
|
|
306
367
|
const cacheSurvivors = committedCacheEntries
|
|
307
368
|
.filter(({ storage }) => !removeSet.has(storage.runId))
|
|
308
|
-
.sort((a, b) => a.mtimeMs - b.mtimeMs)
|
|
309
|
-
.map(({ storage }) => storage);
|
|
369
|
+
.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
310
370
|
cacheSurvivors
|
|
311
371
|
.slice(0, Math.max(0, cacheSurvivors.length - bounds.maxEntries))
|
|
312
|
-
.forEach((
|
|
372
|
+
.forEach((candidate) => {
|
|
373
|
+
removeSet.set(candidate.storage.runId, candidate);
|
|
374
|
+
});
|
|
313
375
|
let evicted = 0;
|
|
314
|
-
for (const storage of removeSet.values()) {
|
|
376
|
+
for (const { storage, admission, directoryAdmission } of removeSet.values()) {
|
|
315
377
|
try {
|
|
316
|
-
const didRemove = await removeCommittedWorkflowContextRun(storage, ops);
|
|
378
|
+
const didRemove = await removeCommittedWorkflowContextRun(storage, admission, directoryAdmission, ops);
|
|
317
379
|
if (didRemove) {
|
|
318
380
|
evicted += 1;
|
|
319
381
|
}
|
|
@@ -337,19 +399,165 @@ export async function evictWorkflowContextRuns(runsDir, options = {}) {
|
|
|
337
399
|
}
|
|
338
400
|
export async function cleanupContextBuilderIncompleteRun(storage, ownerNonce, ops = DEFAULT_STORAGE_OPS) {
|
|
339
401
|
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
340
|
-
return
|
|
402
|
+
return withContextBuilderStorageMutationLock(canonicalStorage, ops, async () => cleanupContextBuilderIncompleteRunLocked(canonicalStorage, ownerNonce, ops), {
|
|
341
403
|
ownerNonce,
|
|
342
|
-
|
|
404
|
+
canRecoverStale: async () => (await canRemoveIncompleteRunDir(canonicalStorage, ownerNonce, ops)).removable,
|
|
343
405
|
});
|
|
344
406
|
}
|
|
407
|
+
/** Hold one run's mutation lock, reusing it for nested storage operations. */
|
|
408
|
+
export async function withContextBuilderRunMutationLease(storage, callback, ops = DEFAULT_STORAGE_OPS, options = {}) {
|
|
409
|
+
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
410
|
+
const effectiveOps = ops ?? DEFAULT_STORAGE_OPS;
|
|
411
|
+
return await withContextBuilderStorageMutationLock(canonicalStorage, effectiveOps, callback, options);
|
|
412
|
+
}
|
|
345
413
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
* artifact paths without the run directory disappearing underneath it.
|
|
414
|
+
* Admit one exact committed-run incarnation, recover only a stale lock bound to
|
|
415
|
+
* that admission, and recheck the manifest after acquiring the lease.
|
|
349
416
|
*/
|
|
350
|
-
export async function
|
|
417
|
+
export async function withCommittedContextBuilderRunMutationLease(storage, callback, ops = DEFAULT_STORAGE_OPS) {
|
|
351
418
|
const canonicalStorage = canonicalizeContextBuilderStorage(storage);
|
|
352
|
-
|
|
419
|
+
const admitted = await admitCommittedContextBuilderRun(canonicalStorage, ops);
|
|
420
|
+
if (admitted === undefined) {
|
|
421
|
+
throw new ContextBuilderCommittedRunChangedError(canonicalStorage.runId);
|
|
422
|
+
}
|
|
423
|
+
return await withAdmittedCommittedContextBuilderRunMutationLease(canonicalStorage, admitted, callback, ops);
|
|
424
|
+
}
|
|
425
|
+
async function withAdmittedCommittedContextBuilderRunMutationLease(storage, admitted, callback, ops) {
|
|
426
|
+
return await withContextBuilderStorageMutationLock(storage, ops, async (leasedOps) => {
|
|
427
|
+
const current = await admitCommittedContextBuilderRun(storage, leasedOps);
|
|
428
|
+
if (current === undefined
|
|
429
|
+
|| !committedRunAdmissionsEqual(admitted, current)) {
|
|
430
|
+
throw new ContextBuilderCommittedRunChangedError(storage.runId);
|
|
431
|
+
}
|
|
432
|
+
return await callback(leasedOps);
|
|
433
|
+
}, {
|
|
434
|
+
canRecoverStale: async () => {
|
|
435
|
+
const current = await admitCommittedContextBuilderRun(storage, ops);
|
|
436
|
+
return current !== undefined
|
|
437
|
+
&& committedRunAdmissionsEqual(admitted, current);
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async function withContextBuilderStorageMutationLock(storage, ops, callback, options = {}) {
|
|
442
|
+
const heldLocks = ops[HELD_CONTEXT_BUILDER_MUTATION_LOCKS];
|
|
443
|
+
if (heldLocks?.has(storage.runDir) === true) {
|
|
444
|
+
return await callback(ops);
|
|
445
|
+
}
|
|
446
|
+
return await withContextBuilderMutationLock(storage, ops, async () => await callback(markContextBuilderMutationLockHeld(ops, storage.runDir)), options);
|
|
447
|
+
}
|
|
448
|
+
function markContextBuilderMutationLockHeld(ops, runDir) {
|
|
449
|
+
const inherited = ops[HELD_CONTEXT_BUILDER_MUTATION_LOCKS];
|
|
450
|
+
const heldLocks = new Set(inherited ?? []);
|
|
451
|
+
heldLocks.add(runDir);
|
|
452
|
+
const leasedOps = Object.create(ops);
|
|
453
|
+
Object.defineProperty(leasedOps, HELD_CONTEXT_BUILDER_MUTATION_LOCKS, {
|
|
454
|
+
value: heldLocks,
|
|
455
|
+
});
|
|
456
|
+
return leasedOps;
|
|
457
|
+
}
|
|
458
|
+
async function admitCommittedContextBuilderRun(storage, ops) {
|
|
459
|
+
const lstat = ops.lstat ?? fsLstat;
|
|
460
|
+
const open = ops.open ?? fsOpen;
|
|
461
|
+
let handle;
|
|
462
|
+
try {
|
|
463
|
+
const pathStats = await lstat(storage.manifestJson, { bigint: true });
|
|
464
|
+
if (!pathStats.isFile()) {
|
|
465
|
+
return undefined;
|
|
466
|
+
}
|
|
467
|
+
handle = await open(storage.manifestJson, "r");
|
|
468
|
+
const before = await handle.stat({ bigint: true });
|
|
469
|
+
if (!before.isFile()
|
|
470
|
+
|| before.nlink === 0n
|
|
471
|
+
|| !sameCommittedRunIncarnation(committedRunFingerprintFromStats(pathStats), committedRunFingerprintFromStats(before))) {
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
const serializedManifest = await handle.readFile({ encoding: "utf8" });
|
|
475
|
+
const afterRead = await handle.stat({ bigint: true });
|
|
476
|
+
const beforeFingerprint = committedRunFingerprintFromStats(before);
|
|
477
|
+
const afterReadFingerprint = committedRunFingerprintFromStats(afterRead);
|
|
478
|
+
if (afterRead.nlink === 0n
|
|
479
|
+
|| !committedRunFingerprintsEqual(beforeFingerprint, afterReadFingerprint)) {
|
|
480
|
+
return undefined;
|
|
481
|
+
}
|
|
482
|
+
const parsed = JSON.parse(serializedManifest);
|
|
483
|
+
if (!isValidContextBuilderManifest(parsed, storage.runId)) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
const artifactKeys = supportedArtifactKeys(parsed.artifacts);
|
|
487
|
+
if (artifactKeys == null
|
|
488
|
+
|| !artifactPathsExactlyMatch(parsed.artifacts, storage.artifacts, artifactKeys)) {
|
|
489
|
+
return undefined;
|
|
490
|
+
}
|
|
491
|
+
await assertRequiredArtifactsReadable(storage, ops, artifactKeys);
|
|
492
|
+
const afterArtifacts = await handle.stat({ bigint: true });
|
|
493
|
+
const finalFingerprint = committedRunFingerprintFromStats(afterArtifacts);
|
|
494
|
+
if (afterArtifacts.nlink === 0n
|
|
495
|
+
|| !committedRunFingerprintsEqual(afterReadFingerprint, finalFingerprint)) {
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
498
|
+
return { serializedManifest, fingerprint: finalFingerprint };
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
return undefined;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
await handle?.close().catch(() => { });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function committedRunFingerprintFromStats(stats) {
|
|
508
|
+
return {
|
|
509
|
+
dev: stats.dev,
|
|
510
|
+
ino: stats.ino,
|
|
511
|
+
mtimeNs: stats.mtimeNs,
|
|
512
|
+
ctimeNs: stats.ctimeNs,
|
|
513
|
+
size: stats.size,
|
|
514
|
+
nlink: stats.nlink,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
function sameCommittedRunIncarnation(left, right) {
|
|
518
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
519
|
+
}
|
|
520
|
+
function committedRunFingerprintsEqual(left, right) {
|
|
521
|
+
return sameCommittedRunIncarnation(left, right)
|
|
522
|
+
&& left.mtimeNs === right.mtimeNs
|
|
523
|
+
&& left.ctimeNs === right.ctimeNs
|
|
524
|
+
&& left.size === right.size
|
|
525
|
+
&& left.nlink === right.nlink;
|
|
526
|
+
}
|
|
527
|
+
function committedRunAdmissionsEqual(left, right) {
|
|
528
|
+
return left.serializedManifest === right.serializedManifest
|
|
529
|
+
&& committedRunFingerprintsEqual(left.fingerprint, right.fingerprint);
|
|
530
|
+
}
|
|
531
|
+
async function admitContextBuilderRunDirectory(storage, ops) {
|
|
532
|
+
const lstat = ops.lstat ?? fsLstat;
|
|
533
|
+
try {
|
|
534
|
+
const stats = await lstat(storage.runDir, { bigint: true });
|
|
535
|
+
if (!stats.isDirectory() || stats.nlink === 0n) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
dev: stats.dev,
|
|
540
|
+
ino: stats.ino,
|
|
541
|
+
mtimeNs: stats.mtimeNs,
|
|
542
|
+
ctimeNs: stats.ctimeNs,
|
|
543
|
+
size: stats.size,
|
|
544
|
+
nlink: stats.nlink,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
if (isNodeError(error) && error.code === "ENOENT") {
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function contextBuilderRunDirectoryAdmissionsEqual(left, right) {
|
|
555
|
+
return left.dev === right.dev
|
|
556
|
+
&& left.ino === right.ino
|
|
557
|
+
&& left.mtimeNs === right.mtimeNs
|
|
558
|
+
&& left.ctimeNs === right.ctimeNs
|
|
559
|
+
&& left.size === right.size
|
|
560
|
+
&& left.nlink === right.nlink;
|
|
353
561
|
}
|
|
354
562
|
async function cleanupContextBuilderIncompleteRunLocked(storage, ownerNonce, ops) {
|
|
355
563
|
if (!await mayRemoveIncompleteRunDir(storage, ownerNonce, ops)) {
|
|
@@ -480,9 +688,9 @@ async function claimContextBuilderRunDir(paths, ops) {
|
|
|
480
688
|
catch (error) {
|
|
481
689
|
if (isNodeError(error) && error.code === "EEXIST") {
|
|
482
690
|
if (await isContextBuilderRunCommitted(paths.runDir, ops)) {
|
|
483
|
-
throw new
|
|
691
|
+
throw new ContextBuilderRunClaimConflictError(paths.runId);
|
|
484
692
|
}
|
|
485
|
-
throw new
|
|
693
|
+
throw new ContextBuilderActiveRunClaimConflictError(paths.runId);
|
|
486
694
|
}
|
|
487
695
|
throw error;
|
|
488
696
|
}
|
|
@@ -508,146 +716,6 @@ function isStorageOps(value) {
|
|
|
508
716
|
|| "open" in value
|
|
509
717
|
|| "link" in value);
|
|
510
718
|
}
|
|
511
|
-
async function withContextBuilderMutationLock(storage, ops, callback, options = {}) {
|
|
512
|
-
const lockPath = mutationLockPathForStorage(storage);
|
|
513
|
-
const lockHandle = await acquireContextBuilderMutationLock(storage, lockPath, ops, options);
|
|
514
|
-
try {
|
|
515
|
-
return await callback();
|
|
516
|
-
}
|
|
517
|
-
finally {
|
|
518
|
-
await lockHandle.close().catch(() => { });
|
|
519
|
-
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function acquireContextBuilderMutationLock(storage, lockPath, ops, options) {
|
|
523
|
-
const open = ops.open ?? fsOpen;
|
|
524
|
-
for (let attempt = 0; attempt < CONTEXT_BUILDER_MUTATION_LOCK_ATTEMPTS; attempt += 1) {
|
|
525
|
-
try {
|
|
526
|
-
const handle = await open(lockPath, "wx", 0o600);
|
|
527
|
-
try {
|
|
528
|
-
await writeMutationLockMetadata(handle, options.ownerNonce);
|
|
529
|
-
return handle;
|
|
530
|
-
}
|
|
531
|
-
catch (error) {
|
|
532
|
-
await handle.close().catch(() => { });
|
|
533
|
-
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
534
|
-
throw error;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
catch (error) {
|
|
538
|
-
if (isNodeError(error) && error.code === "EEXIST") {
|
|
539
|
-
if (options.recoverStaleForCleanup === true
|
|
540
|
-
&& await breakStaleCleanupMutationLock(storage, lockPath, options.ownerNonce, ops)) {
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
await delay(CONTEXT_BUILDER_MUTATION_LOCK_RETRY_MS);
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
throw error;
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
throw new ExecutionError("context_builder timed out waiting for the run mutation lock");
|
|
550
|
-
}
|
|
551
|
-
function mutationLockPathForStorage(storage) {
|
|
552
|
-
validateCallerRunId(storage.runId);
|
|
553
|
-
const resolvedRunsDir = resolve(storage.runsDir);
|
|
554
|
-
const resolvedRunDir = resolve(storage.runDir);
|
|
555
|
-
if (!isPathInsideDirectory(resolvedRunDir, resolvedRunsDir)) {
|
|
556
|
-
throw new ExecutionError("context_builder refused mutation lock outside runs directory");
|
|
557
|
-
}
|
|
558
|
-
return join(resolvedRunsDir, `.${storage.runId}${CONTEXT_BUILDER_MUTATION_LOCK_SUFFIX}`);
|
|
559
|
-
}
|
|
560
|
-
async function writeMutationLockMetadata(handle, ownerNonce) {
|
|
561
|
-
const metadata = {
|
|
562
|
-
pid: process.pid,
|
|
563
|
-
hostname: hostname(),
|
|
564
|
-
created_at: new Date().toISOString(),
|
|
565
|
-
...(ownerNonce == null ? {} : { owner_nonce: ownerNonce }),
|
|
566
|
-
};
|
|
567
|
-
await handle.writeFile(`${JSON.stringify(metadata)}\n`, { encoding: "utf8" });
|
|
568
|
-
}
|
|
569
|
-
async function breakStaleCleanupMutationLock(storage, lockPath, ownerNonce, ops) {
|
|
570
|
-
if (ownerNonce == null) {
|
|
571
|
-
return false;
|
|
572
|
-
}
|
|
573
|
-
const cleanupDecision = await canRemoveIncompleteRunDir(storage, ownerNonce, ops);
|
|
574
|
-
if (!cleanupDecision.removable) {
|
|
575
|
-
return false;
|
|
576
|
-
}
|
|
577
|
-
if (!await isMutationLockOldEnough(lockPath, ops)) {
|
|
578
|
-
return false;
|
|
579
|
-
}
|
|
580
|
-
const metadata = await readMutationLockMetadata(lockPath, ops);
|
|
581
|
-
if (metadata !== "invalid" && !isStaleMutationLockMetadata(metadata)) {
|
|
582
|
-
return false;
|
|
583
|
-
}
|
|
584
|
-
await (ops.rm ?? fsRm)(lockPath, { force: true }).catch(() => { });
|
|
585
|
-
return true;
|
|
586
|
-
}
|
|
587
|
-
async function isMutationLockOldEnough(lockPath, ops) {
|
|
588
|
-
const stat = ops.stat ?? fsStat;
|
|
589
|
-
try {
|
|
590
|
-
const lockStat = await stat(lockPath);
|
|
591
|
-
return Date.now() - lockStat.mtimeMs >= CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS;
|
|
592
|
-
}
|
|
593
|
-
catch {
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
async function readMutationLockMetadata(lockPath, ops) {
|
|
598
|
-
let parsed;
|
|
599
|
-
try {
|
|
600
|
-
parsed = JSON.parse(await ops.readFile(lockPath, "utf8"));
|
|
601
|
-
}
|
|
602
|
-
catch {
|
|
603
|
-
return "invalid";
|
|
604
|
-
}
|
|
605
|
-
if (parsed === null || typeof parsed !== "object") {
|
|
606
|
-
return "invalid";
|
|
607
|
-
}
|
|
608
|
-
const metadata = parsed;
|
|
609
|
-
if (typeof metadata.pid !== "number"
|
|
610
|
-
|| !Number.isInteger(metadata.pid)
|
|
611
|
-
|| typeof metadata.hostname !== "string"
|
|
612
|
-
|| typeof metadata.created_at !== "string") {
|
|
613
|
-
return "invalid";
|
|
614
|
-
}
|
|
615
|
-
if (metadata.owner_nonce !== undefined
|
|
616
|
-
&& typeof metadata.owner_nonce !== "string") {
|
|
617
|
-
return "invalid";
|
|
618
|
-
}
|
|
619
|
-
return {
|
|
620
|
-
pid: metadata.pid,
|
|
621
|
-
hostname: metadata.hostname,
|
|
622
|
-
created_at: metadata.created_at,
|
|
623
|
-
...(metadata.owner_nonce === undefined
|
|
624
|
-
? {}
|
|
625
|
-
: { owner_nonce: metadata.owner_nonce }),
|
|
626
|
-
};
|
|
627
|
-
}
|
|
628
|
-
function isStaleMutationLockMetadata(metadata) {
|
|
629
|
-
const createdAtMs = Date.parse(metadata.created_at);
|
|
630
|
-
if (!Number.isFinite(createdAtMs)
|
|
631
|
-
|| Date.now() - createdAtMs < CONTEXT_BUILDER_STALE_MUTATION_LOCK_MS) {
|
|
632
|
-
return false;
|
|
633
|
-
}
|
|
634
|
-
if (metadata.hostname !== hostname()) {
|
|
635
|
-
return false;
|
|
636
|
-
}
|
|
637
|
-
return !isProcessAlive(metadata.pid);
|
|
638
|
-
}
|
|
639
|
-
function isProcessAlive(pid) {
|
|
640
|
-
if (pid <= 0) {
|
|
641
|
-
return false;
|
|
642
|
-
}
|
|
643
|
-
try {
|
|
644
|
-
process.kill(pid, 0);
|
|
645
|
-
return true;
|
|
646
|
-
}
|
|
647
|
-
catch (error) {
|
|
648
|
-
return !(isNodeError(error) && error.code === "ESRCH");
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
719
|
async function assertRequiredArtifactsReadable(storage, ops, artifactKeys = CONTEXT_BUILDER_NO_DELEGATE_ARTIFACT_KEYS) {
|
|
652
720
|
const missing = [];
|
|
653
721
|
for (const key of artifactKeys) {
|
|
@@ -876,17 +944,27 @@ async function syncDirectoryBestEffort(path, ops) {
|
|
|
876
944
|
}
|
|
877
945
|
}
|
|
878
946
|
}
|
|
879
|
-
async function removeCommittedWorkflowContextRun(storage, ops) {
|
|
947
|
+
async function removeCommittedWorkflowContextRun(storage, admission, directoryAdmission, ops) {
|
|
880
948
|
if (!isWorkflowContextRunId(storage.runId)) {
|
|
881
949
|
return false;
|
|
882
950
|
}
|
|
883
|
-
|
|
884
|
-
|
|
951
|
+
try {
|
|
952
|
+
return await withAdmittedCommittedContextBuilderRunMutationLease(storage, admission, async (leasedOps) => {
|
|
953
|
+
const currentDirectoryAdmission = await admitContextBuilderRunDirectory(storage, leasedOps);
|
|
954
|
+
if (currentDirectoryAdmission === undefined
|
|
955
|
+
|| !contextBuilderRunDirectoryAdmissionsEqual(directoryAdmission, currentDirectoryAdmission)) {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
await (leasedOps.rm ?? fsRm)(storage.runDir, { recursive: true, force: true });
|
|
959
|
+
return true;
|
|
960
|
+
}, ops);
|
|
961
|
+
}
|
|
962
|
+
catch (error) {
|
|
963
|
+
if (error instanceof ContextBuilderCommittedRunChangedError) {
|
|
885
964
|
return false;
|
|
886
965
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
});
|
|
966
|
+
throw error;
|
|
967
|
+
}
|
|
890
968
|
}
|
|
891
969
|
function isWorkflowContextRunId(runId) {
|
|
892
970
|
return runId.startsWith("wfctx-") || runId.startsWith("wfpre-");
|
|
@@ -993,9 +1071,6 @@ function diagnosticText(value, fallback) {
|
|
|
993
1071
|
function isRecord(value) {
|
|
994
1072
|
return value !== null && typeof value === "object";
|
|
995
1073
|
}
|
|
996
|
-
async function delay(ms) {
|
|
997
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
998
|
-
}
|
|
999
1074
|
function isPathInsideDirectory(candidate, directory) {
|
|
1000
1075
|
const relation = relative(directory, candidate);
|
|
1001
1076
|
return relation !== "" && !relation.startsWith("..") && !isAbsolute(relation);
|