@noodleseed/one 0.164.0 → 0.166.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/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 +11 -0
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/commands/solutions-coordination.d.ts +5 -0
- package/dist/commands/solutions-coordination.d.ts.map +1 -0
- package/dist/commands/solutions-coordination.js +94 -0
- package/dist/commands/solutions-coordination.js.map +1 -0
- package/dist/commands/solutions-ops.d.ts.map +1 -1
- package/dist/commands/solutions-ops.js +8 -4
- package/dist/commands/solutions-ops.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +4 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +4 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.d.ts +4 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +6 -2
- package/node_modules/@noodle-borg/authoring/dist/config.d.ts +8 -2
- package/node_modules/@noodle-borg/authoring/dist/config.js +28 -1
- package/node_modules/@noodle-borg/authoring/dist/connectors.d.ts +37 -0
- package/node_modules/@noodle-borg/authoring/dist/connectors.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/server.js +1 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-flags.js +83 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +70 -92
- package/node_modules/@noodle-borg/compiler/dist/artifact/operation-ref.d.ts +2 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +4 -1
- package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +4 -1
- package/node_modules/@noodle-borg/compiler/dist/catalog/types.d.ts +7 -0
- package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
- package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +15 -2
- package/node_modules/@noodle-borg/compiler/dist/nested-operations.js +71 -0
- package/node_modules/@noodle-borg/compute/dist/code-connector.js +14 -2
- package/node_modules/@noodle-borg/compute/dist/deterministic-helpers.js +138 -0
- package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +48 -0
- package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +39 -5
- package/node_modules/@noodle-borg/compute/dist/worker-entry.js +7 -0
- package/node_modules/@noodle-borg/compute/dist/worker-pool.js +14 -3
- package/node_modules/@noodle-borg/connector-defs/dist/compile-http.js +31 -2
- package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js +20 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +84 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js +44 -1
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.d.ts +7 -0
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.js +34 -6
- package/node_modules/@noodle-borg/connector-http/dist/http-response.d.ts +8 -1
- package/node_modules/@noodle-borg/connector-http/dist/http-response.js +19 -10
- package/node_modules/@noodle-borg/connector-http/dist/index.d.ts +1 -1
- 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/runtime/dist/connector/types.d.ts +7 -1
- package/node_modules/@noodle-borg/runtime/dist/connector-snapshot.js +8 -0
- package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +2 -0
- package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/runtime/dist/nested-operation-host.js +25 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-coordination-execution.js +50 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-coordination.d.ts +34 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-evidence.d.ts +4 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +173 -94
- package/node_modules/@noodle-borg/service/dist/application-activity.js +152 -18
- package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +15 -2
- package/node_modules/@noodle-borg/service/dist/business-information/definition-resolver.js +19 -0
- package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-blueprints.js +7 -0
- package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-executable.js +11 -1
- package/node_modules/@noodle-borg/service/dist/business-information/model.js +2 -2
- package/node_modules/@noodle-borg/service/dist/business-information/portable.js +1 -1
- package/node_modules/@noodle-borg/service/dist/business-information/profiles.js +37 -1
- package/node_modules/@noodle-borg/service/dist/credential-binding-index.js +19 -1
- package/node_modules/@noodle-borg/service/dist/customer-auth-audience-binding.js +16 -5
- package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +19 -0
- package/node_modules/@noodle-borg/service/dist/deployment-execution.js +1 -1
- package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +31 -0
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +15 -1
- package/node_modules/@noodle-borg/service/dist/operation-coordination.js +185 -0
- package/node_modules/@noodle-borg/service/dist/operation-evidence.js +3 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +18 -3
- package/node_modules/@noodle-borg/service/dist/registry-compile.js +42 -1
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +15 -0
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +2 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.js +12 -5
- package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -0
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +8 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/registry.js +31 -36
- package/node_modules/@noodle-borg/service/dist/routes/access.js +5 -0
- package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +48 -53
- package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +51 -17
- package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +6 -4
- package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +4 -3
- package/node_modules/@noodle-borg/service/dist/routes/business-information.js +11 -6
- package/node_modules/@noodle-borg/service/dist/routes/deploy-public-embed.js +7 -2
- package/node_modules/@noodle-borg/service/dist/service.js +2 -1
- package/node_modules/@noodle-borg/service/dist/solution-installation-activation.js +58 -3
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +10 -3
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +10 -3
- package/node_modules/@noodle-borg/service/dist/store/records.js +4 -2
- package/node_modules/@noodle-borg/service/dist/store.d.ts +5 -1
- package/node_modules/@noodle-borg/service/package.json +1 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +52 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +6 -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 +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.d.ts +59 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.js +51 -0
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
2
|
+
import { deploymentRecordVersionError } from './deployment-record-version.js';
|
|
2
3
|
import { deployAccessRequiresActor, deployerRequiredError, deploymentOwnerSubject, emptyMembershipSourcesError, membershipSourcesRequireOrgMembersError, missingCapabilityErrors, serverAuthRequiredError, } from './registry-helpers.js';
|
|
4
|
+
import { activeRecord, activeRecordVersion } from './registry-state.js';
|
|
3
5
|
import { idempotentDeploymentId, mintDeploymentId } from './registry-targets.js';
|
|
4
6
|
/** Serialize candidate validation/activation with hierarchical configuration writers. */
|
|
5
7
|
export async function withDeploymentConfiguration(config, org, work) {
|
|
@@ -19,6 +21,15 @@ export async function withDeploymentConfiguration(config, org, work) {
|
|
|
19
21
|
throw error;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
24
|
+
/** Validate the exact destination scope before compilation or publication side effects. */
|
|
25
|
+
export async function deploymentWriteVersionError(state, tenant, serverVersion) {
|
|
26
|
+
const active = serverVersion === undefined
|
|
27
|
+
? await activeRecord(state, tenant)
|
|
28
|
+
: await activeRecordVersion(state, tenant, serverVersion);
|
|
29
|
+
return active !== undefined && active.serverVersion === serverVersion
|
|
30
|
+
? deploymentRecordVersionError(active)
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
22
33
|
export async function preflightRegistryDeploy(input) {
|
|
23
34
|
const { actor, accessMode, orgMembershipSources } = input.options;
|
|
24
35
|
if (input.options.serverVersion !== undefined) {
|
|
@@ -67,6 +78,10 @@ export async function resolveDeployAttempt(input) {
|
|
|
67
78
|
const existing = (await input.store?.get(deploymentId)) ?? input.records.get(deploymentId);
|
|
68
79
|
if (existing === undefined)
|
|
69
80
|
return { deploymentId };
|
|
81
|
+
const versionError = deploymentRecordVersionError(existing);
|
|
82
|
+
if (versionError !== undefined) {
|
|
83
|
+
return { deploymentId, replay: { ok: false, errors: [versionError] } };
|
|
84
|
+
}
|
|
70
85
|
if (!sameIdempotentDeploy(existing, input.tenant, input.manifest, input.options)) {
|
|
71
86
|
return {
|
|
72
87
|
deploymentId,
|
|
@@ -75,10 +75,12 @@ export async function rollbackDeployment(state, ref, deploymentId, compilePersis
|
|
|
75
75
|
const activation = state.store
|
|
76
76
|
? await state.store.activateDeployment(ref, deploymentId, {
|
|
77
77
|
expectedAccessMode: target.accessMode,
|
|
78
|
+
expectedSchemaVersion: target.schemaVersion,
|
|
78
79
|
...(compiledAuth !== undefined ? { serverAuth: compiledAuth } : {}),
|
|
79
80
|
})
|
|
80
81
|
: activateInMemory(state.records, ref, deploymentId, {
|
|
81
82
|
expectedAccessMode: target.accessMode,
|
|
83
|
+
expectedSchemaVersion: target.schemaVersion,
|
|
82
84
|
...(compiledAuth !== undefined ? { serverAuth: compiledAuth } : {}),
|
|
83
85
|
});
|
|
84
86
|
if (activation === undefined) {
|
|
@@ -36,6 +36,7 @@ export declare function persistDeployRecord(view: RegistryStateView, record: Dep
|
|
|
36
36
|
/** No-store activation fallback (moved verbatim from `ServerRegistry.#activateInMemory`). */
|
|
37
37
|
export declare function activateInMemory(records: Map<string, DeployRecord>, ref: TenantRef, deploymentId: string, precondition?: {
|
|
38
38
|
readonly expectedAccessMode: DeployRecord['accessMode'];
|
|
39
|
+
readonly expectedSchemaVersion?: number;
|
|
39
40
|
readonly serverAuth?: DeployRecord['serverAuth'];
|
|
40
41
|
}): {
|
|
41
42
|
readonly active: DeployRecord;
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* durable store first, then reconcile the in-process caches so no instance keeps serving stale state.
|
|
6
6
|
*/
|
|
7
7
|
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
8
|
-
import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceProjectionError, hasExactCustomerAuthProjection, } from './customer-auth-audience-binding.js';
|
|
8
|
+
import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceProjectionError, hasExactCustomerAuthProjection, requiresCustomerAuthProjection, } from './customer-auth-audience-binding.js';
|
|
9
9
|
import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from './deployment-lock.js';
|
|
10
|
+
import { assertDeploymentAppendVersion } from './deployment-record-version.js';
|
|
10
11
|
import { defaultActiveRecord } from './deployment-versioning.js';
|
|
11
12
|
import { recordMatchesTenant, tenantDeploymentKey, tenantKey } from './registry-targets.js';
|
|
12
13
|
import { appArchivedAt, deploymentSummary, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, resolveProductionEnvironment, summarizeApps, summarizeEnvs, withoutArchiveStamp, } from './store/records.js';
|
|
@@ -34,6 +35,7 @@ export async function persistDeployRecord(view, record, tenant, activatesNow) {
|
|
|
34
35
|
return persisted;
|
|
35
36
|
}
|
|
36
37
|
else {
|
|
38
|
+
assertDeploymentAppendVersion([...view.records.values()], record);
|
|
37
39
|
if (!assertDeploymentAppendUnlocked([...view.records.values()], record)) {
|
|
38
40
|
return view.records.get(record.deploymentId);
|
|
39
41
|
}
|
|
@@ -66,18 +68,23 @@ export function activateInMemory(records, ref, deploymentId, precondition) {
|
|
|
66
68
|
if (target === undefined || !recordMatchesTenant(target, ref))
|
|
67
69
|
return undefined;
|
|
68
70
|
assertDeploymentActivationUnlocked([...records.values()], target);
|
|
69
|
-
if (
|
|
71
|
+
if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1) ||
|
|
72
|
+
(precondition !== undefined && target.accessMode !== precondition.expectedAccessMode)) {
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
72
75
|
const previousActive = [...records.values()]
|
|
73
76
|
.filter((record) => record.active && recordMatchesTenant(record, ref))
|
|
74
77
|
.filter((record) => record.serverVersion === target.serverVersion)
|
|
75
78
|
.sort((a, b) => b.deploymentVersion - a.deploymentVersion)[0];
|
|
79
|
+
if (precondition === undefined && previousActive !== undefined) {
|
|
80
|
+
assertDeploymentAppendVersion([previousActive], { ...target, active: true });
|
|
81
|
+
}
|
|
76
82
|
const alreadyActive = previousActive?.deploymentId === deploymentId;
|
|
77
83
|
const activated = {
|
|
78
84
|
...target,
|
|
79
85
|
active: true,
|
|
80
|
-
...(target
|
|
86
|
+
...(requiresCustomerAuthProjection(target, precondition?.serverAuth) &&
|
|
87
|
+
precondition?.serverAuth !== undefined
|
|
81
88
|
? { serverAuth: precondition.serverAuth }
|
|
82
89
|
: {}),
|
|
83
90
|
};
|
|
@@ -207,7 +214,7 @@ export async function registryRestoreApp(state, org, app, compileRecord) {
|
|
|
207
214
|
return undefined;
|
|
208
215
|
const projections = [];
|
|
209
216
|
for (const record of plan.clear) {
|
|
210
|
-
if (!record.active || record
|
|
217
|
+
if (!record.active || !requiresCustomerAuthProjection(record))
|
|
211
218
|
continue;
|
|
212
219
|
const built = await compileRecord(record);
|
|
213
220
|
const compiledAuth = built.ok ? built.served.artifact.server.auth : undefined;
|
|
@@ -243,7 +250,7 @@ export async function registryCustomerAuthRestoreProjections(state, deploymentId
|
|
|
243
250
|
: state.store.get(deploymentId)));
|
|
244
251
|
const projections = [];
|
|
245
252
|
for (const record of records) {
|
|
246
|
-
if (record === undefined || !record.active || record
|
|
253
|
+
if (record === undefined || !record.active || !requiresCustomerAuthProjection(record))
|
|
247
254
|
continue;
|
|
248
255
|
const built = await compileRecord(record);
|
|
249
256
|
const compiledAuth = built.ok ? built.served.artifact.server.auth : undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
2
|
+
import { deploymentAuthenticationFor } from './deployment-authentication.js';
|
|
2
3
|
import { deploymentOwnerSubject, missingSecretNames } from './registry-helpers.js';
|
|
3
4
|
/** Project one compiled deployment into the control-plane status contract. */
|
|
4
5
|
export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions) {
|
|
@@ -8,6 +9,7 @@ export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions
|
|
|
8
9
|
built.served.artifact.server.auth === undefined;
|
|
9
10
|
const unhealthy = missingCustomerAuth || (!built.ok && missingSecrets.length === 0);
|
|
10
11
|
const ownerSubject = deploymentOwnerSubject(record);
|
|
12
|
+
const authentication = deploymentAuthenticationFor(record);
|
|
11
13
|
return {
|
|
12
14
|
target: ref,
|
|
13
15
|
deployment: {
|
|
@@ -20,6 +22,7 @@ export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions
|
|
|
20
22
|
...(record.createdByEmail !== undefined ? { createdByEmail: record.createdByEmail } : {}),
|
|
21
23
|
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
22
24
|
accessMode: record.accessMode ?? 'owner-only',
|
|
25
|
+
...(authentication !== undefined ? { authentication } : {}),
|
|
23
26
|
...(record.deploymentLock !== undefined
|
|
24
27
|
? {
|
|
25
28
|
deploymentLock: {
|
|
@@ -2,6 +2,7 @@ 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
4
|
import { hasExactCustomerAuthProjection } 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';
|
|
7
8
|
/** Replace only the platform record port; unrelated compiled runtime ports retain their authority. */
|
|
@@ -34,6 +35,9 @@ export function rebindNativeRecords(target, record, factory) {
|
|
|
34
35
|
* front-door checks. Legacy alpha records without an identity access mode are not served.
|
|
35
36
|
*/
|
|
36
37
|
export function servedTargetFor(record, served, customerVerifierFactory) {
|
|
38
|
+
const versionError = deploymentRecordVersionError(record);
|
|
39
|
+
if (versionError !== undefined)
|
|
40
|
+
throw new Error(versionError.code);
|
|
37
41
|
const accessMode = record.accessMode;
|
|
38
42
|
if (accessMode === undefined) {
|
|
39
43
|
throw new Error(`deployment ${record.deploymentId} has unsupported legacy access mode`);
|
|
@@ -102,6 +106,10 @@ function bindAppPackageSnapshot(served, value) {
|
|
|
102
106
|
export async function targetForPersistedRecord(record, state) {
|
|
103
107
|
const cached = state.servers.get(record.deploymentId);
|
|
104
108
|
state.records.set(record.deploymentId, record);
|
|
109
|
+
if (deploymentRecordVersionError(record) !== undefined) {
|
|
110
|
+
state.servers.delete(record.deploymentId);
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
105
113
|
if (cached === undefined)
|
|
106
114
|
return state.load?.();
|
|
107
115
|
if (!hasExactCustomerAuthProjection(record, cached.served.artifact.server.auth)) {
|
|
@@ -73,7 +73,7 @@ export type RunDeployResult = DeployResult | {
|
|
|
73
73
|
readonly deploymentVersion: number;
|
|
74
74
|
readonly serverVersion?: string;
|
|
75
75
|
};
|
|
76
|
-
export type AccessUpdateFailureCode = 'no_active_deployment' | 'server_auth_required' | 'customer_auth_audience_conflict' | 'public_user_context_conflict' | 'owner_identity_required' | 'access_update_conflict';
|
|
76
|
+
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
77
|
export type AccessUpdateResult = {
|
|
78
78
|
readonly ok: true;
|
|
79
79
|
readonly changed: boolean;
|
|
@@ -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 { 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 { deploymentWriteVersionError, preflightRegistryDeploy, resolveDeployAttempt, withDeploymentConfiguration, } from './registry-deploy-transaction.js';
|
|
10
|
+
import { deploymentOwnerSubject } from './registry-helpers.js';
|
|
11
11
|
import { registryDeploymentPackage, renderDeploymentPackageSnapshot } from './registry-package.js';
|
|
12
12
|
import { recoverRegistryRecords } from './registry-recover.js';
|
|
13
13
|
import { rollbackDeployment, rollbackWithMappedErrors } from './registry-rollback.js';
|
|
14
14
|
import { activeRecord, activeRecordVersion, persistDeployRecord, reconcilePlatformAccountResetRegistryCache, registryAppArchivedAt, registryArchiveApp, registryCustomerAuthRestoreProjections, registryGetApp, registryGetDeployment, registryGetEnvironment, registryListApps, registryListEnvironments, registryRestoreApp, registrySetProductionEnvironment, registrySweepArchived, setRegistryDeploymentLock, } from './registry-state.js';
|
|
15
15
|
import { deploymentStatusFor } from './registry-status.js';
|
|
16
|
-
import { deploymentSourceFor, emptySecretEnvelope, rebindNativeRecords,
|
|
16
|
+
import { deploymentSourceFor, emptySecretEnvelope, 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';
|
|
@@ -119,6 +119,9 @@ export class ServerRegistry {
|
|
|
119
119
|
if (active?.deploymentLock !== undefined)
|
|
120
120
|
return deploymentLockedConflict();
|
|
121
121
|
}
|
|
122
|
+
const versionError = await deploymentWriteVersionError(this.#stateView(), safeTenant, safeServerVersion);
|
|
123
|
+
if (versionError !== undefined)
|
|
124
|
+
return { ok: false, errors: [versionError] };
|
|
122
125
|
const preflight = await preflightRegistryDeploy({
|
|
123
126
|
options,
|
|
124
127
|
serviceCapabilities: this.#serviceCapabilities,
|
|
@@ -186,6 +189,8 @@ export class ServerRegistry {
|
|
|
186
189
|
if (error instanceof CustomerAuthAudienceConflictError) {
|
|
187
190
|
return customerAuthAudienceConflictFailure();
|
|
188
191
|
}
|
|
192
|
+
if (error instanceof UnsupportedDeploymentRecordVersionError)
|
|
193
|
+
return unsupportedDeploymentRecordFailure();
|
|
189
194
|
if (error instanceof DeploymentLockedError)
|
|
190
195
|
return deploymentLockedConflict();
|
|
191
196
|
throw error;
|
|
@@ -211,6 +216,8 @@ export class ServerRegistry {
|
|
|
211
216
|
if (error instanceof CustomerAuthAudienceConflictError) {
|
|
212
217
|
return customerAuthAudienceConflictFailure();
|
|
213
218
|
}
|
|
219
|
+
if (error instanceof UnsupportedDeploymentRecordVersionError)
|
|
220
|
+
return unsupportedDeploymentRecordFailure();
|
|
214
221
|
if (error instanceof DeploymentLockedError)
|
|
215
222
|
return deploymentLockedConflict();
|
|
216
223
|
throw error;
|
|
@@ -262,6 +269,9 @@ export class ServerRegistry {
|
|
|
262
269
|
if (active?.deploymentLock !== undefined)
|
|
263
270
|
return deploymentLockedPreflight();
|
|
264
271
|
}
|
|
272
|
+
const versionError = await deploymentWriteVersionError(this.#stateView(), safeTenant, safeServerVersion);
|
|
273
|
+
if (versionError !== undefined)
|
|
274
|
+
return { ok: false, errors: [versionError] };
|
|
265
275
|
const result = await preflightRegistryDeploy({
|
|
266
276
|
options,
|
|
267
277
|
serviceCapabilities: this.#serviceCapabilities,
|
|
@@ -305,16 +315,20 @@ export class ServerRegistry {
|
|
|
305
315
|
}, { tenant, manifest, connectors, hostedAssets, deploymentId, renderAppPackage });
|
|
306
316
|
}
|
|
307
317
|
#compilePersistedRecord(record) {
|
|
308
|
-
return this.#compileTarget(
|
|
309
|
-
? undefined
|
|
310
|
-
: normalizePersistedConnectorsForCompile(record.connectors), record.hostedAssets, false, record.deploymentId);
|
|
318
|
+
return compilePersistedRegistryRecord(record, this.#compileTarget.bind(this));
|
|
311
319
|
}
|
|
312
320
|
async get(deploymentId) {
|
|
313
321
|
if (this.#records.get(deploymentId)?.archivedAt !== undefined)
|
|
314
322
|
return undefined;
|
|
315
323
|
const cached = this.#servers.get(deploymentId);
|
|
316
|
-
if (cached)
|
|
317
|
-
|
|
324
|
+
if (cached) {
|
|
325
|
+
const record = this.#store
|
|
326
|
+
? await this.#store.get(deploymentId)
|
|
327
|
+
: this.#records.get(deploymentId);
|
|
328
|
+
return record === undefined || record.archivedAt !== undefined
|
|
329
|
+
? undefined
|
|
330
|
+
: this.#targetForPersistedRecord(record);
|
|
331
|
+
}
|
|
318
332
|
const inflight = this.#inflight.get(deploymentId);
|
|
319
333
|
if (inflight)
|
|
320
334
|
return inflight;
|
|
@@ -495,32 +509,13 @@ export class ServerRegistry {
|
|
|
495
509
|
});
|
|
496
510
|
}
|
|
497
511
|
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;
|
|
512
|
+
return loadPersistedRegistryTarget(source, {
|
|
513
|
+
state: this.#stateView(),
|
|
514
|
+
compile: (record) => this.#compilePersistedRecord(record),
|
|
515
|
+
capabilities: this.#serviceCapabilities,
|
|
516
|
+
customerVerifierFactory: this.#customerVerifierFactory,
|
|
517
|
+
hasCustomerAuthConflict: (record, auth) => this.#hasCustomerAuthAudienceConflict(record, auth),
|
|
518
|
+
});
|
|
524
519
|
}
|
|
525
520
|
async #hasCustomerAuthAudienceConflict(record, auth) {
|
|
526
521
|
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,7 @@ 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 ? { authentication, policyChanged: false } : {}),
|
|
66
69
|
changed: result.changed,
|
|
67
70
|
},
|
|
68
71
|
});
|
|
@@ -75,6 +78,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
75
78
|
? { serverVersion: result.record.serverVersion }
|
|
76
79
|
: {}),
|
|
77
80
|
accessMode,
|
|
81
|
+
...(authentication !== undefined ? { authentication } : {}),
|
|
78
82
|
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
79
83
|
},
|
|
80
84
|
previousAccessMode: result.previousAccessMode,
|
|
@@ -83,6 +87,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
83
87
|
: {}),
|
|
84
88
|
accessChanged: result.accessChanged,
|
|
85
89
|
ownerChanged: result.ownerChanged,
|
|
90
|
+
...(authentication !== undefined ? { policyChanged: false } : {}),
|
|
86
91
|
changed: result.changed,
|
|
87
92
|
});
|
|
88
93
|
}
|
|
@@ -1,83 +1,78 @@
|
|
|
1
1
|
import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
|
|
2
|
-
import {
|
|
3
|
-
import { ActivityPolicyError } from '../application-activity.js';
|
|
2
|
+
import { ActivityPolicyError, } from '../application-activity.js';
|
|
4
3
|
import { businessGrantAllows } from '../business-information/model.js';
|
|
5
4
|
import { requireIdentity, requireInstallationPermission, } from './business-information.js';
|
|
6
|
-
import { parseBusinessPaging } from './business-information-request.js';
|
|
7
5
|
export async function handleApplicationActivity(req, res, url, ref, deps) {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const coordination = ref.action === 'coordination';
|
|
7
|
+
const suffix = url.pathname.split('/').at(-1);
|
|
8
|
+
const action = coordination
|
|
9
|
+
? suffix === 'resolve'
|
|
10
|
+
? 'coordination-resolve'
|
|
11
|
+
: 'coordination-list'
|
|
12
|
+
: suffix === 'settings'
|
|
13
|
+
? req.method === 'PATCH'
|
|
14
|
+
? 'save-settings'
|
|
15
|
+
: 'settings'
|
|
16
|
+
: suffix === 'preview' || suffix === 'export'
|
|
17
|
+
? suffix
|
|
18
|
+
: 'list';
|
|
19
|
+
const allowed = coordination
|
|
20
|
+
? suffix === 'resolve'
|
|
21
|
+
? ['POST']
|
|
22
|
+
: ['GET']
|
|
23
|
+
: suffix === 'settings'
|
|
24
|
+
? ['GET', 'PATCH']
|
|
25
|
+
: ['GET'];
|
|
26
|
+
if (!allowed.includes(req.method ?? '')) {
|
|
27
|
+
res.setHeader('allow', allowed.join(', '));
|
|
13
28
|
return sendJson(res, 405, { error: 'method not allowed' });
|
|
14
29
|
}
|
|
15
30
|
const identity = await requireIdentity(req, res, deps);
|
|
16
31
|
if (identity === false)
|
|
17
32
|
return;
|
|
18
|
-
const permission =
|
|
33
|
+
const permission = coordination || action === 'save-settings'
|
|
19
34
|
? 'installation:administer'
|
|
20
|
-
:
|
|
35
|
+
: action === 'export'
|
|
21
36
|
? 'records:export'
|
|
22
37
|
: 'records:read';
|
|
23
|
-
const
|
|
38
|
+
const authorize = () => requireInstallationPermission(res, ref, identity, permission, deps);
|
|
39
|
+
const authorized = await authorize();
|
|
24
40
|
if (!authorized)
|
|
25
41
|
return;
|
|
26
|
-
const reply = async (body) => {
|
|
27
|
-
if (await requireInstallationPermission(res, ref, identity, permission, deps))
|
|
28
|
-
sendJson(res, 200, body);
|
|
29
|
-
};
|
|
30
42
|
res.setHeader('cache-control', 'private, no-store');
|
|
31
43
|
try {
|
|
32
|
-
const allowedQuery = settings || preview ? [] : ['limit', 'cursor'];
|
|
33
|
-
for (const key of url.searchParams.keys())
|
|
34
|
-
if (!allowedQuery.includes(key) || url.searchParams.getAll(key).length !== 1)
|
|
35
|
-
throw new ActivityPolicyError('activity_invalid');
|
|
36
44
|
if (!deps.activity)
|
|
37
|
-
throw new ActivityPolicyError('activity_unavailable');
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
const
|
|
41
|
-
if (!
|
|
42
|
-
return sendJson(res,
|
|
43
|
-
|
|
44
|
-
if (!
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
throw new ActivityPolicyError(coordination ? 'coordination_unavailable' : 'activity_unavailable');
|
|
46
|
+
let body;
|
|
47
|
+
if (req.method !== 'GET') {
|
|
48
|
+
const parsed = await readJsonBody(req, deps.maxBody);
|
|
49
|
+
if (!parsed.ok)
|
|
50
|
+
return sendJson(res, parsed.status, { error: parsed.error });
|
|
51
|
+
body = parsed.value;
|
|
52
|
+
if (!(await authorize()))
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const result = await deps.activity.project(authorized.scope, action, {
|
|
56
|
+
parameters: [...url.searchParams.entries()],
|
|
57
|
+
body,
|
|
58
|
+
reviewer: identity.subject,
|
|
59
|
+
canEdit: businessGrantAllows(authorized.grant, 'installation:administer'),
|
|
60
|
+
});
|
|
61
|
+
if ('audit' in result)
|
|
47
62
|
await deps.audit?.emit({
|
|
48
|
-
|
|
63
|
+
...result.audit,
|
|
49
64
|
org: ref.org,
|
|
50
65
|
app: authorized.scope.app,
|
|
51
66
|
env: authorized.scope.env,
|
|
52
67
|
actorSubject: identity.subject,
|
|
53
|
-
details: { retentionDays: data.retentionDays },
|
|
54
68
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (preview)
|
|
58
|
-
return reply(ApplicationActivityPreviewResponseSchema.parse({
|
|
59
|
-
ok: true,
|
|
60
|
-
data: await deps.activity.preview(authorized.scope),
|
|
61
|
-
}));
|
|
62
|
-
if (settings)
|
|
63
|
-
return reply(ApplicationActivitySettingsResponseSchema.parse({
|
|
64
|
-
ok: true,
|
|
65
|
-
data: (await deps.activity.settings(authorized.scope, canEdit)).projection,
|
|
66
|
-
}));
|
|
67
|
-
const paging = parseBusinessPaging(url, 100);
|
|
68
|
-
if (!paging.ok)
|
|
69
|
-
return sendJson(res, 400, { error: paging.error });
|
|
70
|
-
const data = await deps.activity.page(authorized.scope, {
|
|
71
|
-
purpose: exporting ? 'export' : 'list',
|
|
72
|
-
limit: paging.value.limit ?? 50,
|
|
73
|
-
...(paging.value.cursor === undefined ? {} : { cursor: paging.value.cursor }),
|
|
74
|
-
});
|
|
75
|
-
return reply(ApplicationActivityListResponseSchema.parse({ ok: true, data }));
|
|
69
|
+
if (await authorize())
|
|
70
|
+
sendJson(res, 200, result.response);
|
|
76
71
|
}
|
|
77
72
|
catch (error) {
|
|
78
73
|
if (!(error instanceof ActivityPolicyError))
|
|
79
74
|
throw error;
|
|
80
|
-
sendJson(res, error.code
|
|
75
|
+
sendJson(res, error.code.endsWith('_unavailable') ? 503 : error.code.endsWith('_conflict') ? 409 : 400, { code: error.code, error: error.message });
|
|
81
76
|
}
|
|
82
77
|
}
|
|
83
78
|
//# sourceMappingURL=business-information-activity.js.map
|
|
@@ -58,7 +58,8 @@ export function dispatchBusinessInformationRoutes(req, res, url, deps) {
|
|
|
58
58
|
return false;
|
|
59
59
|
if (installationRef.action === 'notice')
|
|
60
60
|
return run(req, res, deps, () => handleBusinessNotice(req, res, installationRef, deps));
|
|
61
|
-
if (installationRef.action === 'activity'
|
|
61
|
+
if ((installationRef.action === 'activity' || installationRef.action === 'coordination') &&
|
|
62
|
+
installationRef.collection === undefined)
|
|
62
63
|
return run(req, res, deps, () => handleApplicationActivity(req, res, url, installationRef, deps));
|
|
63
64
|
if (installationRef.action === 'connections')
|
|
64
65
|
return run(req, res, deps, () => handleApplicationConnections(req, res, installationRef, deps));
|
|
@@ -1,24 +1,58 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
|
|
3
|
+
import { SolutionInstallationActivateRequestSchema } from '@noodle-borg/wire-contracts';
|
|
4
|
+
import { sendForbidden } from '../http-util.js';
|
|
5
|
+
import { invalid, methodNotAllowed, requireInstallationPermission, } from './business-information.js';
|
|
6
|
+
import { installationToWire } from './business-information-wire.js';
|
|
7
|
+
import { canManageMembers } from './org-admin.js';
|
|
3
8
|
export function stableInstallationId(org, app, env) {
|
|
4
9
|
return `ins-${createHash('sha256').update(`${org}\0${app}\0${env}`).digest('hex').slice(0, 24)}`;
|
|
5
10
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
/** Read current activation authority; intake is an independent operator setting. */
|
|
12
|
+
export async function installationProjection(installation, role, identity, deps) {
|
|
13
|
+
const state = (await deps.readInstallationActivation?.(installation)) ?? 'unavailable';
|
|
14
|
+
const canRetry = state === 'pending' &&
|
|
15
|
+
deps.activateInstallation !== undefined &&
|
|
16
|
+
role === 'administrator' &&
|
|
17
|
+
(await canManageMembers(deps.controlPlane, installation.scope.org, identity));
|
|
18
|
+
return {
|
|
19
|
+
...installationToWire(installation, role),
|
|
20
|
+
activation: state === 'pending' ? { state, canRetry } : { state, canRetry: false },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export async function retryInstallationActivation(req, res, ref, identity, deps) {
|
|
24
|
+
if (req.method !== 'POST')
|
|
25
|
+
return methodNotAllowed(res);
|
|
26
|
+
const authorized = await requireInstallationPermission(res, ref, identity, 'installation:administer', deps);
|
|
27
|
+
if (!authorized)
|
|
28
|
+
return;
|
|
29
|
+
if (!(await canManageMembers(deps.controlPlane, ref.org, identity)))
|
|
30
|
+
return sendForbidden(res, 'org owner required');
|
|
31
|
+
const body = await readJsonBody(req, deps.maxBody);
|
|
32
|
+
if (!body.ok)
|
|
33
|
+
return sendJson(res, body.status, { error: body.error });
|
|
34
|
+
const parsed = SolutionInstallationActivateRequestSchema.safeParse(body.value);
|
|
35
|
+
if (!parsed.success)
|
|
36
|
+
return invalid(res, parsed.error);
|
|
37
|
+
const result = await deps.activateInstallation?.({
|
|
38
|
+
installation: authorized.installation,
|
|
39
|
+
actor: identity,
|
|
22
40
|
});
|
|
41
|
+
if (!result?.ok)
|
|
42
|
+
return sendJson(res, result?.status ?? 503, {
|
|
43
|
+
ok: false,
|
|
44
|
+
code: result?.code ?? 'installation_activation_unavailable',
|
|
45
|
+
error: result?.message ?? 'The installation is saved. Retry activation when the service is ready.',
|
|
46
|
+
installationId: authorized.scope.installationId,
|
|
47
|
+
});
|
|
48
|
+
const installation = await installationProjection(authorized.installation, authorized.grant.role, identity, deps);
|
|
49
|
+
if (installation.activation?.state !== 'ready')
|
|
50
|
+
return sendJson(res, 503, {
|
|
51
|
+
ok: false,
|
|
52
|
+
code: 'installation_activation_unavailable',
|
|
53
|
+
error: 'The installation is saved, but readiness could not be established. Retry activation after the service setup is restored.',
|
|
54
|
+
installationId: authorized.scope.installationId,
|
|
55
|
+
});
|
|
56
|
+
return sendJson(res, 200, { ok: true, data: { installation } });
|
|
23
57
|
}
|
|
24
58
|
//# sourceMappingURL=business-information-installation.js.map
|
|
@@ -27,7 +27,7 @@ export function parseSolutionInstallationPath(pathname) {
|
|
|
27
27
|
...(connectionAction === undefined ? {} : { connectionAction }),
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(settings|notice|channels|activity(?:\/(?:settings|export|preview))?)$/.exec(pathname);
|
|
30
|
+
const application = /^\/v1\/orgs\/([^/]+)\/solution-installations\/([^/]+)\/(activate|settings|notice|channels|operations\/coordination(?:\/resolve)?|activity(?:\/(?:settings|export|preview))?)$/.exec(pathname);
|
|
31
31
|
if (application !== null) {
|
|
32
32
|
const org = decoded(application, 1);
|
|
33
33
|
const installationId = decoded(application, 2);
|
|
@@ -36,9 +36,11 @@ export function parseSolutionInstallationPath(pathname) {
|
|
|
36
36
|
: {
|
|
37
37
|
org,
|
|
38
38
|
installationId,
|
|
39
|
-
action: application[3]?.startsWith('
|
|
40
|
-
? '
|
|
41
|
-
: application[3]
|
|
39
|
+
action: application[3]?.startsWith('operations/')
|
|
40
|
+
? 'coordination'
|
|
41
|
+
: application[3]?.startsWith('activity')
|
|
42
|
+
? 'activity'
|
|
43
|
+
: application[3],
|
|
42
44
|
};
|
|
43
45
|
}
|
|
44
46
|
let match = /^\/v1\/orgs\/([^/]+)\/solution-installations$/.exec(pathname);
|
|
@@ -3,8 +3,9 @@ import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
|
|
|
3
3
|
import { BusinessInvitationCreateRequestSchema, BusinessInvitationRevokeRequestSchema, formatWireError, } from '@noodle-borg/wire-contracts';
|
|
4
4
|
import { hashToken } from '../oauth/tokens.js';
|
|
5
5
|
import { requireIdentity, requireInstallationPermission, } from './business-information.js';
|
|
6
|
+
import { installationProjection } from './business-information-installation.js';
|
|
6
7
|
import { parseBusinessPaging } from './business-information-request.js';
|
|
7
|
-
import { grantToWire,
|
|
8
|
+
import { grantToWire, invitationToWire } from './business-information-wire.js';
|
|
8
9
|
const HOUR_MS = 60 * 60 * 1_000;
|
|
9
10
|
export async function handleMySolutionInstallations(req, res, deps) {
|
|
10
11
|
if (req.method !== 'GET')
|
|
@@ -28,7 +29,7 @@ export async function handleMySolutionInstallations(req, res, deps) {
|
|
|
28
29
|
sendJson(res, 200, {
|
|
29
30
|
ok: true,
|
|
30
31
|
data: {
|
|
31
|
-
installations: visible.map(({ installation, grant }) =>
|
|
32
|
+
installations: await Promise.all(visible.map(({ installation, grant }) => installationProjection(installation, grant.role, identity, deps))),
|
|
32
33
|
...(joined.length > limit && visible.at(-1) !== undefined
|
|
33
34
|
? { nextCursor: encodeDiscoveryCursor(visible.at(-1)) }
|
|
34
35
|
: {}),
|
|
@@ -186,7 +187,7 @@ export async function handleBusinessInvitationAccept(req, res, rawToken, deps) {
|
|
|
186
187
|
sendJson(res, 201, {
|
|
187
188
|
ok: true,
|
|
188
189
|
data: {
|
|
189
|
-
installation:
|
|
190
|
+
installation: await installationProjection(installation, result.grant.role, identity, deps),
|
|
190
191
|
grant: grantToWire(result.grant),
|
|
191
192
|
},
|
|
192
193
|
});
|