intentdna 1.9.0 → 1.9.3

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.
Files changed (131) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +227 -87
  4. package/dist/cli/commands/run-lifecycle.d.ts +61 -11
  5. package/dist/cli/commands/run-lifecycle.js +184 -50
  6. package/dist/cli/commands/run-observer.d.ts +15 -0
  7. package/dist/cli/commands/run-observer.js +210 -0
  8. package/dist/cli/commands/run.d.ts +16 -20
  9. package/dist/cli/commands/run.js +461 -659
  10. package/dist/cli/commands/sync.js +16 -12
  11. package/dist/cli/index.js +14 -5
  12. package/dist/compiler/compile.d.ts +1 -0
  13. package/dist/compiler/compile.js +1 -1
  14. package/dist/compiler/controller.d.ts +16 -0
  15. package/dist/compiler/controller.js +693 -0
  16. package/dist/compiler/input-resolver.d.ts +2 -0
  17. package/dist/compiler/input-resolver.js +22 -6
  18. package/dist/hooks/cli.d.ts +45 -0
  19. package/dist/hooks/cli.js +263 -31
  20. package/dist/hooks/host-event.d.ts +57 -0
  21. package/dist/hooks/host-event.js +187 -0
  22. package/dist/hooks/index.d.ts +1 -0
  23. package/dist/hooks/index.js +1 -0
  24. package/dist/hooks/protocol.d.ts +7 -0
  25. package/dist/hooks/schema.d.ts +1 -0
  26. package/dist/hooks/schema.js +89 -1
  27. package/dist/hooks/state.d.ts +23 -1
  28. package/dist/hooks/state.js +9 -0
  29. package/dist/index.d.ts +1 -0
  30. package/dist/index.js +1 -0
  31. package/dist/mcp/index.js +2 -0
  32. package/dist/mcp/tools-run.d.ts +4 -0
  33. package/dist/mcp/tools-run.js +419 -0
  34. package/dist/mcp/tools-state.d.ts +1 -1
  35. package/dist/mcp/tools-state.js +13 -241
  36. package/dist/runtime/artifact-store.d.ts +146 -0
  37. package/dist/runtime/artifact-store.js +1436 -0
  38. package/dist/runtime/canonical-attempt-outcome.d.ts +12 -0
  39. package/dist/runtime/canonical-attempt-outcome.js +195 -0
  40. package/dist/runtime/canonical-json.d.ts +10 -0
  41. package/dist/runtime/canonical-json.js +105 -0
  42. package/dist/runtime/canonical-run-application.d.ts +93 -0
  43. package/dist/runtime/canonical-run-application.js +229 -0
  44. package/dist/runtime/canonical-run-service.d.ts +454 -0
  45. package/dist/runtime/canonical-run-service.js +4760 -0
  46. package/dist/runtime/canonical-runtime-composition.d.ts +22 -0
  47. package/dist/runtime/canonical-runtime-composition.js +34 -0
  48. package/dist/runtime/canonical-runtime-projection.d.ts +3 -0
  49. package/dist/runtime/canonical-runtime-projection.js +4 -0
  50. package/dist/runtime/canonical-target-compiler.d.ts +46 -0
  51. package/dist/runtime/canonical-target-compiler.js +514 -0
  52. package/dist/runtime/claude-agent-identity.d.ts +2 -0
  53. package/dist/runtime/claude-agent-identity.js +21 -0
  54. package/dist/runtime/executable-run-plan.d.ts +328 -0
  55. package/dist/runtime/executable-run-plan.js +1485 -0
  56. package/dist/runtime/execution-authority.d.ts +83 -0
  57. package/dist/runtime/execution-authority.js +98 -0
  58. package/dist/runtime/handoff-resolver.js +4 -1
  59. package/dist/runtime/harness-pull-adapter.d.ts +146 -0
  60. package/dist/runtime/harness-pull-adapter.js +315 -0
  61. package/dist/runtime/index.d.ts +39 -14
  62. package/dist/runtime/index.js +29 -7
  63. package/dist/runtime/local-execution-authority.d.ts +194 -0
  64. package/dist/runtime/local-execution-authority.js +2897 -0
  65. package/dist/runtime/local-execution-reconciliation.d.ts +20 -0
  66. package/dist/runtime/local-execution-reconciliation.js +107 -0
  67. package/dist/runtime/local-execution-supervisor-script.d.ts +7 -0
  68. package/dist/runtime/local-execution-supervisor-script.js +793 -0
  69. package/dist/runtime/local-provider-sandbox.d.ts +42 -0
  70. package/dist/runtime/local-provider-sandbox.js +154 -0
  71. package/dist/runtime/local-verifier-execution.d.ts +24 -0
  72. package/dist/runtime/local-verifier-execution.js +290 -0
  73. package/dist/runtime/local-windows-execution-supervisor-script.d.ts +6 -0
  74. package/dist/runtime/local-windows-execution-supervisor-script.js +788 -0
  75. package/dist/runtime/plan-store.d.ts +30 -0
  76. package/dist/runtime/plan-store.js +231 -0
  77. package/dist/runtime/process-tree.d.ts +9 -0
  78. package/dist/runtime/process-tree.js +97 -24
  79. package/dist/runtime/providers/codex.js +2 -2
  80. package/dist/runtime/push-driver.d.ts +184 -0
  81. package/dist/runtime/push-driver.js +1002 -0
  82. package/dist/runtime/result-store.d.ts +34 -3
  83. package/dist/runtime/result-store.js +1073 -2
  84. package/dist/runtime/run-binding.d.ts +68 -0
  85. package/dist/runtime/run-binding.js +278 -0
  86. package/dist/runtime/run-contracts.d.ts +575 -0
  87. package/dist/runtime/run-contracts.js +58 -7
  88. package/dist/runtime/run-controller.d.ts +1 -6
  89. package/dist/runtime/run-controller.js +0 -41
  90. package/dist/runtime/run-store.d.ts +85 -7
  91. package/dist/runtime/run-store.js +2528 -186
  92. package/dist/runtime/skill-adapter.d.ts +18 -7
  93. package/dist/runtime/skill-adapter.js +100 -742
  94. package/dist/runtime/structured-output-validator.d.ts +44 -0
  95. package/dist/runtime/structured-output-validator.js +171 -0
  96. package/dist/runtime/verifier-command-binding.d.ts +22 -0
  97. package/dist/runtime/verifier-command-binding.js +162 -0
  98. package/dist/runtime/verifier.d.ts +46 -6
  99. package/dist/runtime/verifier.js +355 -53
  100. package/dist/runtime/windows-job-keeper.d.ts +47 -0
  101. package/dist/runtime/windows-job-keeper.js +231 -0
  102. package/dist/runtime/worker-executor.d.ts +17 -1
  103. package/dist/runtime/worker-executor.js +26 -5
  104. package/dist/runtime/workflow-plan-adapter.d.ts +19 -0
  105. package/dist/runtime/workflow-plan-adapter.js +502 -6
  106. package/dist/runtime/workflow-runtime-manifest.d.ts +1 -0
  107. package/dist/runtime/workflow-runtime-manifest.js +5 -0
  108. package/dist/runtime/workspace-isolation.d.ts +29 -2
  109. package/dist/runtime/workspace-isolation.js +488 -11
  110. package/dist/runtime/workspace-observation.d.ts +44 -0
  111. package/dist/runtime/workspace-observation.js +216 -0
  112. package/dist/schema/controller-registry.d.ts +0 -7
  113. package/dist/schema/controller-registry.js +0 -8
  114. package/dist/schema/types.d.ts +84 -10
  115. package/dist/schema/types.js +2 -0
  116. package/dist/schema/validate.js +151 -1
  117. package/dist/schema/validators/controllers.d.ts +1 -1
  118. package/dist/schema/validators/controllers.js +126 -41
  119. package/dist/schema/workflow-authoring-contract.js +2 -0
  120. package/dist/schema/yaml-parser.js +1 -1
  121. package/dist/templates/flutter-rewrite-new.dna.yaml +1 -0
  122. package/dist/templates/flutter-rewrite.dna.yaml +87 -60
  123. package/dist/templates/safe-refactoring.dna.yaml +1 -0
  124. package/native/windows-job-keeper/README.md +80 -0
  125. package/native/windows-job-keeper/bin/aarch64/intentdna-windows-job-keeper.exe +0 -0
  126. package/native/windows-job-keeper/bin/x86_64/intentdna-windows-job-keeper.exe +0 -0
  127. package/native/windows-job-keeper/keeper.c +979 -0
  128. package/native/windows-job-keeper/manifest.json +33 -0
  129. package/package.json +6 -2
  130. package/scripts/build-windows-job-keeper.mjs +172 -0
  131. package/scripts/verify-windows-job-keeper.mjs +184 -0
