@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.
Files changed (114) hide show
  1. package/dist/commands/author-loop.d.ts.map +1 -1
  2. package/dist/commands/author-loop.js +18 -3
  3. package/dist/commands/author-loop.js.map +1 -1
  4. package/dist/commands/deploy-ops.d.ts.map +1 -1
  5. package/dist/commands/deploy-ops.js +21 -2
  6. package/dist/commands/deploy-ops.js.map +1 -1
  7. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  8. package/dist/commands/deploy-status-ops.js +16 -0
  9. package/dist/commands/deploy-status-ops.js.map +1 -1
  10. package/dist/control-plane-request.d.ts +2 -0
  11. package/dist/control-plane-request.d.ts.map +1 -1
  12. package/dist/control-plane-request.js +18 -0
  13. package/dist/control-plane-request.js.map +1 -1
  14. package/dist/deploy.d.ts +2 -1
  15. package/dist/deploy.d.ts.map +1 -1
  16. package/dist/deploy.js +15 -0
  17. package/dist/deploy.js.map +1 -1
  18. package/dist/dev.d.ts +2 -0
  19. package/dist/dev.d.ts.map +1 -1
  20. package/dist/dev.js +12 -1
  21. package/dist/dev.js.map +1 -1
  22. package/dist/devtools-auth-session.d.ts +5 -0
  23. package/dist/devtools-auth-session.d.ts.map +1 -1
  24. package/dist/devtools-auth-session.js +41 -2
  25. package/dist/devtools-auth-session.js.map +1 -1
  26. package/dist/devtools-auth-types.d.ts +2 -1
  27. package/dist/devtools-auth-types.d.ts.map +1 -1
  28. package/dist/devtools-auth-types.js.map +1 -1
  29. package/dist/devtools-auth-ui.d.ts +1 -1
  30. package/dist/devtools-auth-ui.d.ts.map +1 -1
  31. package/dist/devtools-auth-ui.js +13 -5
  32. package/dist/devtools-auth-ui.js.map +1 -1
  33. package/dist/devtools-harness.d.ts +1 -0
  34. package/dist/devtools-harness.d.ts.map +1 -1
  35. package/dist/devtools-harness.js +12 -7
  36. package/dist/devtools-harness.js.map +1 -1
  37. package/dist/devtools-preview.d.ts +1 -0
  38. package/dist/devtools-preview.d.ts.map +1 -1
  39. package/dist/devtools-preview.js +21 -6
  40. package/dist/devtools-preview.js.map +1 -1
  41. package/dist/devtools-rpc-forwarder.d.ts +1 -0
  42. package/dist/devtools-rpc-forwarder.d.ts.map +1 -1
  43. package/dist/devtools-rpc-forwarder.js +2 -1
  44. package/dist/devtools-rpc-forwarder.js.map +1 -1
  45. package/dist/preview-session.d.ts +1 -0
  46. package/dist/preview-session.d.ts.map +1 -1
  47. package/dist/preview-session.js +1 -0
  48. package/dist/preview-session.js.map +1 -1
  49. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  50. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
  51. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
  52. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
  53. package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
  54. package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
  55. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
  56. package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
  57. package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
  58. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
  59. package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
  60. package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
  61. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
  62. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  63. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  64. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
  65. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
  66. package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
  67. package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
  68. package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
  69. package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
  70. package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
  71. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
  72. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
  73. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
  74. package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
  75. package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
  76. package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
  77. package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
  78. package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +2 -2
  79. package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
  80. package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +26 -2
  81. package/node_modules/@noodle-borg/service/dist/registry-access.js +33 -31
  82. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +97 -22
  83. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +13 -1
  84. package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -7
  85. package/node_modules/@noodle-borg/service/dist/registry-state.js +6 -5
  86. package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -4
  87. package/node_modules/@noodle-borg/service/dist/registry-targets.js +35 -28
  88. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +3 -1
  89. package/node_modules/@noodle-borg/service/dist/registry.js +40 -39
  90. package/node_modules/@noodle-borg/service/dist/routes/access.js +4 -2
  91. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
  92. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
  93. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
  94. package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
  95. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -8
  96. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +6 -4
  97. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -8
  98. package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -6
  99. package/node_modules/@noodle-borg/service/dist/store/records.js +13 -2
  100. package/node_modules/@noodle-borg/service/dist/store.d.ts +19 -8
  101. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
  102. package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
  103. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
  104. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
  105. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
  106. package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
  107. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
  108. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
  109. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
  110. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
  111. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
  112. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
  113. package/package.json +1 -1
  114. 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.union([
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 { type ToolAuthorizationCaller } from './tool-authorization.js';
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 { filterAuthorizedTools } from './tool-authorization.js';
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 !== undefined ? { _meta: 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
- ...filterAuthorizedTools(artifact.tools, caller).map((tool) => mapTool(tool, options)),
69
- ...(artifactDeclaresContext(artifact) ? [CONTEXT_TOOL_DESCRIPTOR] : []),
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
- /** R0 understands newer ownership records but must never serve their authentication semantics. */
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, requiresCustomerAuthProjection, } from './customer-auth-audience-binding.js';
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({ ...record, accessMode: 'customers' }, auth))) {
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
- if (nextAccessMode === 'customers' && serverAuth === undefined) {
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
- ...(nextAccessMode === 'customers' && serverAuth !== undefined ? { serverAuth } : {}),
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 current = state.records.get(observed.deploymentId);
165
- if (current === undefined ||
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);