@noodleseed/one 0.166.0 → 0.167.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/commands/author-loop.d.ts.map +1 -1
- package/dist/commands/author-loop.js +18 -3
- package/dist/commands/author-loop.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +21 -2
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +16 -0
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/control-plane-request.d.ts +2 -0
- package/dist/control-plane-request.d.ts.map +1 -1
- package/dist/control-plane-request.js +18 -0
- package/dist/control-plane-request.js.map +1 -1
- package/dist/deploy.d.ts +2 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +15 -0
- package/dist/deploy.js.map +1 -1
- package/dist/dev.d.ts +2 -0
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +12 -1
- package/dist/dev.js.map +1 -1
- package/dist/devtools-auth-session.d.ts +5 -0
- package/dist/devtools-auth-session.d.ts.map +1 -1
- package/dist/devtools-auth-session.js +41 -2
- package/dist/devtools-auth-session.js.map +1 -1
- package/dist/devtools-auth-types.d.ts +2 -1
- package/dist/devtools-auth-types.d.ts.map +1 -1
- package/dist/devtools-auth-types.js.map +1 -1
- package/dist/devtools-auth-ui.d.ts +1 -1
- package/dist/devtools-auth-ui.d.ts.map +1 -1
- package/dist/devtools-auth-ui.js +13 -5
- package/dist/devtools-auth-ui.js.map +1 -1
- package/dist/devtools-harness.d.ts +1 -0
- package/dist/devtools-harness.d.ts.map +1 -1
- package/dist/devtools-harness.js +12 -7
- package/dist/devtools-harness.js.map +1 -1
- package/dist/devtools-preview.d.ts +1 -0
- package/dist/devtools-preview.d.ts.map +1 -1
- package/dist/devtools-preview.js +21 -6
- package/dist/devtools-preview.js.map +1 -1
- package/dist/devtools-rpc-forwarder.d.ts +1 -0
- package/dist/devtools-rpc-forwarder.d.ts.map +1 -1
- package/dist/devtools-rpc-forwarder.js +2 -1
- package/dist/devtools-rpc-forwarder.js.map +1 -1
- package/dist/preview-session.d.ts +1 -0
- package/dist/preview-session.d.ts.map +1 -1
- package/dist/preview-session.js +1 -0
- package/dist/preview-session.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
- package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
- package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
- package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
- package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
- package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
- package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
- package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
- package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
- package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
- package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
- package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
- package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
- package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
- package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
- package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
- package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
- package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
- package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +2 -2
- package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
- package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +26 -2
- package/node_modules/@noodle-borg/service/dist/registry-access.js +33 -31
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +97 -22
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +13 -1
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -7
- package/node_modules/@noodle-borg/service/dist/registry-state.js +6 -5
- package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -4
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +35 -28
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/registry.js +40 -39
- package/node_modules/@noodle-borg/service/dist/routes/access.js +4 -2
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
- package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -8
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +6 -4
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -8
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -6
- package/node_modules/@noodle-borg/service/dist/store/records.js +13 -2
- package/node_modules/@noodle-borg/service/dist/store.d.ts +19 -8
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
- package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
- package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
- package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
- package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
- package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
- package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
- package/package.json +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +0 -7
|
@@ -70,25 +70,35 @@ const roleSchema = z
|
|
|
70
70
|
const authorizationListSchema = (item) => z.array(item).min(1).max(MAX_TOOL_AUTHORIZATION_VALUES);
|
|
71
71
|
const requiredScopesSchema = authorizationListSchema(scopeTokenSchema);
|
|
72
72
|
const allowedRolesSchema = authorizationListSchema(roleSchema);
|
|
73
|
-
const toolAuthorizationSchema = z
|
|
73
|
+
const toolAuthorizationSchema = z
|
|
74
|
+
.union([
|
|
74
75
|
z
|
|
75
76
|
.object({
|
|
77
|
+
discovery: z.enum(['authorized', 'public']).optional(),
|
|
76
78
|
requiredScopes: requiredScopesSchema,
|
|
77
79
|
allowedRoles: allowedRolesSchema.optional(),
|
|
78
80
|
})
|
|
79
81
|
.strict(),
|
|
80
82
|
z
|
|
81
83
|
.object({
|
|
84
|
+
discovery: z.enum(['authorized', 'public']).optional(),
|
|
82
85
|
requiredScopes: requiredScopesSchema.optional(),
|
|
83
86
|
allowedRoles: allowedRolesSchema,
|
|
84
87
|
})
|
|
85
88
|
.strict(),
|
|
86
|
-
])
|
|
89
|
+
])
|
|
90
|
+
.overwrite((authorization) => {
|
|
91
|
+
if (authorization.discovery !== 'authorized')
|
|
92
|
+
return authorization;
|
|
93
|
+
const { discovery: _discovery, ...normalized } = authorization;
|
|
94
|
+
return normalized;
|
|
95
|
+
});
|
|
87
96
|
const toolSchema = z.object({
|
|
88
97
|
name: nameSchema,
|
|
89
98
|
title: z.string().trim().min(1).optional(),
|
|
90
99
|
description: z.string().min(1),
|
|
91
100
|
authorization: toolAuthorizationSchema.optional(),
|
|
101
|
+
securitySchemes: z.never().optional(),
|
|
92
102
|
inputSchema: jsonSchemaSchema,
|
|
93
103
|
outputSchema: jsonSchemaSchema.optional(),
|
|
94
104
|
annotations: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -15,6 +15,9 @@ export function registerTools(server, artifact, deps, context) {
|
|
|
15
15
|
server.setRequestHandler(ListToolsRequestSchema, async () => mapToolsList(artifact, context.caller, {
|
|
16
16
|
knowledgeTools: await knowledgeToolsEnabled(artifact, deps),
|
|
17
17
|
intentCapture: context.intentCapture?.enabled === true,
|
|
18
|
+
...(context.toolAuthentication === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: { authentication: context.toolAuthentication }),
|
|
18
21
|
}));
|
|
19
22
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
20
23
|
const toolName = request.params.name;
|
|
@@ -8,6 +8,7 @@ export { buildMcpServer, type ProtocolRequestContext, type ProtocolToolDispatchC
|
|
|
8
8
|
export { handleStatelessHttp } from './stateless.js';
|
|
9
9
|
export { evaluateToolAuthorization, filterAuthorizedTools, TOOL_AUTHORIZATION_DENIED, type ToolAuthorizationCaller, type ToolAuthorizationDecision, type ToolAuthorizationRuleClass, toolAuthorizationRuleClass, toolAuthorizationRuleFingerprint, } from './tool-authorization.js';
|
|
10
10
|
export { toolRequiresConfirmation } from './tool-confirmation.js';
|
|
11
|
+
export { filterDiscoverableTools, type ToolAuthenticationPolicy } from './tool-discovery.js';
|
|
11
12
|
export { createDualEraMcpHandler, createPlatformDualEraMcpHandler, } from './v2/handler.js';
|
|
12
13
|
export { LEGACY_MCP_PROTOCOL_VERSION, type McpProtocolEra, type McpProtocolMode, MODERN_MCP_PROTOCOL_VERSION, SERVED_MCP_PROTOCOL_VERSIONS, type ServedMcpProtocolVersion, } from './v2/versions.js';
|
|
13
14
|
export { injectWidgetBridge } from './widget/inject.js';
|
|
@@ -7,6 +7,7 @@ export { buildMcpServer, } from './sdk-server.js';
|
|
|
7
7
|
export { handleStatelessHttp } from './stateless.js';
|
|
8
8
|
export { evaluateToolAuthorization, filterAuthorizedTools, TOOL_AUTHORIZATION_DENIED, toolAuthorizationRuleClass, toolAuthorizationRuleFingerprint, } from './tool-authorization.js';
|
|
9
9
|
export { toolRequiresConfirmation } from './tool-confirmation.js';
|
|
10
|
+
export { filterDiscoverableTools } from './tool-discovery.js';
|
|
10
11
|
export { createDualEraMcpHandler, createPlatformDualEraMcpHandler, } from './v2/handler.js';
|
|
11
12
|
export { LEGACY_MCP_PROTOCOL_VERSION, MODERN_MCP_PROTOCOL_VERSION, SERVED_MCP_PROTOCOL_VERSIONS, } from './v2/versions.js';
|
|
12
13
|
export { injectWidgetBridge } from './widget/inject.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ArtifactTool, RuntimeArtifact } from '@noodle-borg/compiler';
|
|
2
2
|
import type { JsonRpcErrorObject } from './jsonrpc.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { ToolAuthorizationCaller } from './tool-authorization.js';
|
|
4
|
+
import { type ToolAuthenticationPolicy, type ToolSecurityScheme } from './tool-discovery.js';
|
|
4
5
|
import { type WidgetDomainProjection } from './widget/domain-projection.js';
|
|
5
6
|
/**
|
|
6
7
|
* Artifact → MCP wire mapping. The official `@modelcontextprotocol/sdk` owns JSON-RPC framing and
|
|
@@ -16,6 +17,7 @@ export interface ToolDescriptor {
|
|
|
16
17
|
readonly inputSchema: Record<string, unknown>;
|
|
17
18
|
readonly outputSchema?: Record<string, unknown>;
|
|
18
19
|
readonly annotations?: Record<string, unknown>;
|
|
20
|
+
readonly securitySchemes?: readonly ToolSecurityScheme[];
|
|
19
21
|
/** MCP `_meta` extension bag (e.g. MCP Apps `{ ui: { resourceUri } }` linking the tool to its widget). */
|
|
20
22
|
readonly _meta?: Record<string, unknown>;
|
|
21
23
|
}
|
|
@@ -44,6 +46,7 @@ export { mapExecutionError } from './execution-error-mapping.js';
|
|
|
44
46
|
/** Map a resolved artifact tool to an MCP tool descriptor (carrying its `_meta` extension bag, if any). */
|
|
45
47
|
export declare function mapTool(tool: ArtifactTool, options?: {
|
|
46
48
|
readonly intentCapture?: boolean;
|
|
49
|
+
readonly authentication?: ToolAuthenticationPolicy;
|
|
47
50
|
}): ToolDescriptor;
|
|
48
51
|
/**
|
|
49
52
|
* Map the artifact's tools to a `tools/list` result. App-only widget helpers remain discoverable here with
|
|
@@ -53,6 +56,7 @@ export declare function mapTool(tool: ArtifactTool, options?: {
|
|
|
53
56
|
export declare function mapToolsList(artifact: RuntimeArtifact, caller?: ToolAuthorizationCaller, options?: {
|
|
54
57
|
readonly knowledgeTools?: boolean;
|
|
55
58
|
readonly intentCapture?: boolean;
|
|
59
|
+
readonly authentication?: ToolAuthenticationPolicy;
|
|
56
60
|
}): ToolsListResult;
|
|
57
61
|
/**
|
|
58
62
|
* Map a successful tool output to a `tools/call` result. Structured (object) output is returned as
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
import { splitResultMeta } from '@noodle-borg/runtime';
|
|
2
2
|
import { artifactDeclaresContext, assertNoContextToolCollision, CONTEXT_TOOL_DESCRIPTOR, } from './context-tool.js';
|
|
3
3
|
import { projectIntentCaptureInput } from './intent-capture.js';
|
|
4
|
-
import {
|
|
4
|
+
import { filterDiscoverableTools, toolSecuritySchemes, } from './tool-discovery.js';
|
|
5
5
|
import { projectWidgetResourceMeta, } from './widget/domain-projection.js';
|
|
6
6
|
import { injectWidgetBridge } from './widget/inject.js';
|
|
7
7
|
export { mapExecutionError } from './execution-error-mapping.js';
|
|
8
8
|
/** Map a resolved artifact tool to an MCP tool descriptor (carrying its `_meta` extension bag, if any). */
|
|
9
9
|
export function mapTool(tool, options) {
|
|
10
|
-
return {
|
|
10
|
+
return withToolAuthentication({
|
|
11
11
|
name: tool.name,
|
|
12
12
|
...(tool.title !== undefined ? { title: tool.title } : {}),
|
|
13
13
|
description: tool.description,
|
|
14
14
|
inputSchema: projectIntentCaptureInput(withPortableInteractionInput(tool), options?.intentCapture === true),
|
|
15
15
|
...(tool.outputSchema !== undefined ? { outputSchema: tool.outputSchema } : {}),
|
|
16
16
|
...(tool.annotations !== undefined ? { annotations: tool.annotations } : {}),
|
|
17
|
-
...(tool._meta
|
|
18
|
-
};
|
|
17
|
+
...(tool._meta === undefined ? {} : { _meta: tool._meta }),
|
|
18
|
+
}, tool, options?.authentication);
|
|
19
|
+
}
|
|
20
|
+
function withToolAuthentication(descriptor, tool, policy) {
|
|
21
|
+
if (policy !== 'mixed-customer')
|
|
22
|
+
return descriptor;
|
|
23
|
+
const securitySchemes = toolSecuritySchemes(tool);
|
|
24
|
+
return { ...descriptor, securitySchemes, _meta: { ...descriptor._meta, securitySchemes } };
|
|
19
25
|
}
|
|
20
26
|
const PORTABLE_INTERACTION_INPUT_SCHEMA = {
|
|
21
27
|
type: 'object',
|
|
@@ -65,10 +71,12 @@ export function mapToolsList(artifact, caller, options) {
|
|
|
65
71
|
assertNoContextToolCollision(artifact);
|
|
66
72
|
return {
|
|
67
73
|
tools: [
|
|
68
|
-
...
|
|
69
|
-
...(artifactDeclaresContext(artifact)
|
|
74
|
+
...filterDiscoverableTools(artifact.tools, caller).map((tool) => mapTool(tool, options)),
|
|
75
|
+
...(artifactDeclaresContext(artifact)
|
|
76
|
+
? [withToolAuthentication(CONTEXT_TOOL_DESCRIPTOR, {}, options?.authentication)]
|
|
77
|
+
: []),
|
|
70
78
|
...(options?.knowledgeTools === true
|
|
71
|
-
? knowledgeToolDescriptors(artifact, options?.intentCapture === true)
|
|
79
|
+
? knowledgeToolDescriptors(artifact, options?.intentCapture === true).map((descriptor) => withToolAuthentication(descriptor, {}, options?.authentication))
|
|
72
80
|
: []),
|
|
73
81
|
],
|
|
74
82
|
};
|
|
@@ -18,6 +18,8 @@ export interface ServedArtifact {
|
|
|
18
18
|
readonly appPackageSnapshot?: AppPackageSnapshotV1;
|
|
19
19
|
}
|
|
20
20
|
export interface ProtocolRequestContext {
|
|
21
|
+
/** Resolved endpoint policy used only to derive MCP discovery metadata. */
|
|
22
|
+
readonly toolAuthentication?: import('./tool-discovery.js').ToolAuthenticationPolicy;
|
|
21
23
|
readonly caller?: CallerIdentity;
|
|
22
24
|
/** Internal hosting attribution, never MCP payload metadata or caller-visible context. */
|
|
23
25
|
readonly publicAdmission?: NonNullable<ExecuteDeps['publicAdmission']>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ArtifactTool } from '@noodle-borg/compiler';
|
|
2
|
+
import { type ToolAuthorizationCaller } from './tool-authorization.js';
|
|
3
|
+
/** The hosting plane opts into schemes only for the resolved mixed customer policy. */
|
|
4
|
+
export type ToolAuthenticationPolicy = 'mixed-customer';
|
|
5
|
+
export type ToolSecurityScheme = {
|
|
6
|
+
readonly type: 'noauth';
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: 'oauth2';
|
|
9
|
+
readonly scopes: readonly string[];
|
|
10
|
+
};
|
|
11
|
+
/** Descriptor publication only; execution and non-MCP capability filters remain authorization-only. */
|
|
12
|
+
export declare function filterDiscoverableTools(tools: readonly ArtifactTool[], caller: ToolAuthorizationCaller | undefined): readonly ArtifactTool[];
|
|
13
|
+
export declare function toolSecuritySchemes(tool: Pick<ArtifactTool, 'authorization'>): readonly ToolSecurityScheme[];
|
|
14
|
+
//# sourceMappingURL=tool-discovery.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { evaluateToolAuthorization } from './tool-authorization.js';
|
|
2
|
+
/** Descriptor publication only; execution and non-MCP capability filters remain authorization-only. */
|
|
3
|
+
export function filterDiscoverableTools(tools, caller) {
|
|
4
|
+
return tools.filter((tool) => tool.authorization?.discovery === 'public' ||
|
|
5
|
+
evaluateToolAuthorization(tool.authorization, caller).allow);
|
|
6
|
+
}
|
|
7
|
+
export function toolSecuritySchemes(tool) {
|
|
8
|
+
return tool.authorization === undefined
|
|
9
|
+
? [{ type: 'noauth' }]
|
|
10
|
+
: [{ type: 'oauth2', scopes: tool.authorization.requiredScopes ?? [] }];
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=tool-discovery.js.map
|
|
@@ -93,6 +93,9 @@ function registerV2Tools(server, artifact, deps, context, era) {
|
|
|
93
93
|
const mapped = mapToolsList(artifact, context.caller, {
|
|
94
94
|
knowledgeTools: await knowledgeToolsEnabled(artifact, deps),
|
|
95
95
|
intentCapture: context.intentCapture?.enabled === true,
|
|
96
|
+
...(context.toolAuthentication === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { authentication: context.toolAuthentication }),
|
|
96
99
|
});
|
|
97
100
|
return (era === 'modern' ? shapeModernToolsList(mapped) : mapped);
|
|
98
101
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { MIXED_CUSTOMER_AUTH_FEATURE_VERSION, serviceInfoResponseSchema, } from '@noodle-borg/wire-contracts';
|
|
1
2
|
export function serviceInfoPayload(buildInfo, developerMcp) {
|
|
2
|
-
return {
|
|
3
|
+
return serviceInfoResponseSchema.parse({
|
|
3
4
|
ok: true,
|
|
4
5
|
status: 'ok',
|
|
5
6
|
...buildInfo,
|
|
7
|
+
features: { mixedCustomerAuth: MIXED_CUSTOMER_AUTH_FEATURE_VERSION },
|
|
6
8
|
...(developerMcp
|
|
7
9
|
? { developerPlugin: { mcpCapabilityVersion: DEVELOPER_MCP_CAPABILITY_VERSION } }
|
|
8
10
|
: {}),
|
|
9
|
-
};
|
|
11
|
+
});
|
|
10
12
|
}
|
|
11
13
|
export function cliCompatibilityError(buildInfo, cliVersion) {
|
|
12
14
|
const supported = buildInfo.compatiblePackageVersions?.['@noodleseed/one'];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { parse as parseYaml } from 'yaml';
|
|
2
|
+
export function isIdentityAccessMode(accessMode, manifest) {
|
|
3
|
+
if (accessMode === 'mixed' && manifest !== undefined) {
|
|
4
|
+
try {
|
|
5
|
+
const parsed = parseYaml(manifest);
|
|
6
|
+
if (typeof parsed === 'object' && parsed !== null && 'server' in parsed) {
|
|
7
|
+
const server = parsed.server;
|
|
8
|
+
if (typeof server === 'object' &&
|
|
9
|
+
server !== null &&
|
|
10
|
+
'auth' in server &&
|
|
11
|
+
server.auth != null)
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
/* Compilation owns malformed manifest diagnostics. */
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return (accessMode === 'owner-only' ||
|
|
20
|
+
accessMode === 'org-members' ||
|
|
21
|
+
accessMode === 'customers' ||
|
|
22
|
+
accessMode === 'authenticated');
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=deployment-access-mode.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
2
|
+
/** Compare inside the lifecycle lock/transaction, before any projection or active-pointer writes. */
|
|
3
|
+
export function matchesDeploymentActivation(target, precondition) {
|
|
4
|
+
if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1))
|
|
5
|
+
return false;
|
|
6
|
+
if (precondition === undefined)
|
|
7
|
+
return true;
|
|
8
|
+
if (target.accessMode !== precondition.expectedAccessMode)
|
|
9
|
+
return false;
|
|
10
|
+
const expected = precondition.expectedRevision;
|
|
11
|
+
if (expected === undefined)
|
|
12
|
+
return true;
|
|
13
|
+
// These are all persisted compile inputs plus the authority and lifecycle state used by the target.
|
|
14
|
+
// Active status may legitimately change on repeated activation; it is not an executable input.
|
|
15
|
+
return [
|
|
16
|
+
'manifest',
|
|
17
|
+
'connectors',
|
|
18
|
+
'hostedAssets',
|
|
19
|
+
'serverAuth',
|
|
20
|
+
'serverVersion',
|
|
21
|
+
'ownerSubject',
|
|
22
|
+
'createdBySubject',
|
|
23
|
+
'orgMembershipSources',
|
|
24
|
+
'archivedAt',
|
|
25
|
+
].every((key) => isDeepStrictEqual(target[key], expected[key]));
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=deployment-activation-precondition.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { usesCustomerAuthentication } from '@noodle-borg/module';
|
|
2
2
|
import { deploymentRecordVersionError } from './deployment-record-version.js';
|
|
3
3
|
/** Read the effective authentication authority only when this runtime understands the record. */
|
|
4
|
-
export function deploymentAuthenticationFor(record) {
|
|
4
|
+
export function deploymentAuthenticationFor(record, compiledAuth) {
|
|
5
5
|
if (deploymentRecordVersionError(record) !== undefined || record.accessMode === undefined) {
|
|
6
6
|
return undefined;
|
|
7
7
|
}
|
|
@@ -10,7 +10,7 @@ export function deploymentAuthenticationFor(record) {
|
|
|
10
10
|
if (usesCustomerAuthentication({
|
|
11
11
|
schemaVersion: record.schemaVersion,
|
|
12
12
|
accessMode: record.accessMode,
|
|
13
|
-
hasServerAuth: record.serverAuth !== undefined,
|
|
13
|
+
hasServerAuth: record.serverAuth !== undefined || compiledAuth !== undefined,
|
|
14
14
|
})) {
|
|
15
15
|
return 'customer';
|
|
16
16
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { cspFaultsInManifest } from '@noodle-borg/compiler';
|
|
2
2
|
import { noopLogger } from '@noodle-borg/transport-http';
|
|
3
3
|
import { BUSINESS_SETUP_MESSAGE, BusinessOnboarding } from './business-onboarding.js';
|
|
4
|
+
import { isIdentityAccessMode } from './deployment-access-mode.js';
|
|
4
5
|
import { manifestUsesUserRoot } from './registry-access.js';
|
|
5
|
-
import { isIdentityAccessMode } from './routes/access-mode.js';
|
|
6
6
|
import { provisionPublicEmbed } from './routes/deploy-public-embed.js';
|
|
7
7
|
import { deploymentActivationRejection } from './routes/deployment-activation-response.js';
|
|
8
8
|
/** Shared post-authorization deploy flow. Both API deploys and installations retain registry activation hooks. */
|
|
@@ -42,7 +42,7 @@ export async function executeAuthorizedDeployment(input, dependencies) {
|
|
|
42
42
|
}
|
|
43
43
|
// Identity access modes bind the data plane to verified identity, so the deployer must be authenticated.
|
|
44
44
|
// (Localhost dev runs the gate open; such deploys are rejected for lack of an owner/audit actor.)
|
|
45
|
-
if (isIdentityAccessMode(accessMode) && !identity) {
|
|
45
|
+
if (isIdentityAccessMode(accessMode, manifest) && !identity) {
|
|
46
46
|
return reject(401, {
|
|
47
47
|
error: `${accessMode} deployments require an authenticated deployer`,
|
|
48
48
|
});
|
|
@@ -11,9 +11,9 @@ export class UnsupportedDeploymentRecordVersionError extends Error {
|
|
|
11
11
|
this.name = 'UnsupportedDeploymentRecordVersionError';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
/**
|
|
14
|
+
/** R1 serves the two understood policy versions; future policy remains unavailable. */
|
|
15
15
|
export function deploymentRecordVersionError(record) {
|
|
16
|
-
return record.schemaVersion === 1 ? undefined : VERSION_ERROR;
|
|
16
|
+
return record.schemaVersion === 1 || record.schemaVersion === 2 ? undefined : VERSION_ERROR;
|
|
17
17
|
}
|
|
18
18
|
/** A stale writer cannot replace or deactivate a newer policy, even after registry preflight. */
|
|
19
19
|
export function assertDeploymentAppendVersion(records, candidate) {
|
|
@@ -28,4 +28,28 @@ export function assertDeploymentAppendVersion(records, candidate) {
|
|
|
28
28
|
export function unsupportedDeploymentRecordFailure() {
|
|
29
29
|
return { ok: false, errors: [VERSION_ERROR] };
|
|
30
30
|
}
|
|
31
|
+
/** Reject a deployment compiled against an operator policy that has since changed. */
|
|
32
|
+
export class DeploymentPolicyChangedError extends Error {
|
|
33
|
+
code = 'deployment_policy_changed';
|
|
34
|
+
constructor() {
|
|
35
|
+
super('The active deployment policy changed. Retry the deployment.');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function assertDeploymentAppendPolicy(records, candidate, precondition) {
|
|
39
|
+
if (precondition === undefined)
|
|
40
|
+
return;
|
|
41
|
+
const current = records.find((record) => record.active && record.archivedAt === undefined && sameDeploymentScope(record, candidate));
|
|
42
|
+
const expected = precondition.active;
|
|
43
|
+
if (expected === null
|
|
44
|
+
? current !== undefined
|
|
45
|
+
: current === undefined ||
|
|
46
|
+
current.deploymentId !== expected.deploymentId ||
|
|
47
|
+
current.schemaVersion !== expected.schemaVersion ||
|
|
48
|
+
current.accessMode !== expected.accessMode ||
|
|
49
|
+
current.ownerSubject !== expected.ownerSubject ||
|
|
50
|
+
current.manifest !== expected.manifest ||
|
|
51
|
+
JSON.stringify(current.serverAuth) !== JSON.stringify(expected.serverAuth)) {
|
|
52
|
+
throw new DeploymentPolicyChangedError();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
31
55
|
//# sourceMappingURL=deployment-record-version.js.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceConflictError,
|
|
1
|
+
import { assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceConflictError, hasExactCustomerAuthProjection, } from './customer-auth-audience-binding.js';
|
|
2
2
|
import { deploymentRecordVersionError } from './deployment-record-version.js';
|
|
3
3
|
import { deploymentOwnerSubject } from './registry-helpers.js';
|
|
4
4
|
import { activeRecord, activeRecordVersion } from './registry-state.js';
|
|
5
5
|
import { tenantDeploymentKey, tryServedTargetFor } from './registry-targets.js';
|
|
6
|
+
import { resolveActiveAccessUpdate } from './store/records.js';
|
|
6
7
|
/** Registry-facing access transition with customer audience ownership preflight and atomic translation. */
|
|
7
8
|
export async function updateRegistryAccess(state, tenant, options, loadServed, customerVerifierFactory, hasCustomerAuthConflict) {
|
|
8
|
-
if (options.accessMode === 'customers') {
|
|
9
|
+
if (options.accessMode === 'customers' || options.accessMode === 'mixed') {
|
|
9
10
|
const record = options.serverVersion === undefined
|
|
10
11
|
? await activeRecord(state, tenant)
|
|
11
12
|
: await activeRecordVersion(state, tenant, options.serverVersion);
|
|
@@ -13,7 +14,11 @@ export async function updateRegistryAccess(state, tenant, options, loadServed, c
|
|
|
13
14
|
const auth = served?.served.artifact.server.auth;
|
|
14
15
|
if (record !== undefined &&
|
|
15
16
|
auth !== undefined &&
|
|
16
|
-
(await hasCustomerAuthConflict({
|
|
17
|
+
(await hasCustomerAuthConflict({
|
|
18
|
+
...record,
|
|
19
|
+
accessMode: options.accessMode,
|
|
20
|
+
...(options.accessMode === 'mixed' ? { schemaVersion: 2 } : {}),
|
|
21
|
+
}, auth))) {
|
|
17
22
|
return customerAuthAudienceConflict();
|
|
18
23
|
}
|
|
19
24
|
}
|
|
@@ -57,11 +62,19 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
57
62
|
const nextOwnerSubject = options.ownerSubject ?? previousOwnerSubject;
|
|
58
63
|
const accessChanged = previousAccessMode !== nextAccessMode;
|
|
59
64
|
const ownerChanged = previousOwnerSubject !== nextOwnerSubject;
|
|
60
|
-
const served = accessChanged || ownerChanged || nextAccessMode === 'customers'
|
|
65
|
+
const served = accessChanged || ownerChanged || nextAccessMode === 'customers' || nextAccessMode === 'mixed'
|
|
61
66
|
? await loadServed(record.deploymentId)
|
|
62
67
|
: undefined;
|
|
63
68
|
const serverAuth = served?.served.artifact.server.auth;
|
|
64
|
-
|
|
69
|
+
const schemaVersion = record.schemaVersion === 2 || (nextAccessMode === 'mixed' && serverAuth !== undefined) ? 2 : 1;
|
|
70
|
+
const policyChanged = schemaVersion !== record.schemaVersion;
|
|
71
|
+
if ((nextAccessMode === 'customers' && serverAuth === undefined) ||
|
|
72
|
+
!hasExactCustomerAuthProjection({
|
|
73
|
+
...record,
|
|
74
|
+
schemaVersion,
|
|
75
|
+
accessMode: nextAccessMode,
|
|
76
|
+
...(record.serverAuth === undefined && serverAuth !== undefined ? { serverAuth } : {}),
|
|
77
|
+
}, serverAuth)) {
|
|
65
78
|
return {
|
|
66
79
|
ok: false,
|
|
67
80
|
status: 409,
|
|
@@ -69,19 +82,23 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
69
82
|
message: 'Customer access requires server authentication.',
|
|
70
83
|
};
|
|
71
84
|
}
|
|
72
|
-
if (!accessChanged && !ownerChanged) {
|
|
85
|
+
if (!accessChanged && !ownerChanged && !policyChanged) {
|
|
73
86
|
const committed = state.store === undefined
|
|
74
87
|
? commitInMemoryAccessUpdate(state, tenant, record, {
|
|
75
88
|
accessMode: nextAccessMode,
|
|
76
89
|
expectedAccessMode: record.accessMode,
|
|
90
|
+
...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
|
|
77
91
|
expectedSchemaVersion: record.schemaVersion,
|
|
92
|
+
expectedManifest: record.manifest,
|
|
78
93
|
expectedOwnerSubject: previousOwnerSubject,
|
|
79
94
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
80
95
|
})
|
|
81
96
|
: await state.store.updateActiveAccess(tenant, record.deploymentId, {
|
|
82
97
|
accessMode: nextAccessMode,
|
|
83
98
|
expectedAccessMode: record.accessMode,
|
|
99
|
+
...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
|
|
84
100
|
expectedSchemaVersion: record.schemaVersion,
|
|
101
|
+
expectedManifest: record.manifest,
|
|
85
102
|
expectedOwnerSubject: previousOwnerSubject,
|
|
86
103
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
87
104
|
});
|
|
@@ -92,6 +109,7 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
92
109
|
ok: true,
|
|
93
110
|
changed: false,
|
|
94
111
|
accessChanged: false,
|
|
112
|
+
policyChanged: false,
|
|
95
113
|
ownerChanged: false,
|
|
96
114
|
previousAccessMode,
|
|
97
115
|
...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
|
|
@@ -119,8 +137,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
119
137
|
const updated = {
|
|
120
138
|
...record,
|
|
121
139
|
accessMode: nextAccessMode,
|
|
140
|
+
schemaVersion,
|
|
122
141
|
...(ownerChanged && nextOwnerSubject !== undefined ? { ownerSubject: nextOwnerSubject } : {}),
|
|
123
|
-
...(
|
|
142
|
+
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
124
143
|
};
|
|
125
144
|
const target = tryServedTargetFor(updated, served.served, customerVerifierFactory);
|
|
126
145
|
if (target === undefined)
|
|
@@ -132,7 +151,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
132
151
|
? { ownerSubject: nextOwnerSubject }
|
|
133
152
|
: {}),
|
|
134
153
|
expectedAccessMode: record.accessMode,
|
|
154
|
+
...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
|
|
135
155
|
expectedSchemaVersion: record.schemaVersion,
|
|
156
|
+
expectedManifest: record.manifest,
|
|
136
157
|
expectedOwnerSubject: previousOwnerSubject,
|
|
137
158
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
138
159
|
})
|
|
@@ -142,7 +163,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
142
163
|
? { ownerSubject: nextOwnerSubject }
|
|
143
164
|
: {}),
|
|
144
165
|
expectedAccessMode: record.accessMode,
|
|
166
|
+
...(schemaVersion === 2 ? { schemaVersion: 2 } : {}),
|
|
145
167
|
expectedSchemaVersion: record.schemaVersion,
|
|
168
|
+
expectedManifest: record.manifest,
|
|
146
169
|
expectedOwnerSubject: previousOwnerSubject,
|
|
147
170
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
148
171
|
});
|
|
@@ -154,6 +177,7 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
154
177
|
ok: true,
|
|
155
178
|
changed: true,
|
|
156
179
|
accessChanged,
|
|
180
|
+
policyChanged,
|
|
157
181
|
ownerChanged,
|
|
158
182
|
previousAccessMode,
|
|
159
183
|
...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
|
|
@@ -161,31 +185,9 @@ export async function updateActiveAccess(state, tenant, options, loadServed, cus
|
|
|
161
185
|
};
|
|
162
186
|
}
|
|
163
187
|
function commitInMemoryAccessUpdate(state, tenant, observed, input) {
|
|
164
|
-
const
|
|
165
|
-
if (
|
|
166
|
-
!current.active ||
|
|
167
|
-
current.archivedAt !== undefined ||
|
|
168
|
-
current.orgSlug !== tenant.org ||
|
|
169
|
-
current.appSlug !== tenant.app ||
|
|
170
|
-
current.environment !== tenant.env ||
|
|
171
|
-
current.accessMode !== input.expectedAccessMode ||
|
|
172
|
-
current.schemaVersion !== (input.expectedSchemaVersion ?? 1) ||
|
|
173
|
-
deploymentOwnerSubject(current) !== input.expectedOwnerSubject) {
|
|
188
|
+
const committed = resolveActiveAccessUpdate(state.records.get(observed.deploymentId), tenant, input);
|
|
189
|
+
if (committed === undefined)
|
|
174
190
|
return undefined;
|
|
175
|
-
}
|
|
176
|
-
if ((current.accessMode ?? 'owner-only') === input.accessMode &&
|
|
177
|
-
(input.ownerSubject === undefined || deploymentOwnerSubject(current) === input.ownerSubject) &&
|
|
178
|
-
!requiresCustomerAuthProjection({ ...current, accessMode: input.accessMode }, input.serverAuth)) {
|
|
179
|
-
return current;
|
|
180
|
-
}
|
|
181
|
-
const committed = {
|
|
182
|
-
...current,
|
|
183
|
-
accessMode: input.accessMode,
|
|
184
|
-
...(input.ownerSubject !== undefined ? { ownerSubject: input.ownerSubject } : {}),
|
|
185
|
-
...(requiresCustomerAuthProjection({ ...current, accessMode: input.accessMode }, input.serverAuth) && input.serverAuth !== undefined
|
|
186
|
-
? { serverAuth: input.serverAuth }
|
|
187
|
-
: {}),
|
|
188
|
-
};
|
|
189
191
|
const proposed = new Map(state.records).set(committed.deploymentId, committed);
|
|
190
192
|
assertUniqueActiveCustomerAuthAudienceBindings([...proposed.values()]);
|
|
191
193
|
state.records.set(committed.deploymentId, committed);
|