@noodleseed/one 0.166.0 → 0.167.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/author-loop.d.ts.map +1 -1
- package/dist/commands/author-loop.js +18 -3
- package/dist/commands/author-loop.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +21 -2
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +16 -0
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/control-plane-request.d.ts +2 -0
- package/dist/control-plane-request.d.ts.map +1 -1
- package/dist/control-plane-request.js +18 -0
- package/dist/control-plane-request.js.map +1 -1
- package/dist/deploy.d.ts +2 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +15 -0
- package/dist/deploy.js.map +1 -1
- package/dist/dev.d.ts +2 -0
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +12 -1
- package/dist/dev.js.map +1 -1
- package/dist/devtools-auth-session.d.ts +5 -0
- package/dist/devtools-auth-session.d.ts.map +1 -1
- package/dist/devtools-auth-session.js +41 -2
- package/dist/devtools-auth-session.js.map +1 -1
- package/dist/devtools-auth-types.d.ts +2 -1
- package/dist/devtools-auth-types.d.ts.map +1 -1
- package/dist/devtools-auth-types.js.map +1 -1
- package/dist/devtools-auth-ui.d.ts +1 -1
- package/dist/devtools-auth-ui.d.ts.map +1 -1
- package/dist/devtools-auth-ui.js +13 -5
- package/dist/devtools-auth-ui.js.map +1 -1
- package/dist/devtools-harness.d.ts +1 -0
- package/dist/devtools-harness.d.ts.map +1 -1
- package/dist/devtools-harness.js +12 -7
- package/dist/devtools-harness.js.map +1 -1
- package/dist/devtools-preview.d.ts +1 -0
- package/dist/devtools-preview.d.ts.map +1 -1
- package/dist/devtools-preview.js +21 -6
- package/dist/devtools-preview.js.map +1 -1
- package/dist/devtools-rpc-forwarder.d.ts +1 -0
- package/dist/devtools-rpc-forwarder.d.ts.map +1 -1
- package/dist/devtools-rpc-forwarder.js +2 -1
- package/dist/devtools-rpc-forwarder.js.map +1 -1
- package/dist/preview-session.d.ts +1 -0
- package/dist/preview-session.d.ts.map +1 -1
- package/dist/preview-session.js +1 -0
- package/dist/preview-session.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-auth-ref.js +5 -2
- package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-types.d.ts +1 -0
- package/node_modules/@noodle-borg/agent-packaging/dist/product-skill-validation.js +4 -0
- package/node_modules/@noodle-borg/app-package/dist/schema.js +1 -0
- package/node_modules/@noodle-borg/app-package/dist/types.d.ts +1 -0
- package/node_modules/@noodle-borg/authoring/dist/server.d.ts +3 -1
- package/node_modules/@noodle-borg/authoring/dist/server.js +7 -6
- package/node_modules/@noodle-borg/capabilities/dist/index.js +7 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-local-authoring.js +8 -0
- package/node_modules/@noodle-borg/compiler/dist/app-package/compile.js +9 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/catalog-emission.js +3 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +2 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +2 -1
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +5 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -2
- package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -0
- package/node_modules/@noodle-borg/protocol/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/protocol/dist/index.js +1 -0
- package/node_modules/@noodle-borg/protocol/dist/mapping.d.ts +5 -1
- package/node_modules/@noodle-borg/protocol/dist/mapping.js +15 -7
- package/node_modules/@noodle-borg/protocol/dist/sdk-server.d.ts +2 -0
- package/node_modules/@noodle-borg/protocol/dist/tool-discovery.d.ts +14 -0
- package/node_modules/@noodle-borg/protocol/dist/tool-discovery.js +12 -0
- package/node_modules/@noodle-borg/protocol/dist/v2/server.js +3 -0
- package/node_modules/@noodle-borg/service/dist/build-info.js +4 -2
- package/node_modules/@noodle-borg/service/dist/deployment-access-mode.js +24 -0
- package/node_modules/@noodle-borg/service/dist/deployment-activation-precondition.js +27 -0
- package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +2 -2
- package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
- package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +26 -2
- package/node_modules/@noodle-borg/service/dist/registry-access.js +33 -31
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +97 -22
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +13 -1
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -7
- package/node_modules/@noodle-borg/service/dist/registry-state.js +6 -5
- package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -4
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +35 -28
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/registry.js +40 -39
- package/node_modules/@noodle-borg/service/dist/routes/access.js +4 -2
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +3 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +2 -2
- package/node_modules/@noodle-borg/service/dist/serve-resource-auth.js +1 -1
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +3 -8
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +6 -4
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -8
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -6
- package/node_modules/@noodle-borg/service/dist/store/records.js +13 -2
- package/node_modules/@noodle-borg/service/dist/store.d.ts +19 -8
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +2 -3
- package/node_modules/@noodle-borg/transport-http/dist/handler.js +3 -4
- package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.d.ts +3 -2
- package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +23 -12
- package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +3 -0
- package/node_modules/@noodle-borg/transport-http/dist/target-authentication.d.ts +15 -0
- package/node_modules/@noodle-borg/transport-http/dist/target-authentication.js +9 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +6 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +4 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.d.ts +41 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/service-info.js +26 -0
- package/package.json +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +0 -7
|
@@ -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 {
|
|
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 =
|
|
27
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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.
|
|
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'
|
|
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
|
|
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
|
|
46
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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.
|
|
163
|
-
|
|
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,
|
|
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,
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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.
|
|
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
|
}
|