@noodleseed/one 0.8.0 → 0.11.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/cli.d.ts.map +1 -1
- package/dist/cli.js +16 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/archive-ops.d.ts +3 -0
- package/dist/commands/archive-ops.d.ts.map +1 -0
- package/dist/commands/archive-ops.js +168 -0
- package/dist/commands/archive-ops.js.map +1 -0
- package/dist/commands/author-loop.d.ts.map +1 -1
- package/dist/commands/author-loop.js +78 -3
- package/dist/commands/author-loop.js.map +1 -1
- package/dist/commands/deploy-ops.d.ts +1 -30
- package/dist/commands/deploy-ops.d.ts.map +1 -1
- package/dist/commands/deploy-ops.js +55 -87
- package/dist/commands/deploy-ops.js.map +1 -1
- package/dist/commands/deploy-output.d.ts +31 -0
- package/dist/commands/deploy-output.d.ts.map +1 -0
- package/dist/commands/deploy-output.js +86 -0
- package/dist/commands/deploy-output.js.map +1 -0
- package/dist/commands/deploy-version-resolution.d.ts +20 -0
- package/dist/commands/deploy-version-resolution.d.ts.map +1 -0
- package/dist/commands/deploy-version-resolution.js +77 -0
- package/dist/commands/deploy-version-resolution.js.map +1 -0
- package/dist/commands/docs-mcp.d.ts +32 -0
- package/dist/commands/docs-mcp.d.ts.map +1 -0
- package/dist/commands/docs-mcp.js +66 -0
- package/dist/commands/docs-mcp.js.map +1 -0
- package/dist/commands/mcp-apps.d.ts.map +1 -1
- package/dist/commands/mcp-apps.js +38 -37
- package/dist/commands/mcp-apps.js.map +1 -1
- package/dist/commands/org-admin.d.ts +17 -0
- package/dist/commands/org-admin.d.ts.map +1 -0
- package/dist/commands/org-admin.js +241 -0
- package/dist/commands/org-admin.js.map +1 -0
- package/dist/commands/policy-ops.d.ts.map +1 -1
- package/dist/commands/policy-ops.js +94 -1
- package/dist/commands/policy-ops.js.map +1 -1
- package/dist/commands/project-setup.d.ts.map +1 -1
- package/dist/commands/project-setup.js +29 -0
- package/dist/commands/project-setup.js.map +1 -1
- package/dist/commands/session.d.ts +1 -15
- package/dist/commands/session.d.ts.map +1 -1
- package/dist/commands/session.js +15 -170
- package/dist/commands/session.js.map +1 -1
- package/dist/commands/shared.d.ts +1 -0
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +21 -9
- package/dist/commands/shared.js.map +1 -1
- package/dist/commands/update-ops.d.ts +29 -0
- package/dist/commands/update-ops.d.ts.map +1 -0
- package/dist/commands/update-ops.js +315 -0
- package/dist/commands/update-ops.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/deploy-version.d.ts +3 -0
- package/dist/deploy-version.d.ts.map +1 -0
- package/dist/deploy-version.js +28 -0
- package/dist/deploy-version.js.map +1 -0
- package/dist/deploy.d.ts +5 -1
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +27 -3
- package/dist/deploy.js.map +1 -1
- package/dist/devtools-chat.d.ts +76 -0
- package/dist/devtools-chat.d.ts.map +1 -0
- package/dist/devtools-chat.js +114 -0
- package/dist/devtools-chat.js.map +1 -0
- package/dist/devtools-env.d.ts +16 -0
- package/dist/devtools-env.d.ts.map +1 -0
- package/dist/devtools-env.js +71 -0
- package/dist/devtools-env.js.map +1 -0
- package/dist/devtools-harness.d.ts +28 -0
- package/dist/devtools-harness.d.ts.map +1 -0
- package/dist/devtools-harness.js +387 -0
- package/dist/devtools-harness.js.map +1 -0
- package/dist/devtools-preview.d.ts +42 -0
- package/dist/devtools-preview.d.ts.map +1 -0
- package/dist/devtools-preview.js +394 -0
- package/dist/devtools-preview.js.map +1 -0
- package/dist/preview-session.d.ts +46 -0
- package/dist/preview-session.d.ts.map +1 -0
- package/dist/preview-session.js +132 -0
- package/dist/preview-session.js.map +1 -0
- package/dist/project.d.ts +2 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +2 -0
- package/dist/project.js.map +1 -1
- package/dist/react-widget-build.js +3 -1
- package/dist/react-widget-build.js.map +1 -1
- package/dist/update-binary.d.ts +38 -0
- package/dist/update-binary.d.ts.map +1 -0
- package/dist/update-binary.js +144 -0
- package/dist/update-binary.js.map +1 -0
- package/dist/update-check.d.ts +40 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +212 -0
- package/dist/update-check.js.map +1 -0
- package/dist/update-render.d.ts +17 -0
- package/dist/update-render.d.ts.map +1 -0
- package/dist/update-render.js +39 -0
- package/dist/update-render.js.map +1 -0
- package/dist/update.d.ts +10 -9
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +21 -89
- package/dist/update.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js +13 -1
- package/node_modules/@noodle-borg/agent-kit/dist/curated/command-groups.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.d.ts.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js +2 -0
- package/node_modules/@noodle-borg/agent-kit/dist/generated/surface.js.map +1 -1
- package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.d.ts +23 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.d.ts.map +1 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.js +41 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile-expr.js.map +1 -0
- package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts +2 -6
- package/node_modules/@noodle-borg/connector-defs/dist/compile.d.ts.map +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js +10 -32
- package/node_modules/@noodle-borg/connector-defs/dist/compile.js.map +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts +16 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js +6 -0
- package/node_modules/@noodle-borg/connector-defs/dist/schema.js.map +1 -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.d.ts.map +1 -1
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.js +14 -5
- package/node_modules/@noodle-borg/connector-http/dist/http-connector.js.map +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.d.ts +12 -0
- package/node_modules/@noodle-borg/module/dist/contract.d.ts.map +1 -1
- package/node_modules/@noodle-borg/module/dist/contract.js +41 -0
- package/node_modules/@noodle-borg/module/dist/contract.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/archive-sweeper.d.ts +43 -0
- package/node_modules/@noodle-borg/service/dist/archive-sweeper.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/archive-sweeper.js +122 -0
- package/node_modules/@noodle-borg/service/dist/archive-sweeper.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.d.ts +20 -2
- package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.js +37 -2
- package/node_modules/@noodle-borg/service/dist/auth/deploy-gate.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/commercial-plans.d.ts +11 -0
- package/node_modules/@noodle-borg/service/dist/commercial-plans.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/commercial-plans.js +119 -0
- package/node_modules/@noodle-borg/service/dist/commercial-plans.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/deployment-versioning.d.ts +20 -0
- package/node_modules/@noodle-borg/service/dist/deployment-versioning.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/deployment-versioning.js +29 -0
- package/node_modules/@noodle-borg/service/dist/deployment-versioning.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/index.d.ts +3 -1
- package/node_modules/@noodle-borg/service/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/index.js +2 -0
- package/node_modules/@noodle-borg/service/dist/index.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/main.js +7 -0
- package/node_modules/@noodle-borg/service/dist/main.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/app.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/app.js +2 -1
- package/node_modules/@noodle-borg/service/dist/oauth/app.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/branding.d.ts +30 -0
- package/node_modules/@noodle-borg/service/dist/oauth/branding.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/oauth/branding.js +224 -0
- package/node_modules/@noodle-borg/service/dist/oauth/branding.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/oauth/consent.d.ts +0 -6
- package/node_modules/@noodle-borg/service/dist/oauth/consent.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/consent.js +23 -34
- package/node_modules/@noodle-borg/service/dist/oauth/consent.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.js +13 -28
- package/node_modules/@noodle-borg/service/dist/oauth/customer-bridge.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/options.d.ts +9 -0
- package/node_modules/@noodle-borg/service/dist/options.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-helpers.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js +5 -3
- package/node_modules/@noodle-borg/service/dist/registry-helpers.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts +25 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.js +119 -0
- package/node_modules/@noodle-borg/service/dist/registry-state.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-targets.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-targets.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-targets.js +4 -0
- package/node_modules/@noodle-borg/service/dist/registry-targets.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +59 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.js +2 -0
- package/node_modules/@noodle-borg/service/dist/registry-types.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/registry.d.ts +14 -70
- package/node_modules/@noodle-borg/service/dist/registry.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/registry.js +137 -119
- package/node_modules/@noodle-borg/service/dist/registry.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.d.ts +4 -0
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.js +15 -0
- package/node_modules/@noodle-borg/service/dist/routes/access-mode.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/archive.d.ts +16 -0
- package/node_modules/@noodle-borg/service/dist/routes/archive.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/archive.js +83 -0
- package/node_modules/@noodle-borg/service/dist/routes/archive.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.d.ts +2 -19
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +52 -281
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/deployments.d.ts +17 -0
- package/node_modules/@noodle-borg/service/dist/routes/deployments.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/deployments.js +88 -0
- package/node_modules/@noodle-borg/service/dist/routes/deployments.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin.d.ts +40 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin.js +353 -0
- package/node_modules/@noodle-borg/service/dist/routes/org-admin.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts +15 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/paths.js +47 -0
- package/node_modules/@noodle-borg/service/dist/routes/paths.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/plans.d.ts +20 -0
- package/node_modules/@noodle-borg/service/dist/routes/plans.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/plans.js +159 -0
- package/node_modules/@noodle-borg/service/dist/routes/plans.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/policies.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/policies.js +7 -0
- package/node_modules/@noodle-borg/service/dist/routes/policies.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/rollback.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js +23 -7
- package/node_modules/@noodle-borg/service/dist/routes/rollback.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.d.ts +6 -0
- package/node_modules/@noodle-borg/service/dist/serve.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/serve.js +72 -32
- package/node_modules/@noodle-borg/service/dist/serve.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/server-auth-bindings.d.ts +7 -0
- package/node_modules/@noodle-borg/service/dist/server-auth-bindings.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/server-auth-bindings.js +14 -0
- package/node_modules/@noodle-borg/service/dist/server-auth-bindings.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/service.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/service.js +139 -7
- package/node_modules/@noodle-borg/service/dist/service.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/control-plane.d.ts +14 -0
- package/node_modules/@noodle-borg/service/dist/store/control-plane.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/control-plane.js +38 -0
- package/node_modules/@noodle-borg/service/dist/store/control-plane.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts +25 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file.js +163 -0
- package/node_modules/@noodle-borg/service/dist/store/json-file.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-control-plane.d.ts +14 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-control-plane.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-control-plane.js +16 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-control-plane.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-rows.d.ts +60 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-rows.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-rows.js +101 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-rows.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js +21 -2
- package/node_modules/@noodle-borg/service/dist/store/postgres-schema.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts +23 -8
- package/node_modules/@noodle-borg/service/dist/store/postgres.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/postgres.js +112 -128
- package/node_modules/@noodle-borg/service/dist/store/postgres.js.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store/records.d.ts +35 -0
- package/node_modules/@noodle-borg/service/dist/store/records.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/records.js +87 -0
- package/node_modules/@noodle-borg/service/dist/store/records.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/validate.d.ts +19 -0
- package/node_modules/@noodle-borg/service/dist/store/validate.d.ts.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store/validate.js +61 -0
- package/node_modules/@noodle-borg/service/dist/store/validate.js.map +1 -0
- package/node_modules/@noodle-borg/service/dist/store.d.ts +75 -45
- package/node_modules/@noodle-borg/service/dist/store.d.ts.map +1 -1
- package/node_modules/@noodle-borg/service/dist/store.js +61 -224
- package/node_modules/@noodle-borg/service/dist/store.js.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts +1 -0
- package/node_modules/@noodle-borg/transport-http/dist/handler.d.ts.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/handler.js +37 -0
- package/node_modules/@noodle-borg/transport-http/dist/handler.js.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/index.d.ts +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/index.d.ts.map +1 -1
- package/node_modules/@noodle-borg/transport-http/dist/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { sendForbidden, sendJson } from '../http-util.js';
|
|
2
|
+
import { authorizeControlPlane } from './control-plane.js';
|
|
3
|
+
import { canManageMembers } from './org-admin.js';
|
|
4
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
5
|
+
export async function handleAppArchive(req, res, registry, gate, controlPlane, audit, ref, options) {
|
|
6
|
+
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
7
|
+
if (identity === false)
|
|
8
|
+
return;
|
|
9
|
+
if (!(await canManageMembers(controlPlane, ref.org, identity))) {
|
|
10
|
+
return sendForbidden(res, 'forbidden');
|
|
11
|
+
}
|
|
12
|
+
const at = (options.clock?.() ?? new Date()).toISOString();
|
|
13
|
+
const result = await registry.archiveApp(ref.org, ref.app, at);
|
|
14
|
+
if (result === undefined)
|
|
15
|
+
return sendJson(res, 404, { error: 'app not found' });
|
|
16
|
+
await audit.emit({
|
|
17
|
+
eventType: 'app.archived',
|
|
18
|
+
org: ref.org,
|
|
19
|
+
app: ref.app,
|
|
20
|
+
decision: 'allow',
|
|
21
|
+
status: 200,
|
|
22
|
+
actorSubject: identity.subject,
|
|
23
|
+
...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
24
|
+
details: {
|
|
25
|
+
archivedAt: result.archivedAt,
|
|
26
|
+
archivedDeployments: result.archivedDeployments,
|
|
27
|
+
alreadyArchived: result.alreadyArchived,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return sendJson(res, 200, {
|
|
31
|
+
ok: true,
|
|
32
|
+
target: { org: ref.org, app: ref.app },
|
|
33
|
+
archive: result,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export async function handleAppRestore(req, res, registry, gate, controlPlane, audit, ref, options, retentionDays) {
|
|
37
|
+
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
38
|
+
if (identity === false)
|
|
39
|
+
return;
|
|
40
|
+
if (!(await canManageMembers(controlPlane, ref.org, identity))) {
|
|
41
|
+
return sendForbidden(res, 'forbidden');
|
|
42
|
+
}
|
|
43
|
+
const now = options.clock?.() ?? new Date();
|
|
44
|
+
const archivedAt = await registry.getAppArchivedAt(ref.org, ref.app);
|
|
45
|
+
// Deterministic on the window, not on sweeper timing (ADR 0117 §5): past-retention restores are
|
|
46
|
+
// 410 Gone even before the sweeper hard-deletes; after the sweep the app is a plain 404.
|
|
47
|
+
if (archivedAt !== undefined && now.getTime() - Date.parse(archivedAt) > retentionDays * DAY_MS) {
|
|
48
|
+
await audit.emit({
|
|
49
|
+
eventType: 'app.restore.rejected',
|
|
50
|
+
org: ref.org,
|
|
51
|
+
app: ref.app,
|
|
52
|
+
decision: 'deny',
|
|
53
|
+
status: 410,
|
|
54
|
+
reasonCode: 'retention_elapsed',
|
|
55
|
+
actorSubject: identity.subject,
|
|
56
|
+
...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
57
|
+
details: { archivedAt, retentionDays },
|
|
58
|
+
});
|
|
59
|
+
return sendJson(res, 410, {
|
|
60
|
+
error: 'archive retention window elapsed; the app is pending permanent deletion',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const result = await registry.restoreApp(ref.org, ref.app);
|
|
64
|
+
if (result === undefined)
|
|
65
|
+
return sendJson(res, 404, { error: 'app not found' });
|
|
66
|
+
const alreadyActive = result.restoredDeployments === 0;
|
|
67
|
+
await audit.emit({
|
|
68
|
+
eventType: 'app.restored',
|
|
69
|
+
org: ref.org,
|
|
70
|
+
app: ref.app,
|
|
71
|
+
decision: 'allow',
|
|
72
|
+
status: 200,
|
|
73
|
+
actorSubject: identity.subject,
|
|
74
|
+
...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
75
|
+
details: { restoredDeployments: result.restoredDeployments, alreadyActive },
|
|
76
|
+
});
|
|
77
|
+
return sendJson(res, 200, {
|
|
78
|
+
ok: true,
|
|
79
|
+
target: { org: ref.org, app: ref.app },
|
|
80
|
+
restore: { restoredDeployments: result.restoredDeployments, alreadyActive },
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../src/routes/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,31 +3,14 @@ import type { ControlPlaneIdentity, DeployAuthGate } from '../auth/deploy-gate.j
|
|
|
3
3
|
import type { ServiceOptions } from '../options.js';
|
|
4
4
|
import type { ServerRegistry } from '../registry.js';
|
|
5
5
|
import type { AuditSink } from '../store/audit.js';
|
|
6
|
-
import {
|
|
6
|
+
import type { ConfigStore, ControlPlaneStore, TenantRef } from '../store.js';
|
|
7
7
|
import type { ConfigRouteRef } from './paths.js';
|
|
8
8
|
export declare function handleDeploy(req: IncomingMessage, res: ServerResponse, registry: ServerRegistry, options: ServiceOptions, maxBody: number, tenant: TenantRef, gate: DeployAuthGate, controlPlane: ControlPlaneStore, audit: AuditSink): Promise<void>;
|
|
9
9
|
export declare function handleWhoami(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, options?: ServiceOptions): Promise<void>;
|
|
10
|
-
export declare function handleOrgs(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, maxBody: number): Promise<void>;
|
|
11
|
-
export declare function handleMembers(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, maxBody: number, ref: {
|
|
12
|
-
org: string;
|
|
13
|
-
subject?: string;
|
|
14
|
-
}): Promise<void>;
|
|
15
|
-
export declare function handleInvitations(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, maxBody: number, ref: {
|
|
16
|
-
org: string;
|
|
17
|
-
token?: string;
|
|
18
|
-
action?: 'accept';
|
|
19
|
-
}, now?: () => Date): Promise<void>;
|
|
20
10
|
export declare function handleConfigValues(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, configStore: ConfigStore, maxBody: number, ref: ConfigRouteRef, audit: AuditSink): Promise<void>;
|
|
21
11
|
export declare function authorizeTenantControl(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, org: string): Promise<ControlPlaneIdentity | false>;
|
|
22
|
-
export declare function
|
|
23
|
-
export declare function handleDeploymentStatus(req: IncomingMessage, res: ServerResponse, registry: ServerRegistry, gate: DeployAuthGate, controlPlane: ControlPlaneStore, tenant: TenantRef, options: ServiceOptions): Promise<void>;
|
|
12
|
+
export declare function handleDeploymentStatus(req: IncomingMessage, res: ServerResponse, registry: ServerRegistry, gate: DeployAuthGate, controlPlane: ControlPlaneStore, tenant: TenantRef, options: ServiceOptions, url?: URL): Promise<void>;
|
|
24
13
|
export declare function handleAccessUpdate(req: IncomingMessage, res: ServerResponse, registry: ServerRegistry, gate: DeployAuthGate, controlPlane: ControlPlaneStore, maxBody: number, tenant: TenantRef): Promise<void>;
|
|
25
|
-
export declare function handleDeployments(req: IncomingMessage, res: ServerResponse, registry: ServerRegistry, gate: DeployAuthGate, controlPlane: ControlPlaneStore, ref: {
|
|
26
|
-
org: string;
|
|
27
|
-
}, url: URL): Promise<void>;
|
|
28
|
-
export declare function handleAuditEvents(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, controlPlane: ControlPlaneStore, audit: AuditSink, ref: {
|
|
29
|
-
org: string;
|
|
30
|
-
}, url: URL): Promise<void>;
|
|
31
14
|
export declare function authorizeControlPlane(req: IncomingMessage, res: ServerResponse, gate: DeployAuthGate, options: {
|
|
32
15
|
requireIdentity: true;
|
|
33
16
|
}): Promise<ControlPlaneIdentity | false>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../src/routes/control-plane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../../src/routes/control-plane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AASnF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAG7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC,CAuOf;AAWD,wBAAsB,YAAY,CAChC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAUf;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,WAAW,EAAE,WAAW,EACxB,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,cAAc,EACnB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,IAAI,CAAC,CAgDf;AAwBD,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,oBAAoB,GAAG,KAAK,CAAC,CAWvC;AAED,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,cAAc,EACvB,GAAG,CAAC,EAAE,GAAG,GACR,OAAO,CAAC,IAAI,CAAC,CAgBf;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,cAAc,EACpB,YAAY,EAAE,iBAAiB,EAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE;IAAE,eAAe,EAAE,IAAI,CAAA;CAAE,GACjC,OAAO,CAAC,oBAAoB,GAAG,KAAK,CAAC,CAAC;AACzC,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,eAAe,EACpB,GAAG,EAAE,cAAc,EACnB,IAAI,EAAE,cAAc,EACpB,OAAO,EAAE;IAAE,eAAe,EAAE,KAAK,CAAA;CAAE,GAClC,OAAO,CAAC,oBAAoB,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
1
2
|
import { noopLogger } from '@noodle-borg/transport-http';
|
|
2
3
|
import { baseFromRequest, readBody, readJsonBody, sendForbidden, sendJson, sendUnauthorized, } from '../http-util.js';
|
|
3
|
-
import { hashToken, randomToken } from '../oauth/tokens.js';
|
|
4
4
|
import { ensurePersonalWorkspace } from '../personal-workspace.js';
|
|
5
|
-
import {
|
|
5
|
+
import { tenantMcpUrl } from '../registry-helpers.js';
|
|
6
|
+
import { isAccessMode, isIdentityAccessMode } from './access-mode.js';
|
|
6
7
|
import { parseHostedAssets } from './assets.js';
|
|
7
|
-
const ORG_INVITATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
8
8
|
export async function handleDeploy(req, res, registry, options, maxBody, tenant, gate, controlPlane, audit) {
|
|
9
9
|
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: false });
|
|
10
10
|
if (identity === false)
|
|
@@ -17,6 +17,24 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
17
17
|
if (!member)
|
|
18
18
|
return sendForbidden(res, 'forbidden');
|
|
19
19
|
}
|
|
20
|
+
// Archived apps are soft-deleted (ADR 0117): refuse new deploys so the all-records-archived
|
|
21
|
+
// invariant can't be broken sideways. Restore first, then deploy.
|
|
22
|
+
const appArchivedAt = await registry.getAppArchivedAt(tenant.org, tenant.app);
|
|
23
|
+
if (appArchivedAt !== undefined) {
|
|
24
|
+
await audit.emit({
|
|
25
|
+
eventType: 'deploy.rejected',
|
|
26
|
+
org: tenant.org,
|
|
27
|
+
app: tenant.app,
|
|
28
|
+
env: tenant.env,
|
|
29
|
+
decision: 'deny',
|
|
30
|
+
status: 409,
|
|
31
|
+
reasonCode: 'app_archived',
|
|
32
|
+
...(identity?.subject !== undefined ? { actorSubject: identity.subject } : {}),
|
|
33
|
+
...(identity?.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
34
|
+
details: { archivedAt: appArchivedAt },
|
|
35
|
+
});
|
|
36
|
+
return sendJson(res, 409, { error: 'app is archived; restore it before deploying' });
|
|
37
|
+
}
|
|
20
38
|
const body = await readBody(req, maxBody);
|
|
21
39
|
if (!body.ok)
|
|
22
40
|
return sendJson(res, 413, { error: 'request body too large' });
|
|
@@ -24,6 +42,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
24
42
|
let connectors;
|
|
25
43
|
let hostedAssets;
|
|
26
44
|
let accessMode = 'owner-only';
|
|
45
|
+
let serverVersion;
|
|
27
46
|
try {
|
|
28
47
|
const parsed = JSON.parse(body.text);
|
|
29
48
|
if (typeof parsed.manifest !== 'string')
|
|
@@ -49,6 +68,8 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
49
68
|
if (parsed.hostedAssets !== undefined) {
|
|
50
69
|
hostedAssets = parseHostedAssets(parsed.hostedAssets);
|
|
51
70
|
}
|
|
71
|
+
serverVersion =
|
|
72
|
+
parsed.serverVersion === undefined ? '1' : normalizeParsedServerVersion(parsed.serverVersion);
|
|
52
73
|
}
|
|
53
74
|
catch (error) {
|
|
54
75
|
return sendJson(res, 400, { error: `invalid deploy request: ${error.message}` });
|
|
@@ -103,7 +124,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
103
124
|
}
|
|
104
125
|
hostedAssets = verified.assets;
|
|
105
126
|
}
|
|
106
|
-
const result = await registry.deploy(tenant, manifest, connectors, identity || undefined, accessMode, hostedAssets);
|
|
127
|
+
const result = await registry.deploy(tenant, manifest, connectors, identity || undefined, accessMode, hostedAssets, serverVersion);
|
|
107
128
|
if (!result.ok) {
|
|
108
129
|
// Codes are safe enum strings; never a path-with-value. No secret name or value is logged.
|
|
109
130
|
const codes = result.errors.map((error) => error.code).join(',');
|
|
@@ -130,6 +151,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
130
151
|
// Counts only — never secret names or values.
|
|
131
152
|
logger.info('deploy.ok', {
|
|
132
153
|
deploymentId: result.deploymentId,
|
|
154
|
+
serverVersion: result.serverVersion,
|
|
133
155
|
org: tenant.org,
|
|
134
156
|
app: tenant.app,
|
|
135
157
|
env: tenant.env,
|
|
@@ -147,6 +169,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
147
169
|
...(identity?.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
148
170
|
details: {
|
|
149
171
|
accessMode: result.accessMode ?? accessMode,
|
|
172
|
+
serverVersion: result.serverVersion,
|
|
150
173
|
hasConnectors: connectors !== undefined,
|
|
151
174
|
},
|
|
152
175
|
});
|
|
@@ -159,7 +182,7 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
159
182
|
app: tenant.app,
|
|
160
183
|
env: tenant.env,
|
|
161
184
|
...(result.deploymentId !== undefined ? { deploymentId: result.deploymentId } : {}),
|
|
162
|
-
details: { accessMode: result.accessMode ?? accessMode },
|
|
185
|
+
details: { accessMode: result.accessMode ?? accessMode, serverVersion: result.serverVersion },
|
|
163
186
|
});
|
|
164
187
|
if (result.ok && hostedAssets !== undefined && hostedAssets.length > 0) {
|
|
165
188
|
await audit.emit({
|
|
@@ -185,31 +208,23 @@ export async function handleDeploy(req, res, registry, options, maxBody, tenant,
|
|
|
185
208
|
});
|
|
186
209
|
}
|
|
187
210
|
const base = options.publicBaseUrl ?? baseFromRequest(req, options.tls ?? {});
|
|
211
|
+
const defaultUrl = tenantMcpUrl(base, tenant);
|
|
188
212
|
return sendJson(res, 201, {
|
|
189
213
|
ok: true,
|
|
190
214
|
org: tenant.org,
|
|
191
215
|
app: tenant.app,
|
|
192
216
|
env: tenant.env,
|
|
193
217
|
deploymentId: result.deploymentId,
|
|
218
|
+
serverVersion: result.serverVersion,
|
|
194
219
|
accessMode: result.accessMode,
|
|
195
|
-
url: tenant.
|
|
196
|
-
|
|
197
|
-
: `${base}/o/${tenant.org}/${tenant.app}/${tenant.env}/mcp`,
|
|
220
|
+
url: tenantMcpUrl(base, tenant, result.serverVersion),
|
|
221
|
+
defaultUrl,
|
|
198
222
|
});
|
|
199
223
|
}
|
|
200
|
-
function
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
value === 'mixed' ||
|
|
205
|
-
value === 'customers' ||
|
|
206
|
-
value === 'authenticated');
|
|
207
|
-
}
|
|
208
|
-
function isIdentityAccessMode(accessMode) {
|
|
209
|
-
return (accessMode === 'owner-only' ||
|
|
210
|
-
accessMode === 'org-members' ||
|
|
211
|
-
accessMode === 'customers' ||
|
|
212
|
-
accessMode === 'authenticated');
|
|
224
|
+
function normalizeParsedServerVersion(value) {
|
|
225
|
+
if (typeof value !== 'string')
|
|
226
|
+
throw new Error('"serverVersion" must be a version string');
|
|
227
|
+
return normalizeServerVersion(value);
|
|
213
228
|
}
|
|
214
229
|
function manifestUsesUserRoot(manifest) {
|
|
215
230
|
return /\$\{\s*user(?:[.[]|\s*\})/.test(manifest);
|
|
@@ -226,154 +241,6 @@ export async function handleWhoami(req, res, gate, controlPlane, options = {}) {
|
|
|
226
241
|
: await controlPlane.listOrgsForSubject(identity.subject);
|
|
227
242
|
return sendJson(res, 200, { ok: true, identity, orgs });
|
|
228
243
|
}
|
|
229
|
-
export async function handleOrgs(req, res, gate, controlPlane, maxBody) {
|
|
230
|
-
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
231
|
-
if (identity === false)
|
|
232
|
-
return;
|
|
233
|
-
if (req.method === 'GET') {
|
|
234
|
-
const orgs = identity.superAdmin
|
|
235
|
-
? await controlPlane.listOrgs()
|
|
236
|
-
: await controlPlane.listOrgsForSubject(identity.subject);
|
|
237
|
-
return sendJson(res, 200, { ok: true, orgs });
|
|
238
|
-
}
|
|
239
|
-
if (!identity.superAdmin)
|
|
240
|
-
return sendForbidden(res, 'super-admin required');
|
|
241
|
-
const body = await readJsonBody(req, maxBody);
|
|
242
|
-
if (!body.ok)
|
|
243
|
-
return sendJson(res, body.status, { error: body.error });
|
|
244
|
-
const parsed = body.value;
|
|
245
|
-
if (typeof parsed.slug !== 'string')
|
|
246
|
-
return sendJson(res, 400, { error: '"slug" must be a string' });
|
|
247
|
-
if (parsed.displayName !== undefined && typeof parsed.displayName !== 'string') {
|
|
248
|
-
return sendJson(res, 400, { error: '"displayName" must be a string' });
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
const org = await controlPlane.createOrg({
|
|
252
|
-
slug: parsed.slug,
|
|
253
|
-
...(typeof parsed.displayName === 'string' ? { displayName: parsed.displayName } : {}),
|
|
254
|
-
});
|
|
255
|
-
return sendJson(res, 201, { ok: true, org });
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
return sendJson(res, 400, { error: error.message });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
export async function handleMembers(req, res, gate, controlPlane, maxBody, ref) {
|
|
262
|
-
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
263
|
-
if (identity === false)
|
|
264
|
-
return;
|
|
265
|
-
if (req.method === 'GET' && ref.subject === undefined) {
|
|
266
|
-
if (!(await canViewMembers(controlPlane, ref.org, identity)))
|
|
267
|
-
return sendForbidden(res, 'forbidden');
|
|
268
|
-
try {
|
|
269
|
-
return sendJson(res, 200, { ok: true, members: await controlPlane.listOrgMembers(ref.org) });
|
|
270
|
-
}
|
|
271
|
-
catch (error) {
|
|
272
|
-
return sendJson(res, 400, { error: error.message });
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (req.method === 'POST' && ref.subject === undefined) {
|
|
276
|
-
if (!(await canManageMembers(controlPlane, ref.org, identity)))
|
|
277
|
-
return sendForbidden(res, 'forbidden');
|
|
278
|
-
const body = await readJsonBody(req, maxBody);
|
|
279
|
-
if (!body.ok)
|
|
280
|
-
return sendJson(res, body.status, { error: body.error });
|
|
281
|
-
const parsed = body.value;
|
|
282
|
-
if (typeof parsed.subject !== 'string') {
|
|
283
|
-
return sendJson(res, 400, { error: '"subject" must be a string' });
|
|
284
|
-
}
|
|
285
|
-
if (typeof parsed.email !== 'string') {
|
|
286
|
-
return sendJson(res, 400, { error: '"email" must be a string' });
|
|
287
|
-
}
|
|
288
|
-
let role;
|
|
289
|
-
try {
|
|
290
|
-
role = validateOrgRole(typeof parsed.role === 'string' ? parsed.role : 'developer');
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
return sendJson(res, 400, { error: error.message });
|
|
294
|
-
}
|
|
295
|
-
try {
|
|
296
|
-
const member = await controlPlane.addOrgMember({
|
|
297
|
-
org: ref.org,
|
|
298
|
-
subject: parsed.subject,
|
|
299
|
-
email: parsed.email,
|
|
300
|
-
role,
|
|
301
|
-
});
|
|
302
|
-
return sendJson(res, 201, { ok: true, member });
|
|
303
|
-
}
|
|
304
|
-
catch (error) {
|
|
305
|
-
return sendJson(res, 400, { error: error.message });
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
if (req.method === 'DELETE' && ref.subject !== undefined) {
|
|
309
|
-
if (!(await canManageMembers(controlPlane, ref.org, identity)))
|
|
310
|
-
return sendForbidden(res, 'forbidden');
|
|
311
|
-
await controlPlane.removeOrgMember({ org: ref.org, subject: ref.subject });
|
|
312
|
-
res.writeHead(204);
|
|
313
|
-
res.end();
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
317
|
-
}
|
|
318
|
-
export async function handleInvitations(req, res, gate, controlPlane, maxBody, ref, now = () => new Date()) {
|
|
319
|
-
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
320
|
-
if (identity === false)
|
|
321
|
-
return;
|
|
322
|
-
if (req.method === 'POST' && ref.token === undefined) {
|
|
323
|
-
if (!(await canManageMembers(controlPlane, ref.org, identity)))
|
|
324
|
-
return sendForbidden(res, 'forbidden');
|
|
325
|
-
const body = await readJsonBody(req, maxBody);
|
|
326
|
-
if (!body.ok)
|
|
327
|
-
return sendJson(res, body.status, { error: body.error });
|
|
328
|
-
const parsed = body.value;
|
|
329
|
-
if (typeof parsed.email !== 'string')
|
|
330
|
-
return sendJson(res, 400, { error: '"email" must be a string' });
|
|
331
|
-
let role;
|
|
332
|
-
try {
|
|
333
|
-
role = validateOrgRole(typeof parsed.role === 'string' ? parsed.role : 'developer');
|
|
334
|
-
}
|
|
335
|
-
catch (error) {
|
|
336
|
-
return sendJson(res, 400, { error: error.message });
|
|
337
|
-
}
|
|
338
|
-
const rawToken = randomToken();
|
|
339
|
-
const expiresAt = new Date(now().getTime() + ORG_INVITATION_TTL_MS);
|
|
340
|
-
const invitation = await controlPlane.createOrgInvitation({
|
|
341
|
-
org: ref.org,
|
|
342
|
-
email: parsed.email,
|
|
343
|
-
role,
|
|
344
|
-
tokenHash: hashToken(rawToken),
|
|
345
|
-
createdBySubject: identity.subject,
|
|
346
|
-
createdByEmail: identity.email,
|
|
347
|
-
expiresAt,
|
|
348
|
-
});
|
|
349
|
-
return sendJson(res, 201, {
|
|
350
|
-
ok: true,
|
|
351
|
-
invitation: publicInvitation(invitation),
|
|
352
|
-
acceptPath: invitationAcceptPath(ref.org, rawToken),
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
if (req.method === 'POST' && ref.token !== undefined && ref.action === 'accept') {
|
|
356
|
-
const tokenHash = hashToken(ref.token);
|
|
357
|
-
const invitation = await controlPlane.getOrgInvitation({ tokenHash });
|
|
358
|
-
if (invitation === undefined || invitation.orgSlug !== ref.org) {
|
|
359
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
360
|
-
}
|
|
361
|
-
if (identity.email.toLowerCase() !== invitation.email) {
|
|
362
|
-
return sendForbidden(res, 'forbidden');
|
|
363
|
-
}
|
|
364
|
-
const consumed = await controlPlane.consumeOrgInvitation({ tokenHash });
|
|
365
|
-
if (consumed === undefined)
|
|
366
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
367
|
-
const member = await controlPlane.addOrgMember({
|
|
368
|
-
org: consumed.orgSlug,
|
|
369
|
-
subject: identity.subject,
|
|
370
|
-
email: identity.email,
|
|
371
|
-
role: consumed.role,
|
|
372
|
-
});
|
|
373
|
-
return sendJson(res, 201, { ok: true, member });
|
|
374
|
-
}
|
|
375
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
376
|
-
}
|
|
377
244
|
export async function handleConfigValues(req, res, gate, controlPlane, configStore, maxBody, ref, audit) {
|
|
378
245
|
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
379
246
|
if (identity === false)
|
|
@@ -457,39 +324,17 @@ export async function authorizeTenantControl(req, res, gate, controlPlane, org)
|
|
|
457
324
|
}
|
|
458
325
|
return identity;
|
|
459
326
|
}
|
|
460
|
-
async function
|
|
461
|
-
if (identity.superAdmin)
|
|
462
|
-
return true;
|
|
463
|
-
return (await store.getOrgMember({ org, subject: identity.subject })) !== undefined;
|
|
464
|
-
}
|
|
465
|
-
export async function canManageMembers(store, org, identity) {
|
|
466
|
-
if (identity.superAdmin)
|
|
467
|
-
return true;
|
|
468
|
-
return (await store.getOrgMember({ org, subject: identity.subject }))?.role === 'owner';
|
|
469
|
-
}
|
|
470
|
-
function publicInvitation(invitation) {
|
|
471
|
-
return {
|
|
472
|
-
orgSlug: invitation.orgSlug,
|
|
473
|
-
email: invitation.email,
|
|
474
|
-
role: invitation.role,
|
|
475
|
-
createdAt: invitation.createdAt,
|
|
476
|
-
expiresAt: invitation.expiresAt,
|
|
477
|
-
createdBySubject: invitation.createdBySubject,
|
|
478
|
-
...(invitation.createdByEmail !== undefined
|
|
479
|
-
? { createdByEmail: invitation.createdByEmail }
|
|
480
|
-
: {}),
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function invitationAcceptPath(org, token) {
|
|
484
|
-
return `/v1/orgs/${encodeURIComponent(org)}/invitations/${encodeURIComponent(token)}/accept`;
|
|
485
|
-
}
|
|
486
|
-
export async function handleDeploymentStatus(req, res, registry, gate, controlPlane, tenant, options) {
|
|
327
|
+
export async function handleDeploymentStatus(req, res, registry, gate, controlPlane, tenant, options, url) {
|
|
487
328
|
const identity = await authorizeTenantControl(req, res, gate, controlPlane, tenant.org);
|
|
488
329
|
if (identity === false)
|
|
489
330
|
return;
|
|
490
331
|
try {
|
|
491
332
|
const base = options.publicBaseUrl ?? baseFromRequest(req, options.tls ?? {});
|
|
492
|
-
const
|
|
333
|
+
const rawVersion = url?.searchParams.get('version') ?? undefined;
|
|
334
|
+
const serverVersion = rawVersion !== undefined && rawVersion !== ''
|
|
335
|
+
? normalizeServerVersion(rawVersion)
|
|
336
|
+
: undefined;
|
|
337
|
+
const status = await registry.getStatus(tenant, base, serverVersion);
|
|
493
338
|
if (status === undefined)
|
|
494
339
|
return sendJson(res, 404, { error: 'no active deployment' });
|
|
495
340
|
return sendJson(res, 200, { ok: true, ...status });
|
|
@@ -513,7 +358,15 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
513
358
|
return sendJson(res, 400, { error: message });
|
|
514
359
|
}
|
|
515
360
|
try {
|
|
516
|
-
const
|
|
361
|
+
const serverVersion = parsed.serverVersion === undefined
|
|
362
|
+
? undefined
|
|
363
|
+
: typeof parsed.serverVersion === 'string'
|
|
364
|
+
? normalizeServerVersion(parsed.serverVersion)
|
|
365
|
+
: undefined;
|
|
366
|
+
if (parsed.serverVersion !== undefined && serverVersion === undefined) {
|
|
367
|
+
return sendJson(res, 400, { error: '"serverVersion" must be a version string' });
|
|
368
|
+
}
|
|
369
|
+
const record = await registry.updateAccess(tenant, parsed.accessMode, serverVersion);
|
|
517
370
|
if (record === undefined)
|
|
518
371
|
return sendJson(res, 404, { error: 'no active deployment' });
|
|
519
372
|
return sendJson(res, 200, {
|
|
@@ -521,6 +374,7 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
521
374
|
target: tenant,
|
|
522
375
|
deployment: {
|
|
523
376
|
deploymentId: record.deploymentId,
|
|
377
|
+
...(record.serverVersion !== undefined ? { serverVersion: record.serverVersion } : {}),
|
|
524
378
|
accessMode: record.accessMode ?? 'owner-only',
|
|
525
379
|
},
|
|
526
380
|
});
|
|
@@ -529,89 +383,6 @@ export async function handleAccessUpdate(req, res, registry, gate, controlPlane,
|
|
|
529
383
|
return sendJson(res, 400, { error: error.message });
|
|
530
384
|
}
|
|
531
385
|
}
|
|
532
|
-
export async function handleDeployments(req, res, registry, gate, controlPlane, ref, url) {
|
|
533
|
-
const identity = await authorizeControlPlane(req, res, gate, { requireIdentity: true });
|
|
534
|
-
if (identity === false)
|
|
535
|
-
return;
|
|
536
|
-
if (!identity.superAdmin) {
|
|
537
|
-
const member = await controlPlane.isOrgMember({ org: ref.org, subject: identity.subject });
|
|
538
|
-
if (!member)
|
|
539
|
-
return sendForbidden(res, 'forbidden');
|
|
540
|
-
}
|
|
541
|
-
const app = url.searchParams.get('app') ?? undefined;
|
|
542
|
-
const env = url.searchParams.get('env') ?? undefined;
|
|
543
|
-
try {
|
|
544
|
-
const deployments = await registry.listDeployments({
|
|
545
|
-
org: ref.org,
|
|
546
|
-
...(app !== undefined ? { app } : {}),
|
|
547
|
-
...(env !== undefined ? { env } : {}),
|
|
548
|
-
});
|
|
549
|
-
return sendJson(res, 200, { ok: true, deployments });
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
return sendJson(res, 400, { error: error.message });
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
export async function handleAuditEvents(req, res, gate, controlPlane, audit, ref, url) {
|
|
556
|
-
const identity = await authorizeTenantControl(req, res, gate, controlPlane, ref.org);
|
|
557
|
-
if (identity === false)
|
|
558
|
-
return;
|
|
559
|
-
if (!isAuditStore(audit)) {
|
|
560
|
-
return sendJson(res, 409, {
|
|
561
|
-
ok: false,
|
|
562
|
-
error: 'audit events require an audit store capability',
|
|
563
|
-
});
|
|
564
|
-
}
|
|
565
|
-
const limit = parseAuditLimit(url.searchParams.get('limit'));
|
|
566
|
-
if (limit === false)
|
|
567
|
-
return sendJson(res, 400, { error: '"limit" must be an integer from 1 to 100' });
|
|
568
|
-
try {
|
|
569
|
-
const app = optionalQuery(url, 'app');
|
|
570
|
-
const env = optionalQuery(url, 'env');
|
|
571
|
-
const eventType = optionalQuery(url, 'eventType');
|
|
572
|
-
const events = await audit.list({
|
|
573
|
-
org: ref.org,
|
|
574
|
-
...(app !== undefined ? { app } : {}),
|
|
575
|
-
...(env !== undefined ? { env } : {}),
|
|
576
|
-
...(eventType !== undefined ? { eventType } : {}),
|
|
577
|
-
...(limit !== undefined ? { limit } : {}),
|
|
578
|
-
});
|
|
579
|
-
await audit.emit({
|
|
580
|
-
eventType: 'audit.events.queried',
|
|
581
|
-
org: ref.org,
|
|
582
|
-
...(app !== undefined ? { app } : {}),
|
|
583
|
-
...(env !== undefined ? { env } : {}),
|
|
584
|
-
decision: 'allow',
|
|
585
|
-
status: 200,
|
|
586
|
-
actorSubject: identity.subject,
|
|
587
|
-
...(identity.email !== undefined ? { actorEmail: identity.email } : {}),
|
|
588
|
-
details: {
|
|
589
|
-
...(eventType !== undefined ? { eventType } : {}),
|
|
590
|
-
...(limit !== undefined ? { limit } : {}),
|
|
591
|
-
resultCount: events.length,
|
|
592
|
-
},
|
|
593
|
-
});
|
|
594
|
-
return sendJson(res, 200, { ok: true, events });
|
|
595
|
-
}
|
|
596
|
-
catch (error) {
|
|
597
|
-
return sendJson(res, 400, { error: error.message });
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
function isAuditStore(audit) {
|
|
601
|
-
return 'list' in audit && typeof audit.list === 'function';
|
|
602
|
-
}
|
|
603
|
-
function optionalQuery(url, key) {
|
|
604
|
-
const value = url.searchParams.get(key);
|
|
605
|
-
return value === null || value.trim() === '' ? undefined : value;
|
|
606
|
-
}
|
|
607
|
-
function parseAuditLimit(value) {
|
|
608
|
-
if (value === null || value.trim() === '')
|
|
609
|
-
return undefined;
|
|
610
|
-
const parsed = Number(value);
|
|
611
|
-
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 100)
|
|
612
|
-
return false;
|
|
613
|
-
return parsed;
|
|
614
|
-
}
|
|
615
386
|
export async function authorizeControlPlane(req, res, gate, options) {
|
|
616
387
|
const auth = await gate.authorize(req);
|
|
617
388
|
if (!auth.ok) {
|