@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.
Files changed (116) hide show
  1. package/dist/commands/deploy-status-ops.d.ts +2 -0
  2. package/dist/commands/deploy-status-ops.d.ts.map +1 -1
  3. package/dist/commands/deploy-status-ops.js +11 -0
  4. package/dist/commands/deploy-status-ops.js.map +1 -1
  5. package/dist/commands/solutions-coordination.d.ts +5 -0
  6. package/dist/commands/solutions-coordination.d.ts.map +1 -0
  7. package/dist/commands/solutions-coordination.js +94 -0
  8. package/dist/commands/solutions-coordination.js.map +1 -0
  9. package/dist/commands/solutions-ops.d.ts.map +1 -1
  10. package/dist/commands/solutions-ops.js +8 -4
  11. package/dist/commands/solutions-ops.js.map +1 -1
  12. package/node_modules/@noodle-borg/agent-kit/dist/curated/error-fixes.js +2 -0
  13. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  14. package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
  15. package/node_modules/@noodle-borg/agent-kit/dist/skill-authoring-refs.js +4 -1
  16. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  17. package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +4 -0
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.d.ts +4 -0
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-embed-store.js +6 -2
  20. package/node_modules/@noodle-borg/authoring/dist/config.d.ts +8 -2
  21. package/node_modules/@noodle-borg/authoring/dist/config.js +28 -1
  22. package/node_modules/@noodle-borg/authoring/dist/connectors.d.ts +37 -0
  23. package/node_modules/@noodle-borg/authoring/dist/connectors.js +4 -0
  24. package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
  25. package/node_modules/@noodle-borg/authoring/dist/server.js +1 -0
  26. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solution-flags.js +83 -0
  27. package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-solutions.js +70 -92
  28. package/node_modules/@noodle-borg/compiler/dist/artifact/operation-ref.d.ts +2 -0
  29. package/node_modules/@noodle-borg/compiler/dist/artifact/version.d.ts +4 -1
  30. package/node_modules/@noodle-borg/compiler/dist/artifact/version.js +4 -1
  31. package/node_modules/@noodle-borg/compiler/dist/catalog/types.d.ts +7 -0
  32. package/node_modules/@noodle-borg/compiler/dist/errors.d.ts +1 -1
  33. package/node_modules/@noodle-borg/compiler/dist/fulfilment-emit.js +15 -2
  34. package/node_modules/@noodle-borg/compiler/dist/nested-operations.js +71 -0
  35. package/node_modules/@noodle-borg/compute/dist/code-connector.js +14 -2
  36. package/node_modules/@noodle-borg/compute/dist/deterministic-helpers.js +138 -0
  37. package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +48 -0
  38. package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +39 -5
  39. package/node_modules/@noodle-borg/compute/dist/worker-entry.js +7 -0
  40. package/node_modules/@noodle-borg/compute/dist/worker-pool.js +14 -3
  41. package/node_modules/@noodle-borg/connector-defs/dist/compile-http.js +31 -2
  42. package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts +1 -1
  43. package/node_modules/@noodle-borg/connector-defs/dist/compile.js +20 -0
  44. package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +84 -0
  45. package/node_modules/@noodle-borg/connector-defs/dist/schema.js +44 -1
  46. package/node_modules/@noodle-borg/connector-http/dist/http-connector.d.ts +7 -0
  47. package/node_modules/@noodle-borg/connector-http/dist/http-connector.js +34 -6
  48. package/node_modules/@noodle-borg/connector-http/dist/http-response.d.ts +8 -1
  49. package/node_modules/@noodle-borg/connector-http/dist/http-response.js +19 -10
  50. package/node_modules/@noodle-borg/connector-http/dist/index.d.ts +1 -1
  51. package/node_modules/@noodle-borg/module/dist/customer-auth-access.d.ts +7 -0
  52. package/node_modules/@noodle-borg/module/dist/customer-auth-access.js +6 -0
  53. package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
  54. package/node_modules/@noodle-borg/module/dist/index.js +1 -0
  55. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +7 -1
  56. package/node_modules/@noodle-borg/runtime/dist/connector-snapshot.js +8 -0
  57. package/node_modules/@noodle-borg/runtime/dist/execute.d.ts +2 -0
  58. package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -0
  59. package/node_modules/@noodle-borg/runtime/dist/nested-operation-host.js +25 -0
  60. package/node_modules/@noodle-borg/runtime/dist/operation-coordination-execution.js +50 -0
  61. package/node_modules/@noodle-borg/runtime/dist/operation-coordination.d.ts +34 -0
  62. package/node_modules/@noodle-borg/runtime/dist/operation-evidence.d.ts +4 -0
  63. package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +173 -94
  64. package/node_modules/@noodle-borg/service/dist/application-activity.js +152 -18
  65. package/node_modules/@noodle-borg/service/dist/application-runtime-target.js +15 -2
  66. package/node_modules/@noodle-borg/service/dist/business-information/definition-resolver.js +19 -0
  67. package/node_modules/@noodle-borg/service/dist/business-information/in-memory-store.js +1 -1
  68. package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-blueprints.js +7 -0
  69. package/node_modules/@noodle-borg/service/dist/business-information/managed-solution-executable.js +11 -1
  70. package/node_modules/@noodle-borg/service/dist/business-information/model.js +2 -2
  71. package/node_modules/@noodle-borg/service/dist/business-information/portable.js +1 -1
  72. package/node_modules/@noodle-borg/service/dist/business-information/profiles.js +37 -1
  73. package/node_modules/@noodle-borg/service/dist/credential-binding-index.js +19 -1
  74. package/node_modules/@noodle-borg/service/dist/customer-auth-audience-binding.js +16 -5
  75. package/node_modules/@noodle-borg/service/dist/deployment-authentication.js +19 -0
  76. package/node_modules/@noodle-borg/service/dist/deployment-execution.js +1 -1
  77. package/node_modules/@noodle-borg/service/dist/deployment-record-version.js +31 -0
  78. package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +15 -1
  79. package/node_modules/@noodle-borg/service/dist/operation-coordination.js +185 -0
  80. package/node_modules/@noodle-borg/service/dist/operation-evidence.js +3 -1
  81. package/node_modules/@noodle-borg/service/dist/registry-access.js +18 -3
  82. package/node_modules/@noodle-borg/service/dist/registry-compile.js +42 -1
  83. package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +15 -0
  84. package/node_modules/@noodle-borg/service/dist/registry-rollback.js +2 -0
  85. package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +1 -0
  86. package/node_modules/@noodle-borg/service/dist/registry-state.js +12 -5
  87. package/node_modules/@noodle-borg/service/dist/registry-status.js +3 -0
  88. package/node_modules/@noodle-borg/service/dist/registry-targets.js +8 -0
  89. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
  90. package/node_modules/@noodle-borg/service/dist/registry.js +31 -36
  91. package/node_modules/@noodle-borg/service/dist/routes/access.js +5 -0
  92. package/node_modules/@noodle-borg/service/dist/routes/business-information-activity.js +48 -53
  93. package/node_modules/@noodle-borg/service/dist/routes/business-information-dispatch.js +2 -1
  94. package/node_modules/@noodle-borg/service/dist/routes/business-information-installation.js +51 -17
  95. package/node_modules/@noodle-borg/service/dist/routes/business-information-paths.js +6 -4
  96. package/node_modules/@noodle-borg/service/dist/routes/business-information-staff.js +4 -3
  97. package/node_modules/@noodle-borg/service/dist/routes/business-information.js +11 -6
  98. package/node_modules/@noodle-borg/service/dist/routes/deploy-public-embed.js +7 -2
  99. package/node_modules/@noodle-borg/service/dist/service.js +2 -1
  100. package/node_modules/@noodle-borg/service/dist/solution-installation-activation.js +58 -3
  101. package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +1 -0
  102. package/node_modules/@noodle-borg/service/dist/store/in-memory.js +10 -3
  103. package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +1 -0
  104. package/node_modules/@noodle-borg/service/dist/store/json-file.js +10 -3
  105. package/node_modules/@noodle-borg/service/dist/store/records.js +4 -2
  106. package/node_modules/@noodle-borg/service/dist/store.d.ts +5 -1
  107. package/node_modules/@noodle-borg/service/package.json +1 -1
  108. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.d.ts +52 -0
  109. package/node_modules/@noodle-borg/wire-contracts/dist/business-information.js +6 -0
  110. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +23 -0
  111. package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +34 -4
  112. package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +1 -0
  113. package/node_modules/@noodle-borg/wire-contracts/dist/index.js +1 -0
  114. package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.d.ts +59 -0
  115. package/node_modules/@noodle-borg/wire-contracts/dist/operation-coordination.js +51 -0
  116. package/package.json +2 -2