@@ -0,0 +1,12 @@
1
+ import type { AttemptExecutor, CanonicalAttemptOutcome } from "./run-contracts.js";
2
+ export declare class CanonicalAttemptOutcomeValidationError extends TypeError {
3
+ readonly path: string;
4
+ constructor(path: string, message: string);
5
+ }
6
+ export interface ValidatedCanonicalAttemptOutcome {
7
+ readonly executor: AttemptExecutor;
8
+ readonly outcome: CanonicalAttemptOutcome;
9
+ }
10
+ export type CanonicalAttemptOutcomeStatus = "succeeded" | "failed" | "cancelled";
11
+ export declare function canonicalAttemptOutcomeStatus(outcome: CanonicalAttemptOutcome): CanonicalAttemptOutcomeStatus;
12
+ export declare function validateCanonicalAttemptOutcome(executorValue: unknown, outcomeValue: unknown): ValidatedCanonicalAttemptOutcome;
@@ -0,0 +1,195 @@
1
+ export class CanonicalAttemptOutcomeValidationError extends TypeError {
2
+ path;
3
+ constructor(path, message) {
4
+ super(`${path}: ${message}`);
5
+ this.name = "CanonicalAttemptOutcomeValidationError";
6
+ this.path = path;
7
+ }
8
+ }
9
+ export function canonicalAttemptOutcomeStatus(outcome) {
10
+ if (outcome.kind !== "worker_process")
11
+ return outcome.kind;
12
+ return outcome.status;
13
+ }
14
+ function record(value, path) {
15
+ if (typeof value !== "object"
16
+ || value === null
17
+ || Array.isArray(value)
18
+ || Object.getPrototypeOf(value) !== Object.prototype) {
19
+ throw new CanonicalAttemptOutcomeValidationError(path, "must be an object");
20
+ }
21
+ return value;
22
+ }
23
+ function exactKeys(value, expected, path) {
24
+ const actual = Object.keys(value).sort();
25
+ const wanted = [...expected].sort();
26
+ if (actual.length !== wanted.length
27
+ || !actual.every((key, index) => key === wanted[index])) {
28
+ throw new CanonicalAttemptOutcomeValidationError(path, "has unexpected or missing fields");
29
+ }
30
+ }
31
+ function nonEmptyString(value, path) {
32
+ if (typeof value !== "string" || value.length === 0) {
33
+ throw new CanonicalAttemptOutcomeValidationError(path, "must be a non-empty string");
34
+ }
35
+ }
36
+ function validateExecutor(value) {
37
+ const executor = record(value, "executor");
38
+ if (executor.kind === "worker") {
39
+ exactKeys(executor, ["kind", "worker_session_id"], "executor");
40
+ nonEmptyString(executor.worker_session_id, "executor.worker_session_id");
41
+ return executor;
42
+ }
43
+ if (executor.kind === "system") {
44
+ exactKeys(executor, ["kind", "service_instance_id", "operation"], "executor");
45
+ nonEmptyString(executor.service_instance_id, "executor.service_instance_id");
46
+ nonEmptyString(executor.operation, "executor.operation");
47
+ return executor;
48
+ }
49
+ if (executor.kind === "human") {
50
+ exactKeys(executor, ["kind", "principal_ref", "attestation_ref"], "executor");
51
+ nonEmptyString(executor.principal_ref, "executor.principal_ref");
52
+ nonEmptyString(executor.attestation_ref, "executor.attestation_ref");
53
+ return executor;
54
+ }
55
+ throw new CanonicalAttemptOutcomeValidationError("executor.kind", "must be worker, system, or human");
56
+ }
57
+ function integerOrNull(value, path, nullable) {
58
+ if (!(typeof value === "number" && Number.isInteger(value))
59
+ && !(nullable && value === null)) {
60
+ throw new CanonicalAttemptOutcomeValidationError(path, "is invalid");
61
+ }
62
+ }
63
+ function validateProcessOutcome(value) {
64
+ const outcome = record(value, "outcome.process");
65
+ switch (outcome.kind) {
66
+ case "success":
67
+ exactKeys(outcome, ["kind", "exit_code", "signal"], "outcome.process");
68
+ if (outcome.exit_code !== 0 || outcome.signal !== null) {
69
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process", "success fields are invalid");
70
+ }
71
+ break;
72
+ case "non_zero_exit":
73
+ exactKeys(outcome, ["kind", "exit_code", "signal"], "outcome.process");
74
+ integerOrNull(outcome.exit_code, "outcome.process.exit_code", false);
75
+ if (outcome.exit_code === 0 || outcome.signal !== null) {
76
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process", "non-zero exit fields are invalid");
77
+ }
78
+ break;
79
+ case "malformed_result":
80
+ exactKeys(outcome, ["kind", "exit_code", "signal", "error"], "outcome.process");
81
+ integerOrNull(outcome.exit_code, "outcome.process.exit_code", true);
82
+ if (outcome.signal !== null) {
83
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.signal", "must be null");
84
+ }
85
+ nonEmptyString(outcome.error, "outcome.process.error");
86
+ break;
87
+ case "timeout":
88
+ exactKeys(outcome, ["kind", "exit_code", "signal", "timeout_ms"], "outcome.process");
89
+ integerOrNull(outcome.exit_code, "outcome.process.exit_code", true);
90
+ if (outcome.signal !== null && typeof outcome.signal !== "string") {
91
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.signal", "must be a string or null");
92
+ }
93
+ if (!Number.isInteger(outcome.timeout_ms) || outcome.timeout_ms <= 0) {
94
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.timeout_ms", "must be a positive integer");
95
+ }
96
+ break;
97
+ case "cancelled":
98
+ exactKeys(outcome, ["kind", "exit_code", "signal", "reason"], "outcome.process");
99
+ integerOrNull(outcome.exit_code, "outcome.process.exit_code", true);
100
+ if (outcome.signal !== null && typeof outcome.signal !== "string") {
101
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.signal", "must be a string or null");
102
+ }
103
+ if (outcome.reason !== null && typeof outcome.reason !== "string") {
104
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.reason", "must be a string or null");
105
+ }
106
+ break;
107
+ case "signal":
108
+ exactKeys(outcome, ["kind", "exit_code", "signal"], "outcome.process");
109
+ if (outcome.exit_code !== null) {
110
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.exit_code", "must be null");
111
+ }
112
+ nonEmptyString(outcome.signal, "outcome.process.signal");
113
+ break;
114
+ case "launch_error":
115
+ exactKeys(outcome, ["kind", "exit_code", "signal", "error"], "outcome.process");
116
+ if (outcome.exit_code !== null || outcome.signal !== null) {
117
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process", "launch fields are invalid");
118
+ }
119
+ nonEmptyString(outcome.error, "outcome.process.error");
120
+ break;
121
+ default:
122
+ throw new CanonicalAttemptOutcomeValidationError("outcome.process.kind", "is invalid");
123
+ }
124
+ return outcome;
125
+ }
126
+ export function validateCanonicalAttemptOutcome(executorValue, outcomeValue) {
127
+ const executor = validateExecutor(executorValue);
128
+ const outcome = record(outcomeValue, "outcome");
129
+ if (executor.kind === "worker") {
130
+ if (outcome.kind !== "worker_process") {
131
+ throw new CanonicalAttemptOutcomeValidationError("outcome.kind", "a worker outcome must be worker_process");
132
+ }
133
+ exactKeys(outcome, ["kind", "status", "reason_code", "message", "process"], "outcome");
134
+ const process = validateProcessOutcome(outcome.process);
135
+ if (!new Set(["succeeded", "failed", "cancelled"]).has(outcome.status)) {
136
+ throw new CanonicalAttemptOutcomeValidationError("outcome.status", "must be succeeded, failed, or cancelled");
137
+ }
138
+ if (outcome.message !== null && typeof outcome.message !== "string") {
139
+ throw new CanonicalAttemptOutcomeValidationError("outcome.message", "must be a string or null");
140
+ }
141
+ if (outcome.status === "succeeded") {
142
+ if (process.kind !== "success"
143
+ || outcome.reason_code !== null
144
+ || outcome.message !== null) {
145
+ throw new CanonicalAttemptOutcomeValidationError("outcome", "succeeded requires process success and null reason_code/message");
146
+ }
147
+ }
148
+ else {
149
+ nonEmptyString(outcome.reason_code, "outcome.reason_code");
150
+ }
151
+ return {
152
+ executor,
153
+ outcome: {
154
+ kind: "worker_process",
155
+ status: outcome.status,
156
+ reason_code: outcome.reason_code,
157
+ message: outcome.message,
158
+ process,
159
+ },
160
+ };
161
+ }
162
+ if (outcome.kind === "worker_process") {
163
+ throw new CanonicalAttemptOutcomeValidationError("outcome.kind", `${executor.kind} outcomes must not contain process facts`);
164
+ }
165
+ if (outcome.kind === "succeeded") {
166
+ exactKeys(outcome, ["kind"], "outcome");
167
+ return { executor, outcome: { kind: "succeeded" } };
168
+ }
169
+ if (outcome.kind === "failed") {
170
+ exactKeys(outcome, ["kind", "reason_code", "message"], "outcome");
171
+ nonEmptyString(outcome.reason_code, "outcome.reason_code");
172
+ if (outcome.message !== null && typeof outcome.message !== "string") {
173
+ throw new CanonicalAttemptOutcomeValidationError("outcome.message", "must be a string or null");
174
+ }
175
+ return {
176
+ executor,
177
+ outcome: {
178
+ kind: "failed",
179
+ reason_code: outcome.reason_code,
180
+ message: outcome.message,
181
+ },
182
+ };
183
+ }
184
+ if (outcome.kind === "cancelled") {
185
+ exactKeys(outcome, ["kind", "reason"], "outcome");
186
+ if (outcome.reason !== null && typeof outcome.reason !== "string") {
187
+ throw new CanonicalAttemptOutcomeValidationError("outcome.reason", "must be a string or null");
188
+ }
189
+ return {
190
+ executor,
191
+ outcome: { kind: "cancelled", reason: outcome.reason },
192
+ };
193
+ }
194
+ throw new CanonicalAttemptOutcomeValidationError("outcome.kind", `is invalid for ${executor.kind}`);
195
+ }
@@ -0,0 +1,10 @@
1
+ export type CanonicalJsonValue = null | boolean | number | string | readonly CanonicalJsonValue[] | {
2
+ readonly [key: string]: CanonicalJsonValue;
3
+ };
4
+ export declare class CanonicalJsonError extends TypeError {
5
+ readonly path: string;
6
+ constructor(path: string, message: string);
7
+ }
8
+ export declare function canonicalizeJson(value: unknown): string;
9
+ export declare function canonicalJsonUtf8(value: unknown): Uint8Array;
10
+ export declare function assertCanonicalJsonValue(value: unknown): asserts value is CanonicalJsonValue;
@@ -0,0 +1,105 @@
1
+ export class CanonicalJsonError extends TypeError {
2
+ path;
3
+ constructor(path, message) {
4
+ super(`${path}: ${message}`);
5
+ this.name = "CanonicalJsonError";
6
+ this.path = path;
7
+ }
8
+ }
9
+ function compareUtf16(left, right) {
10
+ return left < right ? -1 : left > right ? 1 : 0;
11
+ }
12
+ function assertUnicodeScalarString(value, path) {
13
+ for (let index = 0; index < value.length; index += 1) {
14
+ const code = value.charCodeAt(index);
15
+ if (code >= 0xd800 && code <= 0xdbff) {
16
+ const next = value.charCodeAt(index + 1);
17
+ if (!(next >= 0xdc00 && next <= 0xdfff)) {
18
+ throw new CanonicalJsonError(path, "contains a lone high surrogate");
19
+ }
20
+ index += 1;
21
+ }
22
+ else if (code >= 0xdc00 && code <= 0xdfff) {
23
+ throw new CanonicalJsonError(path, "contains a lone low surrogate");
24
+ }
25
+ }
26
+ }
27
+ function canonicalize(value, path, ancestors) {
28
+ if (value === null)
29
+ return "null";
30
+ if (typeof value === "boolean")
31
+ return value ? "true" : "false";
32
+ if (typeof value === "number") {
33
+ if (!Number.isFinite(value)) {
34
+ throw new CanonicalJsonError(path, "number must be finite");
35
+ }
36
+ if (Object.is(value, -0))
37
+ return "0";
38
+ return JSON.stringify(value);
39
+ }
40
+ if (typeof value === "string") {
41
+ assertUnicodeScalarString(value, path);
42
+ return JSON.stringify(value);
43
+ }
44
+ if (typeof value !== "object") {
45
+ throw new CanonicalJsonError(path, `${typeof value} is not a JSON value`);
46
+ }
47
+ if (ancestors.has(value)) {
48
+ throw new CanonicalJsonError(path, "contains a cycle");
49
+ }
50
+ ancestors.add(value);
51
+ try {
52
+ if (Object.getOwnPropertySymbols(value).length > 0) {
53
+ throw new CanonicalJsonError(path, "symbol-keyed properties are not JSON values");
54
+ }
55
+ if (Array.isArray(value)) {
56
+ const allowedProperties = new Set(["length"]);
57
+ const items = [];
58
+ for (let index = 0; index < value.length; index += 1) {
59
+ if (!Object.prototype.hasOwnProperty.call(value, index)) {
60
+ throw new CanonicalJsonError(`${path}[${index}]`, "sparse arrays are not supported");
61
+ }
62
+ allowedProperties.add(String(index));
63
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
64
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
65
+ throw new CanonicalJsonError(`${path}[${index}]`, "array entries must be enumerable data properties");
66
+ }
67
+ items.push(canonicalize(value[index], `${path}[${index}]`, ancestors));
68
+ }
69
+ const extraProperty = Object.getOwnPropertyNames(value).find((property) => !allowedProperties.has(property));
70
+ if (extraProperty !== undefined) {
71
+ throw new CanonicalJsonError(path, `array has non-JSON property '${extraProperty}'`);
72
+ }
73
+ return `[${items.join(",")}]`;
74
+ }
75
+ const prototype = Object.getPrototypeOf(value);
76
+ if (prototype !== Object.prototype && prototype !== null) {
77
+ throw new CanonicalJsonError(path, "object must be a plain object");
78
+ }
79
+ const record = value;
80
+ const keys = Object.getOwnPropertyNames(record);
81
+ for (const key of keys) {
82
+ const descriptor = Object.getOwnPropertyDescriptor(record, key);
83
+ if (!descriptor?.enumerable || !("value" in descriptor)) {
84
+ throw new CanonicalJsonError(`${path}.${key}`, "object members must be enumerable data properties");
85
+ }
86
+ }
87
+ const members = keys.sort(compareUtf16).map((key) => {
88
+ assertUnicodeScalarString(key, `${path} key`);
89
+ return `${JSON.stringify(key)}:${canonicalize(record[key], `${path}.${key}`, ancestors)}`;
90
+ });
91
+ return `{${members.join(",")}}`;
92
+ }
93
+ finally {
94
+ ancestors.delete(value);
95
+ }
96
+ }
97
+ export function canonicalizeJson(value) {
98
+ return canonicalize(value, "$", new Set());
99
+ }
100
+ export function canonicalJsonUtf8(value) {
101
+ return new TextEncoder().encode(canonicalizeJson(value));
102
+ }
103
+ export function assertCanonicalJsonValue(value) {
104
+ canonicalizeJson(value);
105
+ }
@@ -0,0 +1,93 @@
1
+ import { type CanonicalDnaSource, type CanonicalRuntimeProjection, type CanonicalTargetCompilation, type CanonicalTarget, type CompileCanonicalTargetRequest } from "./canonical-target-compiler.js";
2
+ import { type ExecutableRunPlanPayload } from "./executable-run-plan.js";
3
+ import { type ProviderConfiguration, type SecretReference, type StoredRunBinding } from "./run-binding.js";
4
+ import type { JsonValue, RunId } from "./run-contracts.js";
5
+ export interface CanonicalTargetCompilerPort {
6
+ compile(request: CompileCanonicalTargetRequest): ExecutableRunPlanPayload;
7
+ compileCascade?(request: CompileCanonicalTargetRequest): CanonicalTargetCompilation;
8
+ }
9
+ export interface ResolveCanonicalDnaSourcesRequest {
10
+ readonly dna_sources: readonly string[];
11
+ readonly project_root: string;
12
+ readonly enterprise_policy_dirs?: readonly string[];
13
+ }
14
+ export interface CanonicalDnaSourceResolver {
15
+ resolve(request: ResolveCanonicalDnaSourcesRequest): Promise<readonly CanonicalDnaSource[]>;
16
+ }
17
+ export interface CanonicalRunStartRequest {
18
+ readonly plan_payload: ExecutableRunPlanPayload;
19
+ readonly run_binding: StoredRunBinding;
20
+ readonly metadata?: JsonValue;
21
+ readonly run_id?: RunId;
22
+ }
23
+ export interface CanonicalRunStarter<StartRunResponse> {
24
+ start(request: CanonicalRunStartRequest): Promise<StartRunResponse> | StartRunResponse;
25
+ }
26
+ export interface StartCanonicalTargetRequest {
27
+ readonly dna_sources: readonly string[];
28
+ readonly target: CanonicalTarget;
29
+ readonly inputs: Readonly<Record<string, unknown>>;
30
+ readonly environment: {
31
+ readonly project_root: string;
32
+ readonly workspace_root?: string;
33
+ readonly enterprise_policy_dirs?: readonly string[];
34
+ readonly secret_references?: readonly SecretReference[];
35
+ };
36
+ readonly provider: ProviderConfiguration;
37
+ readonly max_concurrency: number;
38
+ readonly input_policy?: "strict" | "declared_only";
39
+ readonly context?: string | null;
40
+ readonly attempt_policy?: CompileCanonicalTargetRequest["attempt_policy"];
41
+ readonly metadata?: JsonValue;
42
+ readonly run_id?: RunId;
43
+ }
44
+ export interface CanonicalRuntimeProjectionRequest<StartRunResponse> {
45
+ readonly phase: "before_start" | "after_start";
46
+ readonly projection: CanonicalRuntimeProjection;
47
+ readonly start_response: StartRunResponse | null;
48
+ }
49
+ export interface CanonicalRunMetadataFactoryRequest {
50
+ readonly compiled: CompiledCanonicalTarget;
51
+ readonly metadata: JsonValue | undefined;
52
+ }
53
+ export interface CanonicalRunApplicationOptions<StartRunResponse> {
54
+ readonly starter?: CanonicalRunStarter<StartRunResponse>;
55
+ readonly compiler?: CanonicalTargetCompilerPort;
56
+ readonly source_resolver?: CanonicalDnaSourceResolver;
57
+ readonly metadata_factory?: (request: CanonicalRunMetadataFactoryRequest) => Promise<JsonValue | undefined> | JsonValue | undefined;
58
+ readonly projection_sink?: (request: CanonicalRuntimeProjectionRequest<StartRunResponse>) => Promise<void> | void;
59
+ }
60
+ export interface CompiledCanonicalTarget {
61
+ readonly plan_payload: ExecutableRunPlanPayload;
62
+ readonly run_binding: StoredRunBinding;
63
+ readonly projection: CanonicalRuntimeProjection | null;
64
+ }
65
+ export interface StartCompiledCanonicalTargetRequest {
66
+ readonly compiled: CompiledCanonicalTarget;
67
+ readonly metadata?: JsonValue;
68
+ readonly run_id?: RunId;
69
+ }
70
+ export type CanonicalRunApplicationErrorCode = "invalid_request" | "invalid_service_response" | "source_resolution_failed";
71
+ export declare class CanonicalRunApplicationError extends Error {
72
+ readonly code: CanonicalRunApplicationErrorCode;
73
+ constructor(code: CanonicalRunApplicationErrorCode, message: string, options?: ErrorOptions);
74
+ }
75
+ export declare class FilesystemCanonicalDnaSourceResolver implements CanonicalDnaSourceResolver {
76
+ protected afterSourceSnapshotsLoaded(_files: readonly string[]): Promise<void>;
77
+ resolve(request: ResolveCanonicalDnaSourcesRequest): Promise<readonly CanonicalDnaSource[]>;
78
+ }
79
+ /**
80
+ * The only application boundary allowed to compose source resolution, target
81
+ * compilation, environment binding, and canonical service start.
82
+ */
83
+ export declare class CanonicalRunApplication<StartRunResponse> {
84
+ private readonly starter;
85
+ private readonly compiler;
86
+ private readonly sourceResolver;
87
+ private readonly metadataFactory;
88
+ private readonly projectionSink;
89
+ constructor(options: CanonicalRunApplicationOptions<StartRunResponse>);
90
+ startTarget(request: StartCanonicalTargetRequest): Promise<StartRunResponse>;
91
+ compileTarget(request: StartCanonicalTargetRequest): Promise<CompiledCanonicalTarget>;
92
+ startCompiledTarget(request: StartCompiledCanonicalTargetRequest): Promise<StartRunResponse>;
93
+ }
@@ -0,0 +1,229 @@
1
+ import { isAbsolute, relative, resolve, sep, win32 } from "node:path";
2
+ import { expandDNAInputs, } from "../compiler/index.js";
3
+ import { validateDNA } from "../schema/validate.js";
4
+ import { CanonicalTargetCompiler, } from "./canonical-target-compiler.js";
5
+ import { createStoredExecutableRunPlan, parseStoredExecutableRunPlan, } from "./executable-run-plan.js";
6
+ import { parseStoredRunBinding, RunBindingFactory, } from "./run-binding.js";
7
+ function bindingInputs(request, planPayload) {
8
+ if (request.input_policy !== "declared_only")
9
+ return request.inputs;
10
+ const declaredNames = new Set(planPayload.input_contract.inputs.map((input) => input.name));
11
+ return Object.fromEntries(Object.entries(request.inputs).filter(([name]) => declaredNames.has(name)));
12
+ }
13
+ export class CanonicalRunApplicationError extends Error {
14
+ code;
15
+ constructor(code, message, options) {
16
+ super(message, options);
17
+ this.name = "CanonicalRunApplicationError";
18
+ this.code = code;
19
+ }
20
+ }
21
+ function isInside(root, candidate) {
22
+ const path = relative(root, candidate);
23
+ return path === "" || (path !== ".."
24
+ && !path.startsWith(`..${sep}`)
25
+ && !isAbsolute(path)
26
+ && !win32.isAbsolute(path));
27
+ }
28
+ function portableRelativeRef(projectRoot, file, provenance) {
29
+ if (isInside(projectRoot, file)) {
30
+ const path = relative(projectRoot, file).split(sep).join("/");
31
+ if (path)
32
+ return path;
33
+ }
34
+ const declaredRef = provenance.source_ref;
35
+ if (declaredRef.length > 0
36
+ && !isAbsolute(declaredRef)
37
+ && !win32.isAbsolute(declaredRef)
38
+ && !declaredRef.includes("\\")
39
+ && !declaredRef.includes("\0")
40
+ && !declaredRef.split("/").includes("..")) {
41
+ return declaredRef;
42
+ }
43
+ const sourceId = provenance.source_id.replace(/[^A-Za-z0-9._-]/gu, "_") || "source";
44
+ const fingerprint = provenance.fingerprint.slice("sha256:".length, "sha256:".length + 16);
45
+ return `external/${sourceId}-${fingerprint}.dna`;
46
+ }
47
+ function sourceDigest(value, sourceId) {
48
+ if (!/^sha256:[a-f0-9]{64}$/u.test(value)) {
49
+ throw new CanonicalRunApplicationError("source_resolution_failed", `DNA source '${sourceId}' has an invalid content fingerprint`);
50
+ }
51
+ return value;
52
+ }
53
+ function validateCanonicalStartResponse(value, planId, bindingId) {
54
+ try {
55
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
56
+ throw new Error("start response must be an object");
57
+ }
58
+ const response = value;
59
+ if (typeof response.run_id !== "string" || response.run_id.length === 0) {
60
+ throw new Error("start response run_id must be a non-empty string");
61
+ }
62
+ if (response.view === null || typeof response.view !== "object") {
63
+ throw new Error("start response view must be an object");
64
+ }
65
+ const view = response.view;
66
+ const plan = parseStoredExecutableRunPlan(view.plan);
67
+ const binding = parseStoredRunBinding(view.run?.run_binding);
68
+ if (response.plan_id !== planId
69
+ || response.run_id !== view.run?.run_id
70
+ || plan.plan_id !== planId
71
+ || view.run?.plan_id !== planId
72
+ || view.run?.plan_digest !== plan.plan_digest
73
+ || binding.binding_id !== bindingId
74
+ || binding.binding.plan_id !== planId) {
75
+ throw new Error("start response changed the compiled plan or run binding identity");
76
+ }
77
+ }
78
+ catch (error) {
79
+ throw new CanonicalRunApplicationError("invalid_service_response", "canonical start response does not match the compiled plan and run binding", { cause: error });
80
+ }
81
+ }
82
+ export class FilesystemCanonicalDnaSourceResolver {
83
+ async afterSourceSnapshotsLoaded(_files) { }
84
+ async resolve(request) {
85
+ if (request.dna_sources.length === 0) {
86
+ throw new CanonicalRunApplicationError("invalid_request", "dna_sources must contain at least one source");
87
+ }
88
+ const projectRoot = resolve(request.project_root);
89
+ try {
90
+ const expanded = await expandDNAInputs([...request.dna_sources], {
91
+ cwd: projectRoot,
92
+ enterprisePolicyDirs: request.enterprise_policy_dirs
93
+ ? [...request.enterprise_policy_dirs]
94
+ : undefined,
95
+ });
96
+ await this.afterSourceSnapshotsLoaded(expanded.files);
97
+ return expanded.files.map((file, index) => {
98
+ const provenance = expanded.provenance[index];
99
+ const dna = expanded.dnas[index];
100
+ const snapshotBytes = expanded.snapshot_bytes[index];
101
+ if (!provenance || !dna || !snapshotBytes) {
102
+ throw new CanonicalRunApplicationError("source_resolution_failed", `DNA source snapshot is incomplete for ${file}`);
103
+ }
104
+ const validation = validateDNA(dna);
105
+ if (!validation.valid) {
106
+ throw new CanonicalRunApplicationError("source_resolution_failed", `DNA source '${provenance.source_id}' failed schema validation: ${validation.errors
107
+ .map((error) => `${error.path}: ${error.message}`)
108
+ .join("; ")}`);
109
+ }
110
+ return {
111
+ dna,
112
+ source_id: provenance.source_id,
113
+ relative_ref: portableRelativeRef(projectRoot, file, provenance),
114
+ content_digest: sourceDigest(provenance.fingerprint, provenance.source_id),
115
+ snapshot_bytes: snapshotBytes,
116
+ };
117
+ });
118
+ }
119
+ catch (error) {
120
+ if (error instanceof CanonicalRunApplicationError)
121
+ throw error;
122
+ throw new CanonicalRunApplicationError("source_resolution_failed", "failed to resolve and validate DNA sources", { cause: error });
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * The only application boundary allowed to compose source resolution, target
128
+ * compilation, environment binding, and canonical service start.
129
+ */
130
+ export class CanonicalRunApplication {
131
+ starter;
132
+ compiler;
133
+ sourceResolver;
134
+ metadataFactory;
135
+ projectionSink;
136
+ constructor(options) {
137
+ this.starter = options.starter;
138
+ this.compiler = options.compiler ?? new CanonicalTargetCompiler();
139
+ this.sourceResolver = options.source_resolver
140
+ ?? new FilesystemCanonicalDnaSourceResolver();
141
+ this.metadataFactory = options.metadata_factory;
142
+ this.projectionSink = options.projection_sink;
143
+ }
144
+ async startTarget(request) {
145
+ const compiled = await this.compileTarget(request);
146
+ const metadata = this.metadataFactory
147
+ ? await this.metadataFactory({ compiled, metadata: request.metadata })
148
+ : request.metadata;
149
+ return this.startCompiledTarget({
150
+ compiled,
151
+ ...(metadata !== undefined ? { metadata } : {}),
152
+ ...(request.run_id !== undefined ? { run_id: request.run_id } : {}),
153
+ });
154
+ }
155
+ async compileTarget(request) {
156
+ if (!request.environment.project_root.trim()) {
157
+ throw new CanonicalRunApplicationError("invalid_request", "environment.project_root must be non-empty");
158
+ }
159
+ const projectRoot = resolve(request.environment.project_root);
160
+ const workspaceRoot = request.environment.workspace_root === undefined
161
+ ? projectRoot
162
+ : resolve(projectRoot, request.environment.workspace_root);
163
+ const dnaSources = await this.sourceResolver.resolve({
164
+ dna_sources: request.dna_sources,
165
+ project_root: projectRoot,
166
+ enterprise_policy_dirs: request.environment.enterprise_policy_dirs,
167
+ });
168
+ const compileRequest = {
169
+ dna_sources: dnaSources,
170
+ target: request.target,
171
+ ...(request.context !== undefined ? { context: request.context } : {}),
172
+ ...(request.attempt_policy !== undefined
173
+ ? { attempt_policy: request.attempt_policy }
174
+ : {}),
175
+ };
176
+ const compilation = this.compiler.compileCascade?.(compileRequest);
177
+ const planPayload = compilation?.plan ?? this.compiler.compile(compileRequest);
178
+ const storedPlan = createStoredExecutableRunPlan(planPayload);
179
+ const runBinding = RunBindingFactory.bind({
180
+ plan: storedPlan,
181
+ max_concurrency: request.max_concurrency,
182
+ inputs: bindingInputs(request, planPayload),
183
+ environment: {
184
+ project_root: projectRoot,
185
+ workspace_root: workspaceRoot,
186
+ secret_references: request.environment.secret_references ?? [],
187
+ },
188
+ provider: request.provider,
189
+ });
190
+ return {
191
+ plan_payload: storedPlan.plan,
192
+ run_binding: runBinding,
193
+ projection: compilation?.projection ?? null,
194
+ };
195
+ }
196
+ async startCompiledTarget(request) {
197
+ if (!this.starter) {
198
+ throw new CanonicalRunApplicationError("invalid_request", "canonical run starter is required to start a compiled target");
199
+ }
200
+ const { compiled } = request;
201
+ const storedPlan = createStoredExecutableRunPlan(compiled.plan_payload);
202
+ const runBinding = parseStoredRunBinding(compiled.run_binding);
203
+ if (runBinding.binding.plan_id !== storedPlan.plan_id) {
204
+ throw new CanonicalRunApplicationError("invalid_request", "compiled run binding does not match the compiled plan");
205
+ }
206
+ if (compiled.projection && this.projectionSink) {
207
+ await this.projectionSink({
208
+ phase: "before_start",
209
+ projection: compiled.projection,
210
+ start_response: null,
211
+ });
212
+ }
213
+ const startResponse = await this.starter.start({
214
+ plan_payload: storedPlan.plan,
215
+ run_binding: runBinding,
216
+ ...(request.metadata !== undefined ? { metadata: request.metadata } : {}),
217
+ ...(request.run_id !== undefined ? { run_id: request.run_id } : {}),
218
+ });
219
+ validateCanonicalStartResponse(startResponse, storedPlan.plan_id, runBinding.binding_id);
220
+ if (compiled.projection && this.projectionSink) {
221
+ await this.projectionSink({
222
+ phase: "after_start",
223
+ projection: compiled.projection,
224
+ start_response: startResponse,
225
+ });
226
+ }
227
+ return startResponse;
228
+ }
229
+ }