@noodleseed/one 0.165.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 (126) 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 +2 -0
  8. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  9. package/dist/commands/deploy-status-ops.js +27 -0
  10. package/dist/commands/deploy-status-ops.js.map +1 -1
  11. package/dist/control-plane-request.d.ts +2 -0
  12. package/dist/control-plane-request.d.ts.map +1 -1
  13. package/dist/control-plane-request.js +18 -0
  14. package/dist/control-plane-request.js.map +1 -1
  15. package/dist/deploy.d.ts +2 -1
  16. package/dist/deploy.d.ts.map +1 -1
  17. package/dist/deploy.js +15 -0
  18. package/dist/deploy.js.map +1 -1
  19. package/dist/dev.d.ts +2 -0
  20. package/dist/dev.d.ts.map +1 -1
  21. package/dist/dev.js +12 -1
  22. package/dist/dev.js.map +1 -1
  23. package/dist/devtools-auth-session.d.ts +5 -0
  24. package/dist/devtools-auth-session.d.ts.map +1 -1
  25. package/dist/devtools-auth-session.js +41 -2
  26. package/dist/devtools-auth-session.js.map +1 -1
  27. package/dist/devtools-auth-types.d.ts +2 -1
  28. package/dist/devtools-auth-types.d.ts.map +1 -1
  29. package/dist/devtools-auth-types.js.map +1 -1
  30. package/dist/devtools-auth-ui.d.ts +1 -1
  31. package/dist/devtools-auth-ui.d.ts.map +1 -1
  32. package/dist/devtools-auth-ui.js +13 -5
  33. package/dist/devtools-auth-ui.js.map +1 -1
  34. package/dist/devtools-harness.d.ts +1 -0
  35. package/dist/devtools-harness.d.ts.map +1 -1
  36. package/dist/devtools-harness.js +12 -7
  37. package/dist/devtools-harness.js.map +1 -1
  38. package/dist/devtools-preview.d.ts +1 -0
  39. package/dist/devtools-preview.d.ts.map +1 -1
  40. package/dist/devtools-preview.js +21 -6
  41. package/dist/devtools-preview.js.map +1 -1
  42. package/dist/devtools-rpc-forwarder.d.ts +1 -0
  43. package/dist/devtools-rpc-forwarder.d.ts.map +1 -1
  44. package/dist/devtools-rpc-forwarder.js +2 -1
  45. package/dist/devtools-rpc-forwarder.js.map +1 -1
  46. package/dist/preview-session.d.ts +1 -0
  47. package/dist/preview-session.d.ts.map +1 -1
  48. package/dist/preview-session.js +1 -0
  49. package/dist/preview-session.js.map +1 -1
  50. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  51. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
  52. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  53. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
  54. package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
  55. package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
  56. package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
  57. package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
  58. package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
  59. package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
  60. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
  61. package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
  62. package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
  63. package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
  64. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
  65. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
  66. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
  67. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
  68. package/node_modules/@noodle-borg/module/dist/customer-auth-access.d.ts +7 -0
  69. package/node_modules/@noodle-borg/module/dist/customer-auth-access.js +6 -0
  70. package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
  71. package/node_modules/@noodle-borg/module/dist/index.js +1 -0
  72. package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
  73. package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
  74. package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
  75. package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
  76. package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
  77. package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
  78. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
  79. package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
  80. package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
  81. package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
  82. package/node_modules/@noodle-borg/service/dist/customer-auth-audience-binding.js +16 -5
  83. package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
  84. package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
  85. package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +19 -0
  86. package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
  87. package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +55 -0
  88. package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +15 -1
  89. package/node_modules/@noodle-borg/service/dist/registry-access.js +47 -30
  90. package/node_modules/@noodle-borg/service/dist/registry-compile.js +42 -1
  91. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +105 -15
  92. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +15 -1
  93. package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -6
  94. package/node_modules/@noodle-borg/service/dist/registry-state.js +15 -7
  95. package/node_modules/@noodle-borg/service/dist/registry-status.js +5 -3
  96. package/node_modules/@noodle-borg/service/dist/registry-targets.js +43 -28
  97. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +4 -2
  98. package/node_modules/@noodle-borg/service/dist/registry.js +68 -72
  99. package/node_modules/@noodle-borg/service/dist/routes/access.js +7 -0
  100. package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
  101. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
  102. package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
  103. package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
  104. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -7
  105. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +13 -4
  106. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -7
  107. package/node_modules/@noodle-borg/service/dist/store/json-file.js +15 -6
  108. package/node_modules/@noodle-borg/service/dist/store/records.js +15 -2
  109. package/node_modules/@noodle-borg/service/dist/store.d.ts +23 -8
  110. package/node_modules/@noodle-borg/service/package.json +1 -1
  111. package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
  112. package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
  113. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
  114. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
  115. package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
  116. package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
  117. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
  118. package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
  119. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +23 -0
  120. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +34 -4
  121. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
  122. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
  123. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
  124. package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
  125. package/package.json +2 -2
  126. package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +0 -7
