@noodleseed/one 0.166.0 → 0.168.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 (120) 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/behavior-skills.js +4 -2
  50. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  51. package/node_modules/@noodle-borg/agent-kit/dist/plugin-bootstrap-skill.js +8 -1
  52. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
  53. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +20 -0
  54. package/node_modules/@noodle-borg/agent-kit/dist/skill-router.js +9 -2
  55. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  56. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
  57. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
  58. package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
  59. package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
  60. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
  61. package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
  62. package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
  63. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
  64. package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
  65. package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
  66. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
  67. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  68. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  69. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
  70. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
  71. package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
  72. package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
  73. package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
  74. package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
  75. package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
  76. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
  77. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
  78. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
  79. package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
  80. package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
  81. package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
  82. package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
  83. package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +2 -2
  84. package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
  85. package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +26 -2
  86. package/node_modules/@noodle-borg/service/dist/registry-access.js +33 -31
  87. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +97 -22
  88. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +13 -1
  89. package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -7
  90. package/node_modules/@noodle-borg/service/dist/registry-state.js +6 -5
  91. package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -4
  92. package/node_modules/@noodle-borg/service/dist/registry-targets.js +35 -28
  93. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +3 -1
  94. package/node_modules/@noodle-borg/service/dist/registry.js +40 -39
  95. package/node_modules/@noodle-borg/service/dist/routes/access.js +4 -2
  96. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
  97. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
  98. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
  99. package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
  100. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -8
  101. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +6 -4
  102. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -8
  103. package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -6
  104. package/node_modules/@noodle-borg/service/dist/store/records.js +13 -2
  105. package/node_modules/@noodle-borg/service/dist/store.d.ts +19 -8
  106. package/node_modules/@noodle-borg/service/package.json +1 -1
  107. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
  108. package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
  109. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
  110. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
  111. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
  112. package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
  113. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
  114. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
  115. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
  116. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
  117. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
  118. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
  119. package/package.json +2 -2
  120. package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +0 -7
@@ -1,8 +1,10 @@
1
1
  import { normalizeServerVersion } from '@noodle-borg/module';
2
+ import { deploymentAuthenticationFor } from './deployment-authentication.js';
2
3
  import { deploymentRecordVersionError } from './deployment-record-version.js';
4
+ import { sameTenantRecord } from './deployment-versioning.js';
3
5
  import { deployAccessRequiresActor, deployerRequiredError, deploymentOwnerSubject, emptyMembershipSourcesError, membershipSourcesRequireOrgMembersError, missingCapabilityErrors, serverAuthRequiredError, } from './registry-helpers.js';
4
- import { activeRecord, activeRecordVersion } from './registry-state.js';
5
- import { idempotentDeploymentId, mintDeploymentId } from './registry-targets.js';
6
+ import { activeRecordVersion } from './registry-state.js';
7
+ import { emptySecretEnvelope, idempotentDeploymentId, mintDeploymentId, } from './registry-targets.js';
6
8
  /** Serialize candidate validation/activation with hierarchical configuration writers. */
7
9
  export async function withDeploymentConfiguration(config, org, work) {
8
10
  if (!config.transactConfig)
@@ -23,12 +25,8 @@ export async function withDeploymentConfiguration(config, org, work) {
23
25
  }
24
26
  /** Validate the exact destination scope before compilation or publication side effects. */
25
27
  export async function deploymentWriteVersionError(state, tenant, serverVersion) {
26
- const active = serverVersion === undefined
27
- ? await activeRecord(state, tenant)
28
- : await activeRecordVersion(state, tenant, serverVersion);
29
- return active !== undefined && active.serverVersion === serverVersion
30
- ? deploymentRecordVersionError(active)
31
- : undefined;
28
+ const active = await activeDeploymentWriteScope(state, tenant, serverVersion);
29
+ return active === undefined ? undefined : deploymentRecordVersionError(active);
32
30
  }
33
31
  export async function preflightRegistryDeploy(input) {
34
32
  const { actor, accessMode, orgMembershipSources } = input.options;
@@ -43,6 +41,9 @@ export async function preflightRegistryDeploy(input) {
43
41
  const artifact = built.ok ? built.served.artifact : built.compiledArtifact;
44
42
  if (artifact !== undefined) {
45
43
  errors.push(...missingCapabilityErrors(artifact.requirements?.capabilities ?? [], input.serviceCapabilities));
44
+ if (accessMode === 'mixed' && artifact.server.auth !== undefined && actor === undefined) {
45
+ errors.push(...deployerRequiredError(accessMode).errors);
46
+ }
46
47
  if (accessMode === 'customers' && artifact.server.auth === undefined) {
47
48
  errors.push(...serverAuthRequiredError().errors);
48
49
  }
@@ -93,18 +94,23 @@ export async function resolveDeployAttempt(input) {
93
94
  },
94
95
  };
95
96
  }
96
- const ownerSubject = deploymentOwnerSubject(existing);
97
97
  return {
98
98
  deploymentId,
99
- replay: {
100
- ok: true,
101
- deploymentId: existing.deploymentId,
102
- deploymentVersion: existing.deploymentVersion,
103
- ...(existing.serverVersion !== undefined ? { serverVersion: existing.serverVersion } : {}),
104
- ...(existing.accessMode !== undefined ? { accessMode: existing.accessMode } : {}),
105
- ...(ownerSubject !== undefined ? { ownerSubject } : {}),
106
- replayed: true,
107
- },
99
+ replay: { ...deployedRecordResult(existing), replayed: true },
100
+ };
101
+ }
102
+ /** Deploy and retry responses report the committed operator policy, never the requested intent. */
103
+ export function deployedRecordResult(record) {
104
+ const ownerSubject = deploymentOwnerSubject(record);
105
+ const authentication = deploymentAuthenticationFor(record);
106
+ return {
107
+ ok: true,
108
+ deploymentId: record.deploymentId,
109
+ deploymentVersion: record.deploymentVersion,
110
+ ...(record.serverVersion !== undefined ? { serverVersion: record.serverVersion } : {}),
111
+ ...(record.accessMode !== undefined ? { accessMode: record.accessMode } : {}),
112
+ ...(authentication !== undefined ? { authentication } : {}),
113
+ ...(ownerSubject !== undefined ? { ownerSubject } : {}),
108
114
  };
109
115
  }
110
116
  function sameIdempotentDeploy(record, tenant, manifest, options) {
@@ -117,14 +123,83 @@ function sameIdempotentDeploy(record, tenant, manifest, options) {
117
123
  record.serverVersion === serverVersion &&
118
124
  record.manifest === manifest &&
119
125
  record.connectors === options.connectors &&
120
- record.accessMode === options.accessMode &&
126
+ (record.schemaVersion === 2 ||
127
+ (record.accessMode === options.accessMode &&
128
+ (options.accessMode !== 'owner-only' ||
129
+ deploymentOwnerSubject(record) === (options.ownerSubject ?? options.actor?.subject)))) &&
121
130
  record.deploymentSource === options.deploymentSource &&
122
131
  sameJson(record.hostedAssets, options.hostedAssets) &&
123
- sameJson(record.orgMembershipSources, options.orgMembershipSources) &&
124
- (options.accessMode !== 'owner-only' ||
125
- deploymentOwnerSubject(record) === (options.ownerSubject ?? options.actor?.subject)));
132
+ sameJson(record.orgMembershipSources, options.orgMembershipSources));
126
133
  }
