@stigmer/server 3.20.0 → 3.22.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.
- package/dist/authorization/evaluator.d.ts +7 -1
- package/dist/authorization/evaluator.d.ts.map +1 -1
- package/dist/authorization/evaluator.js.map +1 -1
- package/dist/authorization/list-read-scope.d.ts.map +1 -1
- package/dist/authorization/list-read-scope.js +19 -11
- package/dist/authorization/list-read-scope.js.map +1 -1
- package/dist/authorization/model/organization.d.ts.map +1 -1
- package/dist/authorization/model/organization.js +2 -0
- package/dist/authorization/model/organization.js.map +1 -1
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +0 -1
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/inprocess.d.ts +0 -2
- package/dist/boot/inprocess.d.ts.map +1 -1
- package/dist/boot/inprocess.js +0 -6
- package/dist/boot/inprocess.js.map +1 -1
- package/dist/domain/agent/controller.d.ts +2 -2
- package/dist/domain/agent/controller.d.ts.map +1 -1
- package/dist/domain/agent/controller.js +3 -17
- package/dist/domain/agent/controller.js.map +1 -1
- package/dist/domain/agent/steps.d.ts +0 -1
- package/dist/domain/agent/steps.d.ts.map +1 -1
- package/dist/domain/agent/steps.js +1 -40
- package/dist/domain/agent/steps.js.map +1 -1
- package/dist/domain/agentchannel/controller.d.ts.map +1 -1
- package/dist/domain/agentchannel/controller.js +6 -1
- package/dist/domain/agentchannel/controller.js.map +1 -1
- package/dist/domain/agentchannel/steps.d.ts +19 -0
- package/dist/domain/agentchannel/steps.d.ts.map +1 -1
- package/dist/domain/agentchannel/steps.js +32 -0
- package/dist/domain/agentchannel/steps.js.map +1 -1
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +7 -9
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.d.ts +0 -2
- package/dist/domain/agentexecution/create-execution-context-step.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-execution-context-step.js +179 -60
- package/dist/domain/agentexecution/create-execution-context-step.js.map +1 -1
- package/dist/domain/agentexecution/create-steps.d.ts +16 -31
- package/dist/domain/agentexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/create-steps.js +36 -102
- package/dist/domain/agentexecution/create-steps.js.map +1 -1
- package/dist/domain/agentexecution/run-target.d.ts +7 -3
- package/dist/domain/agentexecution/run-target.d.ts.map +1 -1
- package/dist/domain/agentexecution/run-target.js.map +1 -1
- package/dist/domain/agentinstance/controller.d.ts +5 -5
- package/dist/domain/agentinstance/controller.d.ts.map +1 -1
- package/dist/domain/agentinstance/controller.js +6 -1
- package/dist/domain/agentinstance/controller.js.map +1 -1
- package/dist/domain/agentinstance/steps.d.ts +43 -8
- package/dist/domain/agentinstance/steps.d.ts.map +1 -1
- package/dist/domain/agentinstance/steps.js +71 -8
- package/dist/domain/agentinstance/steps.js.map +1 -1
- package/dist/domain/agentshare/controller.d.ts.map +1 -1
- package/dist/domain/agentshare/controller.js +7 -1
- package/dist/domain/agentshare/controller.js.map +1 -1
- package/dist/domain/agentshare/steps.d.ts +58 -0
- package/dist/domain/agentshare/steps.d.ts.map +1 -1
- package/dist/domain/agentshare/steps.js +109 -4
- package/dist/domain/agentshare/steps.js.map +1 -1
- package/dist/domain/artifact/controller.js +21 -25
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/channelapp/controller.d.ts.map +1 -1
- package/dist/domain/channelapp/controller.js +2 -0
- package/dist/domain/channelapp/controller.js.map +1 -1
- package/dist/domain/environment/controller.d.ts.map +1 -1
- package/dist/domain/environment/controller.js +2 -0
- package/dist/domain/environment/controller.js.map +1 -1
- package/dist/domain/environment/personal.d.ts +42 -0
- package/dist/domain/environment/personal.d.ts.map +1 -0
- package/dist/domain/environment/personal.js +96 -0
- package/dist/domain/environment/personal.js.map +1 -0
- package/dist/domain/executioncontext/controller.d.ts.map +1 -1
- package/dist/domain/executioncontext/controller.js +2 -0
- package/dist/domain/executioncontext/controller.js.map +1 -1
- package/dist/domain/executioncontext/steps.d.ts.map +1 -1
- package/dist/domain/executioncontext/steps.js +2 -1
- package/dist/domain/executioncontext/steps.js.map +1 -1
- package/dist/domain/mcpserver/connect.d.ts +0 -2
- package/dist/domain/mcpserver/connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/connect.js +24 -79
- package/dist/domain/mcpserver/connect.js.map +1 -1
- package/dist/domain/mcpserver/controller.d.ts.map +1 -1
- package/dist/domain/mcpserver/controller.js +2 -0
- package/dist/domain/mcpserver/controller.js.map +1 -1
- package/dist/domain/mcpserver/start-connect.d.ts.map +1 -1
- package/dist/domain/mcpserver/start-connect.js +8 -7
- package/dist/domain/mcpserver/start-connect.js.map +1 -1
- package/dist/domain/memory/controller.d.ts.map +1 -1
- package/dist/domain/memory/controller.js +6 -1
- package/dist/domain/memory/controller.js.map +1 -1
- package/dist/domain/memory/steps.d.ts +23 -0
- package/dist/domain/memory/steps.d.ts.map +1 -1
- package/dist/domain/memory/steps.js +35 -0
- package/dist/domain/memory/steps.js.map +1 -1
- package/dist/domain/oauthapp/controller.d.ts.map +1 -1
- package/dist/domain/oauthapp/controller.js +2 -0
- package/dist/domain/oauthapp/controller.js.map +1 -1
- package/dist/domain/plugin/controller.d.ts.map +1 -1
- package/dist/domain/plugin/controller.js +5 -24
- package/dist/domain/plugin/controller.js.map +1 -1
- package/dist/domain/plugin/overlay/sanitize.js +2 -2
- package/dist/domain/schedule/controller.d.ts.map +1 -1
- package/dist/domain/schedule/controller.js +7 -1
- package/dist/domain/schedule/controller.js.map +1 -1
- package/dist/domain/schedule/steps.d.ts +19 -0
- package/dist/domain/schedule/steps.d.ts.map +1 -1
- package/dist/domain/schedule/steps.js +32 -0
- package/dist/domain/schedule/steps.js.map +1 -1
- package/dist/domain/session/controller.d.ts +0 -7
- package/dist/domain/session/controller.d.ts.map +1 -1
- package/dist/domain/session/controller.js +11 -16
- package/dist/domain/session/controller.js.map +1 -1
- package/dist/domain/session/run-target.d.ts +9 -8
- package/dist/domain/session/run-target.d.ts.map +1 -1
- package/dist/domain/session/run-target.js.map +1 -1
- package/dist/domain/session/steps.d.ts +8 -36
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +27 -172
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/skill/controller.d.ts.map +1 -1
- package/dist/domain/skill/controller.js +8 -17
- package/dist/domain/skill/controller.js.map +1 -1
- package/dist/domain/workflow/controller.d.ts.map +1 -1
- package/dist/domain/workflow/controller.js +30 -107
- package/dist/domain/workflow/controller.js.map +1 -1
- package/dist/domain/workflow/version-resolution.d.ts +5 -0
- package/dist/domain/workflow/version-resolution.d.ts.map +1 -0
- package/dist/domain/workflow/version-resolution.js +113 -0
- package/dist/domain/workflow/version-resolution.js.map +1 -0
- package/dist/domain/workflowexecution/create-steps.d.ts +4 -3
- package/dist/domain/workflowexecution/create-steps.d.ts.map +1 -1
- package/dist/domain/workflowexecution/create-steps.js +4 -3
- package/dist/domain/workflowexecution/create-steps.js.map +1 -1
- package/dist/domain/workflowinstance/controller.d.ts.map +1 -1
- package/dist/domain/workflowinstance/controller.js +6 -1
- package/dist/domain/workflowinstance/controller.js.map +1 -1
- package/dist/domain/workflowinstance/steps.d.ts +20 -0
- package/dist/domain/workflowinstance/steps.d.ts.map +1 -1
- package/dist/domain/workflowinstance/steps.js +39 -0
- package/dist/domain/workflowinstance/steps.js.map +1 -1
- package/dist/extensions/identity.d.ts +19 -0
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js +22 -3
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +41 -7
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js +13 -2
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +2 -2
- package/dist/pipeline/apiresource-labels.js +2 -2
- package/dist/pipeline/errors.d.ts +2 -2
- package/dist/pipeline/errors.js +2 -2
- package/dist/pipeline/steps/authorize-resolved-target.d.ts +88 -0
- package/dist/pipeline/steps/authorize-resolved-target.d.ts.map +1 -0
- package/dist/pipeline/steps/authorize-resolved-target.js +36 -0
- package/dist/pipeline/steps/authorize-resolved-target.js.map +1 -0
- package/dist/pipeline/steps/authorize.d.ts +27 -0
- package/dist/pipeline/steps/authorize.d.ts.map +1 -1
- package/dist/pipeline/steps/authorize.js +49 -10
- package/dist/pipeline/steps/authorize.js.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-memory-capture.js +2 -1
- package/dist/pipeline/steps/guard-memory-capture.js.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.d.ts.map +1 -1
- package/dist/pipeline/steps/guard-reserved-labels.js +12 -12
- package/dist/pipeline/steps/guard-reserved-labels.js.map +1 -1
- package/dist/pipeline/steps/helpers.d.ts +9 -5
- package/dist/pipeline/steps/helpers.d.ts.map +1 -1
- package/dist/pipeline/steps/helpers.js +11 -15
- package/dist/pipeline/steps/helpers.js.map +1 -1
- package/dist/pipeline/steps/version-history.d.ts +11 -0
- package/dist/pipeline/steps/version-history.d.ts.map +1 -1
- package/dist/pipeline/steps/version-history.js +26 -0
- package/dist/pipeline/steps/version-history.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/__tests__/list-read-scope.test.ts +23 -12
- package/src/authorization/__tests__/skip-lane-inventory.test.ts +160 -0
- package/src/authorization/__tests__/wire-permissions.test.ts +5 -5
- package/src/authorization/evaluator.ts +7 -0
- package/src/authorization/list-read-scope.ts +22 -11
- package/src/authorization/model/__tests__/registry.test.ts +2 -0
- package/src/authorization/model/organization.ts +2 -0
- package/src/boot/compose.ts +0 -1
- package/src/boot/inprocess.ts +0 -9
- package/src/domain/agent/__tests__/agent.test.ts +0 -68
- package/src/domain/agent/controller.ts +11 -36
- package/src/domain/agent/steps.ts +1 -60
- package/src/domain/agentchannel/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/agentchannel/controller.ts +19 -0
- package/src/domain/agentchannel/steps.ts +40 -0
- package/src/domain/agentexecution/__tests__/create-execution-context-step.test.ts +216 -0
- package/src/domain/agentexecution/__tests__/create-steps.test.ts +70 -145
- package/src/domain/agentexecution/controller.ts +6 -10
- package/src/domain/agentexecution/create-execution-context-step.ts +271 -83
- package/src/domain/agentexecution/create-steps.ts +38 -138
- package/src/domain/agentexecution/run-target.ts +7 -3
- package/src/domain/agentinstance/__tests__/create-authorization.composed.test.ts +243 -0
- package/src/domain/agentinstance/__tests__/create-authorization.test.ts +144 -0
- package/src/domain/agentinstance/controller.ts +24 -5
- package/src/domain/agentinstance/steps.ts +82 -8
- package/src/domain/agentshare/__tests__/create-authorization.test.ts +86 -0
- package/src/domain/agentshare/__tests__/member-audience.test.ts +113 -0
- package/src/domain/agentshare/controller.ts +21 -0
- package/src/domain/agentshare/steps.ts +181 -15
- package/src/domain/artifact/controller.ts +18 -22
- package/src/domain/channelapp/controller.ts +10 -0
- package/src/domain/environment/__tests__/personal.test.ts +143 -0
- package/src/domain/environment/controller.ts +10 -0
- package/src/domain/environment/personal.ts +141 -0
- package/src/domain/executioncontext/__tests__/executioncontext.test.ts +1 -0
- package/src/domain/executioncontext/controller.ts +10 -0
- package/src/domain/executioncontext/steps.ts +2 -1
- package/src/domain/mcpserver/connect.ts +32 -92
- package/src/domain/mcpserver/controller.ts +13 -1
- package/src/domain/mcpserver/start-connect.ts +17 -11
- package/src/domain/memory/__tests__/create-authorization.test.ts +59 -0
- package/src/domain/memory/controller.ts +11 -0
- package/src/domain/memory/steps.ts +41 -0
- package/src/domain/oauthapp/controller.ts +10 -0
- package/src/domain/plugin/controller.ts +22 -36
- package/src/domain/plugin/overlay/sanitize.ts +2 -2
- package/src/domain/schedule/__tests__/create-authorization.test.ts +58 -0
- package/src/domain/schedule/controller.ts +20 -0
- package/src/domain/schedule/steps.ts +45 -2
- package/src/domain/session/__tests__/run-gate.test.ts +19 -52
- package/src/domain/session/__tests__/session.test.ts +27 -203
- package/src/domain/session/controller.ts +10 -30
- package/src/domain/session/run-target.ts +9 -8
- package/src/domain/session/steps.ts +33 -267
- package/src/domain/skill/controller.ts +24 -23
- package/src/domain/workflow/controller.ts +58 -145
- package/src/domain/workflow/version-resolution.ts +166 -0
- package/src/domain/workflowexecution/create-steps.ts +4 -3
- package/src/domain/workflowinstance/__tests__/create-authorization.test.ts +109 -0
- package/src/domain/workflowinstance/controller.ts +19 -0
- package/src/domain/workflowinstance/steps.ts +49 -1
- package/src/extensions/__tests__/composed-support.ts +38 -4
- package/src/extensions/__tests__/identity.test.ts +44 -1
- package/src/extensions/__tests__/list-read-scope-composed.test.ts +87 -1
- package/src/extensions/__tests__/list-read-scope.test.ts +63 -0
- package/src/extensions/identity.ts +23 -5
- package/src/extensions/list-read-scope.ts +47 -7
- package/src/pipeline/apiresource-labels.ts +2 -2
- package/src/pipeline/errors.ts +2 -2
- package/src/pipeline/steps/__tests__/authorize-resolved-target.test.ts +285 -0
- package/src/pipeline/steps/authorize-resolved-target.ts +129 -0
- package/src/pipeline/steps/authorize.ts +70 -13
- package/src/pipeline/steps/guard-memory-capture.ts +2 -1
- package/src/pipeline/steps/guard-reserved-labels.ts +12 -14
- package/src/pipeline/steps/helpers.ts +11 -15
- package/src/pipeline/steps/version-history.ts +34 -0
- package/dist/domain/agent/defaultagent.d.ts +0 -37
- package/dist/domain/agent/defaultagent.d.ts.map +0 -1
- package/dist/domain/agent/defaultagent.js +0 -104
- package/dist/domain/agent/defaultagent.js.map +0 -1
- package/src/domain/agent/defaultagent.ts +0 -135
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the coverage inventory's own rule for `is_skip_authorization` lanes
|
|
3
|
+
* (docs/authorization-coverage.md, "How to read the tables"): a skip lane
|
|
4
|
+
* reaches no Authorizer in either edition, so what guards it is whatever
|
|
5
|
+
* its Handler cell names — and a cell that names nothing is a lane open to
|
|
6
|
+
* every authenticated caller that nobody has said so about. Two things are
|
|
7
|
+
* held here, so the doc cannot drift from the server it describes:
|
|
8
|
+
*
|
|
9
|
+
* 1. The set of skip-annotated methods the open-source server SERVES
|
|
10
|
+
* (the empty composition's routes replayed into a recorder, a method
|
|
11
|
+
* counted only when its service's implementation map carries it — a
|
|
12
|
+
* partially implemented service lists more on its descriptor than it
|
|
13
|
+
* serves) equals the set of skip rows in the doc, service by method.
|
|
14
|
+
* A new skip lane without a row, or a row for a lane that is no
|
|
15
|
+
* longer a skip, fails here.
|
|
16
|
+
* 2. Every skip row's Handler cell carries one of the three markers the
|
|
17
|
+
* doc defines — `guard: <Name>`, `driver: <Seam>`,
|
|
18
|
+
* `by design: <reason>` — and every `guard:` names a pipeline step or
|
|
19
|
+
* a handler's guard function that exists by that name in `src/`
|
|
20
|
+
* outside the tests, so a renamed or removed guard cannot leave the
|
|
21
|
+
* doc pointing at nothing.
|
|
22
|
+
*
|
|
23
|
+
* The rule lives in the doc; this test only enforces it. The doc is the
|
|
24
|
+
* one inventory (the package guide mandates updating it with every
|
|
25
|
+
* annotation change), and a second list here would be the drift this
|
|
26
|
+
* test exists to prevent.
|
|
27
|
+
*/
|
|
28
|
+
import {
|
|
29
|
+
mkdtempSync,
|
|
30
|
+
readFileSync,
|
|
31
|
+
readdirSync,
|
|
32
|
+
rmSync,
|
|
33
|
+
statSync,
|
|
34
|
+
} from "node:fs";
|
|
35
|
+
import { tmpdir } from "node:os";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
import { fileURLToPath } from "node:url";
|
|
38
|
+
|
|
39
|
+
import { getOption } from "@bufbuild/protobuf";
|
|
40
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
41
|
+
|
|
42
|
+
import { is_skip_authorization } from "@stigmer/protos/ai/stigmer/commons/rpc/method_options_pb";
|
|
43
|
+
|
|
44
|
+
import { loadConfig } from "../../boot/config.js";
|
|
45
|
+
import { composeServer } from "../../boot/compose.js";
|
|
46
|
+
import type { ComposedServer } from "../../boot/compose.js";
|
|
47
|
+
import {
|
|
48
|
+
baseConfig,
|
|
49
|
+
servedMethods,
|
|
50
|
+
silentLogger,
|
|
51
|
+
} from "../../extensions/__tests__/composed-support.js";
|
|
52
|
+
|
|
53
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
const PACKAGE_ROOT = path.resolve(HERE, "../../..");
|
|
55
|
+
const COVERAGE_DOC = path.join(PACKAGE_ROOT, "docs/authorization-coverage.md");
|
|
56
|
+
const SRC = path.join(PACKAGE_ROOT, "src");
|
|
57
|
+
|
|
58
|
+
/** A skip row: `| Service.method | is_skip_authorization | <handler cell> |`. */
|
|
59
|
+
const SKIP_ROW =
|
|
60
|
+
/^\| ([A-Za-z]+)\.([A-Za-z]+) \| is_skip_authorization \| (.*) \|$/;
|
|
61
|
+
|
|
62
|
+
const MARKERS = ["guard:", "driver:", "by design:"] as const;
|
|
63
|
+
|
|
64
|
+
interface SkipRow {
|
|
65
|
+
readonly method: string;
|
|
66
|
+
readonly handler: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function skipRowsOf(doc: string): SkipRow[] {
|
|
70
|
+
const rows: SkipRow[] = [];
|
|
71
|
+
for (const line of doc.split("\n")) {
|
|
72
|
+
const m = SKIP_ROW.exec(line);
|
|
73
|
+
if (m !== null) {
|
|
74
|
+
rows.push({ method: `${m[1]}.${m[2]}`, handler: m[3] ?? "" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return rows;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Every `guard: <Name>` a handler cell names. */
|
|
81
|
+
function guardsOf(handler: string): string[] {
|
|
82
|
+
return [...handler.matchAll(/guard: ([A-Za-z]+)/g)].map((m) => m[1] ?? "");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Every non-test `.ts` source under `src/`, read once. */
|
|
86
|
+
function sourceText(): string {
|
|
87
|
+
const parts: string[] = [];
|
|
88
|
+
const walk = (dir: string): void => {
|
|
89
|
+
for (const entry of readdirSync(dir)) {
|
|
90
|
+
const full = path.join(dir, entry);
|
|
91
|
+
if (statSync(full).isDirectory()) {
|
|
92
|
+
if (entry !== "__tests__") {
|
|
93
|
+
walk(full);
|
|
94
|
+
}
|
|
95
|
+
} else if (full.endsWith(".ts") && !full.endsWith(".test.ts")) {
|
|
96
|
+
parts.push(readFileSync(full, "utf8"));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
walk(SRC);
|
|
101
|
+
return parts.join("\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
describe("the skip-lane inventory (docs/authorization-coverage.md) is true to the served server", () => {
|
|
105
|
+
let dir: string;
|
|
106
|
+
let server: ComposedServer;
|
|
107
|
+
|
|
108
|
+
beforeAll(async () => {
|
|
109
|
+
dir = mkdtempSync(path.join(tmpdir(), "skip-lane-inventory-test-"));
|
|
110
|
+
server = await composeServer({
|
|
111
|
+
config: loadConfig(baseConfig(dir)),
|
|
112
|
+
logger: silentLogger,
|
|
113
|
+
portOverride: 0,
|
|
114
|
+
host: "127.0.0.1",
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterAll(async () => {
|
|
119
|
+
await server.shutdown();
|
|
120
|
+
rmSync(dir, { recursive: true, force: true });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("the doc's skip rows are exactly the skip-annotated methods the server serves", () => {
|
|
124
|
+
const served = new Set<string>();
|
|
125
|
+
for (const method of servedMethods(server.routes)) {
|
|
126
|
+
if (getOption(method, is_skip_authorization)) {
|
|
127
|
+
const serviceName = method.parent.typeName.split(".").at(-1) ?? "";
|
|
128
|
+
served.add(`${serviceName}.${method.name}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const documented = new Set(
|
|
132
|
+
skipRowsOf(readFileSync(COVERAGE_DOC, "utf8")).map((r) => r.method),
|
|
133
|
+
);
|
|
134
|
+
expect([...served].sort()).toEqual([...documented].sort());
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("every skip row names its guard, its driver or says the lane is open by design", () => {
|
|
138
|
+
const bare = skipRowsOf(readFileSync(COVERAGE_DOC, "utf8"))
|
|
139
|
+
.filter((r) => !MARKERS.some((marker) => r.handler.includes(marker)))
|
|
140
|
+
.map((r) => r.method);
|
|
141
|
+
expect(bare).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("every guard a skip row names is a step or a guard function that exists by that name in src/", () => {
|
|
145
|
+
const source = sourceText();
|
|
146
|
+
const dangling: string[] = [];
|
|
147
|
+
for (const row of skipRowsOf(readFileSync(COVERAGE_DOC, "utf8"))) {
|
|
148
|
+
for (const guard of guardsOf(row.handler)) {
|
|
149
|
+
// A step is registered by its quoted name; a guard function by its
|
|
150
|
+
// declaration.
|
|
151
|
+
const isStep = source.includes(`"${guard}"`);
|
|
152
|
+
const isFunction = new RegExp(`function ${guard}\\(`).test(source);
|
|
153
|
+
if (!isStep && !isFunction) {
|
|
154
|
+
dangling.push(`${row.method} names guard ${guard}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
expect(dangling).toEqual([]);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -117,12 +117,11 @@ describe("every static (kind, permission) the served RPCs ask about is a declare
|
|
|
117
117
|
expect(undeclared).toEqual([]);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
it("asks about every served kind the model declares except the
|
|
120
|
+
it("asks about every served kind the model declares except the one no RPC targets statically", () => {
|
|
121
121
|
// iam_policy's RPCs target the policy's RESOURCE (`resource_kind_path`
|
|
122
|
-
// or the load-then-authorize `get`)
|
|
123
|
-
//
|
|
124
|
-
//
|
|
125
|
-
// asked about by at least one static annotation.
|
|
122
|
+
// or the load-then-authorize `get`). Every other declared kind is asked
|
|
123
|
+
// about by at least one static annotation — execution_context through
|
|
124
|
+
// its `get` and `delete`; its runner-token lane stays a skip.
|
|
126
125
|
const asked = new Set(wireQuestions().map((q) => kindEnumName(q.kind)));
|
|
127
126
|
expect([...asked].sort()).toEqual(
|
|
128
127
|
[
|
|
@@ -135,6 +134,7 @@ describe("every static (kind, permission) the served RPCs ask about is a declare
|
|
|
135
134
|
"artifact",
|
|
136
135
|
"channel_app",
|
|
137
136
|
"environment",
|
|
137
|
+
"execution_context",
|
|
138
138
|
"identity_account",
|
|
139
139
|
"mcp_server",
|
|
140
140
|
"memory",
|
|
@@ -67,8 +67,15 @@ import {
|
|
|
67
67
|
*/
|
|
68
68
|
export const MAX_RESOLUTION_DEPTH = 25;
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The faults an evaluation can raise. Two are consumer bugs by the seam
|
|
72
|
+
* contracts (a kind the model does not declare; the `internal` class
|
|
73
|
+
* offered to the list scope's restrict verb, which the shared helper
|
|
74
|
+
* answers before any driver); the rest are the model's own.
|
|
75
|
+
*/
|
|
70
76
|
export type EvaluationFault =
|
|
71
77
|
| "undeclared-target-kind"
|
|
78
|
+
| "internal-caller-offered"
|
|
72
79
|
| "resolution-depth-exceeded"
|
|
73
80
|
| "resolution-cycle"
|
|
74
81
|
| "unknown-condition";
|
|
@@ -38,17 +38,25 @@
|
|
|
38
38
|
* lane can show it either.
|
|
39
39
|
*
|
|
40
40
|
* The `internal` class — the server acting as itself over the in-process
|
|
41
|
-
* transport —
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
41
|
+
* transport — is answered by the seam, not here: `restrictListByReadScope`
|
|
42
|
+
* returns the org-narrowed rows for the class before any driver is asked
|
|
43
|
+
* (extensions/list-read-scope.ts, the header's contract line), the
|
|
44
|
+
* Authorize step's trust-domain rule applied to a list answer. So this
|
|
45
|
+
* driver's `restrictListEntries` REFUSES the class (`internal-caller-offered`):
|
|
46
|
+
* being offered it means a caller reached the driver around the one
|
|
47
|
+
* consumption idiom, and evaluating the server as a person would return
|
|
48
|
+
* a quiet short list — the failure shape stigmer#1207 fixed. The
|
|
49
|
+
* enumeration verb has no helper in front of it and keeps its own arm:
|
|
50
|
+
* every id of the kind, as the built-in directory answers the class with
|
|
51
|
+
* every organization. No in-process edge reaches an enumeration lane
|
|
52
|
+
* today; the arm is pinned so a future one meets the rule.
|
|
46
53
|
*
|
|
47
54
|
* Faults THROW and are never an empty answer (the seam: an empty set means
|
|
48
55
|
* "authorized to see nothing"; an outage is the pipeline's sanitized
|
|
49
|
-
* INTERNAL). A kind the model does not declare
|
|
50
|
-
* seam's contract and faults through the
|
|
51
|
-
* declares exactly the open-source tier, so no
|
|
56
|
+
* INTERNAL). A kind the model does not declare, like the internal class
|
|
57
|
+
* above, is a consumer bug by the seam's contract and faults through the
|
|
58
|
+
* evaluator, loud; the model declares exactly the open-source tier, so no
|
|
59
|
+
* served lane can reach it.
|
|
52
60
|
*
|
|
53
61
|
* Logging. One debug line per call — kind, offered, kept, elapsed — what
|
|
54
62
|
* an operator needs to answer "why is Alice's list short" beside the
|
|
@@ -188,6 +196,12 @@ export function newBuiltInListReadScope(
|
|
|
188
196
|
entries: ReadonlyArray<ListEntryMeta>,
|
|
189
197
|
): Promise<ReadonlySet<string>> {
|
|
190
198
|
requireDeclared(kind);
|
|
199
|
+
if (caller.callerClass === "internal") {
|
|
200
|
+
throw new AuthorizationEvaluationError(
|
|
201
|
+
"internal-caller-offered",
|
|
202
|
+
`the internal caller class was offered to the list scope for kind '${kindEnumName(kind)}' — the shared helper answers that class before any driver; a list lane reached the driver around it`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
191
205
|
// One candidate per unique id: a lane offers each row once, and a
|
|
192
206
|
// duplicate would only spend a walk to learn the same answer.
|
|
193
207
|
const unique = new Map<string, RowFacts>();
|
|
@@ -196,9 +210,6 @@ export function newBuiltInListReadScope(
|
|
|
196
210
|
unique.set(entry.id, rowFactsOfEntry(kind, entry));
|
|
197
211
|
}
|
|
198
212
|
}
|
|
199
|
-
if (caller.callerClass === "internal") {
|
|
200
|
-
return new Set(unique.keys());
|
|
201
|
-
}
|
|
202
213
|
return visibleAmong(caller, kind, [...unique.values()]);
|
|
203
214
|
},
|
|
204
215
|
|
|
@@ -104,6 +104,7 @@ const WIRE_PERMISSIONS_BY_TYPE: Readonly<
|
|
|
104
104
|
"can_create_environment",
|
|
105
105
|
"can_create_skill",
|
|
106
106
|
"can_create_plugin",
|
|
107
|
+
"can_create_mcp_server",
|
|
107
108
|
"can_create_idp",
|
|
108
109
|
"can_create_identity_account",
|
|
109
110
|
"can_create_oauth_app",
|
|
@@ -111,6 +112,7 @@ const WIRE_PERMISSIONS_BY_TYPE: Readonly<
|
|
|
111
112
|
"can_create_channel_app",
|
|
112
113
|
"can_create_execution_in",
|
|
113
114
|
"can_create_agent_share",
|
|
115
|
+
"can_create_agent_instance",
|
|
114
116
|
"can_grant_access",
|
|
115
117
|
"can_view_access",
|
|
116
118
|
"can_view_billing",
|
|
@@ -39,6 +39,7 @@ export const organizationDeclaration = declareKind({
|
|
|
39
39
|
["can_create_environment", computed("member")],
|
|
40
40
|
["can_create_skill", computed("admin")],
|
|
41
41
|
["can_create_plugin", computed("admin")],
|
|
42
|
+
["can_create_mcp_server", computed("admin")],
|
|
42
43
|
["can_create_idp", computed("admin")],
|
|
43
44
|
["can_create_identity_account", computed("admin")],
|
|
44
45
|
["can_create_oauth_app", computed("admin")],
|
|
@@ -46,6 +47,7 @@ export const organizationDeclaration = declareKind({
|
|
|
46
47
|
["can_create_channel_app", computed("admin")],
|
|
47
48
|
["can_create_execution_in", union(computed("member"), computed("guest"))],
|
|
48
49
|
["can_create_agent_share", computed("admin")],
|
|
50
|
+
["can_create_agent_instance", computed("member")],
|
|
49
51
|
["can_grant_access", computed("admin")],
|
|
50
52
|
["can_view_access", computed("viewer")],
|
|
51
53
|
["can_view_billing", computed("viewer")],
|
package/src/boot/compose.ts
CHANGED
|
@@ -1269,7 +1269,6 @@ export async function composeServer(
|
|
|
1269
1269
|
logger,
|
|
1270
1270
|
authorizer,
|
|
1271
1271
|
temporalConfig,
|
|
1272
|
-
agentInstanceCreator: () => requireInProcess().agentInstanceCreator,
|
|
1273
1272
|
gateSteps: extensions.gateSteps,
|
|
1274
1273
|
sandboxLane,
|
|
1275
1274
|
authorizationLifecycle,
|
package/src/boot/inprocess.ts
CHANGED
|
@@ -60,7 +60,6 @@ import type {
|
|
|
60
60
|
} from "../domain/agentexecution/create-execution-context-step.js";
|
|
61
61
|
import type { ConnectExecutionContextClient } from "../domain/mcpserver/connect.js";
|
|
62
62
|
import type { ManagedEnvironmentClient } from "../domain/mcpserver/oauth/managed-env.js";
|
|
63
|
-
import type { AgentInstanceCreator } from "../domain/session/steps.js";
|
|
64
63
|
import type { WorkflowInstanceCreator } from "../domain/workflow/steps.js";
|
|
65
64
|
import type { ExecutionWorkflowInstanceCreator } from "../domain/workflowexecution/create-steps.js";
|
|
66
65
|
import type {
|
|
@@ -89,7 +88,6 @@ import type { CallOptions } from "@connectrpc/connect";
|
|
|
89
88
|
/** The narrow in-process surfaces the domains consume (DD-002). */
|
|
90
89
|
export interface InProcessClients {
|
|
91
90
|
readonly agentInstanceApplier: AgentInstanceApplier;
|
|
92
|
-
readonly agentInstanceCreator: AgentInstanceCreator;
|
|
93
91
|
readonly parentAgentLoader: ParentAgentLoader;
|
|
94
92
|
readonly workflowInstanceCreator: WorkflowInstanceCreator;
|
|
95
93
|
readonly parentWorkflowLoader: ParentWorkflowLoader;
|
|
@@ -254,13 +252,6 @@ export function createInProcessClients(
|
|
|
254
252
|
applyAsCaller: (instance, caller) =>
|
|
255
253
|
agentInstanceCommand.apply(instance, asCaller(caller)),
|
|
256
254
|
},
|
|
257
|
-
// CREATE (not apply) so a duplicate default-instance slug surfaces
|
|
258
|
-
// as AlreadyExists instead of silently updating — as the original
|
|
259
|
-
// caller (ruling R5).
|
|
260
|
-
agentInstanceCreator: {
|
|
261
|
-
createAsCaller: (instance, caller) =>
|
|
262
|
-
agentInstanceCommand.create(instance, asCaller(caller)),
|
|
263
|
-
},
|
|
264
255
|
parentAgentLoader: {
|
|
265
256
|
get: (agentId) =>
|
|
266
257
|
agentQuery.get(create(AgentIdSchema, { value: agentId })),
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
* the router transport, traversing the whole chain).
|
|
7
7
|
*
|
|
8
8
|
* The load-bearing pins:
|
|
9
|
-
* - default-agent resolution (stigmer/stigmer#356): NotFound copy when
|
|
10
|
-
* nothing is labeled; FailedPrecondition copy when labeled agents
|
|
11
|
-
* exist but none is public; incumbent-wins (LOWEST metadata.id) among
|
|
12
|
-
* public labeled candidates — an older non-public labeled agent never
|
|
13
|
-
* wins;
|
|
14
9
|
* - MCP env-merge semantics: agent-declared entries win, among servers
|
|
15
10
|
* first-encountered wins, only declaration fields are copied;
|
|
16
11
|
* - enabled-tools validation (#402): byte-pinned INVALID_ARGUMENT copy
|
|
@@ -46,10 +41,6 @@ import { composeServer } from "../../../boot/compose.js";
|
|
|
46
41
|
import type { ComposedServer } from "../../../boot/compose.js";
|
|
47
42
|
import { createLogger } from "../../../boot/logger.js";
|
|
48
43
|
import { ResourceNotFoundError } from "../../../store/interface.js";
|
|
49
|
-
import {
|
|
50
|
-
DEFAULT_AGENT_LABEL,
|
|
51
|
-
DEFAULT_AGENT_LABEL_VALUE,
|
|
52
|
-
} from "../defaultagent.js";
|
|
53
44
|
|
|
54
45
|
const silentLogger = createLogger({
|
|
55
46
|
level: "error",
|
|
@@ -197,65 +188,6 @@ async function grpcError(run: () => Promise<unknown>): Promise<ConnectError> {
|
|
|
197
188
|
}
|
|
198
189
|
}
|
|
199
190
|
|
|
200
|
-
// The getDefault tests are order-dependent within this describe: the
|
|
201
|
-
// platform default is a GLOBAL singleton, so the empty state must be
|
|
202
|
-
// asserted before any labeled agent exists.
|
|
203
|
-
describe("agent getDefault (platform default resolution, #356)", () => {
|
|
204
|
-
it("answers NotFound with the configured-hint copy when nothing carries the label", async () => {
|
|
205
|
-
// org is required for authorization scoping only; resolution is global.
|
|
206
|
-
const error = await grpcError(() => query.getDefault({ org: ORG }));
|
|
207
|
-
expect(error.code).toBe(Code.NotFound);
|
|
208
|
-
expect(error.rawMessage).toBe(
|
|
209
|
-
"No default agent available. Ensure an agent with label " +
|
|
210
|
-
"stigmer.ai/default-agent=true and visibility_public exists: " +
|
|
211
|
-
"no agent labeled stigmer.ai/default-agent=true",
|
|
212
|
-
);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it("answers FailedPrecondition when labeled agents exist but none is public", async () => {
|
|
216
|
-
await command.create(
|
|
217
|
-
agentInput({
|
|
218
|
-
name: "Labeled But Org Visible",
|
|
219
|
-
labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
|
|
220
|
-
}),
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
const error = await grpcError(() => query.getDefault({ org: ORG }));
|
|
224
|
-
expect(error.code).toBe(Code.FailedPrecondition);
|
|
225
|
-
expect(error.rawMessage).toBe(
|
|
226
|
-
"Default agent exists but is not visibility_public: agents labeled " +
|
|
227
|
-
"stigmer.ai/default-agent=true exist but none is visibility_public",
|
|
228
|
-
);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it("serves the incumbent — the LOWEST metadata.id among PUBLIC labeled candidates", async () => {
|
|
232
|
-
const first = await command.create(
|
|
233
|
-
agentInput({
|
|
234
|
-
name: "Public Default One",
|
|
235
|
-
visibility: ApiResourceVisibility.visibility_public,
|
|
236
|
-
labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
|
|
237
|
-
}),
|
|
238
|
-
);
|
|
239
|
-
const second = await command.create(
|
|
240
|
-
agentInput({
|
|
241
|
-
name: "Public Default Two",
|
|
242
|
-
visibility: ApiResourceVisibility.visibility_public,
|
|
243
|
-
labels: { [DEFAULT_AGENT_LABEL]: DEFAULT_AGENT_LABEL_VALUE },
|
|
244
|
-
}),
|
|
245
|
-
);
|
|
246
|
-
|
|
247
|
-
const firstId = first.metadata!.id;
|
|
248
|
-
const secondId = second.metadata!.id;
|
|
249
|
-
const incumbentId = firstId < secondId ? firstId : secondId;
|
|
250
|
-
|
|
251
|
-
// The older NON-public labeled agent (previous test) has a lower id
|
|
252
|
-
// than both public candidates; incumbent-wins must skip it.
|
|
253
|
-
// A DIFFERENT org resolves the same singleton — resolution is global.
|
|
254
|
-
const resolved = await query.getDefault({ org: "globex" });
|
|
255
|
-
expect(resolved.metadata?.id).toBe(incumbentId);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
|
|
259
191
|
describe("agent MCP env merge (merge_mcp_env_specs)", () => {
|
|
260
192
|
it("merges declarations: agent-declared wins, first-encountered server wins, declaration fields copied", async () => {
|
|
261
193
|
await seedMcpServer({
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* default instance through the in-process agentinstance client (the
|
|
5
5
|
* agent↔agentinstance mutual edge, wired as a lazy provider in the
|
|
6
6
|
* composition root — sub-project DD-002); delete cascades ALL instances
|
|
7
|
-
* (oss#611) and same-org shares before the agent row
|
|
8
|
-
*
|
|
7
|
+
* (oss#611) and same-org shares before the agent row. There is no default
|
|
8
|
+
* agent to serve: a session with no agent runs the built-in assistant.
|
|
9
9
|
*
|
|
10
10
|
* Pipeline per RPC mirrors the Go step chains character-for-character.
|
|
11
11
|
* Proven by agent.conformance.test.ts (CONFORMANCE_TARGET=local) and
|
|
@@ -20,10 +20,7 @@ import { AgentCommandController } from "@stigmer/protos/ai/stigmer/agentic/agent
|
|
|
20
20
|
import { AgentQueryController } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/query_pb";
|
|
21
21
|
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
22
22
|
import type { Agent } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
23
|
-
import type {
|
|
24
|
-
AgentId,
|
|
25
|
-
GetDefaultAgentRequest,
|
|
26
|
-
} from "@stigmer/protos/ai/stigmer/agentic/agent/v1/io_pb";
|
|
23
|
+
import type { AgentId } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/io_pb";
|
|
27
24
|
import type {
|
|
28
25
|
ApiResourceReference,
|
|
29
26
|
UpdateVisibilityInput,
|
|
@@ -40,6 +37,10 @@ import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
|
40
37
|
import { callerIdentityOf } from "../../pipeline/interceptors/auth.js";
|
|
41
38
|
import { RequestContext } from "../../pipeline/request-context.js";
|
|
42
39
|
import { newAuthorizeStep } from "../../pipeline/steps/authorize.js";
|
|
40
|
+
import {
|
|
41
|
+
loadedTargetAsMethod,
|
|
42
|
+
newAuthorizeResolvedTargetStep,
|
|
43
|
+
} from "../../pipeline/steps/authorize-resolved-target.js";
|
|
43
44
|
import { newGuardReservedLabelsStep } from "../../pipeline/steps/guard-reserved-labels.js";
|
|
44
45
|
import {
|
|
45
46
|
newAuthorizeVisibilityTransitionStep,
|
|
@@ -98,7 +99,6 @@ import {
|
|
|
98
99
|
newCascadeDeleteInstancesStep,
|
|
99
100
|
newCascadeDeleteSharesStep,
|
|
100
101
|
newCreateDefaultInstanceStep,
|
|
101
|
-
newLoadDefaultAgentStep,
|
|
102
102
|
newMergeMcpServerEnvSpecsStep,
|
|
103
103
|
newUpdateAgentStatusWithDefaultInstanceStep,
|
|
104
104
|
newValidateEnabledToolsStep,
|
|
@@ -136,7 +136,6 @@ export function registerAgentServices(
|
|
|
136
136
|
router.service(AgentQueryController, {
|
|
137
137
|
get: (id, ctx) => get(deps, id, ctx),
|
|
138
138
|
getByReference: (ref, ctx) => getByReference(deps, ref, ctx),
|
|
139
|
-
getDefault: (req, ctx) => getDefault(deps, req, ctx),
|
|
140
139
|
});
|
|
141
140
|
}
|
|
142
141
|
|
|
@@ -532,36 +531,12 @@ async function getByReference(
|
|
|
532
531
|
)
|
|
533
532
|
.addStep(newValidateProtoStep())
|
|
534
533
|
.addStep(newLoadByReferenceStep(deps.store, AgentSchema))
|
|
535
|
-
.build()
|
|
536
|
-
.execute(reqCtx);
|
|
537
|
-
return reqCtx.get(TARGET_RESOURCE_KEY) as Agent;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
/**
|
|
541
|
-
* GetDefault — the platform default agent (label + visibility_public,
|
|
542
|
-
* incumbent-wins). Used by frontends for the session-first UX where users
|
|
543
|
-
* start a conversation without explicitly selecting an agent.
|
|
544
|
-
*/
|
|
545
|
-
async function getDefault(
|
|
546
|
-
deps: AgentControllerDeps,
|
|
547
|
-
req: GetDefaultAgentRequest,
|
|
548
|
-
ctx: HandlerContext,
|
|
549
|
-
): Promise<Agent> {
|
|
550
|
-
const reqCtx = new RequestContext(
|
|
551
|
-
AgentQueryController.method.getDefault.input,
|
|
552
|
-
req,
|
|
553
|
-
callerIdentityOf(ctx),
|
|
554
|
-
kindOf(ctx),
|
|
555
|
-
);
|
|
556
|
-
await newPipeline<typeof AgentQueryController.method.getDefault.input>(
|
|
557
|
-
"agent-get-default",
|
|
558
|
-
deps.logger,
|
|
559
|
-
)
|
|
560
534
|
.addStep(
|
|
561
|
-
|
|
535
|
+
newAuthorizeResolvedTargetStep(
|
|
536
|
+
deps.authorizer,
|
|
537
|
+
loadedTargetAsMethod(AgentQueryController.method.get),
|
|
538
|
+
),
|
|
562
539
|
)
|
|
563
|
-
.addStep(newValidateProtoStep())
|
|
564
|
-
.addStep(newLoadDefaultAgentStep(deps.store, deps.logger))
|
|
565
540
|
.build()
|
|
566
541
|
.execute(reqCtx);
|
|
567
542
|
return reqCtx.get(TARGET_RESOURCE_KEY) as Agent;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* embody agent-specific contracts: the default-instance choreography, the
|
|
7
7
|
* cascade rules, MCP env merging, and enabled-tools validation.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { ConnectError } from "@connectrpc/connect";
|
|
10
10
|
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
11
11
|
import type { DescMessage } from "@bufbuild/protobuf";
|
|
12
12
|
|
|
@@ -43,15 +43,9 @@ import { notifyDefaultInstanceLinked } from "../../pipeline/steps/authorization-
|
|
|
43
43
|
import type { PipelineStep } from "../../pipeline/pipeline.js";
|
|
44
44
|
import type { RequestContext } from "../../pipeline/request-context.js";
|
|
45
45
|
import { EXISTING_RESOURCE_KEY } from "../../pipeline/steps/load-existing.js";
|
|
46
|
-
import { TARGET_RESOURCE_KEY } from "../../pipeline/steps/load-target.js";
|
|
47
46
|
import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
|
|
48
47
|
import type { Store } from "../../store/interface.js";
|
|
49
48
|
import { buildDefaultInstanceRequest } from "../agentinstance/defaultinstance.js";
|
|
50
|
-
import {
|
|
51
|
-
DefaultAgentNotConfiguredError,
|
|
52
|
-
DefaultAgentNotPublicError,
|
|
53
|
-
findDefaultAgent,
|
|
54
|
-
} from "./defaultagent.js";
|
|
55
49
|
|
|
56
50
|
/** Context key carrying the applied default instance's id (Go DefaultInstanceIDKey). */
|
|
57
51
|
const DEFAULT_INSTANCE_ID_KEY = "default_instance_id";
|
|
@@ -636,56 +630,3 @@ export function newCascadeDeleteSharesStep<Desc extends DescMessage>(
|
|
|
636
630
|
},
|
|
637
631
|
};
|
|
638
632
|
}
|
|
639
|
-
|
|
640
|
-
// ---------------------------------------------------------------------------
|
|
641
|
-
// LoadDefaultAgent — get_default.go's step: resolve via defaultagent and
|
|
642
|
-
// map its sentinels to the wire contract. Go builds the wire message with
|
|
643
|
-
// WrapError ("%s: %v"), so the sentinel's text RIDES the message — both
|
|
644
|
-
// strings are cross-edition conformance surface.
|
|
645
|
-
// ---------------------------------------------------------------------------
|
|
646
|
-
|
|
647
|
-
export function newLoadDefaultAgentStep<Desc extends DescMessage>(
|
|
648
|
-
store: Store,
|
|
649
|
-
logger: Logger,
|
|
650
|
-
): PipelineStep<Desc> {
|
|
651
|
-
return {
|
|
652
|
-
name: "LoadDefaultAgent",
|
|
653
|
-
async execute(ctx: RequestContext<Desc>): Promise<void> {
|
|
654
|
-
logger.info("Resolving platform default agent");
|
|
655
|
-
|
|
656
|
-
let agent: Agent;
|
|
657
|
-
try {
|
|
658
|
-
agent = await findDefaultAgent(store, logger);
|
|
659
|
-
} catch (error) {
|
|
660
|
-
if (error instanceof DefaultAgentNotConfiguredError) {
|
|
661
|
-
throw new ConnectError(
|
|
662
|
-
`No default agent available. Ensure an agent with label stigmer.ai/default-agent=true and visibility_public exists: ${error.message}`,
|
|
663
|
-
Code.NotFound,
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
if (error instanceof DefaultAgentNotPublicError) {
|
|
667
|
-
throw new ConnectError(
|
|
668
|
-
`Default agent exists but is not visibility_public: ${error.message}`,
|
|
669
|
-
Code.FailedPrecondition,
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
// Store/decode failure — an internal fault, not "no default agent".
|
|
673
|
-
// InternalError keeps the cause off the wire (stigmer/stigmer#478).
|
|
674
|
-
logger.error("Failed to resolve platform default agent", {
|
|
675
|
-
error: error instanceof Error ? error.message : String(error),
|
|
676
|
-
});
|
|
677
|
-
throw internalError(
|
|
678
|
-
error,
|
|
679
|
-
"failed to resolve the platform default agent",
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
logger.info("Resolved platform default agent", {
|
|
684
|
-
agentId: agent.metadata?.id ?? "",
|
|
685
|
-
agentName: agent.metadata?.name ?? "",
|
|
686
|
-
});
|
|
687
|
-
|
|
688
|
-
ctx.set(TARGET_RESOURCE_KEY, agent);
|
|
689
|
-
},
|
|
690
|
-
};
|
|
691
|
-
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pins the create lane's authorization question (steps.ts,
|
|
3
|
+
* resolveChannelCreateTargets): can_edit on the REFERENCED AGENT with the
|
|
4
|
+
* Java handler's copy, and nothing on the organization (the same-org
|
|
5
|
+
* invariant already binds the channel to the agent's); a missing
|
|
6
|
+
* referenced agent throws.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
import { create } from "@bufbuild/protobuf";
|
|
10
|
+
|
|
11
|
+
import { AgentSchema } from "@stigmer/protos/ai/stigmer/agentic/agent/v1/api_pb";
|
|
12
|
+
import { AgentChannelSchema } from "@stigmer/protos/ai/stigmer/agentic/agentchannel/v1/api_pb";
|
|
13
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
14
|
+
import { IamPermission } from "@stigmer/protos/ai/stigmer/iam/v1/enum_pb";
|
|
15
|
+
|
|
16
|
+
import { testCallerIdentity } from "../../../pipeline/__tests__/support.js";
|
|
17
|
+
import { RequestContext } from "../../../pipeline/request-context.js";
|
|
18
|
+
import {
|
|
19
|
+
CHANNEL_CREATE_DENIED_MESSAGE,
|
|
20
|
+
REFERENCED_AGENT_KEY,
|
|
21
|
+
resolveChannelCreateTargets,
|
|
22
|
+
} from "../steps.js";
|
|
23
|
+
|
|
24
|
+
const agent = create(AgentSchema, {
|
|
25
|
+
metadata: { id: "agt_01target", name: "helper", org: "acme" },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
function ctxFor(stash = true) {
|
|
29
|
+
const ctx = new RequestContext(
|
|
30
|
+
AgentChannelSchema,
|
|
31
|
+
create(AgentChannelSchema, { metadata: { name: "support", org: "acme" } }),
|
|
32
|
+
testCallerIdentity(),
|
|
33
|
+
ApiResourceKind.agent_channel,
|
|
34
|
+
);
|
|
35
|
+
if (stash) {
|
|
36
|
+
ctx.set(REFERENCED_AGENT_KEY, agent);
|
|
37
|
+
}
|
|
38
|
+
return ctx;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("resolveChannelCreateTargets", () => {
|
|
42
|
+
it("asks can_edit on the referenced agent and nothing else", () => {
|
|
43
|
+
expect(resolveChannelCreateTargets(ctxFor())).toEqual([
|
|
44
|
+
{
|
|
45
|
+
permission: IamPermission.can_edit,
|
|
46
|
+
resourceKind: ApiResourceKind.agent,
|
|
47
|
+
resourceId: "agt_01target",
|
|
48
|
+
deniedMessage: CHANNEL_CREATE_DENIED_MESSAGE,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("a missing referenced agent is a broken chain invariant and throws", () => {
|
|
54
|
+
expect(() => resolveChannelCreateTargets(ctxFor(false))).toThrow(
|
|
55
|
+
"referenced agent not found in context",
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
});
|