@@ -1,9 +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
+ import { deploymentRecordVersionError } from './deployment-record-version.js';
5
6
  import { deploymentOwnerSubject } from './registry-helpers.js';
6
7
  import { validateTenantRef } from './store.js';
8
+ const targetPolicyVersions = new WeakMap();
7
9
  /** Replace only the platform record port; unrelated compiled runtime ports retain their authority. */
8
10
  export function rebindNativeRecords(target, record, factory) {
9
11
  const native = factory?.({
@@ -12,7 +14,7 @@ export function rebindNativeRecords(target, record, factory) {
12
14
  deploymentId: record.deploymentId,
13
15
  });
14
16
  const previous = target.served.deps.connectors;
15
- return {
17
+ return rememberTargetPolicy(record, {
16
18
  ...target,
17
19
  served: {
18
20
  ...target.served,
@@ -27,19 +29,23 @@ export function rebindNativeRecords(target, record, factory) {
27
29
  },
28
30
  },
29
31
  },
30
- };
32
+ });
31
33
  }
32
34
  /**
33
35
  * Build the front-door {@link ServedTarget} for a persisted record: its access mode plus the credential the
34
36
  * front-door checks. Legacy alpha records without an identity access mode are not served.
35
37
  */
36
38
  export function servedTargetFor(record, served, customerVerifierFactory) {
39
+ const versionError = deploymentRecordVersionError(record);
40
+ if (versionError !== undefined)
41
+ throw new Error(versionError.code);
37
42
  const accessMode = record.accessMode;
38
43
  if (accessMode === undefined) {
39
44
  throw new Error(`deployment ${record.deploymentId} has unsupported legacy access mode`);
40
45
  }
41
- const customerAuth = accessMode === 'customers' ? served.artifact.server.auth : undefined;
42
- 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) {
43
49
  throw new MissingCustomerAuthError(record.deploymentId);
44
50
  }
45
51
  if (!hasExactCustomerAuthProjection(record, customerAuth)) {
@@ -47,7 +53,7 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
47
53
  }
48
54
  const boundServed = bindAppPackageSnapshot(served, record.appPackageSnapshot);
49
55
  const ownerSubject = deploymentOwnerSubject(record);
50
- return {
56
+ return rememberTargetPolicy(record, {
51
57
  served: boundServed,
52
58
  deploymentId: record.deploymentId,
53
59
  accessMode,
@@ -58,10 +64,10 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
58
64
  ? { orgMembershipSources: record.orgMembershipSources }
59
65
  : {}),
60
66
  ...(accessMode === 'owner-only' && ownerSubject !== undefined ? { ownerSubject } : {}),
61
- ...(customerAuth !== undefined
67
+ authentication: customerAuth !== undefined
62
68
  ? customerTargetFields(customerAuth, customerVerifierFactory)
63
- : {}),
64
- };
69
+ : { kind: 'platform' },
70
+ });
65
71
  }