@@ -1,14 +1,15 @@
1
1
  import { readJsonBody, sendJson } from '@noodle-borg/transport-http';
2
2
  import { BusinessGrantCreateRequestSchema, formatWireError, ManagedRecordCreateRequestSchema, ManagedRecordMutationRequestSchema, ManagedRecordStatusSchema, SolutionInstallationCreateRequestSchema, SolutionInstallationIntakeRequestSchema, } from '@noodle-borg/wire-contracts';
3
3
  import { admitBusinessMutation, admitBusinessTarget, authorizeBusinessApi, } from '../business-api-admission.js';
4
+ import { resolveInstallDefinition } from '../business-information/definition-resolver.js';
4
5
  import { BUILT_IN_SOLUTION_PROFILES, businessGrantAllows, MANAGED_SOLUTION_PROFILE_KEYS, PayloadValidationError, validateCollectionEnabled, } from '../business-information/portable.js';
5
6
  import { sendForbidden } from '../http-util.js';
6
7
  import { loadExternalRecord, sendExternalRecordPage, sourceBindingKey, unsupportedExternalOperation, } from './business-information-external.js';
7
- import { resolveInstallDefinition, stableInstallationId, } from './business-information-installation.js';
8
+ import { installationProjection, retryInstallationActivation, stableInstallationId, } from './business-information-installation.js';
8
9
  import { createNativeRecord } from './business-information-record-create.js';
9
10
  import { cursorRequest, sendNativeRecordActivity } from './business-information-record-reads.js';
10
11
  import { expectedRevision, mutationPermission, parseNativeRecordQuery, parseBusinessPaging as parsePaging, requestIdempotencyKey, } from './business-information-request.js';
11
- import { collectionToWire, externalRecordToWire, grantToWire, installationToWire, profileToWire, recordDetailToWire, recordToWire, } from './business-information-wire.js';
12
+ import { collectionToWire, externalRecordToWire, grantToWire, profileToWire, recordDetailToWire, recordToWire, } from './business-information-wire.js';
12
13
  import { canManageMembers } from './org-admin.js';
