@noodleseed/one 0.136.0 → 0.138.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/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +30 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- package/dist/commands/deploy-first-flow.d.ts +2 -0
- package/dist/commands/deploy-first-flow.d.ts.map +1 -1
- package/dist/commands/deploy-first-flow.js +46 -14
- package/dist/commands/deploy-first-flow.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +27 -1
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-preflight.d.ts +1 -0
- package/dist/commands/deploy-preflight.d.ts.map +1 -1
- package/dist/commands/deploy-preflight.js +1 -0
- package/dist/commands/deploy-preflight.js.map +1 -1
- package/dist/commands/deploy-status-ops.d.ts +1 -0
- package/dist/commands/deploy-status-ops.d.ts.map +1 -1
- package/dist/commands/deploy-status-ops.js +59 -4
- package/dist/commands/deploy-status-ops.js.map +1 -1
- package/dist/commands/deployments-ops.d.ts.map +1 -1
- package/dist/commands/deployments-ops.js +2 -0
- package/dist/commands/deployments-ops.js.map +1 -1
- package/dist/commands/diagnostics-ops.d.ts +1 -0
- package/dist/commands/diagnostics-ops.d.ts.map +1 -1
- package/dist/commands/diagnostics-ops.js +3 -0
- package/dist/commands/diagnostics-ops.js.map +1 -1
- package/dist/deploy.d.ts +3 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +4 -0
- package/dist/deploy.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/admission-limits/dist/envelope.js +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +4 -4
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +1 -0
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +22 -13
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +6 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +5 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-request.js +37 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/model-runtime.js +4 -0
- package/node_modules/@noodle-borg/{service/dist/assistant-model-stream.js → assistant-gateway/dist/model-stream.js} +34 -2
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-session.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.d.ts +4 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-turn.js +2 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.d.ts +6 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/surface-budget.js +11 -5
- package/node_modules/@noodle-borg/assistant-gateway/package.json +5 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +10 -2
- package/node_modules/@noodle-borg/authoring/dist/assistant.js +4 -0
- package/node_modules/@noodle-borg/authoring/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/authoring/dist/index.js +1 -1
- package/node_modules/@noodle-borg/authoring/dist/load-authored.js +1 -1
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +17 -0
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-hosted-deployment.js +14 -0
- package/node_modules/@noodle-borg/compiler/dist/artifact/types.d.ts +1 -7
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +20 -8
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +12 -8
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/rollback-deployment.d.ts +61 -0
- package/node_modules/@noodle-borg/{service/dist/operations → control-plane/dist}/rollback-deployment.js +3 -2
- package/node_modules/@noodle-borg/control-plane/dist/rollback-result.d.ts +19 -0
- package/node_modules/@noodle-borg/control-plane/package.json +3 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/contracts.js +4 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/app-overview.js +1 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/widgets/deployment-detail.js +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +1 -0
- package/node_modules/@noodle-borg/module/dist/deployment-automation.d.ts +1 -1
- package/node_modules/@noodle-borg/module/dist/endpoint-url.d.ts +11 -0
- package/node_modules/@noodle-borg/module/dist/endpoint-url.js +20 -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/observability/dist/assistant-usage.js +217 -0
- package/node_modules/@noodle-borg/observability/dist/index.js +2 -0
- package/node_modules/@noodle-borg/{service → observability}/dist/request-event-metrics.js +5 -2
- package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +6 -0
- package/node_modules/@noodle-borg/observability/dist/request-events.js +2 -0
- package/node_modules/@noodle-borg/service/dist/assistant-bindings.js +3 -1
- package/node_modules/@noodle-borg/service/dist/developer-mcp/control-plane-adapter.js +3 -3
- package/node_modules/@noodle-borg/service/dist/developer-mcp/inspection.js +2 -1
- package/node_modules/@noodle-borg/service/dist/registry-access.js +73 -24
- package/node_modules/@noodle-borg/service/dist/registry-deploy-transaction.js +5 -2
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +5 -9
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js +4 -18
- package/node_modules/@noodle-borg/service/dist/registry-rollback.js +3 -1
- package/node_modules/@noodle-borg/service/dist/registry-status.js +4 -1
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -4
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +11 -16
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +8 -5
- package/node_modules/@noodle-borg/service/dist/registry.js +8 -5
- package/node_modules/@noodle-borg/service/dist/routes/access.js +19 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics-dispatch.js +17 -1
- package/node_modules/@noodle-borg/service/dist/routes/analytics.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +73 -59
- package/node_modules/@noodle-borg/service/dist/routes/assistant-doctor.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-model-binding.js +28 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +18 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-turn.js +6 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-usage.js +42 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +20 -3
- package/node_modules/@noodle-borg/service/dist/routes/auth-doctor-live.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +49 -4
- package/node_modules/@noodle-borg/service/dist/routes/deploy-preflight.js +11 -1
- package/node_modules/@noodle-borg/service/dist/routes/endpoint-enrichment.js +2 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +6 -0
- package/node_modules/@noodle-borg/service/dist/store/in-memory.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/in-memory.js +9 -21
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +2 -5
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +8 -20
- package/node_modules/@noodle-borg/service/dist/store/records.js +39 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +21 -10
- package/node_modules/@noodle-borg/service/package.json +5 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.d.ts +120 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/control-plane.js +123 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/index.d.ts +4 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/index.js +15 -1
- package/node_modules/@noodleseed/assistant/README.md +43 -8
- package/node_modules/@noodleseed/assistant/package.json +16 -2
- package/package.json +2 -2
|
@@ -10,8 +10,11 @@ function hourBucket(iso) {
|
|
|
10
10
|
return `${iso.slice(0, 13)}:00:00.000Z`;
|
|
11
11
|
}
|
|
12
12
|
export function aggregateRequestEvents(events) {
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Assistant lifecycle records share the durable tenant stream but have their own aggregate. Keep
|
|
14
|
+
// the long-standing MCP metrics contract stable rather than counting one user turn as an MCP call.
|
|
15
|
+
const mcpEvents = events.filter((event) => event.method !== 'assistant');
|
|
16
|
+
const usage = mcpEvents.filter((event) => event.kind === 'usage');
|
|
17
|
+
const discovery = mcpEvents.length - usage.length;
|
|
15
18
|
const durations = usage.map((e) => e.durationMs).sort((a, b) => a - b);
|
|
16
19
|
let ok = 0;
|
|
17
20
|
let toolErrors = 0;
|
|
@@ -51,6 +51,10 @@ export async function ensureRequestEventSchema(pool) {
|
|
|
51
51
|
await pool.query(`
|
|
52
52
|
CREATE INDEX IF NOT EXISTS request_events_tenant_time_idx
|
|
53
53
|
ON request_events (org_slug, app_slug, environment, seq DESC)
|
|
54
|
+
`);
|
|
55
|
+
await pool.query(`
|
|
56
|
+
CREATE INDEX IF NOT EXISTS request_events_tenant_method_time_idx
|
|
57
|
+
ON request_events (org_slug, app_slug, environment, method, seq DESC)
|
|
54
58
|
`);
|
|
55
59
|
await pool.query(`
|
|
56
60
|
CREATE INDEX IF NOT EXISTS request_events_session_idx
|
|
@@ -128,6 +132,8 @@ export class PostgresRequestEventStore {
|
|
|
128
132
|
add('app_slug', filter.app);
|
|
129
133
|
if (filter.env !== undefined)
|
|
130
134
|
add('environment', filter.env);
|
|
135
|
+
if (filter.method !== undefined)
|
|
136
|
+
add('method', filter.method);
|
|
131
137
|
if (filter.outcome !== undefined)
|
|
132
138
|
add('outcome', filter.outcome);
|
|
133
139
|
if (filter.toolName !== undefined)
|
|
@@ -41,6 +41,8 @@ function matches(event, filter) {
|
|
|
41
41
|
return false;
|
|
42
42
|
if (filter.env !== undefined && event.env !== filter.env)
|
|
43
43
|
return false;
|
|
44
|
+
if (filter.method !== undefined && event.method !== filter.method)
|
|
45
|
+
return false;
|
|
44
46
|
if (filter.outcome !== undefined && event.outcome !== filter.outcome)
|
|
45
47
|
return false;
|
|
46
48
|
if (filter.toolName !== undefined && event.toolName !== filter.toolName)
|
|
@@ -3,7 +3,7 @@ import { missingSecretErrors, missingVariableErrors } from './registry-helpers.j
|
|
|
3
3
|
import { serverAuthSecretBindings } from './server-auth-bindings.js';
|
|
4
4
|
const VARIABLE = /\$\{env\.([A-Za-z0-9_]+)\}/g;
|
|
5
5
|
export function assistantSecretBindings(assistant) {
|
|
6
|
-
return assistant
|
|
6
|
+
return assistant?.model.kind === 'openai-compatible'
|
|
7
7
|
? [
|
|
8
8
|
{
|
|
9
9
|
connectorId: 'server.assistant',
|
|
@@ -17,6 +17,8 @@ export function assistantSecretBindings(assistant) {
|
|
|
17
17
|
export function assistantVariableBindings(assistant) {
|
|
18
18
|
if (!assistant)
|
|
19
19
|
return [];
|
|
20
|
+
if (assistant.model.kind !== 'openai-compatible')
|
|
21
|
+
return [];
|
|
20
22
|
const names = new Set();
|
|
21
23
|
for (const value of [assistant.model.baseUrl, assistant.model.model]) {
|
|
22
24
|
for (const match of value.matchAll(VARIABLE)) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { mcpSubdomainEndpointOptions } from '@noodle-borg/control-plane/portable';
|
|
1
|
+
import { mcpSubdomainEndpointOptions, rollbackDeploymentOperation, } from '@noodle-borg/control-plane/portable';
|
|
2
2
|
import { DeveloperControlPlaneError, } from '@noodle-borg/developer-mcp';
|
|
3
|
-
import {
|
|
4
|
-
import { aggregateRequestEvents } from '../request-event-metrics.js';
|
|
3
|
+
import { aggregateRequestEvents } from '@noodle-borg/observability';
|
|
5
4
|
import { inspectCompiledServiceDeployment, inspectServiceDeployment } from './inspection.js';
|
|
6
5
|
const METRICS_SCAN_LIMIT = 50_000;
|
|
7
6
|
const WINDOWS_MS = {
|
|
@@ -261,6 +260,7 @@ function mapDeployment(deployment) {
|
|
|
261
260
|
serverName: deployment.serverName,
|
|
262
261
|
createdAt: deployment.createdAt,
|
|
263
262
|
accessMode: deployment.accessMode,
|
|
263
|
+
...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
|
|
264
264
|
...(deployment.endpointUrl !== undefined ? { endpointUrl: deployment.endpointUrl } : {}),
|
|
265
265
|
};
|
|
266
266
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { tenantMcpUrl } from '
|
|
1
|
+
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
2
2
|
export async function inspectServiceDeployment(registry, tenant, baseUrl, endpointOptions = {}) {
|
|
3
3
|
const status = await registry.getStatus(tenant, baseUrl, undefined, endpointOptions);
|
|
4
4
|
if (status === undefined)
|
|
@@ -109,6 +109,7 @@ export function inspectCompiledServiceDeployment(deployment, artifact, baseUrl,
|
|
|
109
109
|
? {}
|
|
110
110
|
: { createdByEmail: deployment.createdByEmail }),
|
|
111
111
|
accessMode: deployment.accessMode,
|
|
112
|
+
...(deployment.ownerSubject !== undefined ? { ownerSubject: deployment.ownerSubject } : {}),
|
|
112
113
|
},
|
|
113
114
|
health: { state: 'ready', missingSecrets: [] },
|
|
114
115
|
surface,
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { assertUniqueActiveCustomerAuthAudienceBindings, CustomerAuthAudienceConflictError, } from './customer-auth-audience-binding.js';
|
|
2
|
+
import { deploymentOwnerSubject } from './registry-helpers.js';
|
|
2
3
|
import { activeRecord, activeRecordVersion } from './registry-state.js';
|
|
3
4
|
import { tenantDeploymentKey, tryServedTargetFor } from './registry-targets.js';
|
|
4
5
|
/** Registry-facing access transition with customer audience ownership preflight and atomic translation. */
|
|
5
|
-
export async function updateRegistryAccess(state, tenant,
|
|
6
|
-
if (
|
|
7
|
-
const record = serverVersion === undefined
|
|
6
|
+
export async function updateRegistryAccess(state, tenant, options, loadServed, customerVerifierFactory, hasCustomerAuthConflict) {
|
|
7
|
+
if (options.accessMode === 'customers') {
|
|
8
|
+
const record = options.serverVersion === undefined
|
|
8
9
|
? await activeRecord(state, tenant)
|
|
9
|
-
: await activeRecordVersion(state, tenant, serverVersion);
|
|
10
|
+
: await activeRecordVersion(state, tenant, options.serverVersion);
|
|
10
11
|
const served = record === undefined ? undefined : await loadServed(record.deploymentId);
|
|
11
12
|
const auth = served?.served.artifact.server.auth;
|
|
12
13
|
if (record !== undefined &&
|
|
@@ -16,7 +17,7 @@ export async function updateRegistryAccess(state, tenant, nextAccessMode, server
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
try {
|
|
19
|
-
return await updateActiveAccess(state, tenant,
|
|
20
|
+
return await updateActiveAccess(state, tenant, options, loadServed, customerVerifierFactory);
|
|
20
21
|
}
|
|
21
22
|
catch (error) {
|
|
22
23
|
if (error instanceof CustomerAuthAudienceConflictError) {
|
|
@@ -34,14 +35,19 @@ export function manifestUsesUserRoot(manifest) {
|
|
|
34
35
|
* access mode supplies the optimistic guard; in-memory state is reconciled only after the store accepts
|
|
35
36
|
* the compare-and-set.
|
|
36
37
|
*/
|
|
37
|
-
export async function updateActiveAccess(state, tenant,
|
|
38
|
-
const record = serverVersion === undefined
|
|
38
|
+
export async function updateActiveAccess(state, tenant, options, loadServed, customerVerifierFactory) {
|
|
39
|
+
const record = options.serverVersion === undefined
|
|
39
40
|
? await activeRecord(state, tenant)
|
|
40
|
-
: await activeRecordVersion(state, tenant, serverVersion);
|
|
41
|
+
: await activeRecordVersion(state, tenant, options.serverVersion);
|
|
41
42
|
if (record === undefined)
|
|
42
43
|
return noActiveDeployment();
|
|
44
|
+
const nextAccessMode = options.accessMode;
|
|
43
45
|
const previousAccessMode = record.accessMode ?? 'owner-only';
|
|
44
|
-
const
|
|
46
|
+
const previousOwnerSubject = deploymentOwnerSubject(record);
|
|
47
|
+
const nextOwnerSubject = options.ownerSubject ?? previousOwnerSubject;
|
|
48
|
+
const accessChanged = previousAccessMode !== nextAccessMode;
|
|
49
|
+
const ownerChanged = previousOwnerSubject !== nextOwnerSubject;
|
|
50
|
+
const served = accessChanged || ownerChanged || nextAccessMode === 'customers'
|
|
45
51
|
? await loadServed(record.deploymentId)
|
|
46
52
|
: undefined;
|
|
47
53
|
const serverAuth = served?.served.artifact.server.auth;
|
|
@@ -53,20 +59,33 @@ export async function updateActiveAccess(state, tenant, nextAccessMode, serverVe
|
|
|
53
59
|
message: 'Customer access requires server authentication.',
|
|
54
60
|
};
|
|
55
61
|
}
|
|
56
|
-
if (
|
|
62
|
+
if (!accessChanged && !ownerChanged) {
|
|
57
63
|
const committed = state.store === undefined
|
|
58
|
-
? commitInMemoryAccessUpdate(state, tenant, record,
|
|
59
|
-
|
|
64
|
+
? commitInMemoryAccessUpdate(state, tenant, record, {
|
|
65
|
+
accessMode: nextAccessMode,
|
|
60
66
|
expectedAccessMode: record.accessMode,
|
|
67
|
+
expectedOwnerSubject: previousOwnerSubject,
|
|
68
|
+
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
69
|
+
})
|
|
70
|
+
: await state.store.updateActiveAccess(tenant, record.deploymentId, {
|
|
71
|
+
accessMode: nextAccessMode,
|
|
72
|
+
expectedAccessMode: record.accessMode,
|
|
73
|
+
expectedOwnerSubject: previousOwnerSubject,
|
|
61
74
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
62
75
|
});
|
|
63
76
|
if (committed === undefined)
|
|
64
77
|
return accessUpdateConflict();
|
|
65
78
|
reconcileCommittedAccess(state, tenant, committed);
|
|
66
|
-
return {
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
changed: false,
|
|
82
|
+
accessChanged: false,
|
|
83
|
+
ownerChanged: false,
|
|
84
|
+
previousAccessMode,
|
|
85
|
+
...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
|
|
86
|
+
record: committed,
|
|
87
|
+
};
|
|
67
88
|
}
|
|
68
|
-
if (served === undefined)
|
|
69
|
-
return accessUpdateConflict();
|
|
70
89
|
if (nextAccessMode === 'public' && manifestUsesUserRoot(record.manifest)) {
|
|
71
90
|
return {
|
|
72
91
|
ok: false,
|
|
@@ -75,7 +94,7 @@ export async function updateActiveAccess(state, tenant, nextAccessMode, serverVe
|
|
|
75
94
|
message: 'Public access cannot reference the user context.',
|
|
76
95
|
};
|
|
77
96
|
}
|
|
78
|
-
if (nextAccessMode === 'owner-only' &&
|
|
97
|
+
if (nextAccessMode === 'owner-only' && nextOwnerSubject === undefined) {
|
|
79
98
|
return {
|
|
80
99
|
ok: false,
|
|
81
100
|
status: 409,
|
|
@@ -83,27 +102,51 @@ export async function updateActiveAccess(state, tenant, nextAccessMode, serverVe
|
|
|
83
102
|
message: 'Owner-only access requires a recorded deployer identity.',
|
|
84
103
|
};
|
|
85
104
|
}
|
|
105
|
+
if (served === undefined)
|
|
106
|
+
return accessUpdateConflict();
|
|
86
107
|
const updated = {
|
|
87
108
|
...record,
|
|
88
109
|
accessMode: nextAccessMode,
|
|
110
|
+
...(ownerChanged && nextOwnerSubject !== undefined ? { ownerSubject: nextOwnerSubject } : {}),
|
|
89
111
|
...(nextAccessMode === 'customers' && serverAuth !== undefined ? { serverAuth } : {}),
|
|
90
112
|
};
|
|
91
113
|
const target = tryServedTargetFor(updated, served.served, customerVerifierFactory);
|
|
92
114
|
if (target === undefined)
|
|
93
115
|
return accessUpdateConflict();
|
|
94
116
|
const committed = state.store === undefined
|
|
95
|
-
? commitInMemoryAccessUpdate(state, tenant, record,
|
|
96
|
-
|
|
117
|
+
? commitInMemoryAccessUpdate(state, tenant, record, {
|
|
118
|
+
accessMode: nextAccessMode,
|
|
119
|
+
...(ownerChanged && nextOwnerSubject !== undefined
|
|
120
|
+
? { ownerSubject: nextOwnerSubject }
|
|
121
|
+
: {}),
|
|
122
|
+
expectedAccessMode: record.accessMode,
|
|
123
|
+
expectedOwnerSubject: previousOwnerSubject,
|
|
124
|
+
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
125
|
+
})
|
|
126
|
+
: await state.store.updateActiveAccess(tenant, record.deploymentId, {
|
|
127
|
+
accessMode: nextAccessMode,
|
|
128
|
+
...(ownerChanged && nextOwnerSubject !== undefined
|
|
129
|
+
? { ownerSubject: nextOwnerSubject }
|
|
130
|
+
: {}),
|
|
97
131
|
expectedAccessMode: record.accessMode,
|
|
132
|
+
expectedOwnerSubject: previousOwnerSubject,
|
|
98
133
|
...(serverAuth !== undefined ? { serverAuth } : {}),
|
|
99
134
|
});
|
|
100
135
|
if (committed === undefined)
|
|
101
136
|
return accessUpdateConflict();
|
|
102
137
|
reconcileCommittedAccess(state, tenant, committed);
|
|
103
138
|
state.servers.set(committed.deploymentId, target);
|
|
104
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
ok: true,
|
|
141
|
+
changed: true,
|
|
142
|
+
accessChanged,
|
|
143
|
+
ownerChanged,
|
|
144
|
+
previousAccessMode,
|
|
145
|
+
...(previousOwnerSubject !== undefined ? { previousOwnerSubject } : {}),
|
|
146
|
+
record: committed,
|
|
147
|
+
};
|
|
105
148
|
}
|
|
106
|
-
function commitInMemoryAccessUpdate(state, tenant, observed,
|
|
149
|
+
function commitInMemoryAccessUpdate(state, tenant, observed, input) {
|
|
107
150
|
const current = state.records.get(observed.deploymentId);
|
|
108
151
|
if (current === undefined ||
|
|
109
152
|
!current.active ||
|
|
@@ -111,16 +154,22 @@ function commitInMemoryAccessUpdate(state, tenant, observed, nextAccessMode, ser
|
|
|
111
154
|
current.orgSlug !== tenant.org ||
|
|
112
155
|
current.appSlug !== tenant.app ||
|
|
113
156
|
current.environment !== tenant.env ||
|
|
114
|
-
current.accessMode !==
|
|
157
|
+
current.accessMode !== input.expectedAccessMode ||
|
|
158
|
+
deploymentOwnerSubject(current) !== input.expectedOwnerSubject) {
|
|
115
159
|
return undefined;
|
|
116
160
|
}
|
|
117
|
-
if ((current.accessMode ?? 'owner-only') ===
|
|
161
|
+
if ((current.accessMode ?? 'owner-only') === input.accessMode &&
|
|
162
|
+
(input.ownerSubject === undefined || deploymentOwnerSubject(current) === input.ownerSubject) &&
|
|
163
|
+
input.accessMode !== 'customers') {
|
|
118
164
|
return current;
|
|
119
165
|
}
|
|
120
166
|
const committed = {
|
|
121
167
|
...current,
|
|
122
|
-
accessMode:
|
|
123
|
-
...(
|
|
168
|
+
accessMode: input.accessMode,
|
|
169
|
+
...(input.ownerSubject !== undefined ? { ownerSubject: input.ownerSubject } : {}),
|
|
170
|
+
...(input.accessMode === 'customers' && input.serverAuth !== undefined
|
|
171
|
+
? { serverAuth: input.serverAuth }
|
|
172
|
+
: {}),
|
|
124
173
|
};
|
|
125
174
|
const proposed = new Map(state.records).set(committed.deploymentId, committed);
|
|
126
175
|
assertUniqueActiveCustomerAuthAudienceBindings([...proposed.values()]);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
2
|
-
import { deployAccessRequiresActor, deployerRequiredError, emptyMembershipSourcesError, membershipSourcesRequireOrgMembersError, missingCapabilityErrors, serverAuthRequiredError, } from './registry-helpers.js';
|
|
2
|
+
import { deployAccessRequiresActor, deployerRequiredError, deploymentOwnerSubject, emptyMembershipSourcesError, membershipSourcesRequireOrgMembersError, missingCapabilityErrors, serverAuthRequiredError, } from './registry-helpers.js';
|
|
3
3
|
import { idempotentDeploymentId, mintDeploymentId } from './registry-targets.js';
|
|
4
4
|
export async function preflightRegistryDeploy(input) {
|
|
5
5
|
const { actor, accessMode, orgMembershipSources } = input.options;
|
|
@@ -56,6 +56,7 @@ export async function resolveDeployAttempt(input) {
|
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
+
const ownerSubject = deploymentOwnerSubject(existing);
|
|
59
60
|
return {
|
|
60
61
|
deploymentId,
|
|
61
62
|
replay: {
|
|
@@ -64,6 +65,7 @@ export async function resolveDeployAttempt(input) {
|
|
|
64
65
|
deploymentVersion: existing.deploymentVersion,
|
|
65
66
|
...(existing.serverVersion !== undefined ? { serverVersion: existing.serverVersion } : {}),
|
|
66
67
|
...(existing.accessMode !== undefined ? { accessMode: existing.accessMode } : {}),
|
|
68
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
67
69
|
replayed: true,
|
|
68
70
|
},
|
|
69
71
|
};
|
|
@@ -82,7 +84,8 @@ function sameIdempotentDeploy(record, tenant, manifest, options) {
|
|
|
82
84
|
record.deploymentSource === options.deploymentSource &&
|
|
83
85
|
sameJson(record.hostedAssets, options.hostedAssets) &&
|
|
84
86
|
sameJson(record.orgMembershipSources, options.orgMembershipSources) &&
|
|
85
|
-
(options.accessMode !== 'owner-only' ||
|
|
87
|
+
(options.accessMode !== 'owner-only' ||
|
|
88
|
+
deploymentOwnerSubject(record) === (options.ownerSubject ?? options.actor?.subject)));
|
|
86
89
|
}
|
|
87
90
|
function sameJson(left, right) {
|
|
88
91
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
@@ -3,26 +3,22 @@ import type { SecretBinding } from '@noodle-borg/connector-defs';
|
|
|
3
3
|
import type { OrgMembershipSource } from '@noodle-borg/module';
|
|
4
4
|
import type { AccessMode } from '@noodle-borg/transport-http';
|
|
5
5
|
import type { DeployError } from './registry.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { DeployRecord } from './store.js';
|
|
7
7
|
/**
|
|
8
8
|
* Provenance of an env's current active deployment — the inherit source for GHD-3 run-deploy
|
|
9
|
-
* access-mode resolution (ADR 0132 Decision 4). The owner subject
|
|
10
|
-
*
|
|
9
|
+
* access-mode resolution (ADR 0132 Decision 4). The owner subject lets an inherited `owner-only` run deploy
|
|
10
|
+
* preserve the PREVIOUS human owner instead of rebinding the app to the automation subject.
|
|
11
11
|
*/
|
|
12
12
|
export type ActiveDeployProvenance = {
|
|
13
13
|
readonly accessMode: AccessMode | undefined;
|
|
14
14
|
readonly orgMembershipSources: readonly OrgMembershipSource[] | undefined;
|
|
15
15
|
readonly ownerSubject: string | undefined;
|
|
16
|
-
readonly ownerEmail: string | undefined;
|
|
17
16
|
} | undefined;
|
|
17
|
+
/** Resolve the durable owner binding while keeping legacy creator-only records readable. */
|
|
18
|
+
export declare function deploymentOwnerSubject(record: DeployRecord): string | undefined;
|
|
18
19
|
export declare function missingCapabilityErrors(required: readonly CapabilityName[], available: readonly CapabilityName[]): readonly DeployError[];
|
|
19
20
|
export declare function missingSecretErrors(bindings: readonly SecretBinding[], secrets: Readonly<Record<string, string>>): DeployError[];
|
|
20
21
|
export declare function missingVariableErrors(bindings: readonly string[], variables: Readonly<Record<string, string>>): DeployError[];
|
|
21
|
-
export interface EndpointUrlOptions {
|
|
22
|
-
readonly publicBaseDomain?: string;
|
|
23
|
-
readonly mcpSubdomain?: string;
|
|
24
|
-
}
|
|
25
|
-
export declare function tenantMcpUrl(base: string, tenant: TenantRef, serverVersion?: string, options?: EndpointUrlOptions): string;
|
|
26
22
|
export declare function deployAccessRequiresActor(accessMode: AccessMode): boolean;
|
|
27
23
|
/** An identity access mode was requested without an authenticated deployer. */
|
|
28
24
|
export declare function deployerRequiredError(accessMode: AccessMode): {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
/** Resolve the durable owner binding while keeping legacy creator-only records readable. */
|
|
2
|
+
export function deploymentOwnerSubject(record) {
|
|
3
|
+
return record.ownerSubject ?? record.createdBySubject;
|
|
4
|
+
}
|
|
2
5
|
export function missingCapabilityErrors(required, available) {
|
|
3
6
|
const enabled = new Set(available);
|
|
4
7
|
return required
|
|
@@ -41,23 +44,6 @@ export function missingVariableErrors(bindings, variables) {
|
|
|
41
44
|
}
|
|
42
45
|
return errors;
|
|
43
46
|
}
|
|
44
|
-
export function tenantMcpUrl(base, tenant, serverVersion, options = {}) {
|
|
45
|
-
if (options.publicBaseDomain !== undefined) {
|
|
46
|
-
if (options.mcpSubdomain === undefined) {
|
|
47
|
-
throw new Error('MCP subdomain is required to format a public endpoint URL');
|
|
48
|
-
}
|
|
49
|
-
return formatPublicMcpUrl(options.publicBaseDomain, {
|
|
50
|
-
mcpSubdomain: options.mcpSubdomain,
|
|
51
|
-
app: tenant.app,
|
|
52
|
-
env: tenant.env,
|
|
53
|
-
...(serverVersion !== undefined ? { serverVersion } : {}),
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
const versionPath = serverVersion !== undefined ? `/${serverVersionPathSegment(serverVersion)}` : '';
|
|
57
|
-
return tenant.env === 'prod'
|
|
58
|
-
? `${base}/o/${tenant.org}/${tenant.app}${versionPath}/mcp`
|
|
59
|
-
: `${base}/o/${tenant.org}/${tenant.app}/${tenant.env}${versionPath}/mcp`;
|
|
60
|
-
}
|
|
61
47
|
export function deployAccessRequiresActor(accessMode) {
|
|
62
48
|
return accessMode !== 'public' && accessMode !== 'mixed';
|
|
63
49
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { missingCapabilityErrors } from './registry-helpers.js';
|
|
1
|
+
import { deploymentOwnerSubject, missingCapabilityErrors } from './registry-helpers.js';
|
|
2
2
|
import { activateInMemory } from './registry-state.js';
|
|
3
3
|
import { recordMatchesTenant, servedTargetFor, tenantDeploymentKey, tenantKey, tryServedTargetFor, } from './registry-targets.js';
|
|
4
4
|
/**
|
|
@@ -101,6 +101,7 @@ export async function rollbackDeployment(state, ref, deploymentId, compilePersis
|
|
|
101
101
|
state.servers.set(active.deploymentId, servedTargetFor(active, built.served, customerVerifierFactory));
|
|
102
102
|
state.activeTenants.set(tenantDeploymentKey(ref, active.serverVersion), active.deploymentId);
|
|
103
103
|
state.activeTenants.delete(tenantKey(ref));
|
|
104
|
+
const ownerSubject = deploymentOwnerSubject(active);
|
|
104
105
|
return {
|
|
105
106
|
ok: true,
|
|
106
107
|
deploymentId: active.deploymentId,
|
|
@@ -108,6 +109,7 @@ export async function rollbackDeployment(state, ref, deploymentId, compilePersis
|
|
|
108
109
|
...(previous !== undefined ? { previousDeploymentId: previous.deploymentId } : {}),
|
|
109
110
|
alreadyActive: activation.alreadyActive,
|
|
110
111
|
accessMode: active.accessMode ?? 'owner-only',
|
|
112
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
111
113
|
...(previous?.accessMode !== undefined ? { previousAccessMode: previous.accessMode } : {}),
|
|
112
114
|
serverName: active.serverName,
|
|
113
115
|
createdAt: active.createdAt,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
2
|
+
import { deploymentOwnerSubject, missingSecretNames } from './registry-helpers.js';
|
|
2
3
|
/** Project one compiled deployment into the control-plane status contract. */
|
|
3
4
|
export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions) {
|
|
4
5
|
const missingSecrets = built.ok ? [] : missingSecretNames(built.errors);
|
|
@@ -6,6 +7,7 @@ export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions
|
|
|
6
7
|
record.accessMode === 'customers' &&
|
|
7
8
|
built.served.artifact.server.auth === undefined;
|
|
8
9
|
const unhealthy = missingCustomerAuth || (!built.ok && missingSecrets.length === 0);
|
|
10
|
+
const ownerSubject = deploymentOwnerSubject(record);
|
|
9
11
|
return {
|
|
10
12
|
target: ref,
|
|
11
13
|
deployment: {
|
|
@@ -16,6 +18,7 @@ export function deploymentStatusFor(ref, record, built, baseUrl, endpointOptions
|
|
|
16
18
|
serverName: record.serverName,
|
|
17
19
|
createdAt: record.createdAt,
|
|
18
20
|
...(record.createdByEmail !== undefined ? { createdByEmail: record.createdByEmail } : {}),
|
|
21
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
19
22
|
accessMode: record.accessMode ?? 'owner-only',
|
|
20
23
|
...(record.deploymentLock !== undefined
|
|
21
24
|
? {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { parseAppPackageSnapshot } from './app-package-snapshot.js';
|
|
3
3
|
import { hasExactCustomerAuthProjection } from './customer-auth-audience-binding.js';
|
|
4
|
+
import { deploymentOwnerSubject } from './registry-helpers.js';
|
|
4
5
|
/**
|
|
5
6
|
* Build the front-door {@link ServedTarget} for a persisted record: its access mode plus the credential the
|
|
6
7
|
* front-door checks. Legacy alpha records without an identity access mode are not served.
|
|
@@ -18,6 +19,7 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
|
|
|
18
19
|
throw new MissingCustomerAuthError(record.deploymentId);
|
|
19
20
|
}
|
|
20
21
|
const boundServed = bindAppPackageSnapshot(served, record.appPackageSnapshot);
|
|
22
|
+
const ownerSubject = deploymentOwnerSubject(record);
|
|
21
23
|
return {
|
|
22
24
|
served: boundServed,
|
|
23
25
|
deploymentId: record.deploymentId,
|
|
@@ -28,9 +30,7 @@ export function servedTargetFor(record, served, customerVerifierFactory) {
|
|
|
28
30
|
...(accessMode === 'org-members' && record.orgMembershipSources !== undefined
|
|
29
31
|
? { orgMembershipSources: record.orgMembershipSources }
|
|
30
32
|
: {}),
|
|
31
|
-
...(accessMode === 'owner-only' &&
|
|
32
|
-
? { ownerSubject: record.createdBySubject }
|
|
33
|
-
: {}),
|
|
33
|
+
...(accessMode === 'owner-only' && ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
34
34
|
...(customerAuth !== undefined
|
|
35
35
|
? customerTargetFields(customerAuth, customerVerifierFactory)
|
|
36
36
|
: {}),
|
|
@@ -55,7 +55,7 @@ export function tryServedTargetFor(record, served, customerVerifierFactory) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
export function servedTargetMatchesRecord(target, record) {
|
|
58
|
-
const expectedOwner = record.accessMode === 'owner-only' ? record
|
|
58
|
+
const expectedOwner = record.accessMode === 'owner-only' ? deploymentOwnerSubject(record) : undefined;
|
|
59
59
|
const expectedMembership = record.accessMode === 'org-members' ? record.orgMembershipSources : undefined;
|
|
60
60
|
return (target.deploymentId === record.deploymentId &&
|
|
61
61
|
target.accessMode === record.accessMode &&
|
|
@@ -22,6 +22,8 @@ export interface DeployOptions {
|
|
|
22
22
|
readonly connectors?: string | undefined;
|
|
23
23
|
readonly actor?: ControlPlaneIdentity | undefined;
|
|
24
24
|
readonly accessMode?: AccessMode | undefined;
|
|
25
|
+
/** Effective OAuth subject for an owner-only deployment, distinct from the deploy actor. */
|
|
26
|
+
readonly ownerSubject?: string | undefined;
|
|
25
27
|
readonly orgMembershipSources?: readonly OrgMembershipSource[] | undefined;
|
|
26
28
|
readonly hostedAssets?: readonly HostedPackagedAsset[] | undefined;
|
|
27
29
|
readonly serverVersion?: string | undefined;
|
|
@@ -46,6 +48,7 @@ export type DeployResult = {
|
|
|
46
48
|
readonly deploymentVersion: number;
|
|
47
49
|
readonly serverVersion?: string;
|
|
48
50
|
readonly accessMode?: AccessMode;
|
|
51
|
+
readonly ownerSubject?: string;
|
|
49
52
|
readonly replayed?: boolean;
|
|
50
53
|
} | {
|
|
51
54
|
readonly ok: false;
|
|
@@ -69,27 +72,14 @@ export type RunDeployResult = DeployResult | {
|
|
|
69
72
|
readonly deploymentVersion: number;
|
|
70
73
|
readonly serverVersion?: string;
|
|
71
74
|
};
|
|
72
|
-
export type RollbackResult = {
|
|
73
|
-
readonly ok: true;
|
|
74
|
-
readonly deploymentId: string;
|
|
75
|
-
readonly serverVersion?: string;
|
|
76
|
-
readonly previousDeploymentId?: string;
|
|
77
|
-
readonly alreadyActive: boolean;
|
|
78
|
-
readonly accessMode: AccessMode;
|
|
79
|
-
readonly previousAccessMode?: AccessMode;
|
|
80
|
-
readonly serverName: string;
|
|
81
|
-
readonly createdAt: string;
|
|
82
|
-
} | {
|
|
83
|
-
readonly ok: false;
|
|
84
|
-
readonly status: 404 | 409;
|
|
85
|
-
readonly code?: 'deployment_locked';
|
|
86
|
-
readonly error: string;
|
|
87
|
-
};
|
|
88
75
|
export type AccessUpdateFailureCode = 'no_active_deployment' | 'server_auth_required' | 'customer_auth_audience_conflict' | 'public_user_context_conflict' | 'owner_identity_required' | 'access_update_conflict';
|
|
89
76
|
export type AccessUpdateResult = {
|
|
90
77
|
readonly ok: true;
|
|
91
78
|
readonly changed: boolean;
|
|
79
|
+
readonly accessChanged: boolean;
|
|
80
|
+
readonly ownerChanged: boolean;
|
|
92
81
|
readonly previousAccessMode: AccessMode;
|
|
82
|
+
readonly previousOwnerSubject?: string;
|
|
93
83
|
readonly record: DeployRecord;
|
|
94
84
|
} | {
|
|
95
85
|
readonly ok: false;
|
|
@@ -97,6 +87,11 @@ export type AccessUpdateResult = {
|
|
|
97
87
|
readonly code: AccessUpdateFailureCode;
|
|
98
88
|
readonly message: string;
|
|
99
89
|
};
|
|
90
|
+
export interface AccessUpdateOptions {
|
|
91
|
+
readonly accessMode: AccessMode;
|
|
92
|
+
readonly ownerSubject?: string | undefined;
|
|
93
|
+
readonly serverVersion?: string | undefined;
|
|
94
|
+
}
|
|
100
95
|
export interface ServerRegistryOptions {
|
|
101
96
|
/** Pure renderer injection used for deployment-bound package construction and failure testing. */
|
|
102
97
|
readonly renderAppPackage?: AppPackageRenderer;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { CapabilityName } from '@noodle-borg/capabilities';
|
|
2
2
|
import type { CatalogConnector, LocalAssetOptions, PackagedAsset } from '@noodle-borg/compiler';
|
|
3
|
+
import type { RollbackResult } from '@noodle-borg/control-plane/portable';
|
|
3
4
|
import { type KnowledgeDeployHooks, type KnowledgeSearchPortFactory } from '@noodle-borg/knowledge-operations/portable';
|
|
4
5
|
import type { PlatformIdentityRecoveryPlan } from '@noodle-borg/module';
|
|
6
|
+
import { type EndpointUrlOptions } from '@noodle-borg/module';
|
|
5
7
|
import type { Connector, SecretBox } from '@noodle-borg/runtime';
|
|
6
|
-
import type {
|
|
7
|
-
import { type ActiveDeployProvenance
|
|
8
|
-
import type { AccessUpdateResult, DeployOptions, DeployPreflightResult, RecoverResult,
|
|
8
|
+
import type { ServedTarget } from '@noodle-borg/transport-http';
|
|
9
|
+
import { type ActiveDeployProvenance } from './registry-helpers.js';
|
|
10
|
+
import type { AccessUpdateOptions, AccessUpdateResult, DeployOptions, DeployPreflightResult, RecoverResult, RunDeployResult, ServerRegistryOptions } from './registry-types.js';
|
|
9
11
|
import { type AppArchiveResult, type AppRestoreResult, type AppSummary, type ArtifactStore, type ConfigStore, type CustomerAuthRestoreProjection, type DeploymentListFilter, type DeploymentLock, type DeploymentLockUpdateResult, type DeploymentStatus, type DeploymentSummary, type DeployRecord, type EnvSummary, type ProductionEnvironmentChange, type TenantRef } from './store.js';
|
|
12
|
+
export type { RollbackResult } from '@noodle-borg/control-plane/portable';
|
|
10
13
|
export type { DeploymentPackage } from './registry-package.js';
|
|
11
|
-
export type { AccessUpdateFailureCode, AccessUpdateResult, DeployError, DeployOptions, DeployPreflightResult, DeployResult, RecoverResult,
|
|
14
|
+
export type { AccessUpdateFailureCode, AccessUpdateOptions, AccessUpdateResult, DeployError, DeployOptions, DeployPreflightResult, DeployResult, RecoverResult, ServerRegistryOptions, } from './registry-types.js';
|
|
12
15
|
export declare class ServerRegistry {
|
|
13
16
|
#private;
|
|
14
17
|
constructor(store?: ArtifactStore, _secretBox?: SecretBox, configStore?: ConfigStore, options?: ServerRegistryOptions);
|
|
@@ -54,7 +57,7 @@ export declare class ServerRegistry {
|
|
|
54
57
|
reconcilePlatformAccountReset(input: PlatformIdentityRecoveryPlan): void;
|
|
55
58
|
customerAuthRestoreProjections(deploymentIds: readonly string[]): Promise<readonly CustomerAuthRestoreProjection[]>;
|
|
56
59
|
getStatus(ref: TenantRef, baseUrl: string, serverVersion?: string, endpointOptions?: EndpointUrlOptions): Promise<DeploymentStatus | undefined>;
|
|
57
|
-
updateAccess(ref: TenantRef,
|
|
60
|
+
updateAccess(ref: TenantRef, options: AccessUpdateOptions): Promise<AccessUpdateResult>;
|
|
58
61
|
setDeploymentLock(ref: TenantRef, serverVersion: string, expectedDeploymentId: string, deploymentLock: DeploymentLock | undefined): Promise<DeploymentLockUpdateResult>;
|
|
59
62
|
rollback(ref: TenantRef, deploymentId: string): Promise<RollbackResult>;
|
|
60
63
|
get configStore(): ConfigStore;
|
|
@@ -7,7 +7,7 @@ import { defaultActiveRecord } from './deployment-versioning.js';
|
|
|
7
7
|
import { updateRegistryAccess } from './registry-access.js';
|
|
8
8
|
import { compileRegistryTarget } from './registry-compile.js';
|
|
9
9
|
import { preflightRegistryDeploy, resolveDeployAttempt } from './registry-deploy-transaction.js';
|
|
10
|
-
import { missingCapabilityErrors, } from './registry-helpers.js';
|
|
10
|
+
import { deploymentOwnerSubject, missingCapabilityErrors, } 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';
|
|
@@ -130,6 +130,7 @@ export class ServerRegistry {
|
|
|
130
130
|
const version = Date.now();
|
|
131
131
|
const built = preflight.bindDeployment(deploymentId);
|
|
132
132
|
const serverAuth = built.artifact.server.auth;
|
|
133
|
+
const ownerSubject = accessMode === 'owner-only' ? (options.ownerSubject ?? actor?.subject) : undefined;
|
|
133
134
|
const record = {
|
|
134
135
|
schemaVersion: 1,
|
|
135
136
|
deploymentId,
|
|
@@ -142,6 +143,7 @@ export class ServerRegistry {
|
|
|
142
143
|
serverName: built.artifact.server.name,
|
|
143
144
|
createdAt: new Date().toISOString(),
|
|
144
145
|
...(actor ? { createdBySubject: actor.subject, createdByEmail: actor.email } : {}),
|
|
146
|
+
...(ownerSubject !== undefined ? { ownerSubject } : {}),
|
|
145
147
|
...(deploymentSource !== undefined ? { deploymentSource } : {}),
|
|
146
148
|
...(accessMode !== undefined ? { accessMode } : {}),
|
|
147
149
|
...(orgMembershipSources !== undefined ? { orgMembershipSources } : {}),
|
|
@@ -219,12 +221,14 @@ export class ServerRegistry {
|
|
|
219
221
|
this.#servers.set(deploymentId, servedTargetFor(active, built, this.#customerVerifierFactory));
|
|
220
222
|
this.#activeTenants.set(tenantDeploymentKey(safeTenant, safeServerVersion), deploymentId);
|
|
221
223
|
this.#activeTenants.delete(tenantKey(safeTenant));
|
|
224
|
+
const activeOwnerSubject = deploymentOwnerSubject(active);
|
|
222
225
|
return {
|
|
223
226
|
ok: true,
|
|
224
227
|
deploymentId,
|
|
225
228
|
deploymentVersion: version,
|
|
226
229
|
...(safeServerVersion !== undefined ? { serverVersion: safeServerVersion } : {}),
|
|
227
230
|
...(accessMode !== undefined ? { accessMode } : {}),
|
|
231
|
+
...(activeOwnerSubject !== undefined ? { ownerSubject: activeOwnerSubject } : {}),
|
|
228
232
|
};
|
|
229
233
|
}
|
|
230
234
|
/** Read-only deploy validation used by the CLI before config writes, asset upload, or persistence. */
|
|
@@ -338,8 +342,7 @@ export class ServerRegistry {
|
|
|
338
342
|
return {
|
|
339
343
|
accessMode: record.accessMode,
|
|
340
344
|
orgMembershipSources: record.orgMembershipSources,
|
|
341
|
-
ownerSubject: record
|
|
342
|
-
ownerEmail: record.createdByEmail,
|
|
345
|
+
ownerSubject: deploymentOwnerSubject(record),
|
|
343
346
|
};
|
|
344
347
|
}
|
|
345
348
|
async getActiveByTenantVersion(ref, serverVersion) {
|
|
@@ -415,9 +418,9 @@ export class ServerRegistry {
|
|
|
415
418
|
const built = await this.#compilePersistedRecord(record);
|
|
416
419
|
return deploymentStatusFor(safe, record, built, baseUrl, endpointOptions);
|
|
417
420
|
}
|
|
418
|
-
async updateAccess(ref,
|
|
421
|
+
async updateAccess(ref, options) {
|
|
419
422
|
const safe = validateTenantRef(ref);
|
|
420
|
-
return updateRegistryAccess(this.#stateView(), safe,
|
|
423
|
+
return updateRegistryAccess(this.#stateView(), safe, options, (deploymentId) => this.get(deploymentId), this.#customerVerifierFactory, (record, auth) => this.#hasCustomerAuthAudienceConflict(record, auth));
|
|
421
424
|
}
|
|
422
425
|
async setDeploymentLock(ref, serverVersion, expectedDeploymentId, deploymentLock) {
|
|
423
426
|
return setRegistryDeploymentLock(this.#stateView(), validateTenantRef(ref), serverVersion, expectedDeploymentId, deploymentLock);
|