@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.
- 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 +2 -0
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +27 -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-kit/package.json +1 -1
- 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/module/dist/customer-auth-access.d.ts +7 -0
- package/node_modules/@noodle-borg/module/dist/customer-auth-access.js +6 -0
- package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/index.js +1 -0
- 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/customer-auth-audience-binding.js +16 -5
- 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 +19 -0
- package/node_modules/@noodle-borg/service/dist/deployment-execution.js +2 -2
- package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +55 -0
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +15 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +47 -30
- package/node_modules/@noodle-borg/service/dist/registry-compile.js +42 -1
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +105 -15
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +15 -1
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +3 -6
- package/node_modules/@noodle-borg/service/dist/registry-state.js +15 -7
- package/node_modules/@noodle-borg/service/dist/registry-status.js +5 -3
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +43 -28
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +4 -2
- package/node_modules/@noodle-borg/service/dist/registry.js +68 -72
- package/node_modules/@noodle-borg/service/dist/routes/access.js +7 -0
- 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 -7
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +13 -4
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +3 -7
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +15 -6
- package/node_modules/@noodle-borg/service/dist/store/records.js +15 -2
- package/node_modules/@noodle-borg/service/dist/store.d.ts +23 -8
- package/node_modules/@noodle-borg/service/package.json +1 -1
- 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/control-plane.d.ts +23 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +34 -4
- 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 +2 -2
- 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
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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.
|
|
155
|
-
|
|
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
|
|
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,
|
|
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;
|
|
@@ -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
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
156
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
:
|
|
503
|
-
|
|
504
|
-
|
|
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.
|
|
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 {
|
|
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 (
|
|
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
|
|
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 {
|
|
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>;
|