create-windy 0.6.14 → 0.6.15

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 (160) hide show
  1. package/dist/cli.js +215 -24
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +58 -8
  4. package/template/.windy-template.json +2 -2
  5. package/template/apps/server/src/agent/client-session/attempt-authority.test.ts +162 -0
  6. package/template/apps/server/src/agent/client-session/attempt-authority.ts +191 -0
  7. package/template/apps/server/src/agent/client-session/bootstrap.ts +27 -11
  8. package/template/apps/server/src/agent/client-session/completion.ts +75 -24
  9. package/template/apps/server/src/agent/client-session/contracts.ts +23 -0
  10. package/template/apps/server/src/agent/client-session/creation.ts +103 -0
  11. package/template/apps/server/src/agent/client-session/digest.ts +11 -1
  12. package/template/apps/server/src/agent/client-session/drizzle-records.ts +150 -0
  13. package/template/apps/server/src/agent/client-session/drizzle-repository.integration.test.ts +30 -3
  14. package/template/apps/server/src/agent/client-session/drizzle-repository.ts +23 -132
  15. package/template/apps/server/src/agent/client-session/finalization.ts +99 -0
  16. package/template/apps/server/src/agent/client-session/host-auth.ts +22 -10
  17. package/template/apps/server/src/agent/client-session/host-revocation-composition.test.ts +77 -0
  18. package/template/apps/server/src/agent/client-session/host-revocation-composition.ts +28 -0
  19. package/template/apps/server/src/agent/client-session/launch-authority.contract.test.ts +21 -0
  20. package/template/apps/server/src/agent/client-session/manifest-admission.ts +11 -0
  21. package/template/apps/server/src/agent/client-session/service.test-support.ts +22 -0
  22. package/template/apps/server/src/agent/client-session/service.ts +104 -134
  23. package/template/apps/server/src/agent/client-session/terminal-digest.test.ts +23 -0
  24. package/template/apps/server/src/agent/client-workflow-artifact/active-blob-references.ts +8 -0
  25. package/template/apps/server/src/agent/client-workflow-artifact/artifact-builder.ts +60 -0
  26. package/template/apps/server/src/agent/client-workflow-artifact/authority-current.ts +59 -0
  27. package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.test.ts +189 -0
  28. package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.ts +153 -0
  29. package/template/apps/server/src/agent/client-workflow-artifact/bootstrap.ts +77 -0
  30. package/template/apps/server/src/agent/client-workflow-artifact/cancellation.ts +47 -0
  31. package/template/apps/server/src/agent/client-workflow-artifact/completed-recovery.ts +31 -0
  32. package/template/apps/server/src/agent/client-workflow-artifact/contracts.ts +104 -0
  33. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-locks.ts +30 -0
  34. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-mapper.ts +138 -0
  35. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-repository.ts +346 -0
  36. package/template/apps/server/src/agent/client-workflow-artifact/error.ts +21 -0
  37. package/template/apps/server/src/agent/client-workflow-artifact/handler.ts +167 -0
  38. package/template/apps/server/src/agent/client-workflow-artifact/host-revocation.ts +29 -0
  39. package/template/apps/server/src/agent/client-workflow-artifact/in-memory-repository.ts +232 -0
  40. package/template/apps/server/src/agent/client-workflow-artifact/job-port.ts +101 -0
  41. package/template/apps/server/src/agent/client-workflow-artifact/presentation.ts +41 -0
  42. package/template/apps/server/src/agent/client-workflow-artifact/recovery.contract.test.ts +253 -0
  43. package/template/apps/server/src/agent/client-workflow-artifact/release-binding.ts +85 -0
  44. package/template/apps/server/src/agent/client-workflow-artifact/repository.test.ts +204 -0
  45. package/template/apps/server/src/agent/client-workflow-artifact/repository.ts +97 -0
  46. package/template/apps/server/src/agent/client-workflow-artifact/routes-handler.contract.test.ts +201 -0
  47. package/template/apps/server/src/agent/client-workflow-artifact/routes.ts +161 -0
  48. package/template/apps/server/src/agent/client-workflow-artifact/runtime-composition.ts +101 -0
  49. package/template/apps/server/src/agent/client-workflow-artifact/scope-authority.ts +39 -0
  50. package/template/apps/server/src/agent/client-workflow-artifact/service-authorization.ts +117 -0
  51. package/template/apps/server/src/agent/client-workflow-artifact/service-support.ts +28 -0
  52. package/template/apps/server/src/agent/client-workflow-artifact/service.contract.test.ts +345 -0
  53. package/template/apps/server/src/agent/client-workflow-artifact/service.ts +350 -0
  54. package/template/apps/server/src/agent/client-workflow-artifact/session-boundary.ts +39 -0
  55. package/template/apps/server/src/agent/client-workflow-artifact/source-access.contract.test.ts +98 -0
  56. package/template/apps/server/src/agent/client-workflow-artifact/source-access.ts +80 -0
  57. package/template/apps/server/src/agent/client-workflow-artifact/takeover.ts +122 -0
  58. package/template/apps/server/src/agent/client-workflow-artifact/task-authority.contract.test.ts +97 -0
  59. package/template/apps/server/src/agent/client-workflow-artifact/task-authority.ts +147 -0
  60. package/template/apps/server/src/agent/client-workflow-artifact/terminal-authority.contract.test.ts +100 -0
  61. package/template/apps/server/src/agent/client-workflow-artifact/terminal-replay.ts +31 -0
  62. package/template/apps/server/src/agent/client-workflow-artifact/terminal-seal.ts +211 -0
  63. package/template/apps/server/src/agent/client-workflow-artifact/workflow-artifact.test-support.ts +173 -0
  64. package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.contract.test.ts +254 -0
  65. package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.ts +285 -0
  66. package/template/apps/server/src/agent/run-facade.test-support.ts +17 -4
  67. package/template/apps/server/src/agent/run-facade.test.ts +16 -0
  68. package/template/apps/server/src/agent/run-session-authorization.ts +12 -10
  69. package/template/apps/server/src/agent/runtime-bootstrap-input.ts +5 -1
  70. package/template/apps/server/src/agent/runtime-bootstrap.ts +33 -33
  71. package/template/apps/server/src/agent/runtime-public-ports.ts +40 -0
  72. package/template/apps/server/src/agent/session-invalidation/contracts.ts +25 -26
  73. package/template/apps/server/src/agent/session-projection-access.ts +31 -19
  74. package/template/apps/server/src/agent/unavailable-routes.ts +21 -0
  75. package/template/apps/server/src/agent/workflow-runtime-bootstrap.ts +2 -0
  76. package/template/apps/server/src/agent-workflow/artifact-source-authorization.ts +12 -6
  77. package/template/apps/server/src/agent-workflow/command-facade.ts +70 -11
  78. package/template/apps/server/src/agent-workflow/drizzle-artifact-port.ts +9 -9
  79. package/template/apps/server/src/agent-workflow/drizzle-delegation-completion.ts +1 -1
  80. package/template/apps/server/src/agent-workflow/platform-node-adapters.ts +14 -6
  81. package/template/apps/server/src/agent-workflow/project-launch-authority.contract.test.ts +53 -0
  82. package/template/apps/server/src/agent-workflow/project-launch-authority.ts +92 -0
  83. package/template/apps/server/src/agent-workflow/runtime-composition.ts +30 -14
  84. package/template/apps/server/src/app/agent-runtime-composition.ts +6 -1
  85. package/template/apps/server/src/app/client-agent-workflow-artifact-binding.ts +14 -0
  86. package/template/apps/server/src/application-services.ts +6 -0
  87. package/template/apps/server/src/data-access/scope-containment.test.ts +31 -0
  88. package/template/apps/server/src/data-access/scope-containment.ts +48 -0
  89. package/template/apps/server/src/jobs/artifact-publication.test.ts +192 -0
  90. package/template/apps/server/src/jobs/artifact-publication.ts +56 -0
  91. package/template/apps/server/src/jobs/artifact-publisher.ts +101 -0
  92. package/template/apps/server/src/jobs/drizzle-artifact.ts +73 -1
  93. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +14 -1
  94. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +65 -0
  95. package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -0
  96. package/template/apps/server/src/jobs/execution-repository.ts +28 -0
  97. package/template/apps/server/src/jobs/execution.test.ts +3 -1
  98. package/template/apps/server/src/jobs/in-memory-execution.ts +16 -0
  99. package/template/apps/server/src/jobs/in-memory-lease.ts +45 -1
  100. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +2 -0
  101. package/template/apps/server/src/jobs/postgres-cleanup-contract.test.ts +58 -0
  102. package/template/apps/server/src/jobs/worker.ts +8 -14
  103. package/template/apps/server/src/module-host/background-ai-unavailable.ts +18 -0
  104. package/template/apps/server/src/module-host/durable-context.ts +19 -22
  105. package/template/apps/server/src/module-host/implementation-completeness.test.ts +80 -1
  106. package/template/apps/server/src/module-host/implementation-completeness.ts +28 -1
  107. package/template/apps/server/src/module-host/request-context.ts +32 -8
  108. package/template/apps/server/src/module-host/storage-port.ts +3 -0
  109. package/template/apps/server/src/module-host/workflow-input-artifact-port.test.ts +104 -0
  110. package/template/apps/server/src/module-host/workflow-input-artifact-port.ts +229 -0
  111. package/template/apps/server/src/module-host/workflow-input-materialization.test.ts +163 -0
  112. package/template/apps/server/src/module-host/workflow-input-materialization.ts +161 -0
  113. package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +1 -0
  114. package/template/docs/architecture/agent-workflow.md +13 -2
  115. package/template/docs/architecture/durable-jobs.md +7 -0
  116. package/template/docs/operations/agent-session-memory.md +2 -1
  117. package/template/docs/operations/agent-session-troubleshooting.md +87 -2
  118. package/template/docs/operations/agent-workflow-recovery.md +25 -14
  119. package/template/docs/platform/client-agent-session.md +148 -2
  120. package/template/packages/ai-client/package.json +2 -1
  121. package/template/packages/ai-client/src/client-agent/index.ts +1 -0
  122. package/template/packages/ai-client/src/client-agent/workflow-artifact/contracts.ts +85 -0
  123. package/template/packages/ai-client/src/client-agent/workflow-artifact/http.test.ts +49 -0
  124. package/template/packages/ai-client/src/client-agent/workflow-artifact/http.ts +82 -0
  125. package/template/packages/ai-client/src/client-agent/workflow-artifact/index.ts +3 -0
  126. package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.test.ts +154 -0
  127. package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.ts +332 -0
  128. package/template/packages/database/drizzle/0062_client_agent_workflow_artifact.sql +63 -0
  129. package/template/packages/database/drizzle/meta/0062_snapshot.json +14839 -0
  130. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  131. package/template/packages/database/package.json +1 -1
  132. package/template/packages/database/src/agent-runtime-schema.test.ts +177 -0
  133. package/template/packages/database/src/schema/client-agent-workflow-artifact.ts +158 -0
  134. package/template/packages/database/src/schema/index.ts +1 -0
  135. package/template/packages/jobs/package.json +1 -1
  136. package/template/packages/jobs/src/handlers.test.ts +10 -0
  137. package/template/packages/jobs/src/handlers.ts +28 -11
  138. package/template/packages/modules/package.json +1 -1
  139. package/template/packages/modules/src/ai-client-execution-policy.ts +6 -0
  140. package/template/packages/modules/src/manifest.ts +2 -0
  141. package/template/packages/modules/src/system-agent-audit-actions.ts +3 -0
  142. package/template/packages/modules/src/system-ai-api-bindings.ts +15 -0
  143. package/template/packages/modules/src/system-audit-actions.ts +15 -0
  144. package/template/packages/server-sdk/index.ts +52 -0
  145. package/template/packages/server-sdk/package.json +5 -1
  146. package/template/packages/server-sdk/src/client-agent-session.ts +13 -1
  147. package/template/packages/server-sdk/src/client-agent-workflow-artifact.contract.test.ts +113 -0
  148. package/template/packages/server-sdk/src/client-agent-workflow-artifact.ts +183 -0
  149. package/template/packages/server-sdk/src/durable-handler.ts +7 -0
  150. package/template/packages/server-sdk/src/request-context.ts +7 -0
  151. package/template/packages/server-sdk/src/workflow-input-artifact-port.ts +104 -0
  152. package/template/packages/server-sdk/src/workflow-input-artifact-validation.contract.test.ts +124 -0
  153. package/template/packages/server-sdk/src/workflow-input-artifact-validation.ts +347 -0
  154. package/template/packages/server-sdk/src/workflow-input-materialization-handler.ts +43 -0
  155. package/template/packages/storage/package.json +1 -1
  156. package/template/packages/storage/src/blob-store-put-once.test.ts +70 -0
  157. package/template/packages/storage/src/blob-store.ts +9 -0
  158. package/template/packages/storage/src/local-file-blob-put-once.ts +120 -0
  159. package/template/packages/storage/src/local-file-blob-store.ts +53 -1
  160. package/template/packages/storage/src/memory-blob-store.ts +73 -33