66
72
  class MissingCustomerAuthError extends Error {
67
73
  deploymentId;
@@ -86,6 +92,10 @@ function servedTargetMatchesRecord(target, record) {
86
92
  const expectedMembership = record.accessMode === 'org-members' ? record.orgMembershipSources : undefined;
87
93
  return (target.deploymentId === record.deploymentId &&
88
94
  target.accessMode === record.accessMode &&
95
+ target.authentication?.kind ===
96
+ (requiresCustomerAuthProjection(record, target.served.artifact.server.auth)
97
+ ? 'customer'
98
+ : 'platform') &&
89
99
  target.org === record.orgSlug &&
90
100
  target.app === record.appSlug &&
91
101
  target.environment === record.environment &&
@@ -101,7 +111,12 @@ function bindAppPackageSnapshot(served, value) {
101
111
  /** Reconcile one persisted record with its cached served target without trusting stale auth projections. */
102
112
  export async function targetForPersistedRecord(record, state) {
103
113
  const cached = state.servers.get(record.deploymentId);
114
+ const cachedSchemaVersion = cached === undefined ? undefined : targetPolicyVersions.get(cached);
104
115
  state.records.set(record.deploymentId, record);
116
+ if (deploymentRecordVersionError(record) !== undefined) {
117
+ state.servers.delete(record.deploymentId);
118
+ return undefined;
119
+ }
105
120
  if (cached === undefined)
106
121
  return state.load?.();
107
122
  if (!hasExactCustomerAuthProjection(record, cached.served.artifact.server.auth)) {
@@ -112,7 +127,7 @@ export async function targetForPersistedRecord(record, state) {
112
127
  (await state.hasCustomerAuthConflict(record, cached.served.artifact.server.auth))) {
113
128
  return undefined;
114
129
  }
115
- if (servedTargetMatchesRecord(cached, record))
130
+ if (cachedSchemaVersion === record.schemaVersion && servedTargetMatchesRecord(cached, record))
116
131
  return cached;
117
132
  const reconciled = servedTargetFor(record, cached.served, state.customerVerifierFactory);
118
133
  state.servers.set(record.deploymentId, reconciled);
@@ -126,33 +141,29 @@ function sameStrings(left, right) {
126
141
  left.every((value, index) => value === right[index])));
127
142
  }
128
143
  function customerTargetFields(auth, customerVerifierFactory) {
129
- if (auth.kind === 'bridge') {
130
- return {
131
- verifyToken: customerVerifierFactory?.(auth) ?? denyAllCustomerToken,
132
- };
133
- }
134
- if (auth.kind === 'federatedOidc') {
135
- const issuers = auth.issuers.map((issuer) => issuer.issuer);
136
- return {
137
- ...(issuers[0] === undefined ? {} : { authServerIssuer: issuers[0] }),
138
- authServerIssuers: issuers,
139
- verifyToken: customerVerifierFactory?.(auth) ?? denyAllCustomerToken,
140
- };
141
- }
142
144
  return {
143
- authServerIssuer: auth.issuer,
144
- 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) }),
145
156
  };
146
157
  }
147
- const denyAllCustomerToken = async () => null;
148
158
  export async function authorizationMetadataForTenant(registry, tenant) {
149
159
  const target = tenant.serverVersion === undefined
150
160
  ? await registry.getActiveByTenant(tenant)
151
161
  : await registry.getActiveByTenantVersion(tenant, tenant.serverVersion);
152
162
  if (target === undefined)
153
163
  return undefined;
154
- const authorizationServers = target.authServerIssuers ??
155
- (target.authServerIssuer === undefined ? undefined : [target.authServerIssuer]);
164
+ const authorizationServers = target.authentication?.kind === 'customer'
165
+ ? target.authentication.authorizationServers
166
+ : undefined;
156
167
  const requiredScopes = [
157
168
  ...new Set(target.served.artifact.tools.flatMap((tool) => tool.authorization?.requiredScopes ?? [])),
158
169
  ].sort();
@@ -212,4 +223,8 @@ export async function deploymentSourceFor(state, tenant, deploymentId) {
212
223
  ...(record.serverVersion === undefined ? {} : { serverVersion: record.serverVersion }),
213
224
  };
214
225
  }
226
+ function rememberTargetPolicy(record, target) {
227
+ targetPolicyVersions.set(target, record.schemaVersion);
228
+ return target;
229
+ }
215
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
  } | {
@@ -73,11 +74,12 @@ export type RunDeployResult = DeployResult | {
73
74
  readonly deploymentVersion: number;
74
75
  readonly serverVersion?: string;
75
76
  };
76
- export type AccessUpdateFailureCode = 'no_active_deployment' | 'server_auth_required' | 'customer_auth_audience_conflict' | 'public_user_context_conflict' | 'owner_identity_required' | 'access_update_conflict';
77
+ export type AccessUpdateFailureCode = 'unsupported_deployment_record_version' | 'no_active_deployment' | 'server_auth_required' | 'customer_auth_audience_conflict' | 'public_user_context_conflict' | 'owner_identity_required' | 'access_update_conflict';
77
78
  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;
@@ -1,19 +1,19 @@
1
1
  import { KnowledgePublicationError, withKnowledgePublication, } from '@noodle-borg/knowledge-operations/portable';
2
2
  import { DeploymentActivationError, normalizeServerVersion, } from '@noodle-borg/module';
3
- import { normalizePersistedConnectorsForCompile } from './connector-normalize.js';
4
3
  import { CustomerAuthAudienceConflictError, customerAuthAudienceConflictFailure, hasActiveCustomerAuthAudienceConflict, } from './customer-auth-audience-binding.js';
5
4
  import { DeploymentLockedError, deploymentLockedConflict, deploymentLockedPreflight, } from './deployment-lock.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
- import { compileRegistryTarget } from './registry-compile.js';
9
- import { preflightRegistryDeploy, resolveDeployAttempt, withDeploymentConfiguration, } from './registry-deploy-transaction.js';
10
- import { deploymentOwnerSubject, missingCapabilityErrors, } from './registry-helpers.js';
8
+ import { compilePersistedRegistryRecord, compileRegistryTarget, loadPersistedRegistryTarget, } from './registry-compile.js';
9
+ import { createRegistryDeployRecord, deployedRecordResult, deploymentWriteVersionError, preflightRegistryDeploy, replacementDeploymentPolicy, resolveDeployAttempt, withDeploymentConfiguration, } from './registry-deploy-transaction.js';
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, recordTenant, 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;
@@ -119,6 +118,9 @@ export class ServerRegistry {
119
118
  if (active?.deploymentLock !== undefined)
120
119
  return deploymentLockedConflict();
121
120
  }
121
+ const versionError = await deploymentWriteVersionError(this.#stateView(), safeTenant, safeServerVersion);
122
+ if (versionError !== undefined)
123
+ return { ok: false, errors: [versionError] };
122
124
  const preflight = await preflightRegistryDeploy({
123
125
  options,
124
126
  serviceCapabilities: this.#serviceCapabilities,
@@ -148,44 +150,43 @@ export class ServerRegistry {
148
150
  const version = Date.now();
149
151
  const built = preflight.bindDeployment(deploymentId);
150
152
  const serverAuth = built.artifact.server.auth;
151
- const ownerSubject = accessMode === 'owner-only' ? (options.ownerSubject ?? actor?.subject) : undefined;
152
- const record = {
153
- 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,
154
159
  deploymentId,
155
- orgSlug: safeTenant.org,
156
- appSlug: safeTenant.app,
157
- environment: safeTenant.env,
158
- ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
159
- deploymentVersion: version,
160
- active: automationId === undefined,
161
- serverName: built.artifact.server.name,
162
- createdAt: new Date().toISOString(),
163
- ...(actor ? { createdBySubject: actor.subject, createdByEmail: actor.email } : {}),
164
- ...(ownerSubject !== undefined ? { ownerSubject } : {}),
165
- ...(deploymentSource !== undefined ? { deploymentSource } : {}),
166
- ...(accessMode !== undefined ? { accessMode } : {}),
167
- ...(orgMembershipSources !== undefined ? { orgMembershipSources } : {}),
168
- ...(serverAuth !== undefined ? { serverAuth } : {}),
160
+ version,
161
+ built,
169
162
  manifest,
170
- ...(connectors !== undefined ? { connectors } : {}),
171
- ...(hostedAssets !== undefined && hostedAssets.length > 0 ? { hostedAssets } : {}),
172
- ...(appPackageSnapshot !== undefined ? { appPackageSnapshot } : {}),
173
- secrets: emptySecretEnvelope(),
174
- };
163
+ options,
164
+ schemaVersion: policy.schemaVersion,
165
+ appPackageSnapshot,
166
+ });
175
167
  if (await this.#hasCustomerAuthAudienceConflict(record, serverAuth)) {
176
168
  return customerAuthAudienceConflictFailure();
177
169
  }
178
170
  let active;
179
171
  try {
180
- 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
+ }));
181
175
  }
