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.
- package/dist/cli.js +215 -24
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +58 -8
- package/template/.windy-template.json +2 -2
- package/template/apps/server/src/agent/client-session/attempt-authority.test.ts +162 -0
- package/template/apps/server/src/agent/client-session/attempt-authority.ts +191 -0
- package/template/apps/server/src/agent/client-session/bootstrap.ts +27 -11
- package/template/apps/server/src/agent/client-session/completion.ts +75 -24
- package/template/apps/server/src/agent/client-session/contracts.ts +23 -0
- package/template/apps/server/src/agent/client-session/creation.ts +103 -0
- package/template/apps/server/src/agent/client-session/digest.ts +11 -1
- package/template/apps/server/src/agent/client-session/drizzle-records.ts +150 -0
- package/template/apps/server/src/agent/client-session/drizzle-repository.integration.test.ts +30 -3
- package/template/apps/server/src/agent/client-session/drizzle-repository.ts +23 -132
- package/template/apps/server/src/agent/client-session/finalization.ts +99 -0
- package/template/apps/server/src/agent/client-session/host-auth.ts +22 -10
- package/template/apps/server/src/agent/client-session/host-revocation-composition.test.ts +77 -0
- package/template/apps/server/src/agent/client-session/host-revocation-composition.ts +28 -0
- package/template/apps/server/src/agent/client-session/launch-authority.contract.test.ts +21 -0
- package/template/apps/server/src/agent/client-session/manifest-admission.ts +11 -0
- package/template/apps/server/src/agent/client-session/service.test-support.ts +22 -0
- package/template/apps/server/src/agent/client-session/service.ts +104 -134
- package/template/apps/server/src/agent/client-session/terminal-digest.test.ts +23 -0
- package/template/apps/server/src/agent/client-workflow-artifact/active-blob-references.ts +8 -0
- package/template/apps/server/src/agent/client-workflow-artifact/artifact-builder.ts +60 -0
- package/template/apps/server/src/agent/client-workflow-artifact/authority-current.ts +59 -0
- package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.test.ts +189 -0
- package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.ts +153 -0
- package/template/apps/server/src/agent/client-workflow-artifact/bootstrap.ts +77 -0
- package/template/apps/server/src/agent/client-workflow-artifact/cancellation.ts +47 -0
- package/template/apps/server/src/agent/client-workflow-artifact/completed-recovery.ts +31 -0
- package/template/apps/server/src/agent/client-workflow-artifact/contracts.ts +104 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-locks.ts +30 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-mapper.ts +138 -0
- package/template/apps/server/src/agent/client-workflow-artifact/drizzle-repository.ts +346 -0
- package/template/apps/server/src/agent/client-workflow-artifact/error.ts +21 -0
- package/template/apps/server/src/agent/client-workflow-artifact/handler.ts +167 -0
- package/template/apps/server/src/agent/client-workflow-artifact/host-revocation.ts +29 -0
- package/template/apps/server/src/agent/client-workflow-artifact/in-memory-repository.ts +232 -0
- package/template/apps/server/src/agent/client-workflow-artifact/job-port.ts +101 -0
- package/template/apps/server/src/agent/client-workflow-artifact/presentation.ts +41 -0
- package/template/apps/server/src/agent/client-workflow-artifact/recovery.contract.test.ts +253 -0
- package/template/apps/server/src/agent/client-workflow-artifact/release-binding.ts +85 -0
- package/template/apps/server/src/agent/client-workflow-artifact/repository.test.ts +204 -0
- package/template/apps/server/src/agent/client-workflow-artifact/repository.ts +97 -0
- package/template/apps/server/src/agent/client-workflow-artifact/routes-handler.contract.test.ts +201 -0
- package/template/apps/server/src/agent/client-workflow-artifact/routes.ts +161 -0
- package/template/apps/server/src/agent/client-workflow-artifact/runtime-composition.ts +101 -0
- package/template/apps/server/src/agent/client-workflow-artifact/scope-authority.ts +39 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service-authorization.ts +117 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service-support.ts +28 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service.contract.test.ts +345 -0
- package/template/apps/server/src/agent/client-workflow-artifact/service.ts +350 -0
- package/template/apps/server/src/agent/client-workflow-artifact/session-boundary.ts +39 -0
- package/template/apps/server/src/agent/client-workflow-artifact/source-access.contract.test.ts +98 -0
- package/template/apps/server/src/agent/client-workflow-artifact/source-access.ts +80 -0
- package/template/apps/server/src/agent/client-workflow-artifact/takeover.ts +122 -0
- package/template/apps/server/src/agent/client-workflow-artifact/task-authority.contract.test.ts +97 -0
- package/template/apps/server/src/agent/client-workflow-artifact/task-authority.ts +147 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-authority.contract.test.ts +100 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-replay.ts +31 -0
- package/template/apps/server/src/agent/client-workflow-artifact/terminal-seal.ts +211 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-artifact.test-support.ts +173 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.contract.test.ts +254 -0
- package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.ts +285 -0
- package/template/apps/server/src/agent/run-facade.test-support.ts +17 -4
- package/template/apps/server/src/agent/run-facade.test.ts +16 -0
- package/template/apps/server/src/agent/run-session-authorization.ts +12 -10
- package/template/apps/server/src/agent/runtime-bootstrap-input.ts +5 -1
- package/template/apps/server/src/agent/runtime-bootstrap.ts +33 -33
- package/template/apps/server/src/agent/runtime-public-ports.ts +40 -0
- package/template/apps/server/src/agent/session-invalidation/contracts.ts +25 -26
- package/template/apps/server/src/agent/session-projection-access.ts +31 -19
- package/template/apps/server/src/agent/unavailable-routes.ts +21 -0
- package/template/apps/server/src/agent/workflow-runtime-bootstrap.ts +2 -0
- package/template/apps/server/src/agent-workflow/artifact-source-authorization.ts +12 -6
- package/template/apps/server/src/agent-workflow/command-facade.ts +70 -11
- package/template/apps/server/src/agent-workflow/drizzle-artifact-port.ts +9 -9
- package/template/apps/server/src/agent-workflow/drizzle-delegation-completion.ts +1 -1
- package/template/apps/server/src/agent-workflow/platform-node-adapters.ts +14 -6
- package/template/apps/server/src/agent-workflow/project-launch-authority.contract.test.ts +53 -0
- package/template/apps/server/src/agent-workflow/project-launch-authority.ts +92 -0
- package/template/apps/server/src/agent-workflow/runtime-composition.ts +30 -14
- package/template/apps/server/src/app/agent-runtime-composition.ts +6 -1
- package/template/apps/server/src/app/client-agent-workflow-artifact-binding.ts +14 -0
- package/template/apps/server/src/application-services.ts +6 -0
- package/template/apps/server/src/data-access/scope-containment.test.ts +31 -0
- package/template/apps/server/src/data-access/scope-containment.ts +48 -0
- package/template/apps/server/src/jobs/artifact-publication.test.ts +192 -0
- package/template/apps/server/src/jobs/artifact-publication.ts +56 -0
- package/template/apps/server/src/jobs/artifact-publisher.ts +101 -0
- package/template/apps/server/src/jobs/drizzle-artifact.ts +73 -1
- package/template/apps/server/src/jobs/drizzle-execution-repository.ts +14 -1
- package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +65 -0
- package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -0
- package/template/apps/server/src/jobs/execution-repository.ts +28 -0
- package/template/apps/server/src/jobs/execution.test.ts +3 -1
- package/template/apps/server/src/jobs/in-memory-execution.ts +16 -0
- package/template/apps/server/src/jobs/in-memory-lease.ts +45 -1
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +2 -0
- package/template/apps/server/src/jobs/postgres-cleanup-contract.test.ts +58 -0
- package/template/apps/server/src/jobs/worker.ts +8 -14
- package/template/apps/server/src/module-host/background-ai-unavailable.ts +18 -0
- package/template/apps/server/src/module-host/durable-context.ts +19 -22
- package/template/apps/server/src/module-host/implementation-completeness.test.ts +80 -1
- package/template/apps/server/src/module-host/implementation-completeness.ts +28 -1
- package/template/apps/server/src/module-host/request-context.ts +32 -8
- package/template/apps/server/src/module-host/storage-port.ts +3 -0
- package/template/apps/server/src/module-host/workflow-input-artifact-port.test.ts +104 -0
- package/template/apps/server/src/module-host/workflow-input-artifact-port.ts +229 -0
- package/template/apps/server/src/module-host/workflow-input-materialization.test.ts +163 -0
- package/template/apps/server/src/module-host/workflow-input-materialization.ts +161 -0
- package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +1 -0
- package/template/docs/architecture/agent-workflow.md +13 -2
- package/template/docs/architecture/durable-jobs.md +7 -0
- package/template/docs/operations/agent-session-memory.md +2 -1
- package/template/docs/operations/agent-session-troubleshooting.md +87 -2
- package/template/docs/operations/agent-workflow-recovery.md +25 -14
- package/template/docs/platform/client-agent-session.md +148 -2
- package/template/packages/ai-client/package.json +2 -1
- package/template/packages/ai-client/src/client-agent/index.ts +1 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/contracts.ts +85 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/http.test.ts +49 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/http.ts +82 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/index.ts +3 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.test.ts +154 -0
- package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.ts +332 -0
- package/template/packages/database/drizzle/0062_client_agent_workflow_artifact.sql +63 -0
- package/template/packages/database/drizzle/meta/0062_snapshot.json +14839 -0
- package/template/packages/database/drizzle/meta/_journal.json +7 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/agent-runtime-schema.test.ts +177 -0
- package/template/packages/database/src/schema/client-agent-workflow-artifact.ts +158 -0
- package/template/packages/database/src/schema/index.ts +1 -0
- package/template/packages/jobs/package.json +1 -1
- package/template/packages/jobs/src/handlers.test.ts +10 -0
- package/template/packages/jobs/src/handlers.ts +28 -11
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-client-execution-policy.ts +6 -0
- package/template/packages/modules/src/manifest.ts +2 -0
- package/template/packages/modules/src/system-agent-audit-actions.ts +3 -0
- package/template/packages/modules/src/system-ai-api-bindings.ts +15 -0
- package/template/packages/modules/src/system-audit-actions.ts +15 -0
- package/template/packages/server-sdk/index.ts +52 -0
- package/template/packages/server-sdk/package.json +5 -1
- package/template/packages/server-sdk/src/client-agent-session.ts +13 -1
- package/template/packages/server-sdk/src/client-agent-workflow-artifact.contract.test.ts +113 -0
- package/template/packages/server-sdk/src/client-agent-workflow-artifact.ts +183 -0
- package/template/packages/server-sdk/src/durable-handler.ts +7 -0
- package/template/packages/server-sdk/src/request-context.ts +7 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-port.ts +104 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-validation.contract.test.ts +124 -0
- package/template/packages/server-sdk/src/workflow-input-artifact-validation.ts +347 -0
- package/template/packages/server-sdk/src/workflow-input-materialization-handler.ts +43 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage/src/blob-store-put-once.test.ts +70 -0
- package/template/packages/storage/src/blob-store.ts +9 -0
- package/template/packages/storage/src/local-file-blob-put-once.ts +120 -0
- package/template/packages/storage/src/local-file-blob-store.ts +53 -1
- package/template/packages/storage/src/memory-blob-store.ts +73 -33
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import type { AgentWorkflowNode } from "@southwind-ai/shared";
|
|
2
|
+
import { CLIENT_AGENT_WORKFLOW_ARTIFACT_WORK_ID_PREFIX } from "@southwind-ai/server-sdk";
|
|
3
|
+
import type { DurableJobService } from "../../jobs/service.js";
|
|
4
|
+
import { hashAuthorizationScope } from "../../data-access/scope-snapshot.js";
|
|
5
|
+
import { digestJson } from "../client-session/digest.js";
|
|
6
|
+
import { DrizzleAgentWorkflowRepository } from "../../agent-workflow/drizzle-repository.js";
|
|
7
|
+
import { ReceiptAgentWorkflowNodeInputPort } from "../../agent-workflow/node-input-receipt-port.js";
|
|
8
|
+
import { schedulerAdapterContext } from "../../agent-workflow/scheduler-context.js";
|
|
9
|
+
import {
|
|
10
|
+
clientWorkflowBindingCommitment,
|
|
11
|
+
ClientAgentWorkflowArtifactBindingRegistry,
|
|
12
|
+
} from "./binding-registry.js";
|
|
13
|
+
import { assertClientAgentWorkflowReleaseBinding } from "./release-binding.js";
|
|
14
|
+
import {
|
|
15
|
+
clientAgentWorkflowArtifactTaskKey,
|
|
16
|
+
type ClientAgentWorkflowBeginFacts,
|
|
17
|
+
} from "./contracts.js";
|
|
18
|
+
import { requireClientWorkflowFrozenScope } from "./scope-authority.js";
|
|
19
|
+
|
|
20
|
+
export interface ClientAgentWorkflowJobPayload {
|
|
21
|
+
readonly commandId: string;
|
|
22
|
+
readonly runId: string;
|
|
23
|
+
readonly nodeRunId: string;
|
|
24
|
+
readonly effectGeneration?: number;
|
|
25
|
+
readonly requestDigest?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type WorkflowSnapshot = NonNullable<
|
|
29
|
+
Awaited<ReturnType<DrizzleAgentWorkflowRepository["getSnapshot"]>>
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export class ClientAgentWorkflowFactsLoader {
|
|
33
|
+
readonly #inputs = new ReceiptAgentWorkflowNodeInputPort();
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
private readonly workflows: DrizzleAgentWorkflowRepository,
|
|
37
|
+
private readonly jobs: DurableJobService,
|
|
38
|
+
private readonly bindings: ClientAgentWorkflowArtifactBindingRegistry,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
async load(input: {
|
|
42
|
+
jobId: string;
|
|
43
|
+
payload: ClientAgentWorkflowJobPayload;
|
|
44
|
+
signal: AbortSignal;
|
|
45
|
+
}): Promise<ClientAgentWorkflowBeginFacts> {
|
|
46
|
+
const [snapshot, job] = await Promise.all([
|
|
47
|
+
this.workflows.getSnapshot(input.payload.runId),
|
|
48
|
+
this.jobs.get(input.jobId),
|
|
49
|
+
]);
|
|
50
|
+
if (!snapshot || !job) throw invalid("WORKFLOW_FACTS_NOT_FOUND");
|
|
51
|
+
const release = await this.workflows.getRelease(snapshot.run.releaseId);
|
|
52
|
+
const nodeRun = snapshot.nodeRuns.find(
|
|
53
|
+
({ nodeRunId }) => nodeRunId === input.payload.nodeRunId,
|
|
54
|
+
);
|
|
55
|
+
const command = snapshot.sideEffects.find(
|
|
56
|
+
({ commandId }) => commandId === input.payload.commandId,
|
|
57
|
+
);
|
|
58
|
+
const node = release?.definition.nodes.find(
|
|
59
|
+
({ id }) => id === nodeRun?.nodeId,
|
|
60
|
+
);
|
|
61
|
+
if (!release || !nodeRun || !command || !node) {
|
|
62
|
+
throw invalid("WORKFLOW_COORDINATE_NOT_FOUND");
|
|
63
|
+
}
|
|
64
|
+
assertJobAndFence({ input, snapshot, job, nodeRun, command, node });
|
|
65
|
+
const binding = this.bindings.require(
|
|
66
|
+
release.definition.workflowKey,
|
|
67
|
+
node.id,
|
|
68
|
+
);
|
|
69
|
+
assertClientAgentWorkflowReleaseBinding(release, binding);
|
|
70
|
+
const context = schedulerAdapterContext({
|
|
71
|
+
snapshot,
|
|
72
|
+
release,
|
|
73
|
+
nodeRun,
|
|
74
|
+
node,
|
|
75
|
+
signal: input.signal,
|
|
76
|
+
});
|
|
77
|
+
const value = await this.#inputs.readJson({
|
|
78
|
+
context,
|
|
79
|
+
...(node.inputSchemaKey ? { schemaKey: node.inputSchemaKey } : {}),
|
|
80
|
+
});
|
|
81
|
+
const record: ClientAgentWorkflowBeginFacts["record"] = {
|
|
82
|
+
workId: workflowWorkId(command.commandId),
|
|
83
|
+
commandId: command.commandId,
|
|
84
|
+
durableJobId: input.jobId,
|
|
85
|
+
runId: snapshot.run.runId,
|
|
86
|
+
nodeRunId: nodeRun.nodeRunId,
|
|
87
|
+
effectKind: "job",
|
|
88
|
+
taskKey: clientAgentWorkflowArtifactTaskKey,
|
|
89
|
+
operationKey: binding.operationKey,
|
|
90
|
+
bindingDigest: digestJson(clientWorkflowBindingCommitment(binding)),
|
|
91
|
+
actorId: snapshot.run.actorId,
|
|
92
|
+
scopeDigest: requireDigest(snapshot.run.scopeDigest),
|
|
93
|
+
authorizationSnapshotDigest: requireDigest(
|
|
94
|
+
command.authorizationSnapshotDigest,
|
|
95
|
+
),
|
|
96
|
+
runPolicyDigest: requireDigest(command.runPolicyDigest),
|
|
97
|
+
requestDigest: requireDigest(command.requestDigest),
|
|
98
|
+
inputDigest: digestJson(value),
|
|
99
|
+
effectGeneration: command.generation,
|
|
100
|
+
effectFenceTokenDigest: requireDigest(command.fenceTokenDigest),
|
|
101
|
+
status: "pending",
|
|
102
|
+
claimGeneration: 0,
|
|
103
|
+
historySequence: 0,
|
|
104
|
+
version: 1,
|
|
105
|
+
createdAt: Date.now(),
|
|
106
|
+
updatedAt: Date.now(),
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
input: value,
|
|
110
|
+
inputArtifacts: context.inputArtifacts,
|
|
111
|
+
binding,
|
|
112
|
+
frozenScope: requireClientWorkflowFrozenScope(record, job),
|
|
113
|
+
record,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async loadTerminal(input: {
|
|
118
|
+
record: ClientAgentWorkflowBeginFacts["record"];
|
|
119
|
+
signal: AbortSignal;
|
|
120
|
+
}): Promise<
|
|
121
|
+
Pick<
|
|
122
|
+
ClientAgentWorkflowBeginFacts,
|
|
123
|
+
"binding" | "input" | "inputArtifacts" | "frozenScope"
|
|
124
|
+
>
|
|
125
|
+
> {
|
|
126
|
+
const [snapshot, job] = await Promise.all([
|
|
127
|
+
this.workflows.getSnapshot(input.record.runId),
|
|
128
|
+
this.jobs.get(input.record.durableJobId),
|
|
129
|
+
]);
|
|
130
|
+
if (!snapshot || !job) throw invalid("WORKFLOW_FACTS_NOT_FOUND");
|
|
131
|
+
const release = await this.workflows.getRelease(snapshot.run.releaseId);
|
|
132
|
+
const nodeRun = snapshot.nodeRuns.find(
|
|
133
|
+
({ nodeRunId }) => nodeRunId === input.record.nodeRunId,
|
|
134
|
+
);
|
|
135
|
+
const command = snapshot.sideEffects.find(
|
|
136
|
+
({ commandId }) => commandId === input.record.commandId,
|
|
137
|
+
);
|
|
138
|
+
const node = release?.definition.nodes.find(
|
|
139
|
+
({ id }) => id === nodeRun?.nodeId,
|
|
140
|
+
);
|
|
141
|
+
if (!release || !nodeRun || !command || !node) {
|
|
142
|
+
throw invalid("WORKFLOW_COORDINATE_NOT_FOUND");
|
|
143
|
+
}
|
|
144
|
+
const binding = this.bindings.require(
|
|
145
|
+
release.definition.workflowKey,
|
|
146
|
+
node.id,
|
|
147
|
+
);
|
|
148
|
+
assertClientAgentWorkflowReleaseBinding(release, binding);
|
|
149
|
+
const context = schedulerAdapterContext({
|
|
150
|
+
snapshot,
|
|
151
|
+
release,
|
|
152
|
+
nodeRun,
|
|
153
|
+
node,
|
|
154
|
+
signal: input.signal,
|
|
155
|
+
});
|
|
156
|
+
const value = await this.#inputs.readJson({
|
|
157
|
+
context,
|
|
158
|
+
...(node.inputSchemaKey ? { schemaKey: node.inputSchemaKey } : {}),
|
|
159
|
+
});
|
|
160
|
+
assertTerminalIdentity({
|
|
161
|
+
record: input.record,
|
|
162
|
+
snapshot,
|
|
163
|
+
release,
|
|
164
|
+
nodeRun,
|
|
165
|
+
command,
|
|
166
|
+
node,
|
|
167
|
+
binding,
|
|
168
|
+
inputDigest: digestJson(value),
|
|
169
|
+
});
|
|
170
|
+
return {
|
|
171
|
+
binding,
|
|
172
|
+
input: value,
|
|
173
|
+
inputArtifacts: context.inputArtifacts,
|
|
174
|
+
frozenScope: requireClientWorkflowFrozenScope(input.record, job),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function assertJobAndFence(input: {
|
|
180
|
+
input: {
|
|
181
|
+
jobId: string;
|
|
182
|
+
payload: ClientAgentWorkflowJobPayload;
|
|
183
|
+
};
|
|
184
|
+
snapshot: WorkflowSnapshot;
|
|
185
|
+
job: NonNullable<Awaited<ReturnType<DurableJobService["get"]>>>;
|
|
186
|
+
nodeRun: WorkflowSnapshot["nodeRuns"][number];
|
|
187
|
+
command: WorkflowSnapshot["sideEffects"][number];
|
|
188
|
+
node: AgentWorkflowNode;
|
|
189
|
+
}) {
|
|
190
|
+
const { payload } = input.input;
|
|
191
|
+
const { job } = input.job;
|
|
192
|
+
if (
|
|
193
|
+
job.id !== input.input.jobId ||
|
|
194
|
+
job.handlerKey !== clientAgentWorkflowArtifactTaskKey ||
|
|
195
|
+
job.requestedBy !== input.snapshot.run.actorId ||
|
|
196
|
+
!job.authorizationScope ||
|
|
197
|
+
`sha256:${hashAuthorizationScope(job.authorizationScope)}` !==
|
|
198
|
+
input.snapshot.run.scopeDigest ||
|
|
199
|
+
input.node.type !== "job" ||
|
|
200
|
+
input.node.taskKey !== clientAgentWorkflowArtifactTaskKey ||
|
|
201
|
+
input.nodeRun.nodeType !== "job" ||
|
|
202
|
+
input.nodeRun.runId !== input.snapshot.run.runId ||
|
|
203
|
+
input.command.runId !== input.snapshot.run.runId ||
|
|
204
|
+
input.command.nodeRunId !== input.nodeRun.nodeRunId ||
|
|
205
|
+
input.command.effectKind !== "job" ||
|
|
206
|
+
input.command.status !== "dispatched" ||
|
|
207
|
+
input.command.generation < 1 ||
|
|
208
|
+
!input.command.fenceTokenDigest ||
|
|
209
|
+
input.command.authorizationSnapshotDigest !==
|
|
210
|
+
input.snapshot.run.authorizationSnapshotDigest ||
|
|
211
|
+
input.command.runPolicyDigest !==
|
|
212
|
+
input.snapshot.run.executionPolicyDigest ||
|
|
213
|
+
(payload.effectGeneration !== undefined &&
|
|
214
|
+
payload.effectGeneration !== input.command.generation) ||
|
|
215
|
+
(payload.requestDigest !== undefined &&
|
|
216
|
+
payload.requestDigest !== input.command.requestDigest)
|
|
217
|
+
) {
|
|
218
|
+
throw invalid("WORKFLOW_EFFECT_FENCE_INVALID");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function assertTerminalIdentity(input: {
|
|
223
|
+
record: ClientAgentWorkflowBeginFacts["record"];
|
|
224
|
+
snapshot: WorkflowSnapshot;
|
|
225
|
+
release: NonNullable<
|
|
226
|
+
Awaited<ReturnType<DrizzleAgentWorkflowRepository["getRelease"]>>
|
|
227
|
+
>;
|
|
228
|
+
nodeRun: WorkflowSnapshot["nodeRuns"][number];
|
|
229
|
+
command: WorkflowSnapshot["sideEffects"][number];
|
|
230
|
+
node: AgentWorkflowNode;
|
|
231
|
+
binding: ClientAgentWorkflowBeginFacts["binding"];
|
|
232
|
+
inputDigest: `sha256:${string}`;
|
|
233
|
+
}) {
|
|
234
|
+
const { record, snapshot, release, nodeRun, command, node, binding } = input;
|
|
235
|
+
if (
|
|
236
|
+
snapshot.run.releaseId !== release.releaseId ||
|
|
237
|
+
snapshot.run.runId !== record.runId ||
|
|
238
|
+
snapshot.run.actorId !== record.actorId ||
|
|
239
|
+
snapshot.run.scopeDigest !== record.scopeDigest ||
|
|
240
|
+
snapshot.run.authorizationSnapshotDigest !==
|
|
241
|
+
record.authorizationSnapshotDigest ||
|
|
242
|
+
snapshot.run.executionPolicyDigest !== record.runPolicyDigest ||
|
|
243
|
+
!["running", "waiting", "completed"].includes(snapshot.run.status) ||
|
|
244
|
+
nodeRun.nodeRunId !== record.nodeRunId ||
|
|
245
|
+
nodeRun.runId !== record.runId ||
|
|
246
|
+
nodeRun.nodeId !== binding.nodeId ||
|
|
247
|
+
nodeRun.nodeType !== "job" ||
|
|
248
|
+
!["running", "waiting", "completed"].includes(nodeRun.status) ||
|
|
249
|
+
node.type !== "job" ||
|
|
250
|
+
node.taskKey !== clientAgentWorkflowArtifactTaskKey ||
|
|
251
|
+
command.commandId !== record.commandId ||
|
|
252
|
+
command.runId !== record.runId ||
|
|
253
|
+
command.nodeRunId !== record.nodeRunId ||
|
|
254
|
+
command.effectKind !== record.effectKind ||
|
|
255
|
+
command.effectKey !== node.id ||
|
|
256
|
+
command.adapter.kind !== "durable-job" ||
|
|
257
|
+
!["dispatched", "committed", "reconciled"].includes(command.status) ||
|
|
258
|
+
command.generation !== record.effectGeneration ||
|
|
259
|
+
command.requestDigest !== record.requestDigest ||
|
|
260
|
+
command.fenceTokenDigest !== record.effectFenceTokenDigest ||
|
|
261
|
+
command.authorizationSnapshotDigest !==
|
|
262
|
+
record.authorizationSnapshotDigest ||
|
|
263
|
+
command.runPolicyDigest !== record.runPolicyDigest ||
|
|
264
|
+
record.taskKey !== clientAgentWorkflowArtifactTaskKey ||
|
|
265
|
+
record.operationKey !== binding.operationKey ||
|
|
266
|
+
record.bindingDigest !==
|
|
267
|
+
digestJson(clientWorkflowBindingCommitment(binding)) ||
|
|
268
|
+
record.inputDigest !== input.inputDigest
|
|
269
|
+
) {
|
|
270
|
+
throw invalid("WORKFLOW_TERMINAL_IDENTITY_INVALID");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function workflowWorkId(commandId: string) {
|
|
275
|
+
return `${CLIENT_AGENT_WORKFLOW_ARTIFACT_WORK_ID_PREFIX}${digestJson({ commandId }).slice("sha256:".length, 53)}`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function requireDigest(value: string | undefined): `sha256:${string}` {
|
|
279
|
+
if (!value?.startsWith("sha256:")) throw invalid("DIGEST_INVALID");
|
|
280
|
+
return value as `sha256:${string}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function invalid(code: string) {
|
|
284
|
+
return new Error(code);
|
|
285
|
+
}
|
|
@@ -10,8 +10,9 @@ import { createTestRunAuthorizationStore } from "./run-authorization.test-suppor
|
|
|
10
10
|
|
|
11
11
|
export async function createRunFacadeFixture(
|
|
12
12
|
permissionKeys: string[] = ["business.ai.execute", "business.records.read"],
|
|
13
|
+
options: { workflowOnly?: boolean } = {},
|
|
13
14
|
) {
|
|
14
|
-
const module = runFacadeManifest();
|
|
15
|
+
const module = runFacadeManifest(options.workflowOnly);
|
|
15
16
|
const prepared: unknown[] = [];
|
|
16
17
|
const hosts = await initializeModuleHosts({
|
|
17
18
|
modules: [module],
|
|
@@ -90,7 +91,7 @@ export function guardContext(permissionKeys: string[]) {
|
|
|
90
91
|
);
|
|
91
92
|
}
|
|
92
93
|
|
|
93
|
-
export function runFacadeManifest(): ModuleManifest {
|
|
94
|
+
export function runFacadeManifest(workflowOnly = false): ModuleManifest {
|
|
94
95
|
return {
|
|
95
96
|
name: "business",
|
|
96
97
|
version: "0.1.0",
|
|
@@ -144,13 +145,13 @@ export function runFacadeManifest(): ModuleManifest {
|
|
|
144
145
|
},
|
|
145
146
|
},
|
|
146
147
|
],
|
|
147
|
-
aiOperations: [agentOperation()],
|
|
148
|
+
aiOperations: [agentOperation(workflowOnly)],
|
|
148
149
|
providers: [],
|
|
149
150
|
auditActions: ["business.ai.execute", "business.records.read"],
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
|
|
153
|
-
function agentOperation() {
|
|
154
|
+
function agentOperation(workflowOnly: boolean) {
|
|
154
155
|
return {
|
|
155
156
|
key: "business.answer",
|
|
156
157
|
kind: "agent" as const,
|
|
@@ -187,6 +188,18 @@ function agentOperation() {
|
|
|
187
188
|
},
|
|
188
189
|
],
|
|
189
190
|
},
|
|
191
|
+
...(workflowOnly
|
|
192
|
+
? {
|
|
193
|
+
clientExecution: {
|
|
194
|
+
mode: "governed-session" as const,
|
|
195
|
+
launchAuthority: "workflow-artifact" as const,
|
|
196
|
+
maxSteps: 4,
|
|
197
|
+
maxRequestBytes: 4096,
|
|
198
|
+
maxResponseBytes: 4096,
|
|
199
|
+
maxConcurrentRequests: 1,
|
|
200
|
+
},
|
|
201
|
+
}
|
|
202
|
+
: {}),
|
|
190
203
|
toolNetworkPolicy: { mode: "forbidden" as const },
|
|
191
204
|
limits: {
|
|
192
205
|
maxDurationSeconds: 60,
|
|
@@ -96,6 +96,22 @@ describe("Guarded Agent Run facade", () => {
|
|
|
96
96
|
expect(fixture.prepared).toEqual([]);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
+
test("workflow-only Client Operation 不允许普通 Agent Run 直接启动", async () => {
|
|
100
|
+
const fixture = await createRunFacadeFixture(undefined, {
|
|
101
|
+
workflowOnly: true,
|
|
102
|
+
});
|
|
103
|
+
await expect(
|
|
104
|
+
fixture.facade.start(
|
|
105
|
+
"business.answer",
|
|
106
|
+
{ question: "绕过 Workflow" },
|
|
107
|
+
"request-workflow-bypass",
|
|
108
|
+
fixture.context,
|
|
109
|
+
),
|
|
110
|
+
).rejects.toMatchObject({ code: "OPERATION_NOT_FOUND", status: 404 });
|
|
111
|
+
expect(fixture.remote.started).toEqual([]);
|
|
112
|
+
expect(fixture.prepared).toEqual([]);
|
|
113
|
+
});
|
|
114
|
+
|
|
99
115
|
test("恢复时权限已收回会返回不泄露 Run 存在性的 404", async () => {
|
|
100
116
|
const fixture = await createRunFacadeFixture();
|
|
101
117
|
await fixture.facade.start(
|
|
@@ -41,11 +41,7 @@ export class AgentRunSessionBindingAuthorizer {
|
|
|
41
41
|
feature: this.#feature,
|
|
42
42
|
});
|
|
43
43
|
if (!access.decision.allowed) {
|
|
44
|
-
throw new AgentRunFacadeError(
|
|
45
|
-
"FORBIDDEN",
|
|
46
|
-
"无权写入长期 Session",
|
|
47
|
-
403,
|
|
48
|
-
);
|
|
44
|
+
throw new AgentRunFacadeError("FORBIDDEN", "无权写入长期 Session", 403);
|
|
49
45
|
}
|
|
50
46
|
}
|
|
51
47
|
}
|
|
@@ -56,6 +52,16 @@ export async function authorizeAgentRunStart(input: {
|
|
|
56
52
|
readonly context: RequestGuardContext;
|
|
57
53
|
readonly sessionBinding?: AgentRunSessionBindingAuthorizer;
|
|
58
54
|
}): Promise<void> {
|
|
55
|
+
if (
|
|
56
|
+
input.operation.definition.clientExecution?.launchAuthority ===
|
|
57
|
+
"workflow-artifact"
|
|
58
|
+
) {
|
|
59
|
+
throw new AgentRunFacadeError(
|
|
60
|
+
"OPERATION_NOT_FOUND",
|
|
61
|
+
"Agent Operation 不存在",
|
|
62
|
+
404,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
59
65
|
const decision = await evaluateAccessGuards(input.runtime, input.context, {
|
|
60
66
|
permission: {
|
|
61
67
|
permissionKey: input.operation.definition.permissionKey,
|
|
@@ -63,11 +69,7 @@ export async function authorizeAgentRunStart(input: {
|
|
|
63
69
|
feature: input.operation.feature,
|
|
64
70
|
});
|
|
65
71
|
if (!decision.decision.allowed) {
|
|
66
|
-
throw new AgentRunFacadeError(
|
|
67
|
-
"FORBIDDEN",
|
|
68
|
-
"无权执行 Agent Operation",
|
|
69
|
-
403,
|
|
70
|
-
);
|
|
72
|
+
throw new AgentRunFacadeError("FORBIDDEN", "无权执行 Agent Operation", 403);
|
|
71
73
|
}
|
|
72
74
|
await input.sessionBinding?.requireManage(input.context);
|
|
73
75
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import type { PlatformConfig } from "@southwind-ai/config";
|
|
2
2
|
import type { DurableHandlerRegistry } from "@southwind-ai/jobs";
|
|
3
3
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ModuleClientAgentHostAuthAdapter,
|
|
6
|
+
ModuleClientAgentWorkflowArtifactProjectBinding,
|
|
7
|
+
} from "@southwind-ai/server-sdk";
|
|
5
8
|
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
6
9
|
import type { InitializedModuleHosts } from "../module-host.js";
|
|
7
10
|
import type { createServerRuntime } from "../runtime.js";
|
|
@@ -36,6 +39,7 @@ export interface AgentPlatformRuntimeInput {
|
|
|
36
39
|
backgroundAi: DeferredAgentBackgroundOperationPortFactory;
|
|
37
40
|
screenshotPageRegistry?: ScreenshotPageRegistryV2;
|
|
38
41
|
clientAgentHostAuth?: ModuleClientAgentHostAuthAdapter;
|
|
42
|
+
clientAgentWorkflowArtifactBindings?: readonly ModuleClientAgentWorkflowArtifactProjectBinding[];
|
|
39
43
|
authoritativeSessionSources?: readonly ModuleAuthoritativeSourceAdapterRegistration[];
|
|
40
44
|
sessionMemoryObjects?: readonly ModuleMemoryObjectAdapterRegistration[];
|
|
41
45
|
agentWorkflowWorkspacePolicies?: readonly AgentWorkflowWorkspacePolicyDefinition[];
|
|
@@ -43,6 +43,8 @@ import { createModuleAiPorts } from "./module-ai-port-factory.js";
|
|
|
43
43
|
import { createManifestAuthorizedRouteApp } from "../module-host/static-route-authorization.js";
|
|
44
44
|
import { registerUnavailableAiRoutes } from "./unavailable-routes.js";
|
|
45
45
|
import { createClientAgentSessionRuntime } from "./client-session/bootstrap.js";
|
|
46
|
+
import { attachClientAgentWorkflowArtifactRuntime } from "./client-workflow-artifact/runtime-composition.js";
|
|
47
|
+
import { bindClientAgentHostRevocations } from "./client-session/host-revocation-composition.js";
|
|
46
48
|
import { registerAgentSessions } from "./agent-session-bootstrap.js";
|
|
47
49
|
import { createAiRuntimeReadinessSources } from "./runtime-readiness-sources.js";
|
|
48
50
|
import { registerAiRuntimeReporting } from "./runtime-reporting-bootstrap.js";
|
|
@@ -51,10 +53,12 @@ import { createScreenshotIntakeRuntimeV2 } from "./screenshot-intake-bootstrap.j
|
|
|
51
53
|
import { requiredEnvironment } from "./required-environment.js";
|
|
52
54
|
import { composeAgentLongSessionRuntime } from "./session-runtime-composition.js";
|
|
53
55
|
import type { AgentPlatformRuntimeInput } from "./runtime-bootstrap-input.js";
|
|
54
|
-
import { createModuleAgentSessionMemoryPort } from "../module-host/agent-session-memory-port.js";
|
|
55
|
-
import { createModuleAuthoritativeSourceInvalidationPort } from "../module-host/agent-source-invalidation-port.js";
|
|
56
56
|
import { composeAgentToolRuntime } from "./runtime-tool-composition.js";
|
|
57
57
|
import { registerProductionAgentWorkflowRuntime } from "./workflow-runtime-bootstrap.js";
|
|
58
|
+
import {
|
|
59
|
+
createSessionMemoryPortFactory,
|
|
60
|
+
createSourceInvalidationPortFactory,
|
|
61
|
+
} from "./runtime-public-ports.js";
|
|
58
62
|
export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
59
63
|
const resolveAgentActor = async (actorId: string) =>
|
|
60
64
|
resolveCurrentSystemActor(input.systemRepositories, actorId);
|
|
@@ -75,6 +79,7 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
|
75
79
|
aiOperations: undefined,
|
|
76
80
|
aiReadiness: undefined,
|
|
77
81
|
clientAgentSessions: undefined,
|
|
82
|
+
agentWorkflowProjects: undefined,
|
|
78
83
|
sessionMemory: undefined,
|
|
79
84
|
sourceInvalidation: undefined,
|
|
80
85
|
invalidateAuthorization: undefined,
|
|
@@ -113,6 +118,20 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
|
113
118
|
resolveActor: resolveAgentActor,
|
|
114
119
|
});
|
|
115
120
|
const clientAgentSessions = clientAgentSessionRuntime?.service;
|
|
121
|
+
const clientWorkflowArtifacts = attachClientAgentWorkflowArtifactRuntime({
|
|
122
|
+
platform: input,
|
|
123
|
+
aiRouteApp,
|
|
124
|
+
hostApp: clientAgentSessionRuntime?.hostApp,
|
|
125
|
+
routeAuthorizer,
|
|
126
|
+
sessions: clientAgentSessionRuntime?.service,
|
|
127
|
+
sessionRepository: clientAgentSessionRuntime?.repository,
|
|
128
|
+
attemptAuthority: clientAgentSessionRuntime?.attemptAuthority,
|
|
129
|
+
});
|
|
130
|
+
const unbindClientAgentHostRevocations = bindClientAgentHostRevocations({
|
|
131
|
+
adapter: input.clientAgentHostAuth,
|
|
132
|
+
revokeSessionHost: clientAgentSessionRuntime.revokeHost,
|
|
133
|
+
revokeWorkflowHost: clientWorkflowArtifacts.revokeHost,
|
|
134
|
+
});
|
|
116
135
|
const screenshotIntake = createScreenshotIntakeRuntimeV2({
|
|
117
136
|
modules: input.modules,
|
|
118
137
|
runtime: input.runtime,
|
|
@@ -146,7 +165,10 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
|
146
165
|
remote,
|
|
147
166
|
authorizations: runAuthorizations,
|
|
148
167
|
resolveActor: resolveAgentActor,
|
|
149
|
-
authoritativeSources:
|
|
168
|
+
authoritativeSources: [
|
|
169
|
+
...clientWorkflowArtifacts.authoritativeSources,
|
|
170
|
+
...(input.authoritativeSessionSources ?? []),
|
|
171
|
+
],
|
|
150
172
|
memoryObjects: input.sessionMemoryObjects,
|
|
151
173
|
});
|
|
152
174
|
const documentRemote = process.env.DOCUMENT_SERVER_URL?.trim()
|
|
@@ -260,6 +282,9 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
|
260
282
|
resolveActor: resolveAgentActor,
|
|
261
283
|
},
|
|
262
284
|
);
|
|
285
|
+
if (agentWorkflowRuntime?.artifacts) {
|
|
286
|
+
clientWorkflowArtifacts.bindTaskArtifacts?.(agentWorkflowRuntime.artifacts);
|
|
287
|
+
}
|
|
263
288
|
registerScreenshotIntakeRoutes(aiRouteApp, screenshotIntake);
|
|
264
289
|
registerAgentRunRoutes(
|
|
265
290
|
aiRouteApp,
|
|
@@ -309,39 +334,14 @@ export function registerAgentPlatformRuntime(input: AgentPlatformRuntimeInput) {
|
|
|
309
334
|
aiOperations: moduleAi.operations,
|
|
310
335
|
aiReadiness: moduleAi.readiness,
|
|
311
336
|
clientAgentSessions,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
context: Parameters<typeof createModuleAgentSessionMemoryPort>[1],
|
|
316
|
-
signal: AbortSignal,
|
|
317
|
-
) =>
|
|
318
|
-
createModuleAgentSessionMemoryPort(
|
|
319
|
-
moduleName,
|
|
320
|
-
context,
|
|
321
|
-
signal,
|
|
322
|
-
longSessions.memoryProjector,
|
|
323
|
-
)
|
|
324
|
-
: undefined,
|
|
325
|
-
sourceInvalidation: longSessions
|
|
326
|
-
? (
|
|
327
|
-
moduleName: string,
|
|
328
|
-
context: Parameters<
|
|
329
|
-
typeof createModuleAuthoritativeSourceInvalidationPort
|
|
330
|
-
>[1],
|
|
331
|
-
signal: AbortSignal,
|
|
332
|
-
) =>
|
|
333
|
-
createModuleAuthoritativeSourceInvalidationPort(
|
|
334
|
-
moduleName,
|
|
335
|
-
context,
|
|
336
|
-
signal,
|
|
337
|
-
longSessions.authoritativeSources,
|
|
338
|
-
longSessions.invalidationOutbox,
|
|
339
|
-
)
|
|
340
|
-
: undefined,
|
|
337
|
+
agentWorkflowProjects: agentWorkflowRuntime?.projectCommands,
|
|
338
|
+
sessionMemory: createSessionMemoryPortFactory(longSessions),
|
|
339
|
+
sourceInvalidation: createSourceInvalidationPortFactory(longSessions),
|
|
341
340
|
invalidateAuthorization: longSessions?.invalidateAuthorization,
|
|
342
341
|
disposeAgentRuntime() {
|
|
343
342
|
screenshotIntake?.dispose();
|
|
344
|
-
|
|
343
|
+
unbindClientAgentHostRevocations?.();
|
|
344
|
+
clientWorkflowArtifacts.dispose?.();
|
|
345
345
|
longSessions?.dispose();
|
|
346
346
|
},
|
|
347
347
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createModuleAgentSessionMemoryPort } from "../module-host/agent-session-memory-port.js";
|
|
2
|
+
import { createModuleAuthoritativeSourceInvalidationPort } from "../module-host/agent-source-invalidation-port.js";
|
|
3
|
+
import type { composeAgentLongSessionRuntime } from "./session-runtime-composition.js";
|
|
4
|
+
|
|
5
|
+
type LongSessionRuntime = ReturnType<typeof composeAgentLongSessionRuntime>;
|
|
6
|
+
|
|
7
|
+
export function createSessionMemoryPortFactory(runtime: LongSessionRuntime) {
|
|
8
|
+
if (!runtime) return undefined;
|
|
9
|
+
return (
|
|
10
|
+
moduleName: string,
|
|
11
|
+
context: Parameters<typeof createModuleAgentSessionMemoryPort>[1],
|
|
12
|
+
signal: AbortSignal,
|
|
13
|
+
) =>
|
|
14
|
+
createModuleAgentSessionMemoryPort(
|
|
15
|
+
moduleName,
|
|
16
|
+
context,
|
|
17
|
+
signal,
|
|
18
|
+
runtime.memoryProjector,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createSourceInvalidationPortFactory(
|
|
23
|
+
runtime: LongSessionRuntime,
|
|
24
|
+
) {
|
|
25
|
+
if (!runtime) return undefined;
|
|
26
|
+
return (
|
|
27
|
+
moduleName: string,
|
|
28
|
+
context: Parameters<
|
|
29
|
+
typeof createModuleAuthoritativeSourceInvalidationPort
|
|
30
|
+
>[1],
|
|
31
|
+
signal: AbortSignal,
|
|
32
|
+
) =>
|
|
33
|
+
createModuleAuthoritativeSourceInvalidationPort(
|
|
34
|
+
moduleName,
|
|
35
|
+
context,
|
|
36
|
+
signal,
|
|
37
|
+
runtime.authoritativeSources,
|
|
38
|
+
runtime.invalidationOutbox,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -57,6 +57,8 @@ export interface SourceAuthorizationCoordinate {
|
|
|
57
57
|
readonly actorId: string;
|
|
58
58
|
readonly scopeDigest: string;
|
|
59
59
|
readonly generation: number;
|
|
60
|
+
/** Optional server-derived scope; authorities that need containment must require it. */
|
|
61
|
+
readonly scopeSnapshot?: import("@southwind-ai/shared").AuthorizationScopeSnapshot;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
type SourceInvalidationEnvelopeBase = {
|
|
@@ -102,26 +104,26 @@ export type SessionDerivedInvalidationEnvelopeV1 =
|
|
|
102
104
|
|
|
103
105
|
export type SourceInvalidationEnvelopeV1 =
|
|
104
106
|
| (SourceInvalidationEnvelopeBase & {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
readonly causedBy: SourceInvalidationCausation;
|
|
108
|
+
readonly transition: {
|
|
109
|
+
readonly reason: "authorization-revoked";
|
|
110
|
+
readonly targetState: "revoked";
|
|
111
|
+
};
|
|
112
|
+
readonly scope: {
|
|
113
|
+
/** Sinks converge Session-derived projections for this Actor; source matching alone is insufficient. */
|
|
114
|
+
readonly kind: "authorization";
|
|
115
|
+
readonly authorization: SourceAuthorizationCoordinate;
|
|
116
|
+
};
|
|
117
|
+
})
|
|
116
118
|
| (SourceInvalidationEnvelopeBase & {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
119
|
+
readonly causedBy: SourceInvalidationCausation;
|
|
120
|
+
readonly transition: Exclude<
|
|
121
|
+
SourceInvalidationTransition,
|
|
122
|
+
| { readonly reason: "authorization-revoked" }
|
|
123
|
+
| { readonly reason: "source-deleted" }
|
|
124
|
+
>;
|
|
125
|
+
readonly scope: { readonly kind: "resource" };
|
|
126
|
+
})
|
|
125
127
|
| AuthoritativeSourceDeletionEnvelopeV1
|
|
126
128
|
| SessionDerivedInvalidationEnvelopeV1;
|
|
127
129
|
|
|
@@ -133,10 +135,7 @@ export interface SourceInvalidationDelivery {
|
|
|
133
135
|
readonly leaseToken: string;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
export type SourceInvalidationAppendResult =
|
|
137
|
-
| "appended"
|
|
138
|
-
| "duplicate"
|
|
139
|
-
| "stale";
|
|
138
|
+
export type SourceInvalidationAppendResult = "appended" | "duplicate" | "stale";
|
|
140
139
|
|
|
141
140
|
/**
|
|
142
141
|
* 由持久化 Adapter 将 append 与触发它的 Session/领域事实写入同一事务。
|
|
@@ -171,7 +170,8 @@ export type SourceInvalidationProjectionResult =
|
|
|
171
170
|
| "stale";
|
|
172
171
|
|
|
173
172
|
export interface SourceInvalidationProjectionPort<
|
|
174
|
-
TTarget extends SourceInvalidationProjectionTarget =
|
|
173
|
+
TTarget extends SourceInvalidationProjectionTarget =
|
|
174
|
+
SourceInvalidationProjectionTarget,
|
|
175
175
|
> {
|
|
176
176
|
readonly target: TTarget;
|
|
177
177
|
invalidate(input: {
|
|
@@ -181,8 +181,7 @@ export interface SourceInvalidationProjectionPort<
|
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
export type SourceInvalidationProjectionPorts = {
|
|
184
|
-
readonly [TTarget in SourceInvalidationProjectionTarget]:
|
|
185
|
-
SourceInvalidationProjectionPort<TTarget>;
|
|
184
|
+
readonly [TTarget in SourceInvalidationProjectionTarget]: SourceInvalidationProjectionPort<TTarget>;
|
|
186
185
|
};
|
|
187
186
|
|
|
188
187
|
export interface AuthoritativeSourceAccessQuery {
|