127
134
  function sameJson(left, right) {
128
135
  return JSON.stringify(left) === JSON.stringify(right);
129
136
  }
137
+ /** Writes target one exact scope; default routing deliberately prefers a versioned deployment. */
138
+ async function activeDeploymentWriteScope(state, tenant, serverVersion) {
139
+ if (serverVersion !== undefined)
140
+ return activeRecordVersion(state, tenant, serverVersion);
141
+ // The existing store port has no unversioned point lookup. Read durable records, never a worker cache.
142
+ const records = state.store ? await state.store.loadAll() : [...state.records.values()];
143
+ return records
144
+ .filter((record) => record.active &&
145
+ record.archivedAt === undefined &&
146
+ record.serverVersion === undefined &&
147
+ sameTenantRecord(record, tenant))
148
+ .sort((a, b) => b.deploymentVersion - a.deploymentVersion)[0];
149
+ }
150
+ /** Existing dormant mixed declarations require an explicit operator adoption. */
151
+ function mixedCustomerActivationError(previous, previousAuth, accessMode, auth) {
152
+ return previous?.schemaVersion === 1 &&
153
+ previous.accessMode === 'mixed' &&
154
+ previousAuth !== undefined &&
155
+ accessMode === 'mixed' &&
156
+ auth !== undefined
157
+ ? {
158
+ code: 'customer_auth_activation_required',
159
+ path: 'accessMode',
160
+ message: 'Activate customer authentication with noodle access set mixed before redeploying this existing mixed deployment.',
161
+ }
162
+ : undefined;
163
+ }
164
+ /** Bind replacement policy to the observed active revision before the atomic store write. */
165
+ export async function replacementDeploymentPolicy(state, tenant, serverVersion, accessMode, auth, compile) {
166
+ const previous = await activeDeploymentWriteScope(state, tenant, serverVersion);
167
+ const previousBuilt = previous === undefined ? undefined : await compile(previous);
168
+ return {
169
+ previous,
170
+ schemaVersion: previous?.schemaVersion === 2 || (accessMode === 'mixed' && auth !== undefined) ? 2 : 1,
171
+ error: mixedCustomerActivationError(previous, previousBuilt?.ok ? previousBuilt.served.artifact.server.auth : previous?.serverAuth, accessMode, auth),
172
+ };
173
+ }
174
+ /** Construct one immutable deployment candidate from compiled source and resolved operator policy. */
175
+ export function createRegistryDeployRecord(input) {
176
+ const { safeTenant, safeServerVersion, deploymentId, version, built, manifest, options, schemaVersion, appPackageSnapshot, } = input;
177
+ const { actor, accessMode, automationId, deploymentSource, orgMembershipSources, connectors, hostedAssets, } = options;
178
+ const serverAuth = built.artifact.server.auth;
179
+ const ownerSubject = accessMode === 'owner-only' ? (options.ownerSubject ?? actor?.subject) : undefined;
180
+ const record = {
181
+ schemaVersion,
182
+ deploymentId,
183
+ orgSlug: safeTenant.org,
184
+ appSlug: safeTenant.app,
185
+ environment: safeTenant.env,
186
+ ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
187
+ deploymentVersion: version,
188
+ active: automationId === undefined,
189
+ serverName: built.artifact.server.name,
190
+ createdAt: new Date().toISOString(),
191
+ ...(actor ? { createdBySubject: actor.subject, createdByEmail: actor.email } : {}),
192
+ ...(ownerSubject !== undefined ? { ownerSubject } : {}),
193
+ ...(deploymentSource !== undefined ? { deploymentSource } : {}),
194
+ ...(accessMode !== undefined ? { accessMode } : {}),
195
+ ...(orgMembershipSources !== undefined ? { orgMembershipSources } : {}),
196
+ ...(serverAuth !== undefined ? { serverAuth } : {}),
197
+ manifest,
198
+ ...(connectors !== undefined ? { connectors } : {}),
199
+ ...(hostedAssets !== undefined && hostedAssets.length > 0 ? { hostedAssets } : {}),
200
+ ...(appPackageSnapshot !== undefined ? { appPackageSnapshot } : {}),
201
+ secrets: emptySecretEnvelope(),
202
+ };
203
+ return record;
204
+ }
130
205
  //# sourceMappingURL=registry-deploy-transaction.js.map