182
176
  catch (error) {
177
+ if (error instanceof DeploymentPolicyChangedError)
178
+ return {
179
+ ok: false,
180
+ errors: [{ code: error.code, path: 'accessMode', message: error.message }],
181
+ };
183
182
  if (error instanceof KnowledgePublicationError) {
184
183
  return { ok: false, errors: error.errors };
185
184
  }
186
185
  if (error instanceof CustomerAuthAudienceConflictError) {
187
186
  return customerAuthAudienceConflictFailure();
188
187
  }
188
+ if (error instanceof UnsupportedDeploymentRecordVersionError)
189
+ return unsupportedDeploymentRecordFailure();
189
190
  if (error instanceof DeploymentLockedError)
190
191
  return deploymentLockedConflict();
191
192
  throw error;
@@ -193,7 +194,12 @@ export class ServerRegistry {
193
194
  if (automationId !== undefined) {
194
195
  let activation;
195
196
  try {
196
- 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
+ }, {
197
203
  automationId,
198
204
  });
199
205
  }
@@ -208,9 +214,16 @@ export class ServerRegistry {
208
214
  ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
209
215
  };
210
216
  }
217
+ if (error instanceof DeploymentPolicyChangedError)
218
+ return {
219
+ ok: false,
220
+ errors: [{ code: error.code, path: 'accessMode', message: error.message }],
221
+ };
211
222
  if (error instanceof CustomerAuthAudienceConflictError) {
212
223
  return customerAuthAudienceConflictFailure();
213
224
  }
