taskflow-mcp-core 0.3.0-beta.1.1 → 1.0.0

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 (185) hide show
  1. package/CONTROL_GUIDE.md +113 -0
  2. package/README.md +34 -25
  3. package/dist/control/admission-service.d.ts +15 -0
  4. package/dist/control/admission-service.d.ts.map +1 -0
  5. package/dist/control/admission-service.js +64 -0
  6. package/dist/control/admission-service.js.map +1 -0
  7. package/dist/control/approval-edits.d.ts +12 -0
  8. package/dist/control/approval-edits.d.ts.map +1 -0
  9. package/dist/control/approval-edits.js +66 -0
  10. package/dist/control/approval-edits.js.map +1 -0
  11. package/dist/control/approval-service.d.ts +173 -0
  12. package/dist/control/approval-service.d.ts.map +1 -0
  13. package/dist/control/approval-service.js +342 -0
  14. package/dist/control/approval-service.js.map +1 -0
  15. package/dist/control/authorization.d.ts +94 -0
  16. package/dist/control/authorization.d.ts.map +1 -0
  17. package/dist/control/authorization.js +302 -0
  18. package/dist/control/authorization.js.map +1 -0
  19. package/dist/control/control-cli.d.ts +3 -0
  20. package/dist/control/control-cli.d.ts.map +1 -0
  21. package/dist/control/control-cli.js +279 -0
  22. package/dist/control/control-cli.js.map +1 -0
  23. package/dist/control/control-console.d.ts +17 -0
  24. package/dist/control/control-console.d.ts.map +1 -0
  25. package/dist/control/control-console.js +67 -0
  26. package/dist/control/control-console.js.map +1 -0
  27. package/dist/control/control-host.d.ts +98 -0
  28. package/dist/control/control-host.d.ts.map +1 -0
  29. package/dist/control/control-host.js +459 -0
  30. package/dist/control/control-host.js.map +1 -0
  31. package/dist/control/control-mcp.d.ts +11 -0
  32. package/dist/control/control-mcp.d.ts.map +1 -0
  33. package/dist/control/control-mcp.js +78 -0
  34. package/dist/control/control-mcp.js.map +1 -0
  35. package/dist/control/errors.d.ts +44 -0
  36. package/dist/control/errors.d.ts.map +1 -0
  37. package/dist/control/errors.js +93 -0
  38. package/dist/control/errors.js.map +1 -0
  39. package/dist/control/hello.d.ts +39 -0
  40. package/dist/control/hello.d.ts.map +1 -0
  41. package/dist/control/hello.js +80 -0
  42. package/dist/control/hello.js.map +1 -0
  43. package/dist/control/host-runtime.d.ts +32 -0
  44. package/dist/control/host-runtime.d.ts.map +1 -0
  45. package/dist/control/host-runtime.js +542 -0
  46. package/dist/control/host-runtime.js.map +1 -0
  47. package/dist/control/index.d.ts +32 -0
  48. package/dist/control/index.d.ts.map +1 -0
  49. package/dist/control/index.js +32 -0
  50. package/dist/control/index.js.map +1 -0
  51. package/dist/control/legacy-conflict.d.ts +19 -0
  52. package/dist/control/legacy-conflict.d.ts.map +1 -0
  53. package/dist/control/legacy-conflict.js +176 -0
  54. package/dist/control/legacy-conflict.js.map +1 -0
  55. package/dist/control/local-bootstrap.d.ts +21 -0
  56. package/dist/control/local-bootstrap.d.ts.map +1 -0
  57. package/dist/control/local-bootstrap.js +224 -0
  58. package/dist/control/local-bootstrap.js.map +1 -0
  59. package/dist/control/modes.d.ts +60 -0
  60. package/dist/control/modes.d.ts.map +1 -0
  61. package/dist/control/modes.js +86 -0
  62. package/dist/control/modes.js.map +1 -0
  63. package/dist/control/project-admin-cli.d.ts +3 -0
  64. package/dist/control/project-admin-cli.d.ts.map +1 -0
  65. package/dist/control/project-admin-cli.js +155 -0
  66. package/dist/control/project-admin-cli.js.map +1 -0
  67. package/dist/control/project-registry.d.ts +20 -0
  68. package/dist/control/project-registry.d.ts.map +1 -0
  69. package/dist/control/project-registry.js +181 -0
  70. package/dist/control/project-registry.js.map +1 -0
  71. package/dist/control/runtime-checkpoint.d.ts +11 -0
  72. package/dist/control/runtime-checkpoint.d.ts.map +1 -0
  73. package/dist/control/runtime-checkpoint.js +39 -0
  74. package/dist/control/runtime-checkpoint.js.map +1 -0
  75. package/dist/control/runtime-provider.d.ts +100 -0
  76. package/dist/control/runtime-provider.d.ts.map +1 -0
  77. package/dist/control/runtime-provider.js +258 -0
  78. package/dist/control/runtime-provider.js.map +1 -0
  79. package/dist/control/schema/approval.d.ts +108 -0
  80. package/dist/control/schema/approval.d.ts.map +1 -0
  81. package/dist/control/schema/approval.js +65 -0
  82. package/dist/control/schema/approval.js.map +1 -0
  83. package/dist/control/schema/commands.d.ts +663 -0
  84. package/dist/control/schema/commands.d.ts.map +1 -0
  85. package/dist/control/schema/commands.js +147 -0
  86. package/dist/control/schema/commands.js.map +1 -0
  87. package/dist/control/schema/common.d.ts +24 -0
  88. package/dist/control/schema/common.d.ts.map +1 -0
  89. package/dist/control/schema/common.js +38 -0
  90. package/dist/control/schema/common.js.map +1 -0
  91. package/dist/control/schema/coordinator.d.ts +98 -0
  92. package/dist/control/schema/coordinator.d.ts.map +1 -0
  93. package/dist/control/schema/coordinator.js +87 -0
  94. package/dist/control/schema/coordinator.js.map +1 -0
  95. package/dist/control/schema/evidence.d.ts +140 -0
  96. package/dist/control/schema/evidence.d.ts.map +1 -0
  97. package/dist/control/schema/evidence.js +64 -0
  98. package/dist/control/schema/evidence.js.map +1 -0
  99. package/dist/control/schema/header.d.ts +79 -0
  100. package/dist/control/schema/header.d.ts.map +1 -0
  101. package/dist/control/schema/header.js +66 -0
  102. package/dist/control/schema/header.js.map +1 -0
  103. package/dist/control/schema/index.d.ts +31 -0
  104. package/dist/control/schema/index.d.ts.map +1 -0
  105. package/dist/control/schema/index.js +31 -0
  106. package/dist/control/schema/index.js.map +1 -0
  107. package/dist/control/schema/plan.d.ts +291 -0
  108. package/dist/control/schema/plan.d.ts.map +1 -0
  109. package/dist/control/schema/plan.js +80 -0
  110. package/dist/control/schema/plan.js.map +1 -0
  111. package/dist/control/schema/policy.d.ts +96 -0
  112. package/dist/control/schema/policy.d.ts.map +1 -0
  113. package/dist/control/schema/policy.js +73 -0
  114. package/dist/control/schema/policy.js.map +1 -0
  115. package/dist/control/schema/run.d.ts +61 -0
  116. package/dist/control/schema/run.d.ts.map +1 -0
  117. package/dist/control/schema/run.js +56 -0
  118. package/dist/control/schema/run.js.map +1 -0
  119. package/dist/control/schema/te-mirrors.d.ts +145 -0
  120. package/dist/control/schema/te-mirrors.d.ts.map +1 -0
  121. package/dist/control/schema/te-mirrors.js +145 -0
  122. package/dist/control/schema/te-mirrors.js.map +1 -0
  123. package/dist/control/schema/transport.d.ts +479 -0
  124. package/dist/control/schema/transport.d.ts.map +1 -0
  125. package/dist/control/schema/transport.js +181 -0
  126. package/dist/control/schema/transport.js.map +1 -0
  127. package/dist/control/singleton.d.ts +135 -0
  128. package/dist/control/singleton.d.ts.map +1 -0
  129. package/dist/control/singleton.js +607 -0
  130. package/dist/control/singleton.js.map +1 -0
  131. package/dist/control/store/coordinator-store.d.ts +143 -0
  132. package/dist/control/store/coordinator-store.d.ts.map +1 -0
  133. package/dist/control/store/coordinator-store.js +620 -0
  134. package/dist/control/store/coordinator-store.js.map +1 -0
  135. package/dist/control/store/evidence-adapter.d.ts +10 -0
  136. package/dist/control/store/evidence-adapter.d.ts.map +1 -0
  137. package/dist/control/store/evidence-adapter.js +114 -0
  138. package/dist/control/store/evidence-adapter.js.map +1 -0
  139. package/dist/control/store/evidence-explanation.d.ts +55 -0
  140. package/dist/control/store/evidence-explanation.d.ts.map +1 -0
  141. package/dist/control/store/evidence-explanation.js +50 -0
  142. package/dist/control/store/evidence-explanation.js.map +1 -0
  143. package/dist/control/store/evidence-store.d.ts +143 -0
  144. package/dist/control/store/evidence-store.d.ts.map +1 -0
  145. package/dist/control/store/evidence-store.js +437 -0
  146. package/dist/control/store/evidence-store.js.map +1 -0
  147. package/dist/control/store/index.d.ts +3 -0
  148. package/dist/control/store/index.d.ts.map +1 -0
  149. package/dist/control/store/index.js +2 -0
  150. package/dist/control/store/index.js.map +1 -0
  151. package/dist/control/store/journal.d.ts +270 -0
  152. package/dist/control/store/journal.d.ts.map +1 -0
  153. package/dist/control/store/journal.js +50 -0
  154. package/dist/control/store/journal.js.map +1 -0
  155. package/dist/control/store/project-lifecycle.d.ts +49 -0
  156. package/dist/control/store/project-lifecycle.d.ts.map +1 -0
  157. package/dist/control/store/project-lifecycle.js +288 -0
  158. package/dist/control/store/project-lifecycle.js.map +1 -0
  159. package/dist/control/store/store.d.ts +93 -0
  160. package/dist/control/store/store.d.ts.map +1 -0
  161. package/dist/control/store/store.js +873 -0
  162. package/dist/control/store/store.js.map +1 -0
  163. package/dist/control/te-provider.d.ts +86 -0
  164. package/dist/control/te-provider.d.ts.map +1 -0
  165. package/dist/control/te-provider.js +131 -0
  166. package/dist/control/te-provider.js.map +1 -0
  167. package/dist/control/uds.d.ts +114 -0
  168. package/dist/control/uds.d.ts.map +1 -0
  169. package/dist/control/uds.js +456 -0
  170. package/dist/control/uds.js.map +1 -0
  171. package/dist/control/web-console/view.d.ts +5 -0
  172. package/dist/control/web-console/view.d.ts.map +1 -0
  173. package/dist/control/web-console/view.js +122 -0
  174. package/dist/control/web-console/view.js.map +1 -0
  175. package/dist/control/web-console.d.ts +50 -0
  176. package/dist/control/web-console.d.ts.map +1 -0
  177. package/dist/control/web-console.js +317 -0
  178. package/dist/control/web-console.js.map +1 -0
  179. package/dist/mcp/jsonrpc.d.ts.map +1 -1
  180. package/dist/mcp/jsonrpc.js +12 -3
  181. package/dist/mcp/jsonrpc.js.map +1 -1
  182. package/dist/mcp/server.d.ts.map +1 -1
  183. package/dist/mcp/server.js +26 -17
  184. package/dist/mcp/server.js.map +1 -1
  185. package/package.json +13 -5
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Trusted local identity and P1/P2/P12 authorization.
3
+ *
4
+ * A protected bootstrap token proves possession of the local OS user's token,
5
+ * not kernel-verified per-connection peer credentials. The bootstrap owner must
6
+ * protect its directory/token (0700/0600); processes able to read that token act
7
+ * as that user. This module neither grants network/domain transfer authority nor
8
+ * replaces TE PathRef/permit checks. Policy callbacks must read current policy.
9
+ */
10
+ import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
11
+ import { lstatSync, realpathSync, statSync } from "node:fs";
12
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
13
+ import { canonicalJson } from "taskflow-core/flowir/hash";
14
+ import { ControlError } from "./errors.js";
15
+ export const AUTHORIZATION_CAPABILITIES = [
16
+ "project.read", "run.submit", "run.cancel", "approval.decide", "coordinator.setMaxActiveRuns",
17
+ "coordinator.forceRelease", "artifact.read",
18
+ ];
19
+ function denied(message) { throw new ControlError("TF_POLICY_DENIED", message); }
20
+ function revoked() { throw new ControlError("TF_AUTHORITY_REVOKED", "verified context is absent, foreign, or revoked"); }
21
+ function record(value, keys) {
22
+ if (value === null || typeof value !== "object" || Array.isArray(value))
23
+ denied("security object is required");
24
+ const object = value;
25
+ if (![Object.prototype, null].includes(Object.getPrototypeOf(object)))
26
+ denied("security object prototype is unsupported");
27
+ if (Object.keys(object).some((key) => !keys.includes(key)))
28
+ denied("unknown security field");
29
+ return object;
30
+ }
31
+ function text(value) { return typeof value === "string" && value.length > 0; }
32
+ function kind(value) {
33
+ return AUTHORIZATION_CAPABILITIES.includes(value);
34
+ }
35
+ function contained(root, target) {
36
+ const suffix = relative(root, target);
37
+ return suffix === "" || (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`));
38
+ }
39
+ /** Resolve existing ancestors too, so a symlink cannot enlarge lexical scope. */
40
+ function canonicalPath(value) {
41
+ if (!text(value) || !isAbsolute(value) || value.includes("\0"))
42
+ denied("absolute resource scope is required");
43
+ let existing = resolve(value);
44
+ const suffix = [];
45
+ for (;;) {
46
+ try {
47
+ lstatSync(existing);
48
+ }
49
+ catch (error) {
50
+ if (error.code !== "ENOENT")
51
+ denied("resource scope cannot be verified");
52
+ const parent = dirname(existing);
53
+ if (parent === existing)
54
+ denied("resource scope cannot be verified");
55
+ suffix.unshift(relative(parent, existing));
56
+ existing = parent;
57
+ continue;
58
+ }
59
+ // An existing entry whose target cannot be resolved is not a missing
60
+ // path: notably a dangling symlink must never inherit its parent's scope.
61
+ try {
62
+ return resolve(realpathSync(existing), ...suffix);
63
+ }
64
+ catch {
65
+ denied("existing resource scope or symbolic link cannot be verified");
66
+ }
67
+ }
68
+ }
69
+ function trustedBinding(value) {
70
+ const body = record(value, ["projectId", "controlDomainId", "projectRoot"]);
71
+ if (!text(body.projectId) || !text(body.controlDomainId))
72
+ denied("project/domain identity is required");
73
+ // A mount is an existing canonical root, not an unverified client path.
74
+ const root = canonicalPath(body.projectRoot);
75
+ try {
76
+ if (realpathSync(root) !== root || !statSync(root).isDirectory())
77
+ denied("project root is not a canonical directory");
78
+ }
79
+ catch {
80
+ denied("project root must exist");
81
+ }
82
+ return Object.freeze({ projectId: body.projectId, controlDomainId: body.controlDomainId, projectRoot: root });
83
+ }
84
+ function capability(value, root) {
85
+ const body = record(value, ["kind", "scopeRoot"]);
86
+ if (!kind(body.kind))
87
+ denied("unknown capability");
88
+ const scopeRoot = canonicalPath(body.scopeRoot);
89
+ if (!contained(root, scopeRoot))
90
+ denied("policy cannot expand project scope");
91
+ return { kind: body.kind, scopeRoot };
92
+ }
93
+ function array(value) { if (!Array.isArray(value))
94
+ denied("security list is required"); return value; }
95
+ function freezePolicy(policy) {
96
+ function freeze(value) {
97
+ if (value && typeof value === "object") {
98
+ for (const child of Object.values(value))
99
+ freeze(child);
100
+ Object.freeze(value);
101
+ }
102
+ }
103
+ freeze(policy);
104
+ return policy;
105
+ }
106
+ function layer(value, root) {
107
+ const body = record(value, ["capabilities", "deny", "attenuate", "substitute"]);
108
+ const result = {};
109
+ for (const field of ["capabilities", "attenuate"]) {
110
+ if (body[field] !== undefined)
111
+ result[field] = array(body[field]).map((item) => capability(item, root));
112
+ }
113
+ if (body.deny !== undefined)
114
+ result.deny = array(body.deny).map((item) => { if (!kind(item))
115
+ denied("unknown denied capability"); return item; });
116
+ if (body.substitute !== undefined)
117
+ result.substitute = array(body.substitute).map((item) => {
118
+ const substitution = record(item, ["capability", "with"]);
119
+ if (!kind(substitution.capability))
120
+ denied("unknown substitution capability");
121
+ const replacement = capability(substitution.with, root);
122
+ if (substitution.capability !== replacement.kind)
123
+ denied("substitution cannot change command authority");
124
+ return { capability: substitution.capability, with: replacement };
125
+ });
126
+ return result;
127
+ }
128
+ function livePolicy(value, root) {
129
+ const body = record(value, ["host", "user", "project", "invocation", "revokedPrincipals"]);
130
+ const result = { host: layer(body.host, root) };
131
+ for (const field of ["user", "project", "invocation"])
132
+ if (body[field] !== undefined)
133
+ result[field] = layer(body[field], root);
134
+ if (body.revokedPrincipals !== undefined)
135
+ result.revokedPrincipals = array(body.revokedPrincipals).map((item) => { if (!text(item))
136
+ denied("invalid revoked principal"); return item; });
137
+ return result;
138
+ }
139
+ function requestedCapability(request) {
140
+ if (request.commandKind !== undefined && !["run.submit", "run.cancel", "approval.decide", "coordinator.setMaxActiveRuns", "coordinator.forceRelease"].includes(request.commandKind))
141
+ denied("unknown command class");
142
+ if (request.operation === "read")
143
+ return "project.read";
144
+ if (request.operation === "artifact")
145
+ return "artifact.read";
146
+ if ((request.operation === "submit" || request.operation === "replay") && request.commandKind !== undefined)
147
+ return request.commandKind;
148
+ return denied("unknown operation or command class");
149
+ }
150
+ /** Shared core canonical serialization, full SHA256 for wire audit fields. */
151
+ export function authorizationAuditHash(value) {
152
+ return createHash("sha256").update(canonicalJson(value), "utf8").digest("hex");
153
+ }
154
+ /** Client helper: caller obtains secret from protected bootstrap storage. */
155
+ export function answerAuthorizationChallenge(secret, challenge) {
156
+ return createHmac("sha256", secret).update(canonicalJson(challenge), "utf8").digest("hex");
157
+ }
158
+ export function createAuthorizationAuthority(options) {
159
+ if (!Number.isSafeInteger(options.ownerUid) || options.ownerUid < 0 || !process.getuid || options.ownerUid !== process.getuid()) {
160
+ throw new ControlError("TF_BOOTSTRAP_FAILED", "local authorization requires the actual process OS uid");
161
+ }
162
+ if (!(options.bootstrapSecret instanceof Uint8Array) || options.bootstrapSecret.byteLength < 32)
163
+ denied("protected bootstrap secret must have at least 256 bits");
164
+ const secret = Buffer.from(options.bootstrapSecret);
165
+ const baseline = new Set(array([...options.hostBaseline]).map((value) => { if (!kind(value))
166
+ denied("unknown host baseline capability"); return value; }));
167
+ if (options.credentialIssuerId !== undefined && !/^[a-zA-Z0-9._-]{1,64}$/.test(options.credentialIssuerId))
168
+ denied("invalid trusted credential issuer");
169
+ const principal = `os-user:${options.ownerUid}${options.credentialIssuerId === undefined ? "" : `/credential:${options.credentialIssuerId}`}`;
170
+ const now = options.now ?? Date.now;
171
+ const ttl = options.challengeTtlMs ?? 5_000;
172
+ if (!Number.isSafeInteger(ttl) || ttl < 1 || ttl > 60_000)
173
+ denied("challenge lifetime must be bounded");
174
+ const contexts = new WeakMap();
175
+ const challenges = new Map();
176
+ function issue(binding) {
177
+ const context = Object.freeze(Object.create(null));
178
+ contexts.set(context, Object.freeze({ principal, binding }));
179
+ return context;
180
+ }
181
+ function identity(context) {
182
+ if (context === null || typeof context !== "object")
183
+ return revoked();
184
+ const verified = contexts.get(context);
185
+ if (!verified)
186
+ return revoked();
187
+ return verified;
188
+ }
189
+ return {
190
+ issueStandalone: (binding) => issue(trustedBinding(binding)),
191
+ createChallenge(binding) {
192
+ const time = now();
193
+ for (const [id, pending] of challenges)
194
+ if (pending.expiresAt <= time)
195
+ challenges.delete(id);
196
+ if (challenges.size >= 256)
197
+ denied("too many pending authentication challenges");
198
+ const challenge = Object.freeze({ purpose: "taskflow.control.authentication", version: 1, id: randomUUID(), nonce: randomBytes(32).toString("hex"), expiresAt: time + ttl, principal, binding: trustedBinding(binding) });
199
+ challenges.set(challenge.id, challenge);
200
+ return challenge;
201
+ },
202
+ authenticate(id, proof) {
203
+ const challenge = challenges.get(id);
204
+ challenges.delete(id);
205
+ if (!challenge || challenge.expiresAt <= now() || typeof proof !== "string" || !/^[a-f0-9]{64}$/.test(proof))
206
+ denied("authentication challenge is absent, expired, or invalid");
207
+ const expected = Buffer.from(answerAuthorizationChallenge(secret, challenge), "hex");
208
+ if (!timingSafeEqual(expected, Buffer.from(proof, "hex")))
209
+ denied("bootstrap token proof failed");
210
+ return issue(challenge.binding);
211
+ },
212
+ identity,
213
+ revokeContext(context) { contexts.delete(context); },
214
+ async authorize(context, request) {
215
+ const verified = identity(context);
216
+ const body = { ...record(request, ["operation", "commandKind", "resourcePath", "artifactDigest", "projectId", "controlDomainId", "projectRoot"]) };
217
+ if (body.projectId !== verified.binding.projectId || body.controlDomainId !== verified.binding.controlDomainId
218
+ || canonicalPath(body.projectRoot) !== verified.binding.projectRoot)
219
+ denied("verified context does not authorize this project/domain");
220
+ const operation = body.operation;
221
+ const commandKind = body.commandKind;
222
+ const requested = requestedCapability({ ...verified.binding, operation, commandKind });
223
+ if (operation === "artifact" && commandKind === undefined)
224
+ denied("artifact authorization requires its ledger command class");
225
+ const requiredCapabilities = operation === "artifact" ? [requested, commandKind] : [requested];
226
+ for (const required of requiredCapabilities)
227
+ if (!baseline.has(required))
228
+ denied("capability is outside the explicit host baseline");
229
+ let path = canonicalPath(body.resourcePath ?? verified.binding.projectRoot);
230
+ if (!contained(verified.binding.projectRoot, path))
231
+ denied("resource is outside the verified project");
232
+ if (operation === "artifact") {
233
+ if (!text(body.artifactDigest) || !options.verifyArtifactReachability)
234
+ denied("artifact digest is not bearer authorization");
235
+ try {
236
+ if (!await options.verifyArtifactReachability(verified.principal, verified.binding, body.artifactDigest, commandKind))
237
+ denied("artifact is not reachable from an authorized ledger entry");
238
+ }
239
+ catch {
240
+ denied("artifact reachability is unavailable or denied");
241
+ }
242
+ }
243
+ // The policy read comes after any asynchronous reachability check, so a
244
+ // revocation during that check cannot expose historical artifact bytes.
245
+ let policy;
246
+ try {
247
+ policy = livePolicy(await options.loadLivePolicy(verified.principal, verified.binding), verified.binding.projectRoot);
248
+ }
249
+ catch (error) {
250
+ if (error instanceof ControlError)
251
+ throw error;
252
+ return denied("current policy is unavailable");
253
+ }
254
+ identity(context); // Revocation while awaiting a policy read must also deny.
255
+ path = canonicalPath(body.resourcePath ?? verified.binding.projectRoot);
256
+ if (canonicalPath(verified.binding.projectRoot) !== verified.binding.projectRoot || !contained(verified.binding.projectRoot, path))
257
+ denied("project/resource scope changed during authorization");
258
+ if (policy.revokedPrincipals?.includes(verified.principal))
259
+ return revoked();
260
+ let scopeRoot = verified.binding.projectRoot;
261
+ for (const required of requiredCapabilities) {
262
+ let substitution;
263
+ for (const current of [policy.host, policy.user, policy.project, policy.invocation]) {
264
+ if (!current)
265
+ continue;
266
+ if (current.deny?.includes(required))
267
+ denied("capability is denied by current policy");
268
+ const attenuation = current.attenuate?.filter((entry) => entry.kind === required);
269
+ for (const grant of [current.capabilities, attenuation?.length ? attenuation : undefined]) {
270
+ if (grant === undefined)
271
+ continue;
272
+ // No attenuation entry for this kind means inheritance; an explicit
273
+ // capabilities: [] always means deny everything.
274
+ const candidates = grant.filter((entry) => entry.kind === required && contained(entry.scopeRoot, path));
275
+ if (candidates.length === 0)
276
+ denied("capability scope is absent or attenuated");
277
+ const widest = candidates.sort((a, b) => a.scopeRoot.length - b.scopeRoot.length)[0];
278
+ if (contained(scopeRoot, widest.scopeRoot))
279
+ scopeRoot = widest.scopeRoot;
280
+ }
281
+ for (const replacement of current.substitute ?? []) {
282
+ if (replacement.capability !== required)
283
+ continue;
284
+ if (substitution !== undefined && substitution !== replacement.with.scopeRoot)
285
+ denied("substitution conflict");
286
+ substitution = replacement.with.scopeRoot;
287
+ if (!contained(scopeRoot, substitution) || !contained(substitution, path))
288
+ denied("substitution cannot expand effective scope");
289
+ scopeRoot = substitution;
290
+ }
291
+ }
292
+ }
293
+ const effectiveCapability = Object.freeze({ kind: requested, scopeRoot });
294
+ freezePolicy(policy);
295
+ return Object.freeze({ principal: verified.principal, callerPrincipal: verified.principal,
296
+ authorizationContextHash: authorizationAuditHash({ principal: verified.principal, binding: verified.binding, hostBaseline: [...baseline].sort(), policy, operation, commandKind, scopeRoot }),
297
+ capability: effectiveCapability,
298
+ effectivePolicy: Object.freeze({ hostBaseline: Object.freeze([...baseline].sort()), layers: policy, capability: effectiveCapability }) });
299
+ },
300
+ };
301
+ }
302
+ //# sourceMappingURL=authorization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../../taskflow-control/src/authorization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function runControlCli(argv?: string[]): Promise<number>;
3
+ //# sourceMappingURL=control-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-cli.d.ts","sourceRoot":"","sources":["../../../taskflow-control/src/control-cli.ts"],"names":[],"mappings":";AAuEA,wBAAsB,aAAa,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyIjF"}
@@ -0,0 +1,279 @@
1
+ #!/usr/bin/env node
2
+ /** Explicit local entrypoint; auto never falls back to standalone. */
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+ import { randomUUID } from "node:crypto";
7
+ import { ControlHost, defaultServerHello } from "./control-host.js";
8
+ import { ProjectRegistry } from "./project-registry.js";
9
+ import { RuntimeTeExecutionProvider } from "./runtime-provider.js";
10
+ import { answerAuthorizationChallenge } from "./authorization.js";
11
+ import { bootstrapLocalAuthority, createPrivateFileOnce, ensurePrivateDirectory, provisionLocalOperator, readPrivateFile } from "./local-bootstrap.js";
12
+ import { createControlEvidenceStore, readControlEvidence } from "./store/evidence-adapter.js";
13
+ import { launchControlConsole } from "./control-console.js";
14
+ import { serveControlMcp } from "./control-mcp.js";
15
+ import { singletonPaths } from "./singleton.js";
16
+ import { ControlError } from "./errors.js";
17
+ import { Value } from "typebox/value";
18
+ import { registerGracefulSignalOwner } from "taskflow-core/control-execution";
19
+ import { ControlStoreHeaderSchema } from "./schema/header.js";
20
+ const HELP = `taskflow-control run --root DIR --flow FILE [--args JSON] [--mode auto|coordinated|standalone]
21
+ taskflow-control status --root DIR [--run UUID]
22
+ taskflow-control serve --root DIR [--root DIR ...] [--console] [--port NUMBER]
23
+ taskflow-control mcp --root DIR [--mode auto|coordinated|standalone] [--operator]
24
+ taskflow-control operator-provision --root DIR --control-home DIR
25
+ taskflow-control coordinator-status --root DIR [--operator]
26
+ taskflow-control set-max-active-runs --root DIR --operator --command-id UUID --max-active-runs NUMBER
27
+ taskflow-control force-release --root DIR --operator --command-id UUID --reservation UUID --acknowledge-risk --reason TEXT
28
+ Common: --control-home DIR (defaults to TASKFLOW_HOME/control or ~/.taskflow/control).
29
+ Operator provisioning is an explicit OS-owner action; a separate credential and live project policy grant operations.
30
+ maxActiveRuns is user-global. Force release does not stop execution and marks the concurrency guarantee operator-overridden.
31
+ Local owner grants are persisted under CONTROL_HOME/policies and reread per operation.
32
+ The CLI executes script flows directly; agent execution requires an embedded configured runner.
33
+ Console's one-use browser token is saved to a 0600 local file, never printed or put in URLs.
34
+ `;
35
+ function fail(message) { throw new ControlError("TF_BOOTSTRAP_FAILED", message); }
36
+ function parse(argv) {
37
+ const command = argv[0];
38
+ if (!["run", "status", "serve", "mcp", "operator-provision", "coordinator-status", "set-max-active-runs", "force-release"].includes(command))
39
+ fail(HELP);
40
+ const flags = new Map();
41
+ for (let i = 1; i < argv.length; i++) {
42
+ const flag = argv[i];
43
+ if (!["--root", "--flow", "--args", "--mode", "--control-home", "--run", "--console", "--port", "--operator", "--command-id", "--max-active-runs", "--reservation", "--acknowledge-risk", "--reason"].includes(flag))
44
+ fail(`unknown option ${flag}`);
45
+ const value = ["--console", "--operator", "--acknowledge-risk"].includes(flag) ? "true" : argv[++i];
46
+ if (!value || value.startsWith("--"))
47
+ fail(`value required for ${flag}`);
48
+ if (flag !== "--root" && flags.has(flag))
49
+ fail(`duplicate option ${flag}`);
50
+ flags.set(flag, [...(flags.get(flag) ?? []), value]);
51
+ }
52
+ const roots = (flags.get("--root") ?? [process.cwd()]).map(root => fs.realpathSync(root));
53
+ if (roots.some(root => !fs.statSync(root).isDirectory()))
54
+ fail("project root must be a directory");
55
+ if (command !== "serve" && roots.length !== 1)
56
+ fail("multiple roots require serve");
57
+ const get = (key) => flags.get(key)?.[0];
58
+ const mode = get("--mode") ?? (["status", "coordinator-status", "set-max-active-runs", "force-release"].includes(command) ? "coordinated" : "auto");
59
+ if (!["auto", "coordinated", "standalone"].includes(mode))
60
+ fail("invalid control mode");
61
+ const port = get("--port") === undefined ? undefined : Number(get("--port"));
62
+ if (port !== undefined && (!Number.isInteger(port) || port < 0 || port > 65535))
63
+ fail("invalid console port");
64
+ if (command !== "serve" && (get("--console") || port !== undefined))
65
+ fail("console options require serve");
66
+ if (get("--operator") && !["mcp", "status", "coordinator-status", "set-max-active-runs", "force-release"].includes(command))
67
+ fail("--operator requires an explicit operator-capable client command");
68
+ if (["set-max-active-runs", "force-release"].includes(command) && !get("--command-id"))
69
+ fail("operator mutations require --command-id UUID for durable retries");
70
+ if (command === "set-max-active-runs" && (!Number.isSafeInteger(Number(get("--max-active-runs"))) || Number(get("--max-active-runs")) < 1))
71
+ fail("--max-active-runs must be a positive safe integer");
72
+ if (command === "force-release" && (!get("--reservation") || !get("--acknowledge-risk") || !get("--reason")?.trim()))
73
+ fail("force-release requires --reservation, --acknowledge-risk and a nonempty --reason");
74
+ return { operator: !!get("--operator"), commandId: get("--command-id"), maxActiveRuns: Number(get("--max-active-runs")), reservationId: get("--reservation"), reason: get("--reason"), command, roots, mode: mode, controlHome: path.resolve(singletonPaths(get("--control-home")).controlHome), flow: get("--flow"), args: get("--args"), runId: get("--run"), console: !!get("--console"), port };
75
+ }
76
+ function readHeader(root) {
77
+ const file = path.join(root, ".taskflow", "control", "header");
78
+ if (fs.lstatSync(file).isSymbolicLink())
79
+ fail("project header cannot be a symbolic link");
80
+ const header = JSON.parse(fs.readFileSync(file, "utf8"));
81
+ if (!Value.Check(ControlStoreHeaderSchema, header))
82
+ fail("project header is invalid");
83
+ const directory = fs.realpathSync(path.dirname(file)), stat = fs.statSync(directory);
84
+ if (header.directoryBinding.canonicalPath !== directory || header.directoryBinding.device !== String(stat.dev) || header.directoryBinding.inode !== String(stat.ino))
85
+ fail("project store identity mismatch");
86
+ return header;
87
+ }
88
+ export async function runControlCli(argv = process.argv.slice(2)) {
89
+ if (argv.length === 0 || argv.includes("--help")) {
90
+ process.stdout.write(HELP);
91
+ return 0;
92
+ }
93
+ const config = parse(argv);
94
+ for (const root of config.roots) {
95
+ const suffix = path.relative(root, config.controlHome);
96
+ if (suffix === "" || (!path.isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${path.sep}`)))
97
+ fail("control home must be outside every project root; TE authority storage cannot overlap the execution workspace");
98
+ }
99
+ // Reject unreadable input before opening an owner. Runtime validates its schema.
100
+ const flow = config.command === "run" ? JSON.parse(fs.readFileSync(config.flow ?? fail("run requires --flow FILE"), "utf8")) : undefined;
101
+ const args = JSON.parse(config.args ?? "{}");
102
+ ensurePrivateDirectory(config.controlHome);
103
+ if (config.command === "operator-provision") {
104
+ process.stdout.write(JSON.stringify(provisionLocalOperator(config.controlHome, config.roots[0])) + "\n");
105
+ return 0;
106
+ }
107
+ const registry = new ProjectRegistry(path.join(config.controlHome, "registry.json"));
108
+ const { authorization, secret } = bootstrapLocalAuthority(config.controlHome, config.roots, {
109
+ verifyArtifactReachability: (_principal, binding, digest, commandKind) => {
110
+ const mount = registry.resolve(binding.projectId);
111
+ if (mount.projectRoot !== binding.projectRoot || mount.store.header.controlDomainId !== binding.controlDomainId)
112
+ return false;
113
+ const ledger = readControlEvidence(mount.store);
114
+ if (commandKind === "run.submit") {
115
+ for (const run of Object.values(ledger.runs))
116
+ if (run.artifactRefs.some(ref => ref.digest === digest))
117
+ return true;
118
+ for (const refs of Object.values(ledger.receipts))
119
+ if (refs.receiptRef.digest === digest || refs.manifestProofRef.digest === digest)
120
+ return true;
121
+ }
122
+ if (ledger.events.some(({ event }) => event.payload.kind === "artifact.recorded" && event.payload.commandKind === commandKind && event.payload.artifact.digest === digest))
123
+ return true;
124
+ return mount.store.readJournal().some(batch => batch.command?.kind === commandKind && batch.command?.responseArtifactRef?.digest === digest);
125
+ },
126
+ });
127
+ const credentialSecret = config.operator ? readPrivateFile(path.join(config.controlHome, "operator.key")) : secret;
128
+ const host = new ControlHost({ mode: config.mode, controlHome: config.controlHome, registry, authorization, trustedInProcessFeatures: ["durable-approval"],
129
+ projectRoot: config.roots[0], projectStorePath: path.join(config.roots[0], ".taskflow", "control"),
130
+ projectMounts: config.roots.slice(1).map(projectRoot => ({ projectRoot, storePath: path.join(projectRoot, ".taskflow", "control") })),
131
+ evidenceFactory: (mount, verify) => createControlEvidenceStore(mount.store, {
132
+ terminalEvidence: { verify },
133
+ authorize: async (actor, scope) => {
134
+ const context = actor;
135
+ authorization.identity(context);
136
+ const header = mount.store.header;
137
+ if (scope.projectId !== header.projectId || scope.controlDomainId !== header.controlDomainId)
138
+ fail("evidence request does not match the mounted project");
139
+ let commandKind = "run.submit";
140
+ if (scope.target.kind === "command") {
141
+ const command = mount.store.readJournal().find(batch => batch.command?.commandId === scope.target.id)?.command;
142
+ if (!command)
143
+ fail("evidence command is absent from the mounted ledger");
144
+ commandKind = command.kind;
145
+ }
146
+ else {
147
+ if (!mount.store.readRun(scope.target.id))
148
+ fail("evidence run is absent from the mounted ledger");
149
+ if (scope.artifactRef) {
150
+ const recorded = readControlEvidence(mount.store).events.find(({ event }) => event.payload.kind === "artifact.recorded"
151
+ && event.payload.runId === scope.target.id && event.payload.artifact.digest === scope.artifactRef.digest)?.event;
152
+ if (recorded?.payload.kind === "artifact.recorded")
153
+ commandKind = recorded.payload.commandKind;
154
+ }
155
+ }
156
+ return authorization.authorize(context, { projectId: header.projectId, controlDomainId: header.controlDomainId, projectRoot: mount.projectRoot,
157
+ operation: scope.artifactRef ? "artifact" : "replay", commandKind, ...(scope.artifactRef ? { artifactDigest: scope.artifactRef.digest } : {}) });
158
+ },
159
+ }),
160
+ provider: new RuntimeTeExecutionProvider(path.join(config.controlHome, "provider"), { agents: [], usageAccounting: "unavailable" }), });
161
+ let consoleServer;
162
+ let handoffPath;
163
+ let interrupted = false;
164
+ const interrupt = () => { interrupted = true; };
165
+ const releaseSignalOwner = registerGracefulSignalOwner(interrupt);
166
+ process.on("SIGINT", interrupt);
167
+ process.on("SIGTERM", interrupt);
168
+ try {
169
+ await host.start();
170
+ host.hello(defaultServerHello());
171
+ const header = readHeader(config.roots[0]);
172
+ let context;
173
+ if (host.status.singleton === "attached") {
174
+ const challenge = await host.dispatch("auth.challenge", { projectId: header.projectId, ...(config.operator ? { credential: "operator" } : {}) }, { fencingEpoch: host.status.fencingEpoch });
175
+ if (challenge.binding.projectRoot !== config.roots[0] || challenge.binding.projectId !== header.projectId || challenge.binding.controlDomainId !== header.controlDomainId)
176
+ fail("host challenge does not match the configured project");
177
+ await host.dispatch("auth.authenticate", { challengeId: challenge.id, proof: answerAuthorizationChallenge(credentialSecret, challenge) }, { fencingEpoch: host.status.fencingEpoch });
178
+ }
179
+ else {
180
+ const binding = { projectId: header.projectId, controlDomainId: header.controlDomainId, projectRoot: config.roots[0] };
181
+ context = config.operator ? authorization.issueOperatorStandalone(binding) : authorization.issueStandalone(binding);
182
+ }
183
+ const call = (method, params) => context ? host.dispatchAuthenticated(context, method, params) : host.dispatch(method, params, { fencingEpoch: host.status.fencingEpoch });
184
+ if (config.command === "mcp") {
185
+ const stopInput = () => process.stdin.destroy();
186
+ process.on("SIGINT", stopInput);
187
+ process.on("SIGTERM", stopInput);
188
+ try {
189
+ await serveControlMcp({ call, projectId: header.projectId, operator: config.operator });
190
+ }
191
+ finally {
192
+ process.off("SIGINT", stopInput);
193
+ process.off("SIGTERM", stopInput);
194
+ }
195
+ return 0;
196
+ }
197
+ if (["coordinator-status", "set-max-active-runs", "force-release"].includes(config.command)) {
198
+ const method = config.command === "coordinator-status" ? "coordinator.status" : config.command === "set-max-active-runs" ? "coordinator.setMaxActiveRuns" : "coordinator.forceRelease";
199
+ const params = { projectId: header.projectId, ...(config.command === "set-max-active-runs" ? { commandId: config.commandId, maxActiveRuns: config.maxActiveRuns } : config.command === "force-release" ? { commandId: config.commandId, reservationId: config.reservationId, riskAcknowledgement: true, reason: config.reason } : {}) };
200
+ process.stdout.write(JSON.stringify(await call(method, params)) + "\n");
201
+ return 0;
202
+ }
203
+ if (config.command === "run") {
204
+ const accepted = await call("commands.submit", { projectId: header.projectId, commandId: randomUUID(), kind: "run.submit", flow, args });
205
+ // Keep individual RPCs bounded; an approval can remain parked until another
206
+ // authenticated client resolves it. SIGINT retains durable reconciliation facts.
207
+ let run;
208
+ for (;;) {
209
+ run = await call("runs.status", { projectId: header.projectId, runId: accepted.runId });
210
+ if (interrupted && !["completed", "failed", "blocked", "cancelled"].includes(run.status)) {
211
+ try {
212
+ await call("runs.cancel", { projectId: header.projectId, runId: accepted.runId, commandId: randomUUID(), expectedRunVersion: run.runVersion });
213
+ interrupted = false;
214
+ }
215
+ catch (error) {
216
+ if (!(error instanceof ControlError) || error.code !== "TF_STALE_VERSION")
217
+ throw error;
218
+ continue;
219
+ }
220
+ run = await call("runs.status", { projectId: header.projectId, runId: accepted.runId });
221
+ }
222
+ if (["completed", "failed", "blocked", "cancelled", "unknown"].includes(run.status) || run.needsOperator)
223
+ break;
224
+ await new Promise(resolve => setTimeout(resolve, 100));
225
+ }
226
+ const result = run.status === "unknown" ? undefined : await call("runs.result", { projectId: header.projectId, runId: accepted.runId });
227
+ process.stdout.write(JSON.stringify({ ...accepted, run, ...(result ? { result } : {}) }) + "\n");
228
+ return run.status === "completed" ? 0 : 1;
229
+ }
230
+ if (config.command === "status") {
231
+ const result = await call(config.runId ? "runs.status" : "runs.list", { projectId: header.projectId, ...(config.runId ? { runId: config.runId } : {}) });
232
+ process.stdout.write(JSON.stringify({ control: host.status, result }) + "\n");
233
+ return 0;
234
+ }
235
+ if (!context)
236
+ fail("serve requires ownership; a host already owns this control home (use status/run to attach)");
237
+ if (config.console) {
238
+ const contexts = new Map();
239
+ for (const root of config.roots) {
240
+ const h = readHeader(root);
241
+ contexts.set(h.projectId, authorization.issueStandalone({ projectId: h.projectId, controlDomainId: h.controlDomainId, projectRoot: root }));
242
+ }
243
+ consoleServer = await launchControlConsole({ host, authorization, registry, contexts, port: config.port });
244
+ handoffPath = path.join(config.controlHome, `console-handoff-${randomUUID()}.json`);
245
+ createPrivateFileOnce(handoffPath, Buffer.from(JSON.stringify({ url: consoleServer.url, token: consoleServer.bootstrapToken }) + "\n"));
246
+ }
247
+ process.stdout.write(JSON.stringify({ control: host.status, projects: registry.list(), ...(consoleServer ? { consoleUrl: consoleServer.url, browserHandoffFile: handoffPath } : {}) }) + "\n");
248
+ if (!interrupted)
249
+ await new Promise(resolve => {
250
+ const stop = () => { process.off("SIGINT", stop); process.off("SIGTERM", stop); resolve(); };
251
+ process.on("SIGINT", stop);
252
+ process.on("SIGTERM", stop);
253
+ });
254
+ return 0;
255
+ }
256
+ finally {
257
+ releaseSignalOwner();
258
+ process.off("SIGINT", interrupt);
259
+ process.off("SIGTERM", interrupt);
260
+ await consoleServer?.close();
261
+ if (handoffPath) {
262
+ try {
263
+ fs.unlinkSync(handoffPath);
264
+ }
265
+ catch { /* preserve primary shutdown error */ }
266
+ }
267
+ host.stop();
268
+ registry.close();
269
+ secret.fill(0);
270
+ credentialSecret.fill(0);
271
+ }
272
+ }
273
+ if (process.argv[1] && fs.existsSync(process.argv[1]) && import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href) {
274
+ runControlCli().then(code => { process.exitCode = code; }, error => {
275
+ process.stderr.write(JSON.stringify({ error: error instanceof ControlError ? error.code : "TF_BOOTSTRAP_FAILED", message: error instanceof Error ? error.message : "local control failed" }) + "\n");
276
+ process.exitCode = 1;
277
+ });
278
+ }
279
+ //# sourceMappingURL=control-cli.js.map