@@ -14,15 +14,11 @@ import {
14
14
 
15
15
  export interface CurrentSourceAuthorizationPort {
16
16
  /** Resolve the current canonical authorization generation, never a client claim. */
17
- current(
18
- context: RequestGuardContext,
19
- ): Promise<SourceAuthorizationCoordinate>;
17
+ current(context: RequestGuardContext): Promise<SourceAuthorizationCoordinate>;
20
18
  }
21
19
 
22
20
  /** Re-resolves the Actor from the canonical user/role repositories per read. */
23
- export class ResolvingCurrentSourceAuthorizationPort
24
- implements CurrentSourceAuthorizationPort
25
- {
21
+ export class ResolvingCurrentSourceAuthorizationPort implements CurrentSourceAuthorizationPort {
26
22
  constructor(
27
23
  private readonly resolveActor: (
28
24
  actorId: string,
@@ -34,7 +30,7 @@ export class ResolvingCurrentSourceAuthorizationPort
34
30
  ): Promise<SourceAuthorizationCoordinate> {
35
31
  const actor = await this.resolveActor(context.actor.id);
36
32
  if (!actor || actor.type === "anonymous") throw unavailable();
37
- return authorizationCoordinateForActor(actor);
33
+ return authorizationReadCoordinateForActor(actor);
38
34
  }
39
35
  }
40
36
 
@@ -76,9 +72,9 @@ export class SessionProjectionSourceAuthorizer {
76
72
  } catch {
77
73
  throw unavailable();
78
74
  }
79
- const expectedScope = `sha256:${captureAuthorizationScope(
80
- context.actor.scopeContext,
81
- ).hash}`;
75
+ const expectedScope = `sha256:${
76
+ captureAuthorizationScope(context.actor.scopeContext).hash
77
+ }`;
82
78
  if (
83
79
  authorization.actorId !== context.actor.id ||
84
80
  authorization.scopeDigest !== expectedScope ||
@@ -91,11 +87,17 @@ export class SessionProjectionSourceAuthorizer {
91
87
  for (const coordinate of coordinates) {
92
88
  if (!validCoordinate(coordinate)) throw unavailable();
93
89
  try {
94
- await requireAuthoritativeSourceRead(this.sources, withSourceChainAudit({
95
- source: causalSource(coordinate),
96
- authorization,
97
- requestId: context.requestId,
98
- }, chainAudit));
90
+ await requireAuthoritativeSourceRead(
91
+ this.sources,
92
+ withSourceChainAudit(
93
+ {
94
+ source: causalSource(coordinate),
95
+ authorization,
96
+ requestId: context.requestId,
97
+ },
98
+ chainAudit,
99
+ ),
100
+ );
99
101
  } catch {
100
102
  throw unavailable();
101
103
  }
@@ -136,9 +138,8 @@ function unavailable(): SessionProjectionSourceUnavailableError {
136
138
  export function authorizationCoordinateForActor(
137
139
  actor: RequestActor,
138
140
  ): SourceAuthorizationCoordinate {
139
- const scopeDigest = `sha256:${captureAuthorizationScope(
140
- actor.scopeContext,
141
- ).hash}`;
141
+ const scope = captureAuthorizationScope(actor.scopeContext);
142
+ const scopeDigest = `sha256:${scope.hash}`;
142
143
  const fingerprint = JSON.stringify({
143
144
  actorId: actor.id,
144
145
  actorType: actor.type,
@@ -146,10 +147,21 @@ export function authorizationCoordinateForActor(
146
147
  roles: [...actor.roleCodes].sort(),
147
148
  scopeDigest,
148
149
  });
149
- const prefix = createHash("sha256").update(fingerprint).digest("hex").slice(0, 13);
150
+ const prefix = createHash("sha256")
151
+ .update(fingerprint)
152
+ .digest("hex")
153
+ .slice(0, 13);
150
154
  return {
151
155
  actorId: actor.id,
152
156
  scopeDigest,
153
157
  generation: Math.max(1, Number.parseInt(prefix, 16)),
154
158
  };
155
159
  }
160
+
161
+ /** Read-only authority coordinate may carry the server-captured scope; invalidation envelopes stay small. */
162
+ export function authorizationReadCoordinateForActor(actor: RequestActor) {
163
+ return {
164
+ ...authorizationCoordinateForActor(actor),
165
+ scopeSnapshot: captureAuthorizationScope(actor.scopeContext).snapshot,
166
+ } satisfies SourceAuthorizationCoordinate;
167
+ }
@@ -11,6 +11,12 @@ const clientSessionUnavailableRoutes = [
11
11
  ["POST", "/agent/client-sessions/:sessionId/cancel"],
12
12
  ] as const;
13
13
 
14
+ const clientWorkflowArtifactUnavailableRoutes = [
15
+ ["POST", "/agent/client-workflow-artifacts/claim"],
16
+ ["POST", "/agent/client-workflow-artifacts/advance"],
17
+ ["POST", "/agent/client-workflow-artifacts/cancel"],
18
+ ] as const;
19
+
14
20
  const longSessionUnavailableRoutes = [
15
21
  ["GET", "/agent/harness/sessions"],
16
22
  ["GET", "/agent/harness/sessions/:sessionId"],
@@ -65,6 +71,21 @@ export function registerUnavailableAiRoutes(app: RouteApp): void {
65
71
  "AI Runtime 未启用",
66
72
  );
67
73
  registerUnavailableClientAgentSessionRoutes(app, "AI Runtime 未启用");
74
+ registerUnavailableClientAgentWorkflowArtifactRoutes(
75
+ app,
76
+ "AI Runtime 未启用",
77
+ );
78
+ }
79
+
80
+ export function registerUnavailableClientAgentWorkflowArtifactRoutes(
81
+ app: RouteApp,
82
+ message = "Client Agent Workflow Artifact 未装配",
83
+ ): void {
84
+ registerUnavailableRoutes(
85
+ app,
86
+ clientWorkflowArtifactUnavailableRoutes,
87
+ message,
88
+ );
68
89
  }
69
90
 
70
91
  export function registerUnavailableClientAgentSessionRoutes(
@@ -43,6 +43,8 @@ export function registerProductionAgentWorkflowRuntime(
43
43
  modelPricingJson: input.platformConfig.agentWorkflow?.modelPricingJson,
44
44
  evaluationTools: resolveProductionEvaluationToolRegistration(tools),
45
45
  workspacePolicies: input.agentWorkflowWorkspacePolicies,
46
+ clientAgentWorkflowArtifactBindings:
47
+ input.clientAgentWorkflowArtifactBindings,
46
48
  ...dependencies,
47
49
  ...(dependencies
48
50
  ? {
@@ -1,9 +1,12 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import type { AgentWorkflowArtifactRef } from "@southwind-ai/shared";
3
+ import type { AuthorizationScopeSnapshot } from "@southwind-ai/shared";
3
4
  import type { RequestActor } from "../guards.js";
4
5
  import type { AuthoritativeSourceAccessPort } from "../agent/session-invalidation/contracts.js";
5
6
  import { requireAuthoritativeSourceRead } from "../agent/session-invalidation/read-guard.js";
6
- import { authorizationCoordinateForActor } from "../agent/session-projection-access.js";
7
+ import { authorizationReadCoordinateForActor } from "../agent/session-projection-access.js";
8
+ import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
9
+ import { authorizationScopeContains } from "../data-access/scope-containment.js";
7
10
 
8
11
  type CausalArtifactSource = AgentWorkflowArtifactRef["source"] & {
9
12
  readonly sequence: number;
@@ -14,7 +17,7 @@ export interface AgentWorkflowArtifactSourceAuthorizationPort {
14
17
  requireCurrent(input: {
15
18
  readonly artifact: AgentWorkflowArtifactRef;
16
19
  readonly actorId: string;
17
- readonly scopeDigest: string;
20
+ readonly frozenScope: AuthorizationScopeSnapshot;
18
21
  }): Promise<void>;
19
22
  }
20
23
 
@@ -30,20 +33,23 @@ export class ProductionAgentWorkflowArtifactSourceAuthorization implements Agent
30
33
  async requireCurrent(input: {
31
34
  readonly artifact: AgentWorkflowArtifactRef;
32
35
  readonly actorId: string;
33
- readonly scopeDigest: string;
36
+ readonly frozenScope: AuthorizationScopeSnapshot;
34
37
  }) {
35
38
  try {
36
39
  const source = causalSource(input.artifact);
37
40
  assertSource(source);
38
41
  const actor = await this.resolveActor(input.actorId);
39
42
  if (!actor || actor.type === "anonymous") throw denied();
40
- const authorization = authorizationCoordinateForActor(actor);
43
+ const currentScope = captureAuthorizationScope(actor.scopeContext);
41
44
  if (
42
- authorization.actorId !== input.actorId ||
43
- authorization.scopeDigest !== input.scopeDigest
45
+ !authorizationScopeContains(currentScope.snapshot, input.frozenScope)
44
46
  ) {
45
47
  throw denied();
46
48
  }
49
+ const authorization = authorizationReadCoordinateForActor(actor);
50
+ if (authorization.actorId !== input.actorId) {
51
+ throw denied();
52
+ }
47
53
  await requireAuthoritativeSourceRead(this.sources, {
48
54
  source: {
49
55
  moduleKey: source.moduleKey,
@@ -38,11 +38,11 @@ import {
38
38
  workflowRequestText,
39
39
  } from "./command-input.js";
40
40
  import { governedDefinitionToolSchemaVersion } from "./governed-tool-schema.js";
41
+ import { buildAgentWorkflowRelease } from "./release.js";
42
+ import type { ProjectAgentWorkflowLaunchAuthority } from "./project-launch-authority.js";
41
43
 
42
44
  export { AgentWorkflowCommandError } from "./command-error.js";
43
45
 
44
- type ServerRuntime = ReturnType<typeof createServerRuntime>;
45
-
46
46
  export class ProductionAgentWorkflowCommandFacade {
47
47
  constructor(
48
48
  private readonly repository: AgentWorkflowRepository,
@@ -51,20 +51,37 @@ export class ProductionAgentWorkflowCommandFacade {
51
51
  private readonly evaluations: AgentWorkflowEvaluationService,
52
52
  private readonly evaluationRepository: AgentWorkflowEvaluationRepository,
53
53
  private readonly modules: readonly ModuleManifest[],
54
- private readonly runtime: ServerRuntime,
54
+ private readonly runtime: ReturnType<typeof createServerRuntime>,
55
55
  private readonly tools: GovernedToolRegistry,
56
56
  private readonly waitSignals: AgentWorkflowWaitSignalService,
57
57
  private readonly artifacts: AgentWorkflowArtifactPort,
58
58
  private readonly workspacePolicies: AgentWorkflowWorkspacePolicyPort,
59
+ private readonly projectAuthority: ProjectAgentWorkflowLaunchAuthority,
59
60
  private readonly schedulerFor: (
60
61
  context: RequestGuardContext,
61
62
  ) => AgentWorkflowSchedulerCommandPort,
62
63
  ) {}
63
64
 
64
- publish(body: unknown, context: RequestGuardContext) {
65
+ async publish(body: unknown, context: RequestGuardContext) {
66
+ return this.publishWithAuthority(body, context);
67
+ }
68
+
69
+ async publishProject(
70
+ body: unknown,
71
+ context: RequestGuardContext,
72
+ signal: AbortSignal,
73
+ ) {
74
+ return this.publishWithAuthority(body, context, signal);
75
+ }
76
+
77
+ private async publishWithAuthority(
78
+ body: unknown,
79
+ context: RequestGuardContext,
80
+ signal?: AbortSignal,
81
+ ) {
65
82
  const value = workflowRequestObject(body);
66
83
  const coordinates = workflowRequestObject(value.coordinates);
67
- return this.service.publish({
84
+ const publishInput = {
68
85
  definition: value.definition as AgentWorkflowDefinition,
69
86
  coordinates: {
70
87
  runtimeVersion: workflowRequestText(
@@ -87,23 +104,66 @@ export class ProductionAgentWorkflowCommandFacade {
87
104
  } satisfies AgentWorkflowReleaseCoordinates,
88
105
  publishedBy: workflowActor(context),
89
106
  publishedAt: new Date().toISOString(),
90
- });
107
+ };
108
+ const release = buildAgentWorkflowRelease(publishInput);
109
+ if (signal) {
110
+ await this.projectAuthority.authorizeRelease({
111
+ definition: release.definition,
112
+ coordinates: release.coordinates,
113
+ releaseDigest: release.releaseDigest,
114
+ actorId: context.actor.id,
115
+ signal,
116
+ });
117
+ } else {
118
+ this.projectAuthority.requireDirect(release.definition.workflowKey);
119
+ }
120
+ return this.service.publish(publishInput);
91
121
  }
92
122
 
93
123
  async createRun(
94
124
  body: unknown,
95
125
  context: RequestGuardContext,
96
126
  signal: AbortSignal,
127
+ ) {
128
+ return this.createRunWithAuthority(body, context, signal, false);
129
+ }
130
+
131
+ async createProjectRun(
132
+ body: unknown,
133
+ context: RequestGuardContext,
134
+ signal: AbortSignal,
135
+ ) {
136
+ return this.createRunWithAuthority(body, context, signal, true);
137
+ }
138
+
139
+ private async createRunWithAuthority(
140
+ body: unknown,
141
+ context: RequestGuardContext,
142
+ signal: AbortSignal,
143
+ project: boolean,
97
144
  ) {
98
145
  const value = workflowRequestObject(body);
99
146
  const releaseId = workflowRequestText(value.releaseId, "releaseId");
100
147
  const release = await this.repository.getRelease(releaseId);
101
148
  if (!release) throw new AgentWorkflowCommandError("RELEASE_NOT_FOUND", 404);
102
- const authorization = await this.authorization(context, release);
149
+ const inputValue = (value.input ?? {}) as JsonValue;
150
+ const inputArtifacts = workflowArtifactRefs(value.inputArtifacts);
151
+ const authorizedContext = project
152
+ ? await this.projectAuthority.authorizeRun({
153
+ release,
154
+ workflowInput: inputValue,
155
+ inputArtifacts,
156
+ context,
157
+ signal,
158
+ })
159
+ : context;
160
+ if (!project) {
161
+ this.projectAuthority.requireDirect(release.definition.workflowKey);
162
+ }
163
+ const authorization = await this.authorization(authorizedContext, release);
103
164
  const now = new Date();
104
165
  const budget = optionalWorkflowBudget(value.budget);
105
166
  const effectiveBudget = budget ?? release.definition.budget;
106
- const inputValue = (value.input ?? {}) as JsonValue;
107
167
  const entry = release.definition.nodes.find(
108
168
  ({ id }) => id === release.definition.entryNodeId,
109
169
  )!;
@@ -113,7 +173,6 @@ export class ProductionAgentWorkflowCommandFacade {
113
173
  at: now.toISOString(),
114
174
  });
115
175
  const inputDigest = inputReceipt.valueDigest;
116
- const inputArtifacts = workflowArtifactRefs(value.inputArtifacts);
117
176
  if (
118
177
  inputArtifacts.length &&
119
178
  !(await this.artifacts.reauthorize({
@@ -150,12 +209,12 @@ export class ProductionAgentWorkflowCommandFacade {
150
209
  });
151
210
  await driveWorkflowBatch({
152
211
  repository: this.repository,
153
- scheduler: this.schedulerFor(context),
212
+ scheduler: this.schedulerFor(authorizedContext),
154
213
  runId: result.record.run.runId,
155
214
  authorizationFence: authorization.fence,
156
215
  signal,
157
216
  });
158
- return this.query.detail(result.record.run.runId, context);
217
+ return this.query.detail(result.record.run.runId, authorizedContext);
159
218
  }
160
219
 
161
220
  async cancel(
@@ -155,15 +155,6 @@ export class DrizzleAgentWorkflowArtifactPort implements AgentWorkflowArtifactPo
155
155
  ) {
156
156
  const durable = await this.durableById(artifact.artifactId);
157
157
  if (!durable || !sameWorkflowArtifact(durable, artifact)) return undefined;
158
- try {
159
- await this.sourceAuthorization.requireCurrent({
160
- artifact,
161
- actorId,
162
- scopeDigest,
163
- });
164
- } catch {
165
- return undefined;
166
- }
167
158
  const [job] = await this.db
168
159
  .select({ authorizationScope: durableJobs.authorizationScope })
169
160
  .from(durableJobs)
@@ -175,6 +166,15 @@ export class DrizzleAgentWorkflowArtifactPort implements AgentWorkflowArtifactPo
175
166
  ) {
176
167
  return undefined;
177
168
  }
169
+ try {
170
+ await this.sourceAuthorization.requireCurrent({
171
+ artifact,
172
+ actorId,
173
+ frozenScope: job.authorizationScope,
174
+ });
175
+ } catch {
176
+ return undefined;
177
+ }
178
178
  const inspected = await this.durableArtifacts
179
179
  .inspect(durable.jobId, durable.kind, actorId)
180
180
  .catch(() => undefined);
@@ -46,7 +46,7 @@ export async function completeDelegation(input: {
46
46
  await input.artifactSources!.requireCurrent({
47
47
  artifact,
48
48
  actorId: parent.actorId,
49
- scopeDigest: input.row.grantJson.capabilityCeiling.scopeDigest,
49
+ frozenScope: input.row.grantJson.capabilityCeiling.scopeSnapshot,
50
50
  });
51
51
  }
52
52
  await bindDelegationChild({
@@ -18,6 +18,7 @@ import type {
18
18
  AgentWorkflowNodeAdapterContext,
19
19
  } from "./adapter-dispatch.js";
20
20
  import type { WorkflowEffectCommandFence } from "./effect-command-fence.js";
21
+ import { CLIENT_AGENT_WORKFLOW_ARTIFACT_TASK_KEY } from "@southwind-ai/server-sdk";
21
22
  import { createAgentWorkflowNodeAdapterRegistry } from "./node-adapter-factory.js";
22
23
  import {
23
24
  directPlatformNodeAdapter,
@@ -93,7 +94,11 @@ function jobAdapter(
93
94
  const scope = captureAuthorizationScope(context.actor.scopeContext);
94
95
  const submit = async (
95
96
  nodeContext: AgentWorkflowNodeAdapterContext,
96
- commandId: string,
97
+ command: {
98
+ readonly commandId: string;
99
+ readonly generation: number;
100
+ readonly requestDigest: string;
101
+ },
97
102
  idempotencyKey: string,
98
103
  ) => {
99
104
  const node = nodeContext.node;
@@ -103,13 +108,16 @@ function jobAdapter(
103
108
  queue: "agent-workflow",
104
109
  idempotencyKey,
105
110
  payload: {
106
- commandId,
111
+ commandId: command.commandId,
107
112
  runId: nodeContext.run.runId,
108
113
  nodeRunId: nodeContext.nodeRun.nodeRunId,
114
+ effectGeneration: command.generation,
115
+ requestDigest: command.requestDigest,
109
116
  },
110
117
  requestedBy: context.actor.id,
111
118
  authorizationScope: scope.snapshot,
112
- maxAttempts: 1,
119
+ maxAttempts:
120
+ node.taskKey === CLIENT_AGENT_WORKFLOW_ARTIFACT_TASK_KEY ? 3 : 1,
113
121
  timeoutSeconds: node.timeoutSeconds ?? 900,
114
122
  });
115
123
  if (
@@ -140,7 +148,7 @@ function jobAdapter(
140
148
  async dispatch({ context: nodeContext, command, fence }) {
141
149
  const submitted = await submit(
142
150
  nodeContext,
143
- command.commandId,
151
+ { ...command, generation: fence.generation },
144
152
  command.idempotencyKey,
145
153
  );
146
154
  return jobResult(nodeContext, fence, await jobs.get(submitted.job.id));
@@ -153,7 +161,7 @@ function jobAdapter(
153
161
  }) {
154
162
  const submitted = await submit(
155
163
  nodeContext,
156
- command.commandId,
164
+ command,
157
165
  command.idempotencyKey,
158
166
  );
159
167
  const snapshot = await jobs.get(submitted.job.id);
@@ -171,7 +179,7 @@ function jobAdapter(
171
179
  if (!command) return { cancelled: true };
172
180
  const submitted = await submit(
173
181
  nodeContext,
174
- command.commandId,
182
+ command,
175
183
  command.idempotencyKey,
176
184
  );
177
185
  const snapshot = await jobs.get(submitted.job.id);
@@ -0,0 +1,53 @@
1
+ import { expect, test } from "bun:test";
2
+ import type { ModuleClientAgentWorkflowArtifactProjectBinding } from "@southwind-ai/server-sdk";
3
+ import { ClientAgentWorkflowArtifactBindingRegistry } from "../agent/client-workflow-artifact/binding-registry.js";
4
+ import { ProjectAgentWorkflowLaunchAuthority } from "./project-launch-authority.js";
5
+
6
+ const binding = {
7
+ workflowKey: "analysis.full-case",
8
+ nodeId: "client-artifact",
9
+ operationKey: "analysis.page-agent",
10
+ releaseDigest: `sha256:${"d".repeat(64)}`,
11
+ scopeContainmentContractVersion: 1,
12
+ terminalResultAuthority: {
13
+ version: "sharp.result.v1",
14
+ digest: `sha256:${"e".repeat(64)}`,
15
+ async canonicalize({ proposedResult }) {
16
+ return proposedResult;
17
+ },
18
+ },
19
+ clientTaskAuthority: {
20
+ version: "sharp.task.v1",
21
+ digest: `sha256:${"f".repeat(64)}`,
22
+ async build({ workflowInput }) {
23
+ return { taskKey: "sharp.full-case", input: workflowInput };
24
+ },
25
+ },
26
+ launchAuthority: {
27
+ version: "sharp.launch.v1",
28
+ digest: `sha256:${"a".repeat(64)}`,
29
+ async authorizeRelease() {},
30
+ async authorizeRun({ currentAuthorizationScope }) {
31
+ return {
32
+ authorizationScope: currentAuthorizationScope,
33
+ };
34
+ },
35
+ },
36
+ artifact: {
37
+ kind: "analysis",
38
+ contentType: "application/json",
39
+ filename: "analysis.json",
40
+ retentionPolicyKey: "case-analysis",
41
+ maxByteSize: 65_536,
42
+ },
43
+ } satisfies ModuleClientAgentWorkflowArtifactProjectBinding;
44
+
45
+ test("generic command 对 project-protected Workflow 始终隐藏", () => {
46
+ const authority = new ProjectAgentWorkflowLaunchAuthority(
47
+ new ClientAgentWorkflowArtifactBindingRegistry([binding]),
48
+ );
49
+ expect(() => authority.requireDirect(binding.workflowKey)).toThrow(
50
+ "RELEASE_NOT_FOUND",
51
+ );
52
+ expect(() => authority.requireDirect("analysis.direct")).not.toThrow();
53
+ });
@@ -0,0 +1,92 @@
1
+ import type {
2
+ AgentWorkflowArtifactRef,
3
+ AgentWorkflowDefinition,
4
+ AgentWorkflowRelease,
5
+ AgentWorkflowReleaseCoordinates,
6
+ JsonValue,
7
+ } from "@southwind-ai/shared";
8
+ import { authorizationScopeContains } from "../data-access/scope-containment.js";
9
+ import { captureAuthorizationScope } from "../data-access/scope-snapshot.js";
10
+ import { restoreServerScopeContext } from "../data-access/context.js";
11
+ import type { RequestGuardContext } from "../guards.js";
12
+ import { ClientAgentWorkflowArtifactBindingRegistry } from "../agent/client-workflow-artifact/binding-registry.js";
13
+ import { AgentWorkflowCommandError } from "./command-error.js";
14
+
15
+ export class ProjectAgentWorkflowLaunchAuthority {
16
+ constructor(
17
+ private readonly bindings: ClientAgentWorkflowArtifactBindingRegistry,
18
+ ) {}
19
+
20
+ requireDirect(workflowKey: string) {
21
+ if (this.bindings.findWorkflow(workflowKey)) throw hidden();
22
+ }
23
+
24
+ async authorizeRelease(input: {
25
+ definition: AgentWorkflowDefinition;
26
+ coordinates: AgentWorkflowReleaseCoordinates;
27
+ actorId: string;
28
+ releaseDigest: string;
29
+ signal: AbortSignal;
30
+ }) {
31
+ const binding = this.requireBinding(
32
+ input.definition.workflowKey,
33
+ input.releaseDigest,
34
+ );
35
+ try {
36
+ await binding.launchAuthority.authorizeRelease(input);
37
+ } catch {
38
+ throw hidden();
39
+ }
40
+ }
41
+
42
+ async authorizeRun(input: {
43
+ release: AgentWorkflowRelease;
44
+ workflowInput: JsonValue;
45
+ inputArtifacts: readonly AgentWorkflowArtifactRef[];
46
+ context: RequestGuardContext;
47
+ signal: AbortSignal;
48
+ }): Promise<RequestGuardContext> {
49
+ const binding = this.requireBinding(
50
+ input.release.definition.workflowKey,
51
+ input.release.releaseDigest,
52
+ );
53
+ try {
54
+ const current = captureAuthorizationScope(
55
+ input.context.actor.scopeContext,
56
+ ).snapshot;
57
+ const decision = await binding.launchAuthority.authorizeRun({
58
+ releaseId: input.release.releaseId,
59
+ releaseDigest: input.release.releaseDigest,
60
+ workflowInput: input.workflowInput,
61
+ inputArtifacts: input.inputArtifacts,
62
+ actorId: input.context.actor.id,
63
+ currentAuthorizationScope: structuredClone(current),
64
+ signal: input.signal,
65
+ });
66
+ if (!authorizationScopeContains(current, decision.authorizationScope)) {
67
+ throw hidden();
68
+ }
69
+ const scope = restoreServerScopeContext(decision.authorizationScope);
70
+ return {
71
+ ...input.context,
72
+ actor: {
73
+ ...input.context.actor,
74
+ dataAccess: scope,
75
+ scopeContext: scope,
76
+ },
77
+ };
78
+ } catch {
79
+ throw hidden();
80
+ }
81
+ }
82
+
83
+ private requireBinding(workflowKey: string, releaseDigest: string) {
84
+ const binding = this.bindings.findWorkflow(workflowKey);
85
+ if (!binding || binding.releaseDigest !== releaseDigest) throw hidden();
86
+ return binding;
87
+ }
88
+ }
89
+
90
+ function hidden() {
91
+ return new AgentWorkflowCommandError("RELEASE_NOT_FOUND", 404);
92
+ }
@@ -1,4 +1,5 @@
1
1
  import type { ModuleManifest } from "@southwind-ai/modules";
2
+ import type { ModuleClientAgentWorkflowArtifactProjectBinding } from "@southwind-ai/server-sdk";
2
3
  import {
3
4
  DurableHandlerError,
4
5
  type DurableHandlerRegistry,
@@ -55,6 +56,8 @@ import {
55
56
  createProductionWorkflowConditions,
56
57
  createProductionWorkspacePolicies,
57
58
  } from "./production-node-policies.js";
59
+ import { ClientAgentWorkflowArtifactBindingRegistry } from "../agent/client-workflow-artifact/binding-registry.js";
60
+ import { ProjectAgentWorkflowLaunchAuthority } from "./project-launch-authority.js";
58
61
 
59
62
  export function composeProductionAgentWorkflowRuntime(input: {
60
63
  readonly db?: NodePgDatabase;
@@ -80,6 +83,7 @@ export function composeProductionAgentWorkflowRuntime(input: {
80
83
  readonly resolveActor?: (
81
84
  actorId: string,
82
85
  ) => Promise<RequestActor | undefined>;
86
+ readonly clientAgentWorkflowArtifactBindings?: readonly ModuleClientAgentWorkflowArtifactProjectBinding[];
83
87
  }) {
84
88
  if (
85
89
  !input.db ||
@@ -119,6 +123,11 @@ export function composeProductionAgentWorkflowRuntime(input: {
119
123
  artifactSourceAuthorization,
120
124
  input.artifactAccessSecret,
121
125
  );
126
+ const projectAuthority = new ProjectAgentWorkflowLaunchAuthority(
127
+ new ClientAgentWorkflowArtifactBindingRegistry(
128
+ input.clientAgentWorkflowArtifactBindings ?? [],
129
+ ),
130
+ );
122
131
  const modelPricing = AgentWorkflowModelPricingCatalog.fromJson(
123
132
  input.modelPricingJson,
124
133
  );
@@ -275,6 +284,21 @@ export function composeProductionAgentWorkflowRuntime(input: {
275
284
  return port;
276
285
  }
277
286
  continuations.start();
287
+ const commands = new ProductionAgentWorkflowCommandFacade(
288
+ repository,
289
+ service,
290
+ query,
291
+ evaluations,
292
+ evaluationRepository,
293
+ input.modules,
294
+ input.runtime,
295
+ input.tools,
296
+ waitSignals,
297
+ artifacts,
298
+ workspacePolicies,
299
+ projectAuthority,
300
+ schedulerFor,
301
+ );
278
302
  return {
279
303
  query,
280
304
  artifacts,
@@ -290,19 +314,11 @@ export function composeProductionAgentWorkflowRuntime(input: {
290
314
  safeCode: "MODEL_PRICING_UNAVAILABLE",
291
315
  },
292
316
  },
293
- commands: new ProductionAgentWorkflowCommandFacade(
294
- repository,
295
- service,
296
- query,
297
- evaluations,
298
- evaluationRepository,
299
- input.modules,
300
- input.runtime,
301
- input.tools,
302
- waitSignals,
303
- artifacts,
304
- workspacePolicies,
305
- schedulerFor,
306
- ),
317
+ commands,
318
+ /** Project-owned application Seam; intentionally not mounted on generic routes. */
319
+ projectCommands: {
320
+ publish: commands.publishProject.bind(commands),
321
+ createRun: commands.createProjectRun.bind(commands),
322
+ },
307
323
  };
308
324
  }