@@ -1,3 +1,4 @@
1
+ import { hasExactCustomerAuthProjection } from './customer-auth-audience-binding.js';
1
2
  import { withDeploymentConfiguration } from './registry-deploy-transaction.js';
2
3
  import { deploymentOwnerSubject, missingCapabilityErrors } from './registry-helpers.js';
3
4
  import { activateInMemory } from './registry-state.js';
@@ -52,9 +53,18 @@ export async function rollbackDeployment(state, ref, deploymentId, compilePersis
52
53
  };
53
54
  }
54
55
  const compiledAuth = built.served.artifact.server.auth;
56
+ if (target.serverAuth !== undefined && !hasExactCustomerAuthProjection(target, compiledAuth)) {
57
+ return {
58
+ ok: false,
59
+ status: 409,
60
+ error: 'deployment cannot be activated: server_auth_required',
61
+ };
62
+ }
55
63
  const projectedTarget = {
56
64
  ...target,
57
- ...(target.accessMode === 'customers' && compiledAuth !== undefined
65
+ ...((target.accessMode === 'customers' ||
66
+ (target.schemaVersion === 2 && target.accessMode === 'mixed')) &&
67
+ compiledAuth !== undefined
58
68
  ? { serverAuth: compiledAuth }
59
69
  : {}),
60
70
  };
@@ -76,11 +86,13 @@ export async function rollbackDeployment(state, ref, deploymentId, compilePersis
76
86
  ? await state.store.activateDeployment(ref, deploymentId, {
77
87
  expectedAccessMode: target.accessMode,
78
88
  expectedSchemaVersion: target.schemaVersion,
89
+ expectedRevision: target,
79
90
  ...(compiledAuth !== undefined ? { serverAuth: compiledAuth } : {}),
80
91
  })
81
92
  : activateInMemory(state.records, ref, deploymentId, {
82
93
  expectedAccessMode: target.accessMode,
83
94
  expectedSchemaVersion: target.schemaVersion,
95
+ expectedRevision: target,
84
96
  ...(compiledAuth !== undefined ? { serverAuth: compiledAuth } : {}),
85
97
  });
86
98
  if (activation === undefined) {
@@ -1,5 +1,5 @@
1
1
  import type { ServedTarget } from '@noodle-borg/transport-http';
2
- import type { AppArchiveResult, AppRestoreResult, AppSummary, ArtifactStore, CustomerAuthRestoreProjection, DeploymentLock, DeploymentLockUpdateResult, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from './store.js';
2
+ import type { AppArchiveResult, AppRestoreResult, AppSummary, ArtifactStore, CustomerAuthRestoreProjection, DeploymentActivationPrecondition, DeploymentLock, DeploymentLockUpdateResult, DeploymentPolicyPrecondition, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from './store.js';
3
3
  export interface RegistryStateView {
4
4
  readonly store: ArtifactStore | undefined;
5
5
  readonly records: Map<string, DeployRecord>;
@@ -32,13 +32,9 @@ export declare function reconcilePlatformAccountResetRegistryCache(state: Regist
32
32
  * store owns deactivation; without one the caches are reconciled in-process so no instance serves a stale
33
33
  * active record.
34
34
  */
35
- export declare function persistDeployRecord(view: RegistryStateView, record: DeployRecord, tenant: TenantRef, activatesNow: boolean): Promise<DeployRecord>;
35
+ export declare function persistDeployRecord(view: RegistryStateView, record: DeployRecord, tenant: TenantRef, activatesNow: boolean, precondition?: DeploymentPolicyPrecondition): Promise<DeployRecord>;
36
36
  /** No-store activation fallback (moved verbatim from `ServerRegistry.#activateInMemory`). */
37
- export declare function activateInMemory(records: Map<string, DeployRecord>, ref: TenantRef, deploymentId: string, precondition?: {
38
- readonly expectedAccessMode: DeployRecord['accessMode'];
39
- readonly expectedSchemaVersion?: number;
40
- readonly serverAuth?: DeployRecord['serverAuth'];
41
- }): {
37
+ export declare function activateInMemory(records: Map<string, DeployRecord>, ref: TenantRef, deploymentId: string, precondition?: DeploymentActivationPrecondition): {
42
38
  readonly active: DeployRecord;
43
39
  readonly previousActive?: DeployRecord;
44
40
  readonly alreadyActive: boolean;
@@ -6,8 +6,9 @@
6
6
  */
7
7
  import { normalizeServerVersion } from '@noodle-borg/module';
8
8
  import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceProjectionError, hasExactCustomerAuthProjection, requiresCustomerAuthProjection, } from './customer-auth-audience-binding.js';
9
+ import { matchesDeploymentActivation } from './deployment-activation-precondition.js';
9
10
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from './deployment-lock.js';
10
- import { assertDeploymentAppendVersion } from './deployment-record-version.js';
11
+ import { assertDeploymentAppendPolicy, assertDeploymentAppendVersion, } from './deployment-record-version.js';
11
12
  import { defaultActiveRecord } from './deployment-versioning.js';
12
13
  import { recordMatchesTenant, tenantDeploymentKey, tenantKey } from './registry-targets.js';
13
14
  import { appArchivedAt, deploymentSummary, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, resolveProductionEnvironment, summarizeApps, summarizeEnvs, withoutArchiveStamp, } from './store/records.js';
@@ -27,15 +28,16 @@ export function reconcilePlatformAccountResetRegistryCache(state, input) {
27
28
  * store owns deactivation; without one the caches are reconciled in-process so no instance serves a stale
28
29
  * active record.
29
30
  */
30
- export async function persistDeployRecord(view, record, tenant, activatesNow) {
31
+ export async function persistDeployRecord(view, record, tenant, activatesNow, precondition) {
31
32
  if (view.store) {
32
- await view.store.append(record);
33
+ await view.store.append(record, precondition);
33
34
  const persisted = (await view.store.get(record.deploymentId)) ?? record;
34
35
  view.records.set(record.deploymentId, persisted);
35
36
  return persisted;
36
37
  }
37
38
  else {
38
39
  assertDeploymentAppendVersion([...view.records.values()], record);
40
+ assertDeploymentAppendPolicy([...view.records.values()], record, precondition);
39
41
  if (!assertDeploymentAppendUnlocked([...view.records.values()], record)) {
40
42
  return view.records.get(record.deploymentId);
41
43
  }
@@ -68,8 +70,7 @@ export function activateInMemory(records, ref, deploymentId, precondition) {
68
70
  if (target === undefined || !recordMatchesTenant(target, ref))
69
71
  return undefined;
70
72
  assertDeploymentActivationUnlocked([...records.values()], target);
71
- if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1) ||
72
- (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode)) {
73
+ if (!matchesDeploymentActivation(target, precondition)) {
73
74
  return undefined;
74
75
  }
75
76
  const previousActive = [...records.values()]
@@ -1,15 +1,14 @@
1
1
  import { tenantMcpUrl } from '@noodle-borg/module';
2
+ import { hasExactCustomerAuthProjection } from './customer-auth-audience-binding.js';
2
3
  import { deploymentAuthenticationFor } from './deployment-authentication.js';
3
4
  import { deploymentOwnerSubject, missingSecretNames } from './registry-helpers.js';
4
5
  /** Project one compiled deployment into the control-plane status contract. */
5
6
  export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions) {
6
7
  const missingSecrets = built.ok ? [] : missingSecretNames(built.errors);
7
- const missingCustomerAuth = built.ok &&
8
- record.accessMode === 'customers' &&
9
- built.served.artifact.server.auth === undefined;
8
+ const missingCustomerAuth = built.ok && !hasExactCustomerAuthProjection(record, built.served.artifact.server.auth);
10
9
  const unhealthy = missingCustomerAuth || (!built.ok && missingSecrets.length === 0);
11
10
  const ownerSubject = deploymentOwnerSubject(record);
12
- const authentication = deploymentAuthenticationFor(record);
11
+ const authentication = deploymentAuthenticationFor(record, built.ok ? built.served.artifact.server.auth : undefined);
13
12
  return {
14
13
  target: ref,
15
14
  deployment: {
@@ -1,10 +1,11 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { RECORD_CONNECTOR_ID } from '@noodle-borg/compiler';
3
3
  import { parseAppPackageSnapshot } from './app-package-snapshot.js';
4
- import { hasExactCustomerAuthProjection } from './customer-auth-audience-binding.js';
4
+ import { hasExactCustomerAuthProjection, requiresCustomerAuthProjection, } from './customer-auth-audience-binding.js';
5
5
  import { deploymentRecordVersionError } from './deployment-record-version.js';
6
6
  import { deploymentOwnerSubject } from './registry-helpers.js';
7
7
  import { validateTenantRef } from './store.js';
8
+ const targetPolicyVersions = new WeakMap();
8
9
  /** Replace only the platform record port; unrelated compiled runtime ports retain their authority. */
9
10
  export function rebindNativeRecords(target, record, factory) {
10
11
  const native = factory?.({
@@ -13,7 +14,7 @@ export function rebindNativeRecords(target, record, factory) {
13
14
  deploymentId: record.deploymentId,
14
15
  });
15
16
  const previous = target.served.deps.connectors;
16
- return {
17
+ return rememberTargetPolicy(record, {
17
18
  ...target,
18
19
  served: {
19
20
  ...target.served,
@@ -28,7 +29,7 @@ export function rebindNativeRecords(target, record, factory) {
28
29
  },
29
30
  },
30
31
  },
31
- };
32
+ });
32
33
  }
33
34
  /**
34
35
  * Build the front-door {@link ServedTarget} for a persisted record: its access mode plus the credential the
@@ -42,8 +43,9 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
42
43
  if (accessMode === undefined) {
43
44
  throw new Error(`deployment ${record.deploymentId} has unsupported legacy access mode`);
44
45
  }
45
- const customerAuth = accessMode === 'customers' ? served.artifact.server.auth : undefined;
46
- if (accessMode === 'customers' && customerAuth === undefined) {
46
+ const customerOwned = requiresCustomerAuthProjection(record, served.artifact.server.auth);
47
+ const customerAuth = customerOwned ? served.artifact.server.auth : undefined;
48
+ if (customerOwned && customerAuth === undefined) {
47
49
  throw new MissingCustomerAuthError(record.deploymentId);
48
50
  }
49
51
  if (!hasExactCustomerAuthProjection(record, customerAuth)) {
@@ -51,7 +53,7 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
51
53
  }
52
54
  const boundServed = bindAppPackageSnapshot(served, record.appPackageSnapshot);
53
55
  const ownerSubject = deploymentOwnerSubject(record);
54
- return {
56
+ return rememberTargetPolicy(record, {
55
57
  served: boundServed,
56
58
  deploymentId: record.deploymentId,
57
59
  accessMode,
@@ -62,10 +64,10 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
62
64
  ? { orgMembershipSources: record.orgMembershipSources }
63
65
  : {}),
64
66
  ...(accessMode === 'owner-only' && ownerSubject !== undefined ? { ownerSubject } : {}),
65
- ...(customerAuth !== undefined
67
+ authentication: customerAuth !== undefined
66
68
  ? customerTargetFields(customerAuth, customerVerifierFactory)
67
- : {}),
68
- };
69
+ : { kind: 'platform' },
70
+ });
69
71
  }
70
72
  class MissingCustomerAuthError extends Error {
71
73
  deploymentId;
@@ -90,6 +92,10 @@ function servedTargetMatchesRecord(target, record) {
90
92
  const expectedMembership = record.accessMode === 'org-members' ? record.orgMembershipSources : undefined;
91
93
  return (target.deploymentId === record.deploymentId &&
92
94
  target.accessMode === record.accessMode &&
95
+ target.authentication?.kind ===
96
+ (requiresCustomerAuthProjection(record, target.served.artifact.server.auth)
97
+ ? 'customer'
98
+ : 'platform') &&
93
99
  target.org === record.orgSlug &&
94
100
  target.app === record.appSlug &&
95
101
  target.environment === record.environment &&
@@ -105,6 +111,7 @@ function bindAppPackageSnapshot(served, value) {
105
111
  /** Reconcile one persisted record with its cached served target without trusting stale auth projections. */
106
112
  export async function targetForPersistedRecord(record, state) {
107
113
  const cached = state.servers.get(record.deploymentId);
114
+ const cachedSchemaVersion = cached === undefined ? undefined : targetPolicyVersions.get(cached);
108
115
  state.records.set(record.deploymentId, record);
109
116
  if (deploymentRecordVersionError(record) !== undefined) {
110
117
  state.servers.delete(record.deploymentId);
@@ -120,7 +127,7 @@ export async function targetForPersistedRecord(record, state) {
120
127
  (await state.hasCustomerAuthConflict(record, cached.served.artifact.server.auth))) {
121
128
  return undefined;
122
129
  }
123
- if (servedTargetMatchesRecord(cached, record))
130
+ if (cachedSchemaVersion === record.schemaVersion && servedTargetMatchesRecord(cached, record))
124
131
  return cached;
125
132
  const reconciled = servedTargetFor(record, cached.served, state.customerVerifierFactory);
126
133
  state.servers.set(record.deploymentId, reconciled);
@@ -134,33 +141,29 @@ function sameStrings(left, right) {
134
141
  left.every((value, index) => value === right[index])));
135
142
  }
136
143
  function customerTargetFields(auth, customerVerifierFactory) {
137
- if (auth.kind === 'bridge') {
138
- return {
139
- verifyToken: customerVerifierFactory?.(auth) ?? denyAllCustomerToken,
140
- };
141
- }
142
- if (auth.kind === 'federatedOidc') {
143
- const issuers = auth.issuers.map((issuer) => issuer.issuer);
144
- return {
145
- ...(issuers[0] === undefined ? {} : { authServerIssuer: issuers[0] }),
146
- authServerIssuers: issuers,
147
- verifyToken: customerVerifierFactory?.(auth) ?? denyAllCustomerToken,
148
- };
149
- }
150
144
  return {
151
- authServerIssuer: auth.issuer,
152
- verifyToken: customerVerifierFactory?.(auth) ?? denyAllCustomerToken,
145
+ kind: 'customer',
146
+ ...(auth.kind === 'bridge'
147
+ ? {}
148
+ : {
149
+ authorizationServers: auth.kind === 'federatedOidc'
150
+ ? auth.issuers.map((issuer) => issuer.issuer)
151
+ : [auth.issuer],
152
+ }),
153
+ ...(customerVerifierFactory === undefined
154
+ ? {}
155
+ : { verifyToken: customerVerifierFactory(auth) }),
153
156
  };
154
157
  }
155
- const denyAllCustomerToken = async () => null;
156
158
  export async function authorizationMetadataForTenant(registry, tenant) {
157
159
  const target = tenant.serverVersion === undefined
158
160
  ? await registry.getActiveByTenant(tenant)
159
161
  : await registry.getActiveByTenantVersion(tenant, tenant.serverVersion);
160
162
  if (target === undefined)
161
163
  return undefined;
162
- const authorizationServers = target.authServerIssuers ??
163
- (target.authServerIssuer === undefined ? undefined : [target.authServerIssuer]);
164
+ const authorizationServers = target.authentication?.kind === 'customer'
165
+ ? target.authentication.authorizationServers
166
+ : undefined;
164
167
  const requiredScopes = [
165
168
  ...new Set(target.served.artifact.tools.flatMap((tool) => tool.authorization?.requiredScopes ?? [])),
166
169
  ].sort();
@@ -220,4 +223,8 @@ export async function deploymentSourceFor(state, tenant, deploymentId) {
220
223
  ...(record.serverVersion === undefined ? {} : { serverVersion: record.serverVersion }),
221
224
  };
222
225
  }
226
+ function rememberTargetPolicy(record, target) {
227
+ targetPolicyVersions.set(target, record.schemaVersion);
228
+ return target;
229
+ }
223
230
  //# sourceMappingURL=registry-targets.js.map
@@ -4,7 +4,7 @@ import type { ControlPlaneIdentity } from '@noodle-borg/control-plane/portable';
4
4
  import type { OrgMembershipSource, PolicyGate } from '@noodle-borg/module';
5
5
  import type { Connector } from '@noodle-borg/runtime';
6
6
  import type { AccessMode, OwnerTokenVerifier } from '@noodle-borg/transport-http';
7
- import type { DeploymentSource } from '@noodle-borg/wire-contracts';
7
+ import type { DeploymentAuthentication, DeploymentSource } from '@noodle-borg/wire-contracts';
8
8
  import type { AppPackageRenderer } from './app-package-snapshot.js';
9
9
  import type { DelegatedExchangeOptions } from './delegated-token-exchange.js';
10
10
  import type { ExternalCredentialExchangeRuntimeOptions } from './external-credential-exchange.js';
@@ -49,6 +49,7 @@ export type DeployResult = {
49
49
  readonly deploymentVersion: number;
50
50
  readonly serverVersion?: string;
51
51
  readonly accessMode?: AccessMode;
52
+ readonly authentication?: DeploymentAuthentication;
52
53
  readonly ownerSubject?: string;
53
54
  readonly replayed?: boolean;
54
55
  } | {
@@ -78,6 +79,7 @@ export type AccessUpdateResult = {
78
79
  readonly ok: true;
79
80
  readonly changed: boolean;
80
81
  readonly accessChanged: boolean;
82
+ readonly policyChanged: boolean;
81
83
  readonly ownerChanged: boolean;
82
84
  readonly previousAccessMode: AccessMode;
83
85
  readonly previousOwnerSubject?: string;
@@ -2,18 +2,18 @@ import { KnowledgePublicationError, withKnowledgePublication, } from '@noodle-bo
2
2
  import { DeploymentActivationError, normalizeServerVersion, } from '@noodle-borg/module';
3
3
  import { CustomerAuthAudienceConflictError, customerAuthAudienceConflictFailure, hasActiveCustomerAuthAudienceConflict, } from './customer-auth-audience-binding.js';
4
4
  import { DeploymentLockedError, deploymentLockedConflict, deploymentLockedPreflight, } from './deployment-lock.js';
5
- import { UnsupportedDeploymentRecordVersionError, unsupportedDeploymentRecordFailure, } from './deployment-record-version.js';
5
+ import { DeploymentPolicyChangedError, UnsupportedDeploymentRecordVersionError, unsupportedDeploymentRecordFailure, } from './deployment-record-version.js';
6
6
  import { defaultActiveRecord } from './deployment-versioning.js';
7
7
  import { updateRegistryAccess } from './registry-access.js';
8
8
  import { compilePersistedRegistryRecord, compileRegistryTarget, loadPersistedRegistryTarget, } from './registry-compile.js';
9
- import { deploymentWriteVersionError, preflightRegistryDeploy, resolveDeployAttempt, withDeploymentConfiguration, } from './registry-deploy-transaction.js';
9
+ import { createRegistryDeployRecord, deployedRecordResult, deploymentWriteVersionError, preflightRegistryDeploy, replacementDeploymentPolicy, resolveDeployAttempt, withDeploymentConfiguration, } from './registry-deploy-transaction.js';
10
10
  import { deploymentOwnerSubject } from './registry-helpers.js';
11
11
  import { registryDeploymentPackage, renderDeploymentPackageSnapshot } from './registry-package.js';
12
12
  import { recoverRegistryRecords } from './registry-recover.js';
13
13
  import { rollbackDeployment, rollbackWithMappedErrors } from './registry-rollback.js';
14
14
  import { activeRecord, activeRecordVersion, persistDeployRecord, reconcilePlatformAccountResetRegistryCache, registryAppArchivedAt, registryArchiveApp, registryCustomerAuthRestoreProjections, registryGetApp, registryGetDeployment, registryGetEnvironment, registryListApps, registryListEnvironments, registryRestoreApp, registrySetProductionEnvironment, registrySweepArchived, setRegistryDeploymentLock, } from './registry-state.js';
15
15
  import { deploymentStatusFor } from './registry-status.js';
16
- import { deploymentSourceFor, emptySecretEnvelope, rebindNativeRecords, servedTargetFor, targetForPersistedRecord, tenantDeploymentKey, tenantKey, } from './registry-targets.js';
16
+ import { deploymentSourceFor, rebindNativeRecords, servedTargetFor, targetForPersistedRecord, tenantDeploymentKey, tenantKey, } from './registry-targets.js';
17
17
  import { withBuiltinStateCatalog } from './state-catalog.js';
18
18
  import { deploymentSummary, matchesDeploymentFilter, validateDeploymentListFilter, } from './store/records.js';
19
19
  import { InMemoryConfigStore, validateTenantRef, } from './store.js';
@@ -109,8 +109,7 @@ export class ServerRegistry {
109
109
  }
110
110
  async deploy(tenant, manifest, options = {}) {
111
111
  return withDeploymentConfiguration(this.#configStore, tenant.org, async () => {
112
- const { connectors, actor, accessMode, hostedAssets, serverVersion, deploymentSource, idempotencyKey, } = options;
113
- const { orgMembershipSources } = options;
112
+ const { connectors, accessMode, hostedAssets, serverVersion, idempotencyKey } = options;
114
113
  const { automationId } = options;
115
114
  const safeTenant = validateTenantRef(tenant);
116
115
  const safeServerVersion = serverVersion !== undefined ? normalizeServerVersion(serverVersion) : undefined;
@@ -151,38 +150,35 @@ export class ServerRegistry {
151
150
  const version = Date.now();
152
151
  const built = preflight.bindDeployment(deploymentId);
153
152
  const serverAuth = built.artifact.server.auth;
154
- const ownerSubject = accessMode === 'owner-only' ? (options.ownerSubject ?? actor?.subject) : undefined;
155
- const record = {
156
- schemaVersion: 1,
153
+ const policy = await replacementDeploymentPolicy(this.#stateView(), safeTenant, safeServerVersion, accessMode, serverAuth, (previous) => this.#compilePersistedRecord(previous));
154
+ if (policy.error !== undefined)
155
+ return { ok: false, errors: [policy.error] };
156
+ const record = createRegistryDeployRecord({
157
+ safeTenant,
158
+ safeServerVersion,
157
159
  deploymentId,
158
- orgSlug: safeTenant.org,
159
- appSlug: safeTenant.app,
160
- environment: safeTenant.env,
161
- ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
162
- deploymentVersion: version,
163
- active: automationId === undefined,
164
- serverName: built.artifact.server.name,
165
- createdAt: new Date().toISOString(),
166
- ...(actor ? { createdBySubject: actor.subject, createdByEmail: actor.email } : {}),
167
- ...(ownerSubject !== undefined ? { ownerSubject } : {}),
168
- ...(deploymentSource !== undefined ? { deploymentSource } : {}),
169
- ...(accessMode !== undefined ? { accessMode } : {}),
170
- ...(orgMembershipSources !== undefined ? { orgMembershipSources } : {}),
171
- ...(serverAuth !== undefined ? { serverAuth } : {}),
160
+ version,
161
+ built,
172
162
  manifest,
173
- ...(connectors !== undefined ? { connectors } : {}),
174
- ...(hostedAssets !== undefined && hostedAssets.length > 0 ? { hostedAssets } : {}),
175
- ...(appPackageSnapshot !== undefined ? { appPackageSnapshot } : {}),
176
- secrets: emptySecretEnvelope(),
177
- };
163
+ options,
164
+ schemaVersion: policy.schemaVersion,
165
+ appPackageSnapshot,
166
+ });
178
167
  if (await this.#hasCustomerAuthAudienceConflict(record, serverAuth)) {
179
168
  return customerAuthAudienceConflictFailure();
180
169
  }
181
170
  let active;
182
171
  try {
183
- active = await withKnowledgePublication(this.#knowledgeHooks, safeTenant, deploymentId, built.artifact.server.knowledge, () => persistDeployRecord(this.#stateView(), record, safeTenant, automationId === undefined));
172
+ active = await withKnowledgePublication(this.#knowledgeHooks, safeTenant, deploymentId, built.artifact.server.knowledge, () => persistDeployRecord(this.#stateView(), record, safeTenant, automationId === undefined, {
173
+ active: policy.previous ?? null,
174
+ }));
184
175
  }
185
176
  catch (error) {
177
+ if (error instanceof DeploymentPolicyChangedError)
178
+ return {
179
+ ok: false,
180
+ errors: [{ code: error.code, path: 'accessMode', message: error.message }],
181
+ };
186
182
  if (error instanceof KnowledgePublicationError) {
187
183
  return { ok: false, errors: error.errors };
188
184
  }
@@ -198,7 +194,12 @@ export class ServerRegistry {
198
194
  if (automationId !== undefined) {
199
195
  let activation;
200
196
  try {
201
- activation = await this.#store?.activateDeployment(safeTenant, deploymentId, undefined, {
197
+ activation = await this.#store?.activateDeployment(safeTenant, deploymentId, {
198
+ expectedSchemaVersion: record.schemaVersion,
199
+ expectedAccessMode: record.accessMode,
200
+ expectedRevision: record,
201
+ expectedActivePolicy: { active: policy.previous ?? null },
202
+ }, {
202
203
  automationId,
203
204
  });
204
205
  }
@@ -213,6 +214,11 @@ export class ServerRegistry {
213
214
  ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
214
215
  };
215
216
  }
217
+ if (error instanceof DeploymentPolicyChangedError)
218
+ return {
219
+ ok: false,
220
+ errors: [{ code: error.code, path: 'accessMode', message: error.message }],
221
+ };
216
222
  if (error instanceof CustomerAuthAudienceConflictError) {
217
223
  return customerAuthAudienceConflictFailure();
218
224
  }
@@ -247,15 +253,7 @@ export class ServerRegistry {
247
253
  this.#servers.set(deploymentId, servedTargetFor(active, built, this.#customerVerifierFactory));
248
254
  this.#activeTenants.set(tenantDeploymentKey(safeTenant, safeServerVersion), deploymentId);
249
255
  this.#activeTenants.delete(tenantKey(safeTenant));
250
- const activeOwnerSubject = deploymentOwnerSubject(active);
251
- return {
252
- ok: true,
253
- deploymentId,
254
- deploymentVersion: version,
255
- ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
256
- ...(accessMode !== undefined ? { accessMode } : {}),
257
- ...(activeOwnerSubject !== undefined ? { ownerSubject: activeOwnerSubject } : {}),
258
- };
256
+ return deployedRecordResult(active);
259
257
  });
260
258
  }
261
259
  /** Read-only deploy validation used by the CLI before config writes, asset upload, or persistence. */
@@ -277,7 +275,10 @@ export class ServerRegistry {
277
275
  serviceCapabilities: this.#serviceCapabilities,
278
276
  compile: () => this.#compileTarget(safeTenant, manifest, options.connectors, options.hostedAssets, true),
279
277
  });
280
- return result.ok ? { ok: true } : result;
278
+ if (!result.ok)
279
+ return result;
280
+ const policy = await replacementDeploymentPolicy(this.#stateView(), safeTenant, safeServerVersion, options.accessMode, result.bindDeployment('preflight').artifact.server.auth, (previous) => this.#compilePersistedRecord(previous));
281
+ return policy.error === undefined ? { ok: true } : { ok: false, errors: [policy.error] };
281
282
  }
282
283
  async recover() {
283
284
  if (!this.#store)
@@ -65,7 +65,9 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
65
65
  ...(ownerSubject !== undefined ? { ownerSubject } : {}),
66
66
  accessChanged: result.accessChanged,
67
67
  ownerChanged: result.ownerChanged,
68
- ...(authentication !== undefined ? { authentication, policyChanged: false } : {}),
68
+ ...(authentication !== undefined
69
+ ? { authentication, policyChanged: result.policyChanged }
70
+ : {}),
69
71
  changed: result.changed,
70
72
  },
71
73
  });
@@ -87,7 +89,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
87
89
  : {}),
88
90
  accessChanged: result.accessChanged,
89
91
  ownerChanged: result.ownerChanged,
90
- ...(authentication !== undefined ? { policyChanged: false } : {}),
92
+ ...(authentication !== undefined ? { policyChanged: result.policyChanged } : {}),
91
93
  changed: result.changed,
92
94
  });
93
95
  }
@@ -19,7 +19,8 @@ export async function handleLiveAuthDoctor(req, res, registry, tenant, options)
19
19
  : await registry.getActiveByTenantVersion(tenant, serverVersion);
20
20
  if (target === undefined)
21
21
  return sendJson(res, 404, { ok: false, error: 'deployment not found' });
22
- if (target.accessMode !== 'customers' || target.verifyToken === undefined) {
22
+ if (target.authentication?.kind !== 'customer' ||
23
+ target.authentication.verifyToken === undefined) {
23
24
  return sendJson(res, 409, {
24
25
  ok: false,
25
26
  error: 'live auth doctor requires a customers deployment with customer auth',
@@ -30,7 +31,7 @@ export async function handleLiveAuthDoctor(req, res, registry, tenant, options)
30
31
  return sendJson(res, 401, { ok: false, error: 'missing customer bearer token' });
31
32
  const endpointOptions = await options.resolveEndpointOptions?.(tenant.org);
32
33
  const resource = tenantMcpUrl(options.serviceBase, tenant, serverVersion, endpointOptions);
33
- const verification = await target.verifyToken(token, resource).catch(() => null);
34
+ const verification = await target.authentication.verifyToken(token, resource).catch(() => null);
34
35
  if (verification === null || verification.caller.identityKind !== 'customer') {
35
36
  return sendJson(res, 401, { ok: false, error: 'customer token verification failed' });
36
37
  }