13
14
  export function handleSolutionCatalog(req, res) {
14
15
  if (req.method !== 'GET') {
@@ -26,6 +27,8 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
26
27
  const identity = await requireIdentity(req, res, deps);
27
28
  if (identity === false)
28
29
  return;
30
+ if (ref.action === 'activate')
31
+ return retryInstallationActivation(req, res, ref, identity, deps);
29
32
  if (req.method === 'POST' && ref.installationId === undefined) {
30
33
  if (!(await canManageMembers(deps.controlPlane, ref.org, identity))) {
31
34
  return sendForbidden(res, 'organization owner required');
@@ -96,7 +99,7 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
96
99
  return sendJson(res, result.disposition === 'created' ? 201 : 200, {
97
100
  ok: true,
98
101
  data: {
99
- installation: installationToWire(result.installation, grant.role),
102
+ installation: await installationProjection(result.installation, grant.role, identity, deps),
100
103
  },
101
104
  });
102
105
  }
@@ -114,7 +117,7 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
114
117
  return sendJson(res, 200, {
115
118
  ok: true,
116
119
  data: {
117
- installations: permitted.map(({ installation, role }) => installationToWire(installation, role)),
120
+ installations: await Promise.all(permitted.map(({ installation, role }) => installationProjection(installation, role, identity, deps))),
118
121
  },
119
122
  });
120
123
  }
@@ -125,7 +128,7 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
125
128
  return sendJson(res, 200, {
126
129
  ok: true,
127
130
  data: {
128
- installation: installationToWire(authorized.installation, authorized.grant.role),
131
+ installation: await installationProjection(authorized.installation, authorized.grant.role, identity, deps),
129
132
  },
130
133
  });
131
134
  }
@@ -149,7 +152,9 @@ export async function handleSolutionInstallations(req, res, ref, deps) {
149
152
  return mutationFailure(res, result);
150
153
  return sendJson(res, 200, {
151
154
  ok: true,
152
- data: { installation: installationToWire(result.installation, authorized.grant.role) },
155
+ data: {
156
+ installation: await installationProjection(result.installation, authorized.grant.role, identity, deps),
157
+ },
153
158
  });
154
159
  }
155
160
  return methodNotAllowed(res);
@@ -9,7 +9,7 @@ import { publicSurfaceOf } from '@noodle-borg/assistant-gateway/portable';
9
9
  * Read from the just-activated artifact rather than the submitted manifest, so the id follows what the
10
10
  * runtime will actually serve — the same source minting and projection read.
11
11
  */
12
- export async function provisionPublicEmbed(registry, options, tenant, deploymentId) {
12
+ export async function provisionPublicEmbed(registry, options, tenant, deploymentId, allowRevokedReplacement = true) {
13
13
  const embeds = options.publicEmbeds;
14
14
  if (embeds === undefined || deploymentId === undefined)
15
15
  return undefined;
@@ -18,7 +18,12 @@ export async function provisionPublicEmbed(registry, options, tenant, deployment
18
18
  if (surface === undefined)
19
19
  return undefined;
20
20
  try {
21
- const record = await embeds.ensure({ ...tenant, surfaceMode: surface.mode, now: new Date() });
21
+ const record = await embeds.ensure({
22
+ ...tenant,
23
+ surfaceMode: surface.mode,
24
+ now: new Date(),
25
+ allowRevokedReplacement,
26
+ });
22
27
  return record.embedId;
23
28
  }
24
29
  catch {
@@ -96,7 +96,7 @@ export function createServiceHandler(registry, options = {}) {
96
96
  allowance: async (org, request) => moduleHost.resolveActivityHistoryAllowance?.(org, request),
97
97
  });
98
98
  const withIntentMode = createIntentTargetResolver(intentSettings, intentPreviewOrgs);
99
- const { activateInstallation, resolveRuntimeTarget, businessOnboarding } = createApplicationServingRuntime(registry, providerOptions, controlPlane, audit, activity);
99
+ const { activateInstallation, readInstallationActivation, resolveRuntimeTarget, businessOnboarding, } = createApplicationServingRuntime(registry, providerOptions, controlPlane, audit, activity);
100
100
  const runtimeTarget = async (target) => target === undefined ? undefined : resolveRuntimeTarget(target);
101
101
  const router = createMcpRouter(async (id) => withIntentMode(await runtimeTarget(await registry.getServing(id))), {
102
102
  logger,
@@ -282,6 +282,7 @@ export function createServiceHandler(registry, options = {}) {
282
282
  store: businessInformationStore,
283
283
  ...(businessOnboarding ? { businessOnboarding } : {}),
284
284
  activateInstallation,
285
+ readInstallationActivation,
285
286
  ...(activity === undefined ? {} : { activity }),
286
287
  ...(options.connectionRuntime === undefined
287
288
  ? {}
@@ -1,9 +1,44 @@
1
1
  import { sha256Canonical } from '@noodle-borg/app-package';
2
+ import { publicSurfaceOf } from '@noodle-borg/assistant-gateway/portable';
2
3
  import { ApplicationSettings, installationSettingsTarget } from './application-settings.js';
3
4
  import { privateDefinitionFromDeployment } from './business-information/definition-resolver.js';
4
5
  import { managedSolutionManifest } from './business-information/managed-solution-executable.js';
5
6
  import { BUSINESS_SETUP_MESSAGE, BusinessOnboarding } from './business-onboarding.js';
6
7
  import { executeAuthorizedDeployment, } from './deployment-execution.js';
8
+ import { provisionPublicEmbed } from './routes/deploy-public-embed.js';
9
+ /** Deployment/binding readiness only; channel model, origin and connection checks remain separate. */
10
+ export async function readSolutionInstallationActivation(installation, dependencies) {
11
+ try {
12
+ const { registry, businessInformationStore: store, options, controlPlane } = dependencies;
13
+ const stored = await store?.getInstallation(installation.scope);
14
+ if (!store || !stored)
15
+ return 'unavailable';
16
+ const { org, app } = stored.scope;
17
+ const generation = await registry.getAppGeneration(org, app);
18
+ if ((await registry.getAppArchivedAt(org, app)) !== undefined ||
19
+ (stored.applicationGeneration !== 'pending' &&
20
+ (!generation ||
21
+ (stored.applicationGeneration !== undefined &&
22
+ stored.applicationGeneration !== generation))) ||
23
+ (options.businessOnboarding !== undefined &&
24
+ !(await new BusinessOnboarding(options.businessOnboarding, controlPlane, store).ready(stored))))
25
+ return 'unavailable';
26
+ const target = await registry.getActiveByTenant(stored.scope);
27
+ if (!target || stored.applicationGeneration === 'pending')
28
+ return 'pending';
29
+ if (publicSurfaceOf(target.served.artifact.server.assistant)) {
30
+ if (!options.publicEmbeds)
31
+ return 'unavailable';
32
+ const embeds = await options.publicEmbeds.list(stored.scope, { includeRevoked: true });
33
+ if (!embeds.some((embed) => embed.revokedAt === undefined))
34
+ return embeds.length > 0 ? 'unavailable' : 'pending';
35
+ }
36
+ return 'ready';
37
+ }
38
+ catch {
39
+ return 'unavailable';
40
+ }
41
+ }
7
42
  /** Called after installation authorization, and by the managed-runtime refresh boundary. Never copies configuration. */
8
43
  export async function activateSolutionInstallation(input, dependencies) {
9
44
  const { installation, actor } = input;
@@ -32,13 +67,32 @@ export async function activateSolutionInstallation(input, dependencies) {
32
67
  !(await store.bindApplication(installation.scope, generation ?? ''))) {
33
68
  return reject(409, 'installation_application_unavailable', 'The retained installation requires application ownership recovery before activation.');
34
69
  }
35
- const bind = async (deploymentId) => {
70
+ const bind = async (deploymentId, reconcileEmbed) => {
36
71
  const currentGeneration = await dependencies.registry.getAppGeneration(tenant.org, tenant.app);
37
72
  if (store &&
38
73
  (!currentGeneration || !(await store.bindApplication(installation.scope, currentGeneration))))
39
74
  return reject(409, 'installation_application_unavailable', 'Application activation was interrupted; retained records remain accessible.');
40
75
  if ((installation.definition.variables?.length ?? 0) > 0)
41
76
  await new ApplicationSettings(dependencies.registry.configStore).initialize(installationSettingsTarget(installation));
77
+ const target = await dependencies.registry.get(deploymentId);
78
+ if (input.purpose !== 'runtime-refresh' &&
79
+ dependencies.options.publicEmbeds &&
80
+ publicSurfaceOf(target?.served.artifact.server.assistant)) {
81
+ try {
82
+ if (reconcileEmbed)
83
+ await provisionPublicEmbed(dependencies.registry, dependencies.options, tenant, deploymentId, false);
84
+ const embeds = await dependencies.options.publicEmbeds.list(tenant, {
85
+ includeRevoked: true,
86
+ });
87
+ if (!embeds.some((embed) => embed.revokedAt === undefined))
88
+ return embeds.length > 0
89
+ ? reject(409, 'installation_embed_revoked', 'The assistant was deliberately revoked. Use explicit channel recovery; activation does not replace revoked access.')
90
+ : reject(503, 'installation_activation_incomplete', 'The installation is saved. Retry activation to finish assistant setup.');
91
+ }
92
+ catch {
93
+ return reject(503, 'installation_activation_incomplete', 'The installation is saved. Retry activation when assistant setup is available.');
94
+ }
95
+ }
42
96
  return { ok: true, deploymentId };
43
97
  };
44
98
  const source = await executableSource(installation, dependencies.registry);
@@ -63,7 +117,7 @@ export async function activateSolutionInstallation(input, dependencies) {
63
117
  currentSource.manifest === source.value.manifest &&
64
118
  currentSource.connectors === source.value.connectors &&
65
119
  JSON.stringify(currentSource.hostedAssets) === JSON.stringify(source.value.hostedAssets)) {
66
- return bind(current.deploymentId);
120
+ return bind(current.deploymentId, true);
67
121
  }
68
122
  const key = sha256Canonical({
69
123
  scope: installation.scope,
@@ -84,6 +138,7 @@ export async function activateSolutionInstallation(input, dependencies) {
84
138
  ? {}
85
139
  : { orgMembershipSources: current.orgMembershipSources }),
86
140
  deploymentSource: 'api',
141
+ allowRevokedEmbedReplacement: false,
87
142
  idempotencyKey: key,
88
143
  }, dependencies);
89
144
  if (!deployed.ok)
@@ -95,7 +150,7 @@ export async function activateSolutionInstallation(input, dependencies) {
95
150
  : 'installation_activation_failed',
96
151
  message: 'The installation is saved, but its application could not be activated. Retry installation after resolving the deployment requirement.',
97
152
  };
98
- return bind(deployed.result.deploymentId);
153
+ return bind(deployed.result.deploymentId, false);
99
154
  }
100
155
  async function executableSource(installation, registry) {
101
156
  const reference = installation.definition.reference;
@@ -12,6 +12,7 @@ export declare class InMemoryArtifactStore implements ArtifactStore {
12
12
  findActiveCustomerAuthAudienceConflict(ref: TenantRef, auth: TenantAuthConfig): Promise<TenantRef | undefined>;
13
13
  activateDeployment(ref: TenantRef, deploymentId: string, precondition?: {
14
14
  readonly expectedAccessMode: AccessMode | undefined;
15
+ readonly expectedSchemaVersion?: number;
15
16
  readonly serverAuth?: TenantAuthConfig;
16
17
  }, _options?: {
17
18
  readonly automationId?: string;
@@ -1,7 +1,8 @@
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
4
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
5
+ import { assertDeploymentAppendVersion } from '../deployment-record-version.js';
5
6
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
6
7
  import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
7
8
  import { validateSlug, validateTenantRef } from './validate.js';
@@ -10,6 +11,7 @@ export class InMemoryArtifactStore {
10
11
  #records = new Map();
11
12
  #productionEnvironments = new Map();
12
13
  async append(record) {
14
+ assertDeploymentAppendVersion([...this.#records.values()], record);
13
15
  const existing = this.#records.get(record.deploymentId);
14
16
  if (existing !== undefined)
15
17
  assertSameAppPackageSnapshot(existing, record);
@@ -83,17 +85,22 @@ export class InMemoryArtifactStore {
83
85
  if (target === undefined || !sameTenantRecord(target, safe))
84
86
  return Promise.resolve(undefined);
85
87
  assertDeploymentActivationUnlocked([...this.#records.values()], target);
86
- if (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode) {
88
+ if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1) ||
89
+ (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode)) {
87
90
  return Promise.resolve(undefined);
88
91
  }
89
92
  const previousActive = [...this.#records.values()]
90
93
  .filter((record) => record.active && sameDeploymentScope(record, target))
91
94
  .sort((a, b) => b.deploymentVersion - a.deploymentVersion)[0];
95
+ if (precondition === undefined && previousActive !== undefined) {
96
+ assertDeploymentAppendVersion([previousActive], { ...target, active: true });
97
+ }
92
98
  const alreadyActive = previousActive?.deploymentId === target.deploymentId;
93
99
  const activated = {
94
100
  ...target,
95
101
  active: true,
96
- ...(target.accessMode === 'customers' && precondition?.serverAuth !== undefined
102
+ ...(requiresCustomerAuthProjection(target, precondition?.serverAuth) &&
103
+ precondition?.serverAuth !== undefined
97
104
  ? { serverAuth: precondition.serverAuth }
98
105
  : {}),
99
106
  };
@@ -18,6 +18,7 @@ export declare class JsonFileArtifactStore implements ArtifactStore {
18
18
  updateActiveAccess(ref: TenantRef, deploymentId: string, input: ActiveAccessUpdateInput): Promise<DeployRecord | undefined>;
19
19
  activateDeployment(ref: TenantRef, deploymentId: string, precondition?: {
20
20
  readonly expectedAccessMode: AccessMode | undefined;
21
+ readonly expectedSchemaVersion?: number;
21
22
  readonly serverAuth?: TenantAuthConfig;
22
23
  }): Promise<DeploymentActivationResult | undefined>;
23
24
  loadAll(): Promise<readonly DeployRecord[]>;
@@ -3,8 +3,9 @@ import { mkdir, readdir, readFile, rename, rm, writeFile } from 'node:fs/promise
3
3
  import { join } from 'node:path';
4
4
  import { normalizeServerVersion } from '@noodle-borg/module';
5
5
  import { assertSameAppPackageSnapshot, sanitizeDeployRecordAppPackageSnapshot, } from '../app-package-snapshot.js';
6
- import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, } from '../customer-auth-audience-binding.js';
6
+ import { assertCustomerAuthRestorePrecondition, assertUniqueActiveCustomerAuthAudienceBindings, findActiveCustomerAuthAudienceConflict, requiresCustomerAuthProjection, } from '../customer-auth-audience-binding.js';
7
7
  import { assertDeploymentActivationUnlocked, assertDeploymentAppendUnlocked, } from '../deployment-lock.js';
8
+ import { assertDeploymentAppendVersion } from '../deployment-record-version.js';
8
9
  import { defaultActiveRecord, sameDeploymentScope, sameTenantRecord, } from '../deployment-versioning.js';
9
10
  import { appArchivedAt, deploymentSummary, matchesDeploymentFilter, paginateAppSummaries, planAppArchive, planAppRestore, planArchivedAppSweep, preserveDeploymentOwnerState, resolveActiveAccessUpdate, resolveProductionEnvironment, summarizeApps, summarizeEnvs, validateDeploymentListFilter, withoutArchiveStamp, } from './records.js';
10
11
  import { DEPLOYMENT_ID_PATTERN, validateSlug, validateTenantRef } from './validate.js';
@@ -34,6 +35,7 @@ export class JsonFileArtifactStore {
34
35
  throw new Error(`invalid deploymentId for persistence: "${record.deploymentId}"`);
35
36
  }
36
37
  const records = await this.loadAll();
38
+ assertDeploymentAppendVersion(records, record);
37
39
  const existing = records.find((candidate) => candidate.deploymentId === record.deploymentId);
38
40
  if (existing !== undefined)
39
41
  assertSameAppPackageSnapshot(existing, record);
@@ -127,7 +129,8 @@ export class JsonFileArtifactStore {
127
129
  const target = await this.get(deploymentId);
128
130
  if (target === undefined || !sameTenantRecord(target, safe))
129
131
  return undefined;
130
- if (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode) {
132
+ if (target.schemaVersion !== (precondition?.expectedSchemaVersion ?? 1) ||
133
+ (precondition !== undefined && target.accessMode !== precondition.expectedAccessMode)) {
131
134
  return undefined;
132
135
  }
133
136
  const records = await this.loadAll();
@@ -135,11 +138,15 @@ export class JsonFileArtifactStore {
135
138
  const previousActive = records
136
139
  .filter((record) => record.active && sameDeploymentScope(record, target))
137
140
  .sort((a, b) => b.deploymentVersion - a.deploymentVersion)[0];
141
+ if (precondition === undefined && previousActive !== undefined) {
142
+ assertDeploymentAppendVersion([previousActive], { ...target, active: true });
143
+ }
138
144
  const alreadyActive = previousActive?.deploymentId === target.deploymentId;
139
145
  const activated = {
140
146
  ...target,
141
147
  active: true,
142
- ...(target.accessMode === 'customers' && precondition?.serverAuth !== undefined
148
+ ...(requiresCustomerAuthProjection(target, precondition?.serverAuth) &&
149
+ precondition?.serverAuth !== undefined
143
150
  ? { serverAuth: precondition.serverAuth }
144
151
  : {}),
145
152
  };
@@ -5,6 +5,7 @@
5
5
  * own persistence and feed their own raw records/anchors through these pure functions so every
6
6
  * backend groups, ranks, and sorts identically.
7
7
  */
8
+ import { requiresCustomerAuthProjection } from '../customer-auth-audience-binding.js';
8
9
  import { sameTenantRecord } from '../deployment-versioning.js';
9
10
  import { deploymentOwnerSubject } from '../registry-helpers.js';
10
11
  import { validateSlug } from './validate.js';
@@ -139,17 +140,18 @@ export function resolveActiveAccessUpdate(record, ref, input) {
139
140
  record.archivedAt !== undefined ||
140
141
  !sameTenantRecord(record, ref) ||
141
142
  record.accessMode !== input.expectedAccessMode ||
143
+ record.schemaVersion !== (input.expectedSchemaVersion ?? 1) ||
142
144
  deploymentOwnerSubject(record) !== input.expectedOwnerSubject)
143
145
  return undefined;
144
146
  if ((record.accessMode ?? 'owner-only') === input.accessMode &&
145
147
  (input.ownerSubject === undefined || deploymentOwnerSubject(record) === input.ownerSubject) &&
146
- input.accessMode !== 'customers')
148
+ !requiresCustomerAuthProjection({ ...record, accessMode: input.accessMode }, input.serverAuth))
147
149
  return record;
148
150
  return {
149
151
  ...record,
150
152
  accessMode: input.accessMode,
151
153
  ...(input.ownerSubject !== undefined ? { ownerSubject: input.ownerSubject } : {}),
152
- ...(input.accessMode === 'customers' && input.serverAuth !== undefined
154
+ ...(requiresCustomerAuthProjection({ ...record, accessMode: input.accessMode }, input.serverAuth) && input.serverAuth !== undefined
153
155
  ? { serverAuth: input.serverAuth }
154
156
  : {}),
155
157
  };
@@ -3,7 +3,7 @@ import type { OrganizationStore } from '@noodle-borg/control-plane/portable';
3
3
  import type { OrgMembershipSource } from '@noodle-borg/module';
4
4
  import type { SealedSecret } from '@noodle-borg/runtime';
5
5
  import type { AccessMode } from '@noodle-borg/transport-http';
6
- import type { DeploymentSource } from '@noodle-borg/wire-contracts';
6
+ import type { DeploymentAuthentication, DeploymentSource } from '@noodle-borg/wire-contracts';
7
7
  import type { AppPackageSnapshotV1 } from './app-package-snapshot.js';
8
8
  import type { ProductionEnvironmentChange } from './store/environment-production.js';
9
9
  export type { ActiveMcpSubdomainClaim, ChangeMcpSubdomainInput, CreateOrgWithOwnerInput, McpSubdomainMutationResult, McpSubdomainSetting, OrgDomainRecord, OrgInvitationRecord, OrgMemberRecord, OrgOpenAIAppsChallengeRecord, OrgRecord, OrgRole, PersonalWorkspaceProvisionInput, PersonalWorkspaceProvisionResult, SignupAllowlistKind, SignupAllowlistRecord, WelcomeEmailRecord, } from '@noodle-borg/control-plane/portable';
@@ -241,6 +241,7 @@ export interface DeploymentStatus {
241
241
  readonly createdByEmail?: string;
242
242
  readonly ownerSubject?: string;
243
243
  readonly accessMode: AccessMode;
244
+ readonly authentication?: DeploymentAuthentication;
244
245
  readonly deploymentLock?: DeploymentLockMetadata;
245
246
  };
246
247
  readonly health: {
@@ -262,6 +263,8 @@ export interface ActiveAccessUpdateInput {
262
263
  /** Desired owner mutation. Omission preserves the current effective owner; clearing is unsupported. */
263
264
  readonly ownerSubject?: string;
264
265
  readonly expectedAccessMode: AccessMode | undefined;
266
+ /** Older callers omit this and are restricted to version-1 records. */
267
+ readonly expectedSchemaVersion?: number;
265
268
  readonly expectedOwnerSubject: string | undefined;
266
269
  /** Compiler-authoritative projection persisted atomically when enabling customer access. */
267
270
  readonly serverAuth?: TenantAuthConfig;
@@ -303,6 +306,7 @@ export interface ArtifactStore {
303
306
  findActiveCustomerAuthAudienceConflict?(ref: TenantRef, auth: TenantAuthConfig): Promise<TenantRef | undefined>;
304
307
  activateDeployment(ref: TenantRef, deploymentId: string, precondition?: {
305
308
  readonly expectedAccessMode: AccessMode | undefined;
309
+ readonly expectedSchemaVersion?: number;
306
310
  /** Compiler-authoritative projection persisted atomically when activating customer access. */
307
311
  readonly serverAuth?: TenantAuthConfig;
308
312
  }, options?: {
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
41
  "@noodle-borg/admission-limits": "0.0.0",
42
- "@noodle-borg/agent-kit": "0.101.0",
42
+ "@noodle-borg/agent-kit": "0.102.0",
43
43
  "@noodle-borg/app-package": "0.0.0",
44
44
  "@noodle-borg/assistant-gateway": "0.0.0",
45
45
  "@noodle-borg/auth": "0.0.0",
@@ -427,6 +427,18 @@ export declare const SolutionInstallationIntakeRequestSchema: z.ZodObject<{
427
427
  expectedRevision: z.ZodNumber;
428
428
  }, z.core.$strict>;
429
429
  export type SolutionInstallationIntakeRequest = z.infer<typeof SolutionInstallationIntakeRequestSchema>;
430
+ export declare const SolutionInstallationActivateRequestSchema: z.ZodObject<{}, z.core.$strict>;
431
+ export declare const SolutionInstallationActivationSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
432
+ state: z.ZodLiteral<"pending">;
433
+ canRetry: z.ZodBoolean;
434
+ }, z.core.$strict>, z.ZodObject<{
435
+ state: z.ZodEnum<{
436
+ unavailable: "unavailable";
437
+ ready: "ready";
438
+ }>;
439
+ canRetry: z.ZodLiteral<false>;
440
+ }, z.core.$strict>], "state">;
441
+ export type SolutionInstallationActivation = z.infer<typeof SolutionInstallationActivationSchema>;
430
442
  export declare const ResolvedManagedSolutionDefinitionSchema: z.ZodObject<{
431
443
  kind: z.ZodLiteral<"managed">;
432
444
  definitionId: z.ZodEnum<{
@@ -574,6 +586,16 @@ export declare const SolutionInstallationSchema: z.ZodObject<{
574
586
  profileId: z.ZodOptional<z.ZodEnum<{
575
587
  b2b_saas: "b2b_saas";
576
588
  }>>;
589
+ activation: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
590
+ state: z.ZodLiteral<"pending">;
591
+ canRetry: z.ZodBoolean;
592
+ }, z.core.$strict>, z.ZodObject<{
593
+ state: z.ZodEnum<{
594
+ unavailable: "unavailable";
595
+ ready: "ready";
596
+ }>;
597
+ canRetry: z.ZodLiteral<false>;
598
+ }, z.core.$strict>], "state">>;
577
599
  id: z.ZodString;
578
600
  organizationId: z.ZodString;
579
601
  appSlug: z.ZodString;
@@ -690,6 +712,16 @@ export declare const SolutionInstallationResponseSchema: z.ZodObject<{
690
712
  profileId: z.ZodOptional<z.ZodEnum<{
691
713
  b2b_saas: "b2b_saas";
692
714
  }>>;
715
+ activation: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
716
+ state: z.ZodLiteral<"pending">;
717
+ canRetry: z.ZodBoolean;
718
+ }, z.core.$strict>, z.ZodObject<{
719
+ state: z.ZodEnum<{
720
+ unavailable: "unavailable";
721
+ ready: "ready";
722
+ }>;
723
+ canRetry: z.ZodLiteral<false>;
724
+ }, z.core.$strict>], "state">>;
693
725
  id: z.ZodString;
694
726
  organizationId: z.ZodString;
695
727
  appSlug: z.ZodString;
@@ -808,6 +840,16 @@ export declare const SolutionInstallationListResponseSchema: z.ZodObject<{
808
840
  profileId: z.ZodOptional<z.ZodEnum<{
809
841
  b2b_saas: "b2b_saas";
810
842
  }>>;
843
+ activation: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
844
+ state: z.ZodLiteral<"pending">;
845
+ canRetry: z.ZodBoolean;
846
+ }, z.core.$strict>, z.ZodObject<{
847
+ state: z.ZodEnum<{
848
+ unavailable: "unavailable";
849
+ ready: "ready";
850
+ }>;
851
+ canRetry: z.ZodLiteral<false>;
852
+ }, z.core.$strict>], "state">>;
811
853
  id: z.ZodString;
812
854
  organizationId: z.ZodString;
813
855
  appSlug: z.ZodString;
@@ -1136,6 +1178,16 @@ export declare const BusinessInvitationAcceptResponseSchema: z.ZodObject<{
1136
1178
  profileId: z.ZodOptional<z.ZodEnum<{
1137
1179
  b2b_saas: "b2b_saas";
1138
1180
  }>>;
1181
+ activation: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
1182
+ state: z.ZodLiteral<"pending">;
1183
+ canRetry: z.ZodBoolean;
1184
+ }, z.core.$strict>, z.ZodObject<{
1185
+ state: z.ZodEnum<{
1186
+ unavailable: "unavailable";
1187
+ ready: "ready";
1188
+ }>;
1189
+ canRetry: z.ZodLiteral<false>;
1190
+ }, z.core.$strict>], "state">>;
1139
1191
  id: z.ZodString;
1140
1192
  organizationId: z.ZodString;
1141
1193
  appSlug: z.ZodString;
@@ -153,7 +153,13 @@ export const SolutionInstallationIntakeRequestSchema = z.strictObject({
153
153
  active: z.boolean(),
154
154
  expectedRevision: positiveRevision,
155
155
  });
156
+ export const SolutionInstallationActivateRequestSchema = z.strictObject({});
157
+ export const SolutionInstallationActivationSchema = z.discriminatedUnion('state', [
158
+ z.strictObject({ state: z.literal('pending'), canRetry: z.boolean() }),
159
+ z.strictObject({ state: z.enum(['ready', 'unavailable']), canRetry: z.literal(false) }),
160
+ ]);
156
161
  const installationShape = {
162
+ activation: SolutionInstallationActivationSchema.optional(),
157
163
  id,
158
164
  organizationId: id,
159
165
  appSlug: slug,
@@ -1,5 +1,12 @@
1
1
  /** Strict service-output contracts for control-plane reads and mutations (ADR 0128). */
2
2
  import { z } from 'zod';
3
+ /** Effective authority enforcing caller authentication for one deployment. */
4
+ export declare const deploymentAuthenticationSchema: z.ZodEnum<{
5
+ customer: "customer";
6
+ none: "none";
7
+ platform: "platform";
8
+ }>;
9
+ export type DeploymentAuthentication = z.infer<typeof deploymentAuthenticationSchema>;
3
10
  /** Exact OAuth subject bound by an operator to an owner-only deployment. */
4
11
  export declare const deploymentOwnerSubjectSchema: z.ZodString;
5
12
  export declare const accessUpdateRequestSchema: z.ZodObject<{
@@ -32,6 +39,11 @@ export declare const accessUpdateResponseSchema: z.ZodObject<{
32
39
  mixed: "mixed";
33
40
  customers: "customers";
34
41
  }>;
42
+ authentication: z.ZodOptional<z.ZodEnum<{
43
+ customer: "customer";
44
+ none: "none";
45
+ platform: "platform";
46
+ }>>;
35
47
  ownerSubject: z.ZodOptional<z.ZodString>;
36
48
  }, z.core.$strict>;
37
49
  previousAccessMode: z.ZodEnum<{
@@ -45,6 +57,7 @@ export declare const accessUpdateResponseSchema: z.ZodObject<{
45
57
  previousOwnerSubject: z.ZodOptional<z.ZodString>;
46
58
  accessChanged: z.ZodBoolean;
47
59
  ownerChanged: z.ZodBoolean;
60
+ policyChanged: z.ZodOptional<z.ZodBoolean>;
48
61
  changed: z.ZodBoolean;
49
62
  }, z.core.$strict>;
50
63
  export type AccessUpdateResponse = z.output<typeof accessUpdateResponseSchema>;
@@ -71,6 +84,11 @@ export declare const accessUpdateClientResponseSchema: z.ZodPipe<z.ZodObject<{
71
84
  mixed: "mixed";
72
85
  customers: "customers";
73
86
  }>;
87
+ authentication: z.ZodOptional<z.ZodEnum<{
88
+ customer: "customer";
89
+ none: "none";
90
+ platform: "platform";
91
+ }>>;
74
92
  ownerSubject: z.ZodOptional<z.ZodString>;
75
93
  }, z.core.$strip>;
76
94
  previousAccessMode: z.ZodEnum<{
@@ -84,6 +102,7 @@ export declare const accessUpdateClientResponseSchema: z.ZodPipe<z.ZodObject<{
84
102
  previousOwnerSubject: z.ZodOptional<z.ZodString>;
85
103
  accessChanged: z.ZodOptional<z.ZodBoolean>;
86
104
  ownerChanged: z.ZodOptional<z.ZodBoolean>;
105
+ policyChanged: z.ZodOptional<z.ZodBoolean>;
87
106
  changed: z.ZodBoolean;
88
107
  }, z.core.$strip>, z.ZodTransform<{
89
108
  accessChanged: boolean;
@@ -97,12 +116,14 @@ export declare const accessUpdateClientResponseSchema: z.ZodPipe<z.ZodObject<{
97
116
  deploymentId: string;
98
117
  accessMode: "owner-only" | "org-members" | "authenticated" | "public" | "mixed" | "customers";
99
118
  serverVersion?: string | undefined;
119
+ authentication?: "customer" | "none" | "platform" | undefined;
100
120
  ownerSubject?: string | undefined;
101
121
  };
102
122
  previousAccessMode: "owner-only" | "org-members" | "authenticated" | "public" | "mixed" | "customers";
103
123
  changed: boolean;
104
124
  previousOwnerSubject?: string | undefined;
105
125
  ownerChanged?: boolean | undefined;
126
+ policyChanged?: boolean | undefined;
106
127
  }, {
107
128
  ok: true;
108
129
  target: {
@@ -114,6 +135,7 @@ export declare const accessUpdateClientResponseSchema: z.ZodPipe<z.ZodObject<{
114
135
  deploymentId: string;
115
136
  accessMode: "owner-only" | "org-members" | "authenticated" | "public" | "mixed" | "customers";
116
137
  serverVersion?: string | undefined;
138
+ authentication?: "customer" | "none" | "platform" | undefined;
117
139
  ownerSubject?: string | undefined;
118
140
  };
119
141
  previousAccessMode: "owner-only" | "org-members" | "authenticated" | "public" | "mixed" | "customers";
@@ -121,6 +143,7 @@ export declare const accessUpdateClientResponseSchema: z.ZodPipe<z.ZodObject<{
121
143
  previousOwnerSubject?: string | undefined;
122
144
  accessChanged?: boolean | undefined;
123
145
  ownerChanged?: boolean | undefined;
146
+ policyChanged?: boolean | undefined;
124
147
  }>>;
125
148
  export type AccessUpdateClientResponse = z.output<typeof accessUpdateClientResponseSchema>;
126
149
  export declare const WhoamiIdentityResponseSchema: z.ZodObject<{