225
+ if (error instanceof UnsupportedDeploymentRecordVersionError)
226
+ return unsupportedDeploymentRecordFailure();
214
227
  if (error instanceof DeploymentLockedError)
215
228
  return deploymentLockedConflict();
216
229
  throw error;
@@ -240,15 +253,7 @@ export class ServerRegistry {
240
253
  this.#servers.set(deploymentId, servedTargetFor(active, built, this.#customerVerifierFactory));
241
254
  this.#activeTenants.set(tenantDeploymentKey(safeTenant, safeServerVersion), deploymentId);
242
255
  this.#activeTenants.delete(tenantKey(safeTenant));
243
- const activeOwnerSubject = deploymentOwnerSubject(active);
244
- return {
245
- ok: true,
246
- deploymentId,
247
- deploymentVersion: version,
248
- ...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
249
- ...(accessMode !== undefined ? { accessMode } : {}),
250
- ...(activeOwnerSubject !== undefined ? { ownerSubject: activeOwnerSubject } : {}),
251
- };
256
+ return deployedRecordResult(active);
252
257
  });
253
258
  }
254
259
  /** Read-only deploy validation used by the CLI before config writes, asset upload, or persistence. */
@@ -262,12 +267,18 @@ export class ServerRegistry {
262
267
  if (active?.deploymentLock !== undefined)
263
268
  return deploymentLockedPreflight();
264
269
  }
270
+ const versionError = await deploymentWriteVersionError(this.#stateView(), safeTenant, safeServerVersion);
271
+ if (versionError !== undefined)
272
+ return { ok: false, errors: [versionError] };
265
273
  const result = await preflightRegistryDeploy({
266
274
  options,
267
275
  serviceCapabilities: this.#serviceCapabilities,
268
276
  compile: () => this.#compileTarget(safeTenant, manifest, options.connectors, options.hostedAssets, true),
269
277
  });
270
- 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] };
271
282
  }
272
283
  async recover() {
273
284
  if (!this.#store)
@@ -305,16 +316,20 @@ export class ServerRegistry {
305
316
  }, { tenant, manifest, connectors, hostedAssets, deploymentId, renderAppPackage });
306
317
  }
307
318
  #compilePersistedRecord(record) {
308
- return this.#compileTarget(recordTenant(record), record.manifest, record.connectors === undefined
309
- ? undefined
310
- : normalizePersistedConnectorsForCompile(record.connectors), record.hostedAssets, false, record.deploymentId);
319
+ return compilePersistedRegistryRecord(record, this.#compileTarget.bind(this));
311
320
  }
312
321
  async get(deploymentId) {
313
322
  if (this.#records.get(deploymentId)?.archivedAt !== undefined)
314
323
  return undefined;
315
324
  const cached = this.#servers.get(deploymentId);
316
- if (cached)
317
- return cached;
325
+ if (cached) {
326
+ const record = this.#store
327
+ ? await this.#store.get(deploymentId)
328
+ : this.#records.get(deploymentId);
329
+ return record === undefined || record.archivedAt !== undefined
330
+ ? undefined
331
+ : this.#targetForPersistedRecord(record);
332
+ }
318
333
  const inflight = this.#inflight.get(deploymentId);
319
334
  if (inflight)
320
335
  return inflight;
@@ -495,32 +510,13 @@ export class ServerRegistry {
495
510
  });
496
511
  }
497
512
  async #loadAndCompile(source) {
