@stigmer/server 3.18.0-dev.20260918103812 → 3.18.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/boot/compose.d.ts +12 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +4 -2
- package/dist/boot/compose.js.map +1 -1
- package/dist/domain/agentexecution/lifecycle.js +1 -1
- package/dist/domain/agentexecution/lifecycle.js.map +1 -1
- package/dist/domain/iampolicy/constants.d.ts +3 -3
- package/dist/domain/iampolicy/constants.js +3 -3
- package/dist/domain/oauthapp/refresolution/refresolution.js +1 -1
- package/dist/domain/oauthapp/refresolution.js +1 -1
- package/dist/domain/platform/controller.d.ts +8 -1
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -3
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/plugin/constants.d.ts +7 -1
- package/dist/domain/plugin/constants.d.ts.map +1 -1
- package/dist/domain/plugin/constants.js +7 -1
- package/dist/domain/plugin/constants.js.map +1 -1
- package/dist/domain/plugin/materialize/plan.d.ts.map +1 -1
- package/dist/domain/plugin/materialize/plan.js +16 -1
- package/dist/domain/plugin/materialize/plan.js.map +1 -1
- package/dist/domain/plugin/members.d.ts +46 -0
- package/dist/domain/plugin/members.d.ts.map +1 -1
- package/dist/domain/plugin/members.js +31 -8
- package/dist/domain/plugin/members.js.map +1 -1
- package/dist/domain/plugin/push.d.ts +5 -3
- package/dist/domain/plugin/push.d.ts.map +1 -1
- package/dist/domain/plugin/push.js +58 -14
- package/dist/domain/plugin/push.js.map +1 -1
- package/dist/domain/workflowexecution/lifecycle.js +1 -1
- package/dist/domain/workflowexecution/lifecycle.js.map +1 -1
- package/dist/pipeline/apiresource-labels.d.ts +20 -1
- package/dist/pipeline/apiresource-labels.d.ts.map +1 -1
- package/dist/pipeline/apiresource-labels.js +17 -0
- package/dist/pipeline/apiresource-labels.js.map +1 -1
- package/dist/sandbox/kubernetes.d.ts.map +1 -1
- package/dist/sandbox/kubernetes.js +1 -8
- package/dist/sandbox/kubernetes.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +5 -20
- package/dist/sandbox/provisioner.d.ts.map +1 -1
- package/dist/sandbox/provisioner.js.map +1 -1
- package/dist/sandbox/steps.d.ts +8 -22
- package/dist/sandbox/steps.d.ts.map +1 -1
- package/dist/sandbox/steps.js +12 -17
- package/dist/sandbox/steps.js.map +1 -1
- package/dist/store/interface.d.ts +6 -2
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/package.json +5 -5
- package/src/boot/compose.ts +16 -2
- package/src/domain/agentexecution/lifecycle.ts +1 -1
- package/src/domain/iampolicy/constants.ts +3 -3
- package/src/domain/oauthapp/__tests__/oauthapp.test.ts +2 -2
- package/src/domain/oauthapp/__tests__/refresolution.test.ts +2 -2
- package/src/domain/oauthapp/refresolution/refresolution.ts +1 -1
- package/src/domain/oauthapp/refresolution.ts +1 -1
- package/src/domain/platform/__tests__/platform.test.ts +47 -0
- package/src/domain/platform/controller.ts +10 -4
- package/src/domain/plugin/__tests__/materialize.test.ts +42 -47
- package/src/domain/plugin/__tests__/members.test.ts +149 -0
- package/src/domain/plugin/__tests__/plugin.test.ts +219 -8
- package/src/domain/plugin/constants.ts +7 -1
- package/src/domain/plugin/materialize/plan.ts +20 -1
- package/src/domain/plugin/members.ts +76 -7
- package/src/domain/plugin/push.ts +80 -20
- package/src/domain/workflowexecution/lifecycle.ts +1 -1
- package/src/pipeline/apiresource-labels.ts +26 -1
- package/src/sandbox/__tests__/docker.integration.test.ts +0 -2
- package/src/sandbox/__tests__/kubernetes.test.ts +2 -10
- package/src/sandbox/__tests__/local-process.integration.test.ts +0 -3
- package/src/sandbox/__tests__/steps.test.ts +19 -72
- package/src/sandbox/kubernetes.ts +1 -8
- package/src/sandbox/provisioner.ts +5 -20
- package/src/sandbox/steps.ts +12 -29
- package/src/store/interface.ts +6 -2
|
@@ -44,9 +44,15 @@ export type MaterializedKindName = (typeof MATERIALIZATION_ORDER)[number];
|
|
|
44
44
|
export const TRANSFER_LANE_NOT_CONFIGURED =
|
|
45
45
|
"plugin artifact transfer lane is not configured on this server";
|
|
46
46
|
|
|
47
|
-
/**
|
|
47
|
+
/**
|
|
48
|
+
* Warning kinds the SERVER adds to the library's; the wire carries them as
|
|
49
|
+
* strings, and the list in PluginWarning.kind's comment (plugin/v1/status.proto)
|
|
50
|
+
* is kept equal to this one because the SDK docs are generated from it.
|
|
51
|
+
*/
|
|
48
52
|
export const SERVER_WARNING_KINDS = {
|
|
49
53
|
componentIgnored: "component-ignored",
|
|
54
|
+
/** A system-content row the plugin took over in place (members.ts, judgeSlug). */
|
|
55
|
+
memberAdopted: "member-adopted",
|
|
50
56
|
modelHintUnresolved: "model-hint-unresolved",
|
|
51
57
|
subAgentNameBuiltin: "sub-agent-name-builtin",
|
|
52
58
|
versionNotTaggable: "version-not-taggable",
|
|
@@ -19,6 +19,11 @@ import { PluginWarningSchema } from "@stigmer/protos/ai/stigmer/agentic/plugin/v
|
|
|
19
19
|
import type { PluginWarning } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/status_pb";
|
|
20
20
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
21
21
|
|
|
22
|
+
import {
|
|
23
|
+
RESERVED_LABEL_TRUE,
|
|
24
|
+
SYSTEM_LABEL,
|
|
25
|
+
isSystemContent,
|
|
26
|
+
} from "../../../pipeline/apiresource-labels.js";
|
|
22
27
|
import { SERVER_WARNING_KINDS, VERSION_TAG_PATTERN } from "../constants.js";
|
|
23
28
|
import type { PlannedMember } from "../members.js";
|
|
24
29
|
import type { ParsedOverlays } from "../overlay/documents.js";
|
|
@@ -105,24 +110,38 @@ export function planMaterialization(
|
|
|
105
110
|
);
|
|
106
111
|
const workflows = planWorkflows(overlays.workflows, identity);
|
|
107
112
|
|
|
113
|
+
// `system` is read from the labels each member will be written with (the
|
|
114
|
+
// overlay author's, the plugin's two merged over them). A skill's request
|
|
115
|
+
// carries only the plugin's two, so a skill never declares system content
|
|
116
|
+
// and can never adopt a row; the overlay kinds can.
|
|
108
117
|
const members: PlannedMember[] = [
|
|
109
118
|
...skills.map((skill) => ({
|
|
110
119
|
kind: ApiResourceKind.skill,
|
|
111
120
|
slug: skill.slug,
|
|
112
121
|
name: skill.name,
|
|
122
|
+
system: skill.request.labels[SYSTEM_LABEL] === RESERVED_LABEL_TRUE,
|
|
113
123
|
})),
|
|
114
124
|
...mcpServers.map((server) => ({
|
|
115
125
|
kind: ApiResourceKind.mcp_server,
|
|
116
126
|
slug: server.slug,
|
|
117
127
|
name: server.name,
|
|
128
|
+
system: isSystemContent(server.resource.metadata),
|
|
118
129
|
})),
|
|
119
130
|
...(agent === undefined
|
|
120
131
|
? []
|
|
121
|
-
: [
|
|
132
|
+
: [
|
|
133
|
+
{
|
|
134
|
+
kind: ApiResourceKind.agent,
|
|
135
|
+
slug: agent.slug,
|
|
136
|
+
name: agent.name,
|
|
137
|
+
system: isSystemContent(agent.resource.metadata),
|
|
138
|
+
},
|
|
139
|
+
]),
|
|
122
140
|
...workflows.map((workflow) => ({
|
|
123
141
|
kind: ApiResourceKind.workflow,
|
|
124
142
|
slug: workflow.slug,
|
|
125
143
|
name: workflow.name,
|
|
144
|
+
system: isSystemContent(workflow.resource.metadata),
|
|
126
145
|
})),
|
|
127
146
|
];
|
|
128
147
|
|
|
@@ -8,14 +8,17 @@
|
|
|
8
8
|
* pattern.
|
|
9
9
|
*
|
|
10
10
|
* The same module answers the two questions a push asks before any write:
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* converge on the head's digest (every planned
|
|
15
|
-
* carrying `stigmer.ai/plugin-version` equal to it),
|
|
16
|
-
* re-push of the same archive return without a child
|
|
11
|
+
* what to do about each planned slug in the organization (`judgeSlug`:
|
|
12
|
+
* free; ours, an upgrade; system content the plugin replaces, adopted;
|
|
13
|
+
* another plugin's or a user's, refused naming the holder); and whether the
|
|
14
|
+
* plugin's members already converge on the head's digest (every planned
|
|
15
|
+
* member present, each carrying `stigmer.ai/plugin-version` equal to it),
|
|
16
|
+
* which is what lets a re-push of the same archive return without a child
|
|
17
|
+
* write.
|
|
17
18
|
*
|
|
18
|
-
* Proven by __tests__/members.test.ts
|
|
19
|
+
* Proven by __tests__/members.test.ts (the slug decision on plain values,
|
|
20
|
+
* convergence, the dropped set), __tests__/plugin.test.ts (adoption and
|
|
21
|
+
* refusal through a composed server) and the conformance suite's
|
|
19
22
|
* listMembers, collision and reconcile arms.
|
|
20
23
|
*/
|
|
21
24
|
import { fromBinary } from "@bufbuild/protobuf";
|
|
@@ -31,6 +34,7 @@ import type { ApiResourceMetadata } from "@stigmer/protos/ai/stigmer/commons/api
|
|
|
31
34
|
import {
|
|
32
35
|
PLUGIN_LABEL,
|
|
33
36
|
PLUGIN_VERSION_LABEL,
|
|
37
|
+
isSystemContent,
|
|
34
38
|
pluginIdOf,
|
|
35
39
|
} from "../../pipeline/apiresource-labels.js";
|
|
36
40
|
import { findResourceBySlug } from "../../pipeline/steps/helpers.js";
|
|
@@ -56,6 +60,8 @@ export interface Member {
|
|
|
56
60
|
readonly name: string;
|
|
57
61
|
/** The `stigmer.ai/plugin-version` value it was last materialised from. */
|
|
58
62
|
readonly version: string;
|
|
63
|
+
/** Whether the stored row carries `stigmer.ai/system` (isSystemContent). */
|
|
64
|
+
readonly system: boolean;
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
/** A member the plan intends to exist after this push. */
|
|
@@ -63,6 +69,8 @@ export interface PlannedMember {
|
|
|
63
69
|
readonly kind: ApiResourceKind;
|
|
64
70
|
readonly slug: string;
|
|
65
71
|
readonly name: string;
|
|
72
|
+
/** Whether the member the plugin brings declares `stigmer.ai/system`. */
|
|
73
|
+
readonly system: boolean;
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
function memberOf(
|
|
@@ -75,6 +83,7 @@ function memberOf(
|
|
|
75
83
|
slug: metadata.slug,
|
|
76
84
|
name: metadata.name,
|
|
77
85
|
version: metadata.labels[PLUGIN_VERSION_LABEL] ?? "",
|
|
86
|
+
system: isSystemContent(metadata),
|
|
78
87
|
};
|
|
79
88
|
}
|
|
80
89
|
|
|
@@ -145,6 +154,66 @@ export async function slugHolder(
|
|
|
145
154
|
};
|
|
146
155
|
}
|
|
147
156
|
|
|
157
|
+
/**
|
|
158
|
+
* What a push does about one planned slug, decided before any write.
|
|
159
|
+
*
|
|
160
|
+
* free nothing holds it; the member is created
|
|
161
|
+
* ours this plugin's own member; the push is an upgrade
|
|
162
|
+
* adopt system content no plugin manages, and the plugin
|
|
163
|
+
* brings system content for the same slug: the child's
|
|
164
|
+
* apply (upsert-by-slug) rewrites the row in place, so
|
|
165
|
+
* its id, its default instance, every personal instance
|
|
166
|
+
* and every session bound to them survive
|
|
167
|
+
* held-unmanaged a resource no plugin manages and the rule above does
|
|
168
|
+
* not admit; refused naming it
|
|
169
|
+
* held-by-plugin another plugin's member; refused naming that plugin
|
|
170
|
+
*
|
|
171
|
+
* Adoption is deliberately narrow, "system replaces system": the held row's
|
|
172
|
+
* label narrows which rows are eligible (a user's row never carries it on
|
|
173
|
+
* an edition that enforces reserved labels), and the plugin's own claim to
|
|
174
|
+
* the label was charged to `can_write_reserved_labels` by the sanitiser
|
|
175
|
+
* moments before, so the grant is the caller's live permission and never
|
|
176
|
+
* the label alone (pipeline/apiresource-labels.ts). It never transfers a
|
|
177
|
+
* member between plugins: another plugin's slug is refused whatever labels
|
|
178
|
+
* either side carries. The one case it exists for is the platform re-homing
|
|
179
|
+
* the content it seeded before plugins existed (the default agent; the
|
|
180
|
+
* platform's own server) without orphaning a single conversation.
|
|
181
|
+
*/
|
|
182
|
+
export type SlugDecision =
|
|
183
|
+
| { readonly kind: "free" }
|
|
184
|
+
| { readonly kind: "ours"; readonly holder: Member }
|
|
185
|
+
| { readonly kind: "adopt"; readonly holder: Member }
|
|
186
|
+
| { readonly kind: "held-unmanaged"; readonly holder: Member }
|
|
187
|
+
| {
|
|
188
|
+
readonly kind: "held-by-plugin";
|
|
189
|
+
readonly holder: Member;
|
|
190
|
+
readonly pluginId: string;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
export function judgeSlug(
|
|
194
|
+
holder: SlugHolder,
|
|
195
|
+
planned: PlannedMember,
|
|
196
|
+
pluginId: string,
|
|
197
|
+
): SlugDecision {
|
|
198
|
+
if (!holder.held) {
|
|
199
|
+
return { kind: "free" };
|
|
200
|
+
}
|
|
201
|
+
if (holder.byPlugin === pluginId) {
|
|
202
|
+
return { kind: "ours", holder: holder.holder };
|
|
203
|
+
}
|
|
204
|
+
if (holder.byPlugin !== undefined) {
|
|
205
|
+
return {
|
|
206
|
+
kind: "held-by-plugin",
|
|
207
|
+
holder: holder.holder,
|
|
208
|
+
pluginId: holder.byPlugin,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
if (holder.holder.system && planned.system) {
|
|
212
|
+
return { kind: "adopt", holder: holder.holder };
|
|
213
|
+
}
|
|
214
|
+
return { kind: "held-unmanaged", holder: holder.holder };
|
|
215
|
+
}
|
|
216
|
+
|
|
148
217
|
/**
|
|
149
218
|
* Whether the stored members already ARE the plan at this digest: every
|
|
150
219
|
* planned member present, none extra, each stamped with the digest.
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
* digest, READY, every member present and stamped with it) the controller
|
|
20
20
|
* returns the head and the install chain never runs, so a re-push writes
|
|
21
21
|
* nothing. Everything that can refuse — the archive, the package, the
|
|
22
|
-
* overlay, a reserved label, a held slug
|
|
23
|
-
*
|
|
22
|
+
* overlay, a reserved label, a held slug (unless it is system content the
|
|
23
|
+
* plugin replaces, which is adopted in place: members.ts, judgeSlug), a
|
|
24
|
+
* missing permission, the level — refuses in the plan chain BEFORE any
|
|
25
|
+
* write; the install chain's own
|
|
24
26
|
* failures are a child's, wrapped with the plugin's name, and leave a head
|
|
25
27
|
* that says FAILED so the next push of the same archive converges.
|
|
26
28
|
*
|
|
@@ -52,6 +54,7 @@ import {
|
|
|
52
54
|
PluginStatusSchema,
|
|
53
55
|
PluginWarningSchema,
|
|
54
56
|
} from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/status_pb";
|
|
57
|
+
import type { PluginWarning } from "@stigmer/protos/ai/stigmer/agentic/plugin/v1/status_pb";
|
|
55
58
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
56
59
|
import { ApiResourceVisibility } from "@stigmer/protos/ai/stigmer/commons/apiresource/enum_pb";
|
|
57
60
|
import {
|
|
@@ -95,7 +98,7 @@ import { requireOperatorMaySetPublic } from "../../pipeline/steps/visibility-gat
|
|
|
95
98
|
import { ResourceNotFoundError } from "../../store/interface.js";
|
|
96
99
|
import type { Store } from "../../store/interface.js";
|
|
97
100
|
import { openPluginArchive } from "./archive.js";
|
|
98
|
-
import { VERSION_TAG_PATTERN } from "./constants.js";
|
|
101
|
+
import { SERVER_WARNING_KINDS, VERSION_TAG_PATTERN } from "./constants.js";
|
|
99
102
|
import type { OpenedPluginArchive } from "./archive.js";
|
|
100
103
|
import type { PluginIdentity } from "./materialize/identity.js";
|
|
101
104
|
import { McpServerOverlayError } from "./materialize/mcp-servers.js";
|
|
@@ -105,10 +108,11 @@ import type { PluginMaterializerProvider } from "./materialize/ports.js";
|
|
|
105
108
|
import {
|
|
106
109
|
droppedMembers,
|
|
107
110
|
findMembers,
|
|
111
|
+
judgeSlug,
|
|
108
112
|
membersConverge,
|
|
109
113
|
slugHolder,
|
|
110
114
|
} from "./members.js";
|
|
111
|
-
import type { Member } from "./members.js";
|
|
115
|
+
import type { Member, PlannedMember } from "./members.js";
|
|
112
116
|
import { parseOverlays } from "./overlay/documents.js";
|
|
113
117
|
import type { ParsedOverlays } from "./overlay/documents.js";
|
|
114
118
|
import { OverlayParseError } from "./overlay/parse.js";
|
|
@@ -339,9 +343,11 @@ export function requestedVisibility(
|
|
|
339
343
|
|
|
340
344
|
/**
|
|
341
345
|
* PlanMaterialization — everything the push will write, checked whole:
|
|
342
|
-
* every child slug against the organization (free, ours,
|
|
343
|
-
* naming the holder), the caller's permission for
|
|
344
|
-
* the plugin's current members for the convergence
|
|
346
|
+
* every child slug against the organization (free, ours, adopted system
|
|
347
|
+
* content, or refused naming the holder), the caller's permission for
|
|
348
|
+
* every member kind, and the plugin's current members for the convergence
|
|
349
|
+
* and drop decisions. An adopted slug is recorded as a warning on the plan,
|
|
350
|
+
* so the install receipt says which rows the plugin took over.
|
|
345
351
|
*/
|
|
346
352
|
export function newPlanMaterializationStep(
|
|
347
353
|
store: Store,
|
|
@@ -376,6 +382,7 @@ export function newPlanMaterializationStep(
|
|
|
376
382
|
throw error;
|
|
377
383
|
}
|
|
378
384
|
|
|
385
|
+
const adopted: PluginWarning[] = [];
|
|
379
386
|
for (const member of plan.members) {
|
|
380
387
|
let holder;
|
|
381
388
|
try {
|
|
@@ -383,20 +390,41 @@ export function newPlanMaterializationStep(
|
|
|
383
390
|
} catch (error) {
|
|
384
391
|
throw internalError(error, "failed to check a member slug");
|
|
385
392
|
}
|
|
386
|
-
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
393
|
+
const decision = judgeSlug(holder, member, identity.id);
|
|
389
394
|
const noun = memberNoun(member.kind);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
switch (decision.kind) {
|
|
396
|
+
case "free":
|
|
397
|
+
case "ours":
|
|
398
|
+
break;
|
|
399
|
+
case "adopt":
|
|
400
|
+
adopted.push(
|
|
401
|
+
create(PluginWarningSchema, {
|
|
402
|
+
kind: SERVER_WARNING_KINDS.memberAdopted,
|
|
403
|
+
path: declaringDocumentOf(overlays, member),
|
|
404
|
+
message:
|
|
405
|
+
`${noun} '${member.slug}' (${decision.holder.id}) was seeded by the platform before this plugin and is now managed by it; ` +
|
|
406
|
+
"its definition is the plugin's, and its instances and conversations continue",
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
break;
|
|
410
|
+
case "held-unmanaged":
|
|
411
|
+
throw alreadyExistsError(
|
|
412
|
+
noun,
|
|
413
|
+
`'${member.slug}' exists in org '${identity.org}' and is not managed by a plugin; rename or delete it first`,
|
|
414
|
+
);
|
|
415
|
+
case "held-by-plugin":
|
|
416
|
+
throw alreadyExistsError(
|
|
417
|
+
noun,
|
|
418
|
+
`'${member.slug}' is held by plugin '${await pluginSlugOf(store, decision.pluginId)}'`,
|
|
419
|
+
);
|
|
420
|
+
default: {
|
|
421
|
+
const exhaustive: never = decision;
|
|
422
|
+
throw internalError(
|
|
423
|
+
new Error(`unknown slug decision ${JSON.stringify(exhaustive)}`),
|
|
424
|
+
"failed to check a member slug",
|
|
425
|
+
);
|
|
426
|
+
}
|
|
395
427
|
}
|
|
396
|
-
throw alreadyExistsError(
|
|
397
|
-
noun,
|
|
398
|
-
`'${member.slug}' is held by plugin '${await pluginSlugOf(store, holder.byPlugin)}'`,
|
|
399
|
-
);
|
|
400
428
|
}
|
|
401
429
|
|
|
402
430
|
const missing: string[] = [];
|
|
@@ -452,7 +480,12 @@ export function newPlanMaterializationStep(
|
|
|
452
480
|
} catch (error) {
|
|
453
481
|
throw internalError(error, "failed to list plugin members");
|
|
454
482
|
}
|
|
455
|
-
|
|
483
|
+
// An adoption is something this push noticed and did not refuse, so it
|
|
484
|
+
// rides the plan's warnings onto the install receipt like the rest.
|
|
485
|
+
ctx.set(PLUGIN_PLAN_KEY, {
|
|
486
|
+
...plan,
|
|
487
|
+
warnings: [...plan.warnings, ...adopted],
|
|
488
|
+
} satisfies MaterializationPlan);
|
|
456
489
|
ctx.set(EXISTING_MEMBERS_KEY, members);
|
|
457
490
|
},
|
|
458
491
|
};
|
|
@@ -986,6 +1019,33 @@ function memberNoun(kind: ApiResourceKind): string {
|
|
|
986
1019
|
return MEMBER_NOUNS.get(kind) ?? ApiResourceKind[kind] ?? String(kind);
|
|
987
1020
|
}
|
|
988
1021
|
|
|
1022
|
+
/**
|
|
1023
|
+
* The overlay document that declared an adopted member, for the warning's
|
|
1024
|
+
* path: only an overlay can carry the system label, so a skill (whose
|
|
1025
|
+
* request carries the plugin's labels alone) never reaches this.
|
|
1026
|
+
*/
|
|
1027
|
+
function declaringDocumentOf(
|
|
1028
|
+
overlays: ParsedOverlays,
|
|
1029
|
+
member: PlannedMember,
|
|
1030
|
+
): string {
|
|
1031
|
+
switch (member.kind) {
|
|
1032
|
+
case ApiResourceKind.agent:
|
|
1033
|
+
return overlays.agent?.path ?? "";
|
|
1034
|
+
case ApiResourceKind.mcp_server:
|
|
1035
|
+
return (
|
|
1036
|
+
overlays.mcpServers.find((document) => document.server === member.name)
|
|
1037
|
+
?.path ?? ""
|
|
1038
|
+
);
|
|
1039
|
+
case ApiResourceKind.workflow:
|
|
1040
|
+
return (
|
|
1041
|
+
overlays.workflows.find((document) => document.name === member.name)
|
|
1042
|
+
?.path ?? ""
|
|
1043
|
+
);
|
|
1044
|
+
default:
|
|
1045
|
+
return "";
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
989
1049
|
/** The holding plugin's slug for a refusal; a dangling id names itself. */
|
|
990
1050
|
async function pluginSlugOf(store: Store, pluginId: string): Promise<string> {
|
|
991
1051
|
try {
|
|
@@ -13,7 +13,11 @@
|
|
|
13
13
|
* authorizer's permissive default ALLOWS reserved-label writes by design
|
|
14
14
|
* (the self-hosted operator owns the store), which is why a restrict-shaped
|
|
15
15
|
* predicate here still pairs the label with server-owned state: the
|
|
16
|
-
* plugin-managed guard refuses only beside an existing plugin row
|
|
16
|
+
* plugin-managed guard refuses only beside an existing plugin row, and a
|
|
17
|
+
* plugin adopts a system-content row only when its own claim to the same
|
|
18
|
+
* label was just charged to `can_write_reserved_labels` by the sanitiser
|
|
19
|
+
* (domain/plugin/members.ts) — the label narrows which rows are eligible,
|
|
20
|
+
* the live authorization is what grants.
|
|
17
21
|
*/
|
|
18
22
|
import type { ApiResourceMetadata } from "@stigmer/protos/ai/stigmer/commons/apiresource/metadata_pb";
|
|
19
23
|
|
|
@@ -39,6 +43,20 @@ export const DEFAULT_INSTANCE_LABEL = `${RESERVED_LABEL_PREFIX}default-instance`
|
|
|
39
43
|
*/
|
|
40
44
|
export const SYSTEM_MANAGED_LABEL = `${RESERVED_LABEL_PREFIX}system-managed`;
|
|
41
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Marks system content: a resource the platform itself seeded rather than a
|
|
48
|
+
* user authored — the system organization, the default agent, the
|
|
49
|
+
* platform's own MCP servers. The label predates this module (the retired
|
|
50
|
+
* seedpack wrote it on every row it applied; the official plugins' overlays
|
|
51
|
+
* carry it on the resources they replace) and has one server-side meaning:
|
|
52
|
+
* a plugin push may adopt a system-content row that holds a slug the plugin
|
|
53
|
+
* needs, keeping the row's id and everything bound to it, instead of
|
|
54
|
+
* refusing the slug as taken. It never marks a user's resource, so a user's
|
|
55
|
+
* row is never adopted (domain/plugin/members.ts, judgeSlug). The CLI names
|
|
56
|
+
* the same bytes as SYSTEM_ORG_LABEL for the organization it ensures.
|
|
57
|
+
*/
|
|
58
|
+
export const SYSTEM_LABEL = `${RESERVED_LABEL_PREFIX}system`;
|
|
59
|
+
|
|
42
60
|
/**
|
|
43
61
|
* The only value that activates a reserved marker label; any other value is
|
|
44
62
|
* inert (matching cloud's "true".equals(...)). Stamped by the flows that
|
|
@@ -86,3 +104,10 @@ export function isDefaultInstance(
|
|
|
86
104
|
): boolean {
|
|
87
105
|
return metadata?.labels[DEFAULT_INSTANCE_LABEL] === RESERVED_LABEL_TRUE;
|
|
88
106
|
}
|
|
107
|
+
|
|
108
|
+
/** Whether the metadata carries the SYSTEM_LABEL marker (undefined-safe). */
|
|
109
|
+
export function isSystemContent(
|
|
110
|
+
metadata: ApiResourceMetadata | undefined,
|
|
111
|
+
): boolean {
|
|
112
|
+
return metadata?.labels[SYSTEM_LABEL] === RESERVED_LABEL_TRUE;
|
|
113
|
+
}
|
|
@@ -68,7 +68,6 @@ describe.skipIf(!optedIn || !dockerAnswers())(
|
|
|
68
68
|
await driver.ensureSessionSandbox(sessionId, {
|
|
69
69
|
taskQueue: `session:${sessionId}`,
|
|
70
70
|
stigmerToken: "tok-docker-smoke",
|
|
71
|
-
callerClass: "user",
|
|
72
71
|
});
|
|
73
72
|
expect(await driver.probe("session", sessionId)).toBe("running");
|
|
74
73
|
|
|
@@ -92,7 +91,6 @@ describe.skipIf(!optedIn || !dockerAnswers())(
|
|
|
92
91
|
await driver.ensureSessionSandbox(sessionId, {
|
|
93
92
|
taskQueue: `session:${sessionId}`,
|
|
94
93
|
stigmerToken: "tok-docker-smoke",
|
|
95
|
-
callerClass: "user",
|
|
96
94
|
});
|
|
97
95
|
expect(await driver.probe("session", sessionId)).toBe("running");
|
|
98
96
|
|
|
@@ -89,11 +89,7 @@ function fakeGateway(
|
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
const env = {
|
|
93
|
-
taskQueue: "session:ses_1",
|
|
94
|
-
stigmerToken: "tok-1",
|
|
95
|
-
callerClass: "user",
|
|
96
|
-
};
|
|
92
|
+
const env = { taskQueue: "session:ses_1", stigmerToken: "tok-1" };
|
|
97
93
|
|
|
98
94
|
describe("the ensure state machine", () => {
|
|
99
95
|
it("absent → Secret, PVC, Deployment — in that order, no readiness wait", async () => {
|
|
@@ -215,11 +211,7 @@ describe("the manifest shapes (the Java SandboxManifestFactory pins)", () => {
|
|
|
215
211
|
});
|
|
216
212
|
|
|
217
213
|
it("a token-less sandbox omits the token env but keeps the (empty) Secret", () => {
|
|
218
|
-
const tokenless = {
|
|
219
|
-
taskQueue: "session:ses_1",
|
|
220
|
-
stigmerToken: "",
|
|
221
|
-
callerClass: "user",
|
|
222
|
-
};
|
|
214
|
+
const tokenless = { taskQueue: "session:ses_1", stigmerToken: "" };
|
|
223
215
|
const deployment = buildSandboxDeployment(
|
|
224
216
|
"session",
|
|
225
217
|
"ses_1",
|
|
@@ -81,7 +81,6 @@ describe.skipIf(process.platform === "win32")(
|
|
|
81
81
|
await driver.ensureSessionSandbox("ses_smoke", {
|
|
82
82
|
taskQueue: "session:ses_smoke",
|
|
83
83
|
stigmerToken: "tok-smoke",
|
|
84
|
-
callerClass: "user",
|
|
85
84
|
});
|
|
86
85
|
await vi.waitFor(() => expect(spawnCount()).toBe(1), {
|
|
87
86
|
timeout: CHILD_SETTLE_TIMEOUT_MS,
|
|
@@ -92,7 +91,6 @@ describe.skipIf(process.platform === "win32")(
|
|
|
92
91
|
await driver.ensureSessionSandbox("ses_smoke", {
|
|
93
92
|
taskQueue: "session:ses_smoke",
|
|
94
93
|
stigmerToken: "tok-smoke",
|
|
95
|
-
callerClass: "user",
|
|
96
94
|
});
|
|
97
95
|
expect(spawnCount()).toBe(1);
|
|
98
96
|
|
|
@@ -126,7 +124,6 @@ describe.skipIf(process.platform === "win32")(
|
|
|
126
124
|
await driver.ensureSessionSandbox("ses_smoke", {
|
|
127
125
|
taskQueue: "session:ses_smoke",
|
|
128
126
|
stigmerToken: "",
|
|
129
|
-
callerClass: "user",
|
|
130
127
|
});
|
|
131
128
|
await vi.waitFor(() => expect(spawnCount()).toBe(2), {
|
|
132
129
|
timeout: CHILD_SETTLE_TIMEOUT_MS,
|