498
- const record = typeof source === 'string'
499
- ? this.#store
500
- ? await this.#store.get(source)
501
- : this.#records.get(source)
502
- : source;
503
- if (!record)
504
- return undefined;
505
- const deploymentId = record.deploymentId;
506
- if (record.archivedAt !== undefined)
507
- return undefined;
508
- const built = await this.#compilePersistedRecord(record);
509
- if (!built.ok) {
510
- throw new Error(`deployment ${deploymentId} failed to recompile (${built.errors.map((e) => e.code).join(',')})`);
511
- }
512
- const missingCapabilities = missingCapabilityErrors(built.served.artifact.requirements?.capabilities ?? [], this.#serviceCapabilities);
513
- if (missingCapabilities.length > 0) {
514
- throw new Error(`deployment ${deploymentId} failed capability requirements (${missingCapabilities.map((e) => e.path).join(',')})`);
515
- }
516
- if (record.active &&
517
- (await this.#hasCustomerAuthAudienceConflict(record, built.served.artifact.server.auth))) {
518
- return undefined;
519
- }
520
- const target = servedTargetFor(record, built.served, this.#customerVerifierFactory);
521
- this.#servers.set(deploymentId, target);
522
- this.#records.set(deploymentId, record);
523
- return target;
513
+ return loadPersistedRegistryTarget(source, {
514
+ state: this.#stateView(),
515
+ compile: (record) => this.#compilePersistedRecord(record),
516
+ capabilities: this.#serviceCapabilities,
517
+ customerVerifierFactory: this.#customerVerifierFactory,
518
+ hasCustomerAuthConflict: (record, auth) => this.#hasCustomerAuthAudienceConflict(record, auth),
519
+ });
524
520
  }
525
521
  async #hasCustomerAuthAudienceConflict(record, auth) {
526
522
  return hasActiveCustomerAuthAudienceConflict(this.#store, this.#records, record, auth);
@@ -1,6 +1,7 @@
1
1
  import { normalizeServerVersion } from '@noodle-borg/module';
2
2
  import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
3
3
  import { accessUpdateRequestSchema } from '@noodle-borg/wire-contracts';
4
+ import { deploymentAuthenticationFor } from '../deployment-authentication.js';
4
5
  import { deploymentOwnerSubject } from '../registry-helpers.js';
5
6
  import { authorizeControlPlane } from './control-plane.js';
6
7
  import { canManageMembers } from './org-admin.js';
@@ -44,6 +45,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
44
45
  }
45
46
  const accessMode = result.record.accessMode ?? 'owner-only';
46
47
  const ownerSubject = deploymentOwnerSubject(result.record);
48
+ const authentication = deploymentAuthenticationFor(result.record);
47
49
  await audit.emit({
48
50
  eventType: 'deployment.access.updated',
49
51
  org: tenant.org,
@@ -63,6 +65,9 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
63
65
  ...(ownerSubject !== undefined ? { ownerSubject } : {}),
64
66
  accessChanged: result.accessChanged,
65
67
  ownerChanged: result.ownerChanged,
68
+ ...(authentication !== undefined
69
+ ? { authentication, policyChanged: result.policyChanged }
70
+ : {}),
66
71
  changed: result.changed,
67
72
  },
68
73
  });
@@ -75,6 +80,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
75
80
  ? { serverVersion: result.record.serverVersion }
76
81
  : {}),
77
82
  accessMode,
83
+ ...(authentication !== undefined ? { authentication } : {}),
78
84
  ...(ownerSubject !== undefined ? { ownerSubject } : {}),
79
85
  },
80
86
  previousAccessMode: result.previousAccessMode,
@@ -83,6 +89,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
83
89
  : {}),
84
90
  accessChanged: result.accessChanged,
85
91
  ownerChanged: result.ownerChanged,
92
+ ...(authentication !== undefined ? { policyChanged: result.policyChanged } : {}),
86
93
  changed: result.changed,
87
94
  });
88
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
  }
@@ -195,6 +195,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
195
195
  deploymentId: result.deploymentId,
196
196
  serverVersion: result.serverVersion,
197
197
  accessMode: result.accessMode,
198
+ ...(result.authentication !== undefined ? { authentication: result.authentication } : {}),
198
199
  ...(result.ownerSubject !== undefined ? { ownerSubject: result.ownerSubject } : {}),
199
200
  url: tenantMcpUrl(base, tenant, result.serverVersion, endpointOptions),
200
201
  defaultUrl: tenantMcpUrl(base, tenant, undefined, endpointOptions),
@@ -2,8 +2,8 @@ import { randomUUID } from 'node:crypto';
2
2
  import { cspFaultsInManifest } from '@noodle-borg/compiler';
3
3
  import { readDeployBody, sendJson } from '@noodle-borg/transport-http';
4
4
  import { DEPLOY_PREFLIGHT_CHECK_TIMEOUT_MS, deployPreflightRequestSchema, formatWireError, } from '@noodle-borg/wire-contracts';
5
+ import { isIdentityAccessMode } from '../deployment-access-mode.js';
5
6
  import { manifestUsesUserRoot } from '../registry-access.js';
6
- import { isIdentityAccessMode } from './access-mode.js';
7
7
  import { authorizeDeploymentOwnerSelection, authorizeTenantControl, developerGrantRouteAccess, } from './control-plane.js';
8
8
  // Per-process resource admission, not a tenant quota or distributed state authority.
9
9
  const activePreflights = new WeakSet();
@@ -91,7 +91,7 @@ export async function handleDeployPreflight(req, res, registry, options, maxBody
91
91
  });
92
92
  }
93
93
  const accessMode = parsed.accessMode ?? 'owner-only';
94
- if (isIdentityAccessMode(accessMode) && identity === undefined) {
94
+ if (isIdentityAccessMode(accessMode, parsed.manifest) && identity === undefined) {
95
95
  return sendJson(res, 401, {
96
96
  code: 'deployer_identity_required',
97
97
  error: `${accessMode} deployments require an authenticated deployer`,
@@ -44,7 +44,7 @@ export async function bridgeAuthForResource(registry, resource, allowedBaseDomai
44
44
  ? await registry.getActiveByTenant(tenant)
45
45
  : await registry.getActiveByTenantVersion(tenant, tenant.serverVersion);
46
46
  const auth = target?.served.artifact.server.auth;
47
- if (auth?.kind !== 'bridge')
47
+ if (target?.authentication?.kind !== 'customer' || auth?.kind !== 'bridge')
48
48
  return undefined;
49
49
  const variables = await registry.configStore.resolveConfigValues('variable', resolveConfigScope(tenant));
50
50
  return resolveTenantBridgeAuthVariables(auth, variables);
@@ -1,19 +1,15 @@
1
- import type { AccessMode } from '@noodle-borg/transport-http';
2
- import type { ActiveAccessUpdateInput, AppArchiveResult, AppRestorePrecondition, AppRestoreResult, AppSummary, ArtifactStore, DeploymentActivationResult, DeploymentListFilter, DeploymentLock, DeploymentLockUpdateResult, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from '../store.js';
1
+ import type { ActiveAccessUpdateInput, AppArchiveResult, AppRestorePrecondition, AppRestoreResult, AppSummary, ArtifactStore, DeploymentActivationPrecondition, DeploymentActivationResult, DeploymentListFilter, DeploymentLock, DeploymentLockUpdateResult, DeploymentPolicyPrecondition, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from '../store.js';
3
2
  /** In-memory store: keeps records in a Map. Used by tests and as an explicit, non-persistent option. */
4
3
  export declare class InMemoryArtifactStore implements ArtifactStore {
5
4
  #private;
6
- append(record: DeployRecord): Promise<void>;
5
+ append(record: DeployRecord, precondition?: DeploymentPolicyPrecondition): Promise<void>;
7
6
  loadAll(): Promise<readonly DeployRecord[]>;
8
7
  get(deploymentId: string): Promise<DeployRecord | undefined>;
9
8
  getActiveByTenant(ref: TenantRef): Promise<DeployRecord | undefined>;
10
9
  getActiveByTenantVersion(ref: TenantRef, serverVersion: string): Promise<DeployRecord | undefined>;
11
10
  setDeploymentLock(ref: TenantRef, serverVersion: string, expectedDeploymentId: string, deploymentLock: DeploymentLock | undefined): Promise<DeploymentLockUpdateResult>;
12
11
  findActiveCustomerAuthAudienceConflict(ref: TenantRef, auth: TenantAuthConfig): Promise<TenantRef | undefined>;
13
- activateDeployment(ref: TenantRef, deploymentId: string, precondition?: {
14
- readonly expectedAccessMode: AccessMode | undefined;
15
- readonly serverAuth?: TenantAuthConfig;
16
- }, _options?: {
12
+ activateDeployment(ref: TenantRef, deploymentId: string, precondition?: DeploymentActivationPrecondition, _options?: {
17
13
  readonly automationId?: string;
18
14
  }): Promise<DeploymentActivationResult | undefined>;
19
15
  updateActiveAccess(ref: TenantRef, deploymentId: string, input: ActiveAccessUpdateInput): Promise<DeployRecord | undefined>;
@@ -1,7 +1,9 @@
1
1
  import { normalizeServerVersion } from '@noodle-borg/module';
2
2
  import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, } from '../app-package-snapshot.js';
3
- import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
3
+ import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, requiresCustomerAuthProjection, } from '../customer-auth-audience-binding.js';
4
+ import { matchesDeploymentActivation } from '../deployment-activation-precondition.js';
4
5
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
6
+ import { assertDeploymentAppendPolicy, assertDeploymentAppendVersion, } from '../deployment-record-version.js';
5
7
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
6
8
  import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
7
9
  import { validateSlug, validateTenantRef } from './validate.js';
@@ -9,7 +11,9 @@ import { validateSlug, validateTenantRef } from './validate.js';
9
11
  export class InMemoryArtifactStore {
10
12
  #records = new Map();
11
13
  #productionEnvironments = new Map();
12
- async append(record) {
14
+ async append(record, precondition) {
15
+ assertDeploymentAppendVersion([...this.#records.values()], record);
16
+ assertDeploymentAppendPolicy([...this.#records.values()], record, precondition);
13
17
  const existing = this.#records.get(record.deploymentId);
14
18
  if (existing !== undefined)
15
19
  assertSameAppPackageSnapshot(existing, record);
@@ -83,17 +87,22 @@ export class InMemoryArtifactStore {
83
87
  if (target === undefined || !sameTenantRecord(target, safe))
84
88
  return Promise.resolve(undefined);
85
89
  assertDeploymentActivationUnlocked([...this.#records.values()], target);
86
- if (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode) {
90
+ if (!matchesDeploymentActivation(target, precondition)) {
87
91
  return Promise.resolve(undefined);
88
92
  }
89
93
  const previousActive = [...this.#records.values()]
90
94
  .filter((record) => record.active && sameDeploymentScope(record, target))
91
95
  .sort((a, b) => b.deploymentVersion - a.deploymentVersion)[0];
96
+ if (precondition === undefined && previousActive !== undefined) {
97
+ assertDeploymentAppendVersion([previousActive], { ...target, active: true });
98
+ }
99
+ assertDeploymentAppendPolicy([...this.#records.values()], { ...target, active: true }, precondition?.expectedActivePolicy);
92
100
  const alreadyActive = previousActive?.deploymentId === target.deploymentId;
93
101
  const activated = {
94
102
  ...target,
95
103
  active: true,
96
- ...(target.accessMode === 'customers' && precondition?.serverAuth !== undefined
104
+ ...(requiresCustomerAuthProjection(target, precondition?.serverAuth) &&
105
+ precondition?.serverAuth !== undefined
97
106
  ? { serverAuth: precondition.serverAuth }
98
107
  : {}),
99
108
  };
@@ -1,5 +1,4 @@
1
- import type { AccessMode } from '@noodle-borg/transport-http';
2
- import type { ActiveAccessUpdateInput, AppArchiveResult, AppRestorePrecondition, AppRestoreResult, AppSummary, ArtifactStore, DeploymentActivationResult, DeploymentListFilter, DeploymentLock, DeploymentLockUpdateResult, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from '../store.js';
1
+ import type { ActiveAccessUpdateInput, AppArchiveResult, AppRestorePrecondition, AppRestoreResult, AppSummary, ArtifactStore, DeploymentActivationPrecondition, DeploymentActivationResult, DeploymentListFilter, DeploymentLock, DeploymentLockUpdateResult, DeploymentPolicyPrecondition, DeploymentSummary, DeployRecord, EnvSummary, ProductionEnvironmentChange, TenantAuthConfig, TenantRef } from '../store.js';
3
2
  /**
4
3
  * JSON-file store: one file per deployment at `<dataDir>/deployments/<deploymentId>.json`, written atomically
5
4
  * (write a temp file, then `rename` over the target — no partial file is ever read). `deploymentId` is a
@@ -9,17 +8,14 @@ import type { ActiveAccessUpdateInput, AppArchiveResult, AppRestorePrecondition,
9
8
  export declare class JsonFileArtifactStore implements ArtifactStore {
10
9
  #private;
11
10
  constructor(dataDir: string);
12
- append(record: DeployRecord): Promise<void>;
11
+ append(record: DeployRecord, precondition?: DeploymentPolicyPrecondition): Promise<void>;
13
12
  get(deploymentId: string): Promise<DeployRecord | undefined>;
14
13
  getActiveByTenant(ref: TenantRef): Promise<DeployRecord | undefined>;
15
14
  getActiveByTenantVersion(ref: TenantRef, serverVersion: string): Promise<DeployRecord | undefined>;
16
15
  setDeploymentLock(ref: TenantRef, serverVersion: string, expectedDeploymentId: string, deploymentLock: DeploymentLock | undefined): Promise<DeploymentLockUpdateResult>;
17
16
  findActiveCustomerAuthAudienceConflict(ref: TenantRef, auth: TenantAuthConfig): Promise<TenantRef | undefined>;
18
17
  updateActiveAccess(ref: TenantRef, deploymentId: string, input: ActiveAccessUpdateInput): Promise<DeployRecord | undefined>;
19
- activateDeployment(ref: TenantRef, deploymentId: string, precondition?: {
20
- readonly expectedAccessMode: AccessMode | undefined;
21
- readonly serverAuth?: TenantAuthConfig;
22
- }): Promise<DeploymentActivationResult | undefined>;
18
+ activateDeployment(ref: TenantRef, deploymentId: string, precondition?: DeploymentActivationPrecondition): Promise<DeploymentActivationResult | undefined>;
23
19
  loadAll(): Promise<readonly DeployRecord[]>;
24
20
  listDeployments(filter: DeploymentListFilter): Promise<readonly DeploymentSummary[]>;
25
21
  getAppArchivedAt(org: string, app: string): Promise<string | undefined>;