okengine 0.1.4
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/AGENTS.md +91 -0
- package/README.md +327 -0
- package/docs/spec/complete-example.md +1188 -0
- package/docs/spec/console.md +739 -0
- package/docs/spec/four-applications.md +1188 -0
- package/docs/spec/unified-theory.md +489 -0
- package/package.json +152 -0
- package/spec/manifest.v1.schema.json +624 -0
- package/src/auth/api-keys.ts +168 -0
- package/src/auth/attenuation.ts +116 -0
- package/src/auth/auth.test.ts +273 -0
- package/src/auth/cross-plane.ts +98 -0
- package/src/auth/index.ts +112 -0
- package/src/auth/invites.ts +66 -0
- package/src/auth/invoke-as.ts +49 -0
- package/src/auth/operator.ts +178 -0
- package/src/auth/planes.ts +102 -0
- package/src/auth/plugin.ts +44 -0
- package/src/auth/roles.ts +84 -0
- package/src/auth/sessions.ts +418 -0
- package/src/auth/tables.ts +131 -0
- package/src/cli/args.ts +50 -0
- package/src/cli/branch.ts +75 -0
- package/src/cli/build.ts +86 -0
- package/src/cli/client-add.test.ts +140 -0
- package/src/cli/client-add.ts +164 -0
- package/src/cli/completion.test.ts +58 -0
- package/src/cli/completion.ts +296 -0
- package/src/cli/dev-app-runner.ts +77 -0
- package/src/cli/dev.test.ts +435 -0
- package/src/cli/dev.ts +594 -0
- package/src/cli/doc-drift.test.ts +146 -0
- package/src/cli/doc-drift.ts +397 -0
- package/src/cli/docker-cli.test.ts +221 -0
- package/src/cli/docker.ts +127 -0
- package/src/cli/doctor-diff-examples.ts +92 -0
- package/src/cli/doctor-diff.test.ts +91 -0
- package/src/cli/doctor-diff.ts +274 -0
- package/src/cli/doctor-pii.test.ts +98 -0
- package/src/cli/doctor-pii.ts +151 -0
- package/src/cli/doctor.test.ts +150 -0
- package/src/cli/doctor.ts +223 -0
- package/src/cli/eval.ts +122 -0
- package/src/cli/exit.ts +19 -0
- package/src/cli/gates-list.test.ts +47 -0
- package/src/cli/gates-list.ts +84 -0
- package/src/cli/images.ts +277 -0
- package/src/cli/index.ts +99 -0
- package/src/cli/json-out.test.ts +129 -0
- package/src/cli/load-config.ts +71 -0
- package/src/cli/mcp-from-console.ts +50 -0
- package/src/cli/privacy-erase.ts +73 -0
- package/src/cli/registry.ts +455 -0
- package/src/cli/safe-defaults.test.ts +92 -0
- package/src/cli/schema.ts +196 -0
- package/src/cli/stack.ts +82 -0
- package/src/cli/start.ts +106 -0
- package/src/cli/upgrade.ts +105 -0
- package/src/cli/vault-cmd.ts +140 -0
- package/src/client/budget-entry.ts +34 -0
- package/src/client/budget.test.ts +17 -0
- package/src/client/create.test.ts +159 -0
- package/src/client/create.ts +152 -0
- package/src/client/errors.ts +60 -0
- package/src/client/index.ts +35 -0
- package/src/client/notes-contract.test.ts +289 -0
- package/src/client/transport.test.ts +123 -0
- package/src/client/transport.ts +254 -0
- package/src/client/types.ts +282 -0
- package/src/compiler/aot.test.ts +248 -0
- package/src/compiler/aot.ts +182 -0
- package/src/compiler/differential.test.ts +241 -0
- package/src/compiler/dynamic.ts +54 -0
- package/src/compiler/effects-infer.ts +541 -0
- package/src/compiler/emit.ts +40 -0
- package/src/compiler/extract.test.ts +177 -0
- package/src/compiler/extract.ts +1368 -0
- package/src/compiler/fixtures/raw/raw-unannotated.ts +28 -0
- package/src/compiler/fixtures/skyport/oke.config.ts +24 -0
- package/src/compiler/fixtures/skyport/src/ai.ts +15 -0
- package/src/compiler/fixtures/skyport/src/app.ts +10 -0
- package/src/compiler/fixtures/skyport/src/channels.ts +6 -0
- package/src/compiler/fixtures/skyport/src/core.ts +3 -0
- package/src/compiler/fixtures/skyport/src/flows/bookings/index.ts +57 -0
- package/src/compiler/fixtures/skyport/src/flows/bookings/signals.ts +11 -0
- package/src/compiler/fixtures/skyport/src/flows/payments/index.ts +14 -0
- package/src/compiler/fixtures/skyport/src/flows/support/index.ts +20 -0
- package/src/compiler/fixtures/skyport/src/gates.ts +14 -0
- package/src/compiler/fixtures/skyport/src/journeys.ts +9 -0
- package/src/compiler/fixtures/skyport/src/schema.ts +3 -0
- package/src/compiler/fixtures/skyport/src/vault.ts +6 -0
- package/src/compiler/fixtures/skyport.expected.json +241 -0
- package/src/compiler/fixtures/triggers/five-triggers.ts +69 -0
- package/src/compiler/http-parse.ts +214 -0
- package/src/compiler/index.ts +67 -0
- package/src/compiler/response.ts +89 -0
- package/src/compiler/sucrose.ts +238 -0
- package/src/config/index.ts +167 -0
- package/src/console/budget.test.ts +73 -0
- package/src/console/index.ts +30 -0
- package/src/console/server/access.test.ts +370 -0
- package/src/console/server/access.ts +777 -0
- package/src/console/server/ai.test.ts +172 -0
- package/src/console/server/ai.ts +632 -0
- package/src/console/server/app.ts +294 -0
- package/src/console/server/auth-rate.test.ts +81 -0
- package/src/console/server/auth-rate.ts +72 -0
- package/src/console/server/bind.ts +25 -0
- package/src/console/server/channels.test.ts +179 -0
- package/src/console/server/channels.ts +469 -0
- package/src/console/server/claim.test.ts +66 -0
- package/src/console/server/claim.ts +134 -0
- package/src/console/server/clock.test.ts +239 -0
- package/src/console/server/clock.ts +390 -0
- package/src/console/server/console.test.ts +338 -0
- package/src/console/server/diff.test.ts +299 -0
- package/src/console/server/diff.ts +352 -0
- package/src/console/server/dry-run.audit.test.ts +118 -0
- package/src/console/server/flows-invoke.test.ts +148 -0
- package/src/console/server/flows.ts +3633 -0
- package/src/console/server/gates.test.ts +360 -0
- package/src/console/server/gates.ts +869 -0
- package/src/console/server/index.ts +141 -0
- package/src/console/server/live.test.ts +30 -0
- package/src/console/server/live.ts +109 -0
- package/src/console/server/plugin.ts +50 -0
- package/src/console/server/plugins.test.ts +131 -0
- package/src/console/server/plugins.ts +429 -0
- package/src/console/server/runs-pii.ts +98 -0
- package/src/console/server/security-headers.ts +97 -0
- package/src/console/server/security.gate.test.ts +286 -0
- package/src/console/server/serve.ts +319 -0
- package/src/console/server/signals.test.ts +88 -0
- package/src/console/server/signals.ts +282 -0
- package/src/console/server/state.ts +982 -0
- package/src/console/server/store.test.ts +233 -0
- package/src/console/server/store.ts +861 -0
- package/src/console/server/structural.ts +95 -0
- package/src/console/server/vault.test.ts +251 -0
- package/src/console/server/vault.ts +430 -0
- package/src/console/ui/access/AccessA11yView.tsx +237 -0
- package/src/console/ui/access/a11y.test.tsx +122 -0
- package/src/console/ui/access/acknowledgement.test.ts +13 -0
- package/src/console/ui/access/acknowledgement.ts +35 -0
- package/src/console/ui/access/blast-radius.test.ts +26 -0
- package/src/console/ui/access/blast-radius.ts +40 -0
- package/src/console/ui/access/confirmation.test.ts +38 -0
- package/src/console/ui/access/confirmation.ts +36 -0
- package/src/console/ui/access/fixture.ts +203 -0
- package/src/console/ui/access/grantable.test.ts +17 -0
- package/src/console/ui/access/grantable.ts +36 -0
- package/src/console/ui/access/hygiene.test.ts +12 -0
- package/src/console/ui/access/hygiene.ts +39 -0
- package/src/console/ui/access/index.ts +54 -0
- package/src/console/ui/access/provenance.test.ts +12 -0
- package/src/console/ui/access/provenance.ts +29 -0
- package/src/console/ui/access/search.test.ts +17 -0
- package/src/console/ui/access/search.ts +72 -0
- package/src/console/ui/access/types.ts +111 -0
- package/src/console/ui/ai/AiA11yView.tsx +265 -0
- package/src/console/ui/ai/a11y.test.tsx +99 -0
- package/src/console/ui/ai/findings.ts +41 -0
- package/src/console/ui/ai/fixture.ts +182 -0
- package/src/console/ui/ai/format.ts +90 -0
- package/src/console/ui/ai/group.test.ts +37 -0
- package/src/console/ui/ai/group.ts +88 -0
- package/src/console/ui/ai/index.ts +66 -0
- package/src/console/ui/ai/promotion.test.ts +77 -0
- package/src/console/ui/ai/promotion.ts +150 -0
- package/src/console/ui/ai/search.test.ts +40 -0
- package/src/console/ui/ai/search.ts +89 -0
- package/src/console/ui/ai/types.ts +139 -0
- package/src/console/ui/architecture/ArchitectureA11yView.tsx +163 -0
- package/src/console/ui/architecture/a11y.test.tsx +97 -0
- package/src/console/ui/architecture/boundary.ts +42 -0
- package/src/console/ui/architecture/declared.ts +165 -0
- package/src/console/ui/architecture/fixture.ts +74 -0
- package/src/console/ui/architecture/index.ts +84 -0
- package/src/console/ui/architecture/layers.ts +64 -0
- package/src/console/ui/architecture/layout.ts +68 -0
- package/src/console/ui/architecture/pathologies.ts +260 -0
- package/src/console/ui/architecture/search.ts +145 -0
- package/src/console/ui/architecture/traffic.ts +102 -0
- package/src/console/ui/architecture/types.ts +144 -0
- package/src/console/ui/architecture/view.test.ts +165 -0
- package/src/console/ui/architecture/view.ts +516 -0
- package/src/console/ui/channels/ChannelsA11yView.tsx +180 -0
- package/src/console/ui/channels/a11y.test.tsx +96 -0
- package/src/console/ui/channels/confirmation.test.ts +37 -0
- package/src/console/ui/channels/confirmation.ts +27 -0
- package/src/console/ui/channels/fallback.test.ts +15 -0
- package/src/console/ui/channels/fallback.ts +20 -0
- package/src/console/ui/channels/findings.ts +38 -0
- package/src/console/ui/channels/fixture.ts +134 -0
- package/src/console/ui/channels/group.ts +25 -0
- package/src/console/ui/channels/index.ts +57 -0
- package/src/console/ui/channels/locale.test.ts +17 -0
- package/src/console/ui/channels/locale.ts +31 -0
- package/src/console/ui/channels/mask.test.ts +14 -0
- package/src/console/ui/channels/mask.ts +12 -0
- package/src/console/ui/channels/search.ts +58 -0
- package/src/console/ui/channels/taxonomy.test.ts +17 -0
- package/src/console/ui/channels/taxonomy.ts +49 -0
- package/src/console/ui/channels/types.ts +117 -0
- package/src/console/ui/clock/ClockA11yView.tsx +182 -0
- package/src/console/ui/clock/a11y.test.tsx +99 -0
- package/src/console/ui/clock/confirmation.test.ts +32 -0
- package/src/console/ui/clock/confirmation.ts +34 -0
- package/src/console/ui/clock/findings.ts +40 -0
- package/src/console/ui/clock/fixture.ts +111 -0
- package/src/console/ui/clock/health.test.ts +25 -0
- package/src/console/ui/clock/health.ts +61 -0
- package/src/console/ui/clock/index.ts +49 -0
- package/src/console/ui/clock/search.test.ts +39 -0
- package/src/console/ui/clock/search.ts +76 -0
- package/src/console/ui/clock/timeline.test.ts +44 -0
- package/src/console/ui/clock/timeline.ts +41 -0
- package/src/console/ui/clock/types.ts +78 -0
- package/src/console/ui/clock/waiting-on.test.ts +34 -0
- package/src/console/ui/clock/waiting-on.ts +83 -0
- package/src/console/ui/confirmation.unify.test.ts +92 -0
- package/src/console/ui/diff/DiffA11yView.tsx +85 -0
- package/src/console/ui/diff/a11y.test.tsx +99 -0
- package/src/console/ui/diff/fixture.ts +85 -0
- package/src/console/ui/diff/group.test.ts +58 -0
- package/src/console/ui/diff/group.ts +97 -0
- package/src/console/ui/diff/index.ts +38 -0
- package/src/console/ui/diff/search.test.ts +32 -0
- package/src/console/ui/diff/search.ts +75 -0
- package/src/console/ui/diff/summary.ts +74 -0
- package/src/console/ui/diff/types.ts +48 -0
- package/src/console/ui/dist/assets/index-B71Yl_SS.js +10 -0
- package/src/console/ui/dist/assets/panel-access-Dd37LU2c.js +64 -0
- package/src/console/ui/dist/assets/panel-ai-CC7LR6-J.js +1 -0
- package/src/console/ui/dist/assets/panel-architecture-B5b3iKCz.js +1 -0
- package/src/console/ui/dist/assets/panel-channels-CeNjTKXp.js +1 -0
- package/src/console/ui/dist/assets/panel-clock-DgFTLoHV.js +1 -0
- package/src/console/ui/dist/assets/panel-diff-DxehccqB.js +1 -0
- package/src/console/ui/dist/assets/panel-flows-BtrVn-Eg.js +45 -0
- package/src/console/ui/dist/assets/panel-gates-Z9MKRGdH.js +1 -0
- package/src/console/ui/dist/assets/panel-overview-Bd48d9km.js +1 -0
- package/src/console/ui/dist/assets/panel-plugins-DWd0TowH.js +1 -0
- package/src/console/ui/dist/assets/panel-runs-BwsWqKeB.js +1 -0
- package/src/console/ui/dist/assets/panel-signals-9najbZY2.js +1 -0
- package/src/console/ui/dist/assets/panel-store-OHkP2pDp.js +1 -0
- package/src/console/ui/dist/assets/panel-traces-tn2JoY8U.js +1 -0
- package/src/console/ui/dist/assets/panel-vault-BbfWdox0.js +1 -0
- package/src/console/ui/dist/assets/rolldown-runtime-CNC7AqOf.js +1 -0
- package/src/console/ui/dist/assets/style-Cnl7WLya.css +3 -0
- package/src/console/ui/dist/index.html +14 -0
- package/src/console/ui/flows/FlowsA11yView.tsx +155 -0
- package/src/console/ui/flows/a11y.test.tsx +105 -0
- package/src/console/ui/flows/buffer.test.ts +91 -0
- package/src/console/ui/flows/buffer.ts +118 -0
- package/src/console/ui/flows/confirmation.ts +131 -0
- package/src/console/ui/flows/contract.test.ts +89 -0
- package/src/console/ui/flows/contract.ts +353 -0
- package/src/console/ui/flows/fixture.ts +81 -0
- package/src/console/ui/flows/graph.test.ts +101 -0
- package/src/console/ui/flows/graph.ts +549 -0
- package/src/console/ui/flows/index.ts +87 -0
- package/src/console/ui/flows/save-as-test.test.ts +47 -0
- package/src/console/ui/flows/save-as-test.ts +99 -0
- package/src/console/ui/flows/search.ts +255 -0
- package/src/console/ui/flows/tiers.ts +140 -0
- package/src/console/ui/gates/GatesA11yView.tsx +192 -0
- package/src/console/ui/gates/a11y.test.tsx +116 -0
- package/src/console/ui/gates/audit.test.ts +40 -0
- package/src/console/ui/gates/audit.ts +63 -0
- package/src/console/ui/gates/denial.test.ts +36 -0
- package/src/console/ui/gates/denial.ts +51 -0
- package/src/console/ui/gates/findings.ts +19 -0
- package/src/console/ui/gates/fixture.ts +189 -0
- package/src/console/ui/gates/group.test.ts +30 -0
- package/src/console/ui/gates/group.ts +102 -0
- package/src/console/ui/gates/index.ts +47 -0
- package/src/console/ui/gates/search.test.ts +49 -0
- package/src/console/ui/gates/search.ts +115 -0
- package/src/console/ui/gates/types.ts +121 -0
- package/src/console/ui/overview/OverviewA11yView.tsx +152 -0
- package/src/console/ui/overview/a11y.test.tsx +113 -0
- package/src/console/ui/overview/busiest.ts +26 -0
- package/src/console/ui/overview/compose.ts +133 -0
- package/src/console/ui/overview/cost.test.ts +39 -0
- package/src/console/ui/overview/cost.ts +138 -0
- package/src/console/ui/overview/fixture.ts +161 -0
- package/src/console/ui/overview/golden.ts +57 -0
- package/src/console/ui/overview/index.ts +59 -0
- package/src/console/ui/overview/rank.test.ts +136 -0
- package/src/console/ui/overview/rank.ts +210 -0
- package/src/console/ui/overview/slo.test.ts +73 -0
- package/src/console/ui/overview/slo.ts +229 -0
- package/src/console/ui/overview/types.ts +121 -0
- package/src/console/ui/overview/verdict.ts +131 -0
- package/src/console/ui/plugins/PluginsA11yView.tsx +227 -0
- package/src/console/ui/plugins/a11y.test.tsx +97 -0
- package/src/console/ui/plugins/command.test.ts +22 -0
- package/src/console/ui/plugins/command.ts +27 -0
- package/src/console/ui/plugins/findings.ts +39 -0
- package/src/console/ui/plugins/fixture.ts +233 -0
- package/src/console/ui/plugins/group.test.ts +38 -0
- package/src/console/ui/plugins/group.ts +67 -0
- package/src/console/ui/plugins/index.ts +40 -0
- package/src/console/ui/plugins/search.ts +67 -0
- package/src/console/ui/plugins/types.ts +115 -0
- package/src/console/ui/runs/RunsA11yView.tsx +267 -0
- package/src/console/ui/runs/a11y.test.tsx +99 -0
- package/src/console/ui/runs/explain.test.ts +23 -0
- package/src/console/ui/runs/explain.ts +32 -0
- package/src/console/ui/runs/fixture.ts +189 -0
- package/src/console/ui/runs/group.test.ts +24 -0
- package/src/console/ui/runs/group.ts +85 -0
- package/src/console/ui/runs/histogram.test.ts +31 -0
- package/src/console/ui/runs/histogram.ts +85 -0
- package/src/console/ui/runs/index.ts +75 -0
- package/src/console/ui/runs/project.ts +122 -0
- package/src/console/ui/runs/query.test.ts +50 -0
- package/src/console/ui/runs/query.ts +283 -0
- package/src/console/ui/runs/search.test.ts +45 -0
- package/src/console/ui/runs/search.ts +141 -0
- package/src/console/ui/runs/trace-link.test.ts +30 -0
- package/src/console/ui/runs/trace-link.ts +99 -0
- package/src/console/ui/runs/types.ts +137 -0
- package/src/console/ui/shell/App.tsx +56 -0
- package/src/console/ui/shell/client.ts +1715 -0
- package/src/console/ui/shell/components/ui.tsx +110 -0
- package/src/console/ui/shell/index.html +12 -0
- package/src/console/ui/shell/layout/Shell.tsx +95 -0
- package/src/console/ui/shell/main.tsx +281 -0
- package/src/console/ui/shell/panels/Access.tsx +5 -0
- package/src/console/ui/shell/panels/Ai.tsx +5 -0
- package/src/console/ui/shell/panels/Architecture.tsx +5 -0
- package/src/console/ui/shell/panels/Channels.tsx +5 -0
- package/src/console/ui/shell/panels/Clock.tsx +5 -0
- package/src/console/ui/shell/panels/Diff.tsx +5 -0
- package/src/console/ui/shell/panels/Flows.tsx +5 -0
- package/src/console/ui/shell/panels/Gates.tsx +5 -0
- package/src/console/ui/shell/panels/Overview.tsx +6 -0
- package/src/console/ui/shell/panels/Plugins.tsx +5 -0
- package/src/console/ui/shell/panels/Runs.tsx +5 -0
- package/src/console/ui/shell/panels/Signals.tsx +5 -0
- package/src/console/ui/shell/panels/Store.tsx +5 -0
- package/src/console/ui/shell/panels/Traces.tsx +5 -0
- package/src/console/ui/shell/panels/Vault.tsx +5 -0
- package/src/console/ui/shell/panels/access/AccessPanel.tsx +813 -0
- package/src/console/ui/shell/panels/ai/AiPanel.tsx +563 -0
- package/src/console/ui/shell/panels/architecture/ArchitecturePanel.tsx +377 -0
- package/src/console/ui/shell/panels/channels/ChannelsPanel.tsx +454 -0
- package/src/console/ui/shell/panels/clock/ClockPanel.tsx +479 -0
- package/src/console/ui/shell/panels/diff/DiffPanel.tsx +275 -0
- package/src/console/ui/shell/panels/flows/ContractEditor.tsx +301 -0
- package/src/console/ui/shell/panels/flows/FlowDrawer.tsx +350 -0
- package/src/console/ui/shell/panels/flows/FlowsPanel.tsx +502 -0
- package/src/console/ui/shell/panels/gates/GatesPanel.tsx +482 -0
- package/src/console/ui/shell/panels/overview/OverviewPanel.tsx +428 -0
- package/src/console/ui/shell/panels/plugins/PluginsPanel.tsx +413 -0
- package/src/console/ui/shell/panels/runs/RunsPanel.tsx +649 -0
- package/src/console/ui/shell/panels/signals/SignalsPanel.tsx +840 -0
- package/src/console/ui/shell/panels/store/StorePanel.tsx +787 -0
- package/src/console/ui/shell/panels/traces/TracesPanel.tsx +539 -0
- package/src/console/ui/shell/panels/vault/VaultPanel.tsx +449 -0
- package/src/console/ui/shell/plugin/Sandbox.tsx +29 -0
- package/src/console/ui/shell/setup/Wizard.tsx +189 -0
- package/src/console/ui/shell/styles.css +75 -0
- package/src/console/ui/signals/SignalsA11yView.tsx +276 -0
- package/src/console/ui/signals/a11y.test.tsx +99 -0
- package/src/console/ui/signals/confirmation.test.ts +39 -0
- package/src/console/ui/signals/confirmation.ts +63 -0
- package/src/console/ui/signals/dry-run.test.ts +18 -0
- package/src/console/ui/signals/dry-run.ts +36 -0
- package/src/console/ui/signals/durable.test.ts +22 -0
- package/src/console/ui/signals/durable.ts +41 -0
- package/src/console/ui/signals/findings.ts +33 -0
- package/src/console/ui/signals/fixture.ts +242 -0
- package/src/console/ui/signals/group.test.ts +32 -0
- package/src/console/ui/signals/group.ts +49 -0
- package/src/console/ui/signals/index.ts +52 -0
- package/src/console/ui/signals/monitor.test.ts +30 -0
- package/src/console/ui/signals/monitor.ts +79 -0
- package/src/console/ui/signals/schema-form.test.ts +34 -0
- package/src/console/ui/signals/schema-form.ts +112 -0
- package/src/console/ui/signals/search.ts +102 -0
- package/src/console/ui/signals/types.ts +71 -0
- package/src/console/ui/store/StoreA11yView.tsx +199 -0
- package/src/console/ui/store/a11y.test.tsx +99 -0
- package/src/console/ui/store/cache-view.test.ts +13 -0
- package/src/console/ui/store/cache-view.ts +36 -0
- package/src/console/ui/store/confirmation.test.ts +38 -0
- package/src/console/ui/store/confirmation.ts +55 -0
- package/src/console/ui/store/dry-run.test.ts +15 -0
- package/src/console/ui/store/dry-run.ts +29 -0
- package/src/console/ui/store/fixture.ts +163 -0
- package/src/console/ui/store/group.test.ts +21 -0
- package/src/console/ui/store/group.ts +41 -0
- package/src/console/ui/store/index.ts +40 -0
- package/src/console/ui/store/search.ts +78 -0
- package/src/console/ui/store/types.ts +65 -0
- package/src/console/ui/store/will-not-fire.test.ts +19 -0
- package/src/console/ui/store/will-not-fire.ts +40 -0
- package/src/console/ui/traces/TracesA11yView.tsx +200 -0
- package/src/console/ui/traces/a11y.test.tsx +97 -0
- package/src/console/ui/traces/chain.test.ts +61 -0
- package/src/console/ui/traces/chain.ts +144 -0
- package/src/console/ui/traces/critical-path.test.ts +24 -0
- package/src/console/ui/traces/critical-path.ts +59 -0
- package/src/console/ui/traces/filter.test.ts +78 -0
- package/src/console/ui/traces/filter.ts +112 -0
- package/src/console/ui/traces/fixture.ts +126 -0
- package/src/console/ui/traces/fold.test.ts +67 -0
- package/src/console/ui/traces/fold.ts +170 -0
- package/src/console/ui/traces/index.ts +64 -0
- package/src/console/ui/traces/mini.ts +65 -0
- package/src/console/ui/traces/replay.test.ts +24 -0
- package/src/console/ui/traces/replay.ts +35 -0
- package/src/console/ui/traces/sampling.test.ts +26 -0
- package/src/console/ui/traces/sampling.ts +66 -0
- package/src/console/ui/traces/search.ts +148 -0
- package/src/console/ui/traces/tier.ts +72 -0
- package/src/console/ui/traces/types.ts +125 -0
- package/src/console/ui/tsconfig.json +20 -0
- package/src/console/ui/vault/VaultA11yView.tsx +177 -0
- package/src/console/ui/vault/a11y.test.tsx +97 -0
- package/src/console/ui/vault/blast-radius.test.ts +35 -0
- package/src/console/ui/vault/blast-radius.ts +55 -0
- package/src/console/ui/vault/confirmation.test.ts +41 -0
- package/src/console/ui/vault/confirmation.ts +39 -0
- package/src/console/ui/vault/dormant.test.ts +37 -0
- package/src/console/ui/vault/dormant.ts +28 -0
- package/src/console/ui/vault/export-safe.test.ts +30 -0
- package/src/console/ui/vault/export-safe.ts +65 -0
- package/src/console/ui/vault/fixture.ts +75 -0
- package/src/console/ui/vault/group.test.ts +25 -0
- package/src/console/ui/vault/group.ts +50 -0
- package/src/console/ui/vault/index.ts +45 -0
- package/src/console/ui/vault/search.ts +62 -0
- package/src/console/ui/vault/types.ts +57 -0
- package/src/console/ui/vite.config.ts +84 -0
- package/src/console/xss.gate.test.ts +118 -0
- package/src/docker/compose.ts +226 -0
- package/src/docker/credentials.ts +25 -0
- package/src/docker/derive.ts +83 -0
- package/src/docker/docker.test.ts +185 -0
- package/src/docker/dockerfile.integration.test.ts +170 -0
- package/src/docker/dockerfile.ts +39 -0
- package/src/docker/helpers.ts +103 -0
- package/src/docker/index.ts +52 -0
- package/src/docker/pin.ts +90 -0
- package/src/docker/recipes/index.ts +33 -0
- package/src/docker/recipes/postgres.ts +18 -0
- package/src/docker/recipes/redis.ts +18 -0
- package/src/docker/stack.integration.test.ts +140 -0
- package/src/docker/stack.ts +62 -0
- package/src/docker/types.ts +134 -0
- package/src/drivers/ai-anthropic.ts +128 -0
- package/src/drivers/ai-mock.ts +82 -0
- package/src/drivers/ai-openai-compatible.ts +139 -0
- package/src/drivers/ai-providers.test.ts +163 -0
- package/src/drivers/ai-types.ts +83 -0
- package/src/drivers/bun-native-completeness.test.ts +155 -0
- package/src/drivers/channel-console.ts +118 -0
- package/src/drivers/channel-fcm.ts +96 -0
- package/src/drivers/channel-resend.ts +27 -0
- package/src/drivers/channel-smtp.ts +33 -0
- package/src/drivers/channel-types.ts +160 -0
- package/src/drivers/channel-unifonic.ts +79 -0
- package/src/drivers/channel-wa-cloud.ts +84 -0
- package/src/drivers/channel-webpush.ts +317 -0
- package/src/drivers/conformance.test.ts +89 -0
- package/src/drivers/conformance.ts +162 -0
- package/src/drivers/fs.ts +73 -0
- package/src/drivers/index.ts +194 -0
- package/src/drivers/kv-lua.ts +222 -0
- package/src/drivers/memory.ts +472 -0
- package/src/drivers/pgvector.ts +130 -0
- package/src/drivers/postgres.ts +221 -0
- package/src/drivers/redis.ts +254 -0
- package/src/drivers/s3.ts +129 -0
- package/src/drivers/signal-engine.ts +690 -0
- package/src/drivers/signal-memory.ts +27 -0
- package/src/drivers/signal-nats.ts +131 -0
- package/src/drivers/signal-postgres.ts +1089 -0
- package/src/drivers/signal-redis.ts +275 -0
- package/src/drivers/signal-types.ts +328 -0
- package/src/drivers/sqlite.ts +53 -0
- package/src/drivers/types.ts +334 -0
- package/src/drivers/vault-dotenv-parse.ts +56 -0
- package/src/drivers/vault-env.ts +101 -0
- package/src/drivers/vault-infisical.ts +61 -0
- package/src/drivers/vault-managed.ts +42 -0
- package/src/drivers/vault-memory.ts +38 -0
- package/src/drivers/vault-openbao.ts +80 -0
- package/src/drivers/vault-sops.ts +252 -0
- package/src/drivers/vault-types.ts +84 -0
- package/src/elements/ai/declare.ts +206 -0
- package/src/elements/ai/eval.ts +113 -0
- package/src/elements/ai/pii.ts +108 -0
- package/src/elements/ai/runtime.ts +540 -0
- package/src/elements/ai/schema.ts +194 -0
- package/src/elements/ai.test.ts +393 -0
- package/src/elements/ai.ts +56 -0
- package/src/elements/channel/consent.ts +81 -0
- package/src/elements/channel/costs.test.ts +62 -0
- package/src/elements/channel/costs.ts +130 -0
- package/src/elements/channel/declare.ts +141 -0
- package/src/elements/channel/email-auth.test.ts +47 -0
- package/src/elements/channel/email-auth.ts +109 -0
- package/src/elements/channel/locale.ts +97 -0
- package/src/elements/channel/mask.test.ts +19 -0
- package/src/elements/channel/mask.ts +60 -0
- package/src/elements/channel/mime.ts +23 -0
- package/src/elements/channel/outcomes.test.ts +40 -0
- package/src/elements/channel/outcomes.ts +126 -0
- package/src/elements/channel/receipts.ts +121 -0
- package/src/elements/channel/runtime.ts +500 -0
- package/src/elements/channel/suppression.ts +136 -0
- package/src/elements/channel.test.ts +232 -0
- package/src/elements/channel.ts +109 -0
- package/src/elements/clock/actions.ts +170 -0
- package/src/elements/clock/declare.ts +58 -0
- package/src/elements/clock/dst.ts +197 -0
- package/src/elements/clock/durable.ts +140 -0
- package/src/elements/clock/duration.ts +29 -0
- package/src/elements/clock/health.test.ts +103 -0
- package/src/elements/clock/health.ts +112 -0
- package/src/elements/clock/leader.ts +127 -0
- package/src/elements/clock/reconcile.ts +241 -0
- package/src/elements/clock/runtime.ts +243 -0
- package/src/elements/clock/schedule.ts +262 -0
- package/src/elements/clock/time-travel.ts +53 -0
- package/src/elements/clock.test.ts +365 -0
- package/src/elements/clock.ts +80 -0
- package/src/elements/gate/declare.ts +134 -0
- package/src/elements/gate/permissions.ts +104 -0
- package/src/elements/gate/runtime.ts +177 -0
- package/src/elements/gate/strategies.ts +388 -0
- package/src/elements/gate.test.ts +145 -0
- package/src/elements/gate.ts +41 -0
- package/src/elements/index.ts +169 -0
- package/src/elements/signal/chaos-child.ts +47 -0
- package/src/elements/signal/declare.ts +77 -0
- package/src/elements/signal/dry-run-replay.test.ts +95 -0
- package/src/elements/signal/dry-run-write-isolation.test.ts +94 -0
- package/src/elements/signal/reconcile.test.ts +23 -0
- package/src/elements/signal/reconcile.ts +122 -0
- package/src/elements/signal/runtime.ts +94 -0
- package/src/elements/signal.test.ts +457 -0
- package/src/elements/signal.ts +26 -0
- package/src/elements/store/cache.ts +197 -0
- package/src/elements/store/classify.ts +125 -0
- package/src/elements/store/declare.ts +200 -0
- package/src/elements/store/files-policy.test.ts +31 -0
- package/src/elements/store/files-policy.ts +70 -0
- package/src/elements/store/replica.ts +68 -0
- package/src/elements/store/runtime.ts +393 -0
- package/src/elements/store/sql-condition.ts +205 -0
- package/src/elements/store/sql-session.ts +579 -0
- package/src/elements/store/table.ts +282 -0
- package/src/elements/store.test.ts +370 -0
- package/src/elements/store.ts +99 -0
- package/src/elements/vault/declare.ts +143 -0
- package/src/elements/vault/fingerprint.ts +30 -0
- package/src/elements/vault/redact.ts +52 -0
- package/src/elements/vault/runtime.ts +461 -0
- package/src/elements/vault.test.ts +296 -0
- package/src/elements/vault.ts +44 -0
- package/src/index.ts +183 -0
- package/src/kernel/adopt-routes.ts +194 -0
- package/src/kernel/app.ts +1068 -0
- package/src/kernel/auth-resolve.ts +93 -0
- package/src/kernel/boot.test.ts +173 -0
- package/src/kernel/boot.ts +439 -0
- package/src/kernel/budget-entry.ts +55 -0
- package/src/kernel/budget.test.ts +19 -0
- package/src/kernel/call.test.ts +83 -0
- package/src/kernel/capability.ts +113 -0
- package/src/kernel/dry-run.test.ts +64 -0
- package/src/kernel/dry-run.ts +171 -0
- package/src/kernel/effects.test.ts +89 -0
- package/src/kernel/effects.ts +140 -0
- package/src/kernel/errors.registry.test.ts +39 -0
- package/src/kernel/errors.ts +225 -0
- package/src/kernel/flow.test.ts +154 -0
- package/src/kernel/flow.ts +290 -0
- package/src/kernel/fx.test.ts +245 -0
- package/src/kernel/fx.ts +996 -0
- package/src/kernel/hook-timing.test.ts +72 -0
- package/src/kernel/hook-timing.ts +190 -0
- package/src/kernel/hooks.test.ts +181 -0
- package/src/kernel/hooks.ts +275 -0
- package/src/kernel/index.ts +278 -0
- package/src/kernel/journal.ts +405 -0
- package/src/kernel/journey.ts +75 -0
- package/src/kernel/on.ts +74 -0
- package/src/kernel/pipeline.test.ts +349 -0
- package/src/kernel/pipeline.ts +245 -0
- package/src/kernel/plug.ts +82 -0
- package/src/kernel/plugin/capabilities.test.ts +84 -0
- package/src/kernel/plugin/conflicts.test.ts +106 -0
- package/src/kernel/plugin/decorate.test.ts +111 -0
- package/src/kernel/plugin/scoping.test.ts +138 -0
- package/src/kernel/plugin.ts +485 -0
- package/src/kernel/rate-limit.ts +25 -0
- package/src/kernel/registry.ts +398 -0
- package/src/kernel/router.test.ts +114 -0
- package/src/kernel/router.ts +556 -0
- package/src/kernel/routing-budget.test.ts +19 -0
- package/src/kernel/run-telemetry.ts +70 -0
- package/src/kernel/triggers.ts +324 -0
- package/src/kernel/unit.ts +88 -0
- package/src/manifest/diff.test.ts +866 -0
- package/src/manifest/diff.ts +1670 -0
- package/src/manifest/fixtures/base.manifest.json +112 -0
- package/src/manifest/fixtures/skyport.excerpt.json +51 -0
- package/src/manifest/fixtures/skyport.manifest.json +153 -0
- package/src/manifest/index.ts +27 -0
- package/src/manifest/types.ts +358 -0
- package/src/manifest/undeclared.test.ts +82 -0
- package/src/manifest/undeclared.ts +65 -0
- package/src/manifest/validate.test.ts +105 -0
- package/src/manifest/validate.ts +174 -0
- package/src/mcp/authorization.ts +340 -0
- package/src/mcp/confirmation.ts +174 -0
- package/src/mcp/data.ts +90 -0
- package/src/mcp/index.ts +80 -0
- package/src/mcp/injection.gate.test.ts +176 -0
- package/src/mcp/mcp.test.ts +348 -0
- package/src/mcp/protocol.ts +172 -0
- package/src/mcp/server.ts +283 -0
- package/src/mcp/session.ts +139 -0
- package/src/mcp/tools.ts +391 -0
- package/src/plugins/catalogue.test.ts +48 -0
- package/src/plugins/catalogue.ts +113 -0
- package/src/plugins/node-import-scan.test.ts +28 -0
- package/src/plugins/node-import-scan.ts +98 -0
- package/src/plugins/supply-chain.test.ts +48 -0
- package/src/plugins/supply-chain.ts +460 -0
- package/src/release/index.ts +30 -0
- package/src/release/limits.ts +20 -0
- package/src/release/measure.ts +443 -0
- package/src/release/publish.ts +56 -0
- package/src/runs/bench.test.ts +93 -0
- package/src/runs/collect.ts +144 -0
- package/src/runs/drivers/clickhouse.ts +108 -0
- package/src/runs/drivers/files.ts +201 -0
- package/src/runs/drivers/memory.ts +94 -0
- package/src/runs/drivers/postgres.ts +110 -0
- package/src/runs/duckdb.ts +99 -0
- package/src/runs/index.ts +82 -0
- package/src/runs/outlier.ts +180 -0
- package/src/runs/parquet.ts +210 -0
- package/src/runs/privacy.ts +49 -0
- package/src/runs/runs.test.ts +324 -0
- package/src/runs/runtime.ts +229 -0
- package/src/runs/shred.ts +243 -0
- package/src/runs/telemetry.ts +10 -0
- package/src/runs/types.ts +179 -0
- package/src/runtime/bun.ts +140 -0
- package/src/runtime/cold-start.bench.ts +20 -0
- package/src/runtime/index.ts +46 -0
- package/src/runtime/primitives.ts +242 -0
- package/src/runtime/security.test.ts +170 -0
- package/src/runtime/security.ts +168 -0
- package/src/runtime/serve.test.ts +223 -0
- package/src/runtime/types.ts +217 -0
- package/src/runtime/web-standard.ts +77 -0
- package/src/test/create-test-app.test.ts +161 -0
- package/src/test/create-test-app.ts +533 -0
- package/src/test/index.ts +21 -0
- package/src/test/provisions.integration.test.ts +174 -0
- package/src/upgrade/codemods.test.ts +58 -0
- package/src/upgrade/codemods.ts +83 -0
- package/src/validation/index.ts +23 -0
- package/src/validation/standard-schema.test.ts +96 -0
- package/src/validation/standard-schema.ts +316 -0
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Access projection — two planes, attenuation by absence,
|
|
3
|
+
* effective permissions with provenance, revocation blast radius
|
|
4
|
+
* (console §9.14).
|
|
5
|
+
*
|
|
6
|
+
* Grantable scopes are derived via {@link grantableScopes} /
|
|
7
|
+
* {@link attenuateScopes} against an expanded ceiling — never a
|
|
8
|
+
* hand-maintained allow-list in the UI.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
ACCESS_TTL_MS,
|
|
13
|
+
assertAttenuated,
|
|
14
|
+
createApiKey,
|
|
15
|
+
createOperatorInvite,
|
|
16
|
+
expandHeldScopes,
|
|
17
|
+
grantableScopes,
|
|
18
|
+
isInviteExpired,
|
|
19
|
+
revokeApiKey,
|
|
20
|
+
revokeFamily,
|
|
21
|
+
rotateApiKey,
|
|
22
|
+
setRoleGrants,
|
|
23
|
+
scopesForRoles,
|
|
24
|
+
type ApiKeyStore,
|
|
25
|
+
type OperatorInviteStore,
|
|
26
|
+
type OperatorStore,
|
|
27
|
+
type RoleStore,
|
|
28
|
+
type SessionStore,
|
|
29
|
+
} from "../../auth/index.ts";
|
|
30
|
+
import { deriveModuleActions } from "../../elements/gate.ts";
|
|
31
|
+
import type { Manifest } from "../../manifest/types.ts";
|
|
32
|
+
import type { WideEvent } from "../../runs/types.ts";
|
|
33
|
+
import { isApplicationScope } from "./gates.ts";
|
|
34
|
+
|
|
35
|
+
/** 90 days — hygiene threshold for unused keys. */
|
|
36
|
+
export const KEY_UNUSED_MS = 90 * 24 * 60 * 60 * 1000;
|
|
37
|
+
|
|
38
|
+
/** Identity slice for Access (user plane). */
|
|
39
|
+
export interface AccessIdentity {
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly email: string;
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly status: "active" | "disabled";
|
|
44
|
+
readonly scopes: readonly string[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Operator row for Access. */
|
|
48
|
+
export interface AccessOperatorRow {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
readonly email: string;
|
|
51
|
+
readonly name: string;
|
|
52
|
+
readonly status: "active" | "suspended" | "invited";
|
|
53
|
+
readonly roles: readonly string[];
|
|
54
|
+
readonly scopes: readonly string[];
|
|
55
|
+
readonly lastSeenAt: number | null;
|
|
56
|
+
readonly neverSignedIn: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** User-plane identity row. */
|
|
60
|
+
export interface AccessUserRow {
|
|
61
|
+
readonly id: string;
|
|
62
|
+
readonly email: string;
|
|
63
|
+
readonly name: string;
|
|
64
|
+
readonly status: "active" | "disabled";
|
|
65
|
+
readonly roles: readonly string[];
|
|
66
|
+
readonly scopes: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Role row with grants. */
|
|
70
|
+
export interface AccessRoleRow {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
readonly name: string;
|
|
73
|
+
readonly plane: "user" | "operator";
|
|
74
|
+
readonly description: string;
|
|
75
|
+
readonly scopes: readonly string[];
|
|
76
|
+
readonly memberCount: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** API key row (never includes the secret). */
|
|
80
|
+
export interface AccessKeyRow {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly name: string;
|
|
83
|
+
readonly plane: "user" | "operator";
|
|
84
|
+
readonly scopes: readonly string[];
|
|
85
|
+
readonly createdAt: number;
|
|
86
|
+
readonly lastUsedAt: number | null;
|
|
87
|
+
readonly expiresAt: number | null;
|
|
88
|
+
readonly revokedAt: number | null;
|
|
89
|
+
readonly rateLimit: { max: number; per: string } | null;
|
|
90
|
+
readonly ipAllowlist: readonly string[];
|
|
91
|
+
readonly unused90d: boolean;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Invitation row. */
|
|
95
|
+
export interface AccessInviteRow {
|
|
96
|
+
readonly id: string;
|
|
97
|
+
readonly email: string;
|
|
98
|
+
readonly invitedBy: string;
|
|
99
|
+
readonly createdAt: number;
|
|
100
|
+
readonly expiresAt: number;
|
|
101
|
+
readonly expired: boolean;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Provenance for one effective scope. */
|
|
105
|
+
export interface AccessScopeProvenance {
|
|
106
|
+
readonly scope: string;
|
|
107
|
+
/** Roles that grant this scope (`direct` for key-owned scopes). */
|
|
108
|
+
readonly sources: readonly {
|
|
109
|
+
readonly kind: "role" | "direct";
|
|
110
|
+
readonly id: string;
|
|
111
|
+
readonly name: string;
|
|
112
|
+
}[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Effective permissions for a principal. */
|
|
116
|
+
export interface AccessEffectivePermissions {
|
|
117
|
+
readonly kind: "operator" | "user" | "role" | "key";
|
|
118
|
+
readonly id: string;
|
|
119
|
+
readonly plane: "user" | "operator";
|
|
120
|
+
readonly scopes: readonly AccessScopeProvenance[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Blast radius from Runs for key revoke/rotate. */
|
|
124
|
+
export interface AccessKeyBlastRadius {
|
|
125
|
+
readonly callVolume: number;
|
|
126
|
+
readonly lastUsedAt: number | null;
|
|
127
|
+
readonly sourceAddresses: readonly string[];
|
|
128
|
+
/** Access-token TTL from config (ms) — residual validity after revoke. */
|
|
129
|
+
readonly accessTtlMs: number;
|
|
130
|
+
/** Honest hybrid-session delay note. */
|
|
131
|
+
readonly residualAccessNote: string;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Hygiene findings. */
|
|
135
|
+
export interface AccessHygiene {
|
|
136
|
+
readonly unusedKeys: readonly AccessKeyRow[];
|
|
137
|
+
readonly neverSignedInOperators: readonly AccessOperatorRow[];
|
|
138
|
+
readonly expiredInvitations: readonly AccessInviteRow[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** One plane's Access surface — never merged with the other. */
|
|
142
|
+
export interface AccessPlaneSection {
|
|
143
|
+
readonly plane: "user" | "operator";
|
|
144
|
+
readonly operators?: readonly AccessOperatorRow[];
|
|
145
|
+
readonly users?: readonly AccessUserRow[];
|
|
146
|
+
readonly roles: readonly AccessRoleRow[];
|
|
147
|
+
readonly keys: readonly AccessKeyRow[];
|
|
148
|
+
readonly invites?: readonly AccessInviteRow[];
|
|
149
|
+
/** Scopes the actor may grant on this plane (absence = impossible). */
|
|
150
|
+
readonly grantableScopes: readonly string[];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Full Access panel projection. */
|
|
154
|
+
export interface AccessPanelProjection {
|
|
155
|
+
readonly operatorPlane: AccessPlaneSection;
|
|
156
|
+
readonly userPlane: AccessPlaneSection;
|
|
157
|
+
readonly hygiene: AccessHygiene;
|
|
158
|
+
readonly accessTtlMs: number;
|
|
159
|
+
readonly catalog: readonly string[];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Options for {@link projectAccessPanel}. */
|
|
163
|
+
export interface ProjectAccessOptions {
|
|
164
|
+
readonly manifest: Manifest | null;
|
|
165
|
+
readonly roles: RoleStore;
|
|
166
|
+
readonly apiKeys: ApiKeyStore;
|
|
167
|
+
readonly operators: OperatorStore;
|
|
168
|
+
readonly invites: OperatorInviteStore;
|
|
169
|
+
readonly identities: readonly AccessIdentity[];
|
|
170
|
+
/** roleId → member principal ids. */
|
|
171
|
+
readonly roleMembers: ReadonlyMap<string, readonly string[]>;
|
|
172
|
+
/** Acting operator's held scopes (session / roles). */
|
|
173
|
+
readonly actorScopes: Iterable<string>;
|
|
174
|
+
readonly accessTtlMs?: number;
|
|
175
|
+
readonly now?: () => number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Project Access panel — planes kept separate; grantable via attenuation.
|
|
180
|
+
*
|
|
181
|
+
* @param options - Auth stores + actor ceiling
|
|
182
|
+
*/
|
|
183
|
+
export function projectAccessPanel(
|
|
184
|
+
options: ProjectAccessOptions,
|
|
185
|
+
): AccessPanelProjection {
|
|
186
|
+
const now = options.now ?? (() => Date.now());
|
|
187
|
+
const accessTtlMs = options.accessTtlMs ?? ACCESS_TTL_MS;
|
|
188
|
+
const catalog = options.manifest
|
|
189
|
+
? deriveModuleActions(options.manifest)
|
|
190
|
+
: collectCatalogFromStores(options.roles, options.apiKeys);
|
|
191
|
+
|
|
192
|
+
const planeOf = (scope: string): "user" | "operator" =>
|
|
193
|
+
isApplicationScope(scope) ? "user" : "operator";
|
|
194
|
+
|
|
195
|
+
const held = expandAccessCeiling(options.actorScopes, catalog);
|
|
196
|
+
const grantableOperator = grantableScopes({
|
|
197
|
+
held,
|
|
198
|
+
catalog,
|
|
199
|
+
planeOf,
|
|
200
|
+
plane: "operator",
|
|
201
|
+
});
|
|
202
|
+
const grantableUser = grantableScopes({
|
|
203
|
+
held,
|
|
204
|
+
catalog,
|
|
205
|
+
planeOf,
|
|
206
|
+
plane: "user",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const operators = projectOperators(options);
|
|
210
|
+
const users = projectUsers(options);
|
|
211
|
+
const roles = projectRoles(options);
|
|
212
|
+
const keys = projectKeys(options, now);
|
|
213
|
+
const invites = projectInvites(options, now);
|
|
214
|
+
|
|
215
|
+
const operatorPlane: AccessPlaneSection = {
|
|
216
|
+
plane: "operator",
|
|
217
|
+
operators,
|
|
218
|
+
roles: roles.filter((r) => r.plane === "operator"),
|
|
219
|
+
keys: keys.filter((k) => k.plane === "operator"),
|
|
220
|
+
invites,
|
|
221
|
+
grantableScopes: grantableOperator,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const userPlane: AccessPlaneSection = {
|
|
225
|
+
plane: "user",
|
|
226
|
+
users,
|
|
227
|
+
roles: roles.filter((r) => r.plane === "user"),
|
|
228
|
+
keys: keys.filter((k) => k.plane === "user"),
|
|
229
|
+
grantableScopes: grantableUser,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const hygiene: AccessHygiene = {
|
|
233
|
+
unusedKeys: keys.filter((k) => k.unused90d && k.revokedAt === null),
|
|
234
|
+
neverSignedInOperators: operators.filter((o) => o.neverSignedIn),
|
|
235
|
+
expiredInvitations: invites.filter((i) => i.expired),
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
operatorPlane,
|
|
240
|
+
userPlane,
|
|
241
|
+
hygiene,
|
|
242
|
+
accessTtlMs,
|
|
243
|
+
catalog,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Effective permissions with provenance — inverse of the Gates simulator.
|
|
249
|
+
*
|
|
250
|
+
* @param options - Principal + stores
|
|
251
|
+
*/
|
|
252
|
+
export function effectivePermissions(options: {
|
|
253
|
+
readonly kind: "operator" | "user" | "role" | "key";
|
|
254
|
+
readonly id: string;
|
|
255
|
+
readonly roles: RoleStore;
|
|
256
|
+
readonly apiKeys: ApiKeyStore;
|
|
257
|
+
readonly operators: OperatorStore;
|
|
258
|
+
readonly identities: readonly AccessIdentity[];
|
|
259
|
+
readonly roleMembers: ReadonlyMap<string, readonly string[]>;
|
|
260
|
+
}): AccessEffectivePermissions | null {
|
|
261
|
+
if (options.kind === "key") {
|
|
262
|
+
const key = options.apiKeys.keys.get(options.id);
|
|
263
|
+
if (!key) return null;
|
|
264
|
+
return {
|
|
265
|
+
kind: "key",
|
|
266
|
+
id: key.id,
|
|
267
|
+
plane: key.plane,
|
|
268
|
+
scopes: [...key.scopes]
|
|
269
|
+
.sort((a, b) => a.localeCompare(b))
|
|
270
|
+
.map((scope) => ({
|
|
271
|
+
scope,
|
|
272
|
+
sources: [
|
|
273
|
+
{ kind: "direct" as const, id: key.id, name: key.name },
|
|
274
|
+
],
|
|
275
|
+
})),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (options.kind === "role") {
|
|
280
|
+
const role = options.roles.roles.get(options.id);
|
|
281
|
+
if (!role) return null;
|
|
282
|
+
const scopes = [...(options.roles.grants.get(role.id) ?? [])].sort((a, b) =>
|
|
283
|
+
a.localeCompare(b),
|
|
284
|
+
);
|
|
285
|
+
return {
|
|
286
|
+
kind: "role",
|
|
287
|
+
id: role.id,
|
|
288
|
+
plane: role.plane,
|
|
289
|
+
scopes: scopes.map((scope) => ({
|
|
290
|
+
scope,
|
|
291
|
+
sources: [
|
|
292
|
+
{ kind: "role" as const, id: role.id, name: role.name },
|
|
293
|
+
],
|
|
294
|
+
})),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (options.kind === "operator") {
|
|
299
|
+
const op = options.operators.operators.get(options.id);
|
|
300
|
+
if (!op) return null;
|
|
301
|
+
const roleIds = options.operators.roles.get(options.id) ?? [];
|
|
302
|
+
return {
|
|
303
|
+
kind: "operator",
|
|
304
|
+
id: op.id,
|
|
305
|
+
plane: "operator",
|
|
306
|
+
scopes: provenanceForRoles(options.roles, roleIds, "operator"),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const identity = options.identities.find((i) => i.id === options.id);
|
|
311
|
+
if (!identity) return null;
|
|
312
|
+
const roleIds = roleIdsForMember(options.roleMembers, options.id);
|
|
313
|
+
const fromRoles = provenanceForRoles(options.roles, roleIds, "user");
|
|
314
|
+
// Identity may also carry direct scopes (dev seed) — mark as direct.
|
|
315
|
+
const covered = new Set(fromRoles.map((s) => s.scope));
|
|
316
|
+
const scopes = [...fromRoles];
|
|
317
|
+
for (const scope of identity.scopes) {
|
|
318
|
+
if (covered.has(scope)) continue;
|
|
319
|
+
scopes.push({
|
|
320
|
+
scope,
|
|
321
|
+
sources: [
|
|
322
|
+
{ kind: "direct" as const, id: identity.id, name: identity.name },
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
scopes.sort((a, b) => a.scope.localeCompare(b.scope));
|
|
327
|
+
return {
|
|
328
|
+
kind: "user",
|
|
329
|
+
id: identity.id,
|
|
330
|
+
plane: "user",
|
|
331
|
+
scopes,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Blast radius for key revoke/rotate — queried from Runs, not guessed.
|
|
337
|
+
*
|
|
338
|
+
* @param options - Key id, runs, TTL config
|
|
339
|
+
*/
|
|
340
|
+
export function keyBlastRadius(options: {
|
|
341
|
+
readonly keyId: string;
|
|
342
|
+
readonly apiKeys: ApiKeyStore;
|
|
343
|
+
readonly runs: readonly WideEvent[];
|
|
344
|
+
readonly accessTtlMs?: number;
|
|
345
|
+
}): AccessKeyBlastRadius {
|
|
346
|
+
const accessTtlMs = options.accessTtlMs ?? ACCESS_TTL_MS;
|
|
347
|
+
const key = options.apiKeys.keys.get(options.keyId);
|
|
348
|
+
const matching = options.runs.filter((r) => runTouchesKey(r, options.keyId));
|
|
349
|
+
const addresses = new Set<string>();
|
|
350
|
+
let lastUsedAt: number | null = key?.lastUsedAt ?? null;
|
|
351
|
+
for (const run of matching) {
|
|
352
|
+
if (lastUsedAt === null || run.startedAt > lastUsedAt) {
|
|
353
|
+
lastUsedAt = run.startedAt;
|
|
354
|
+
}
|
|
355
|
+
const ip = sourceAddressOf(run);
|
|
356
|
+
if (ip) addresses.add(ip);
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
callVolume: matching.length,
|
|
360
|
+
lastUsedAt,
|
|
361
|
+
sourceAddresses: [...addresses].sort((a, b) => a.localeCompare(b)),
|
|
362
|
+
accessTtlMs,
|
|
363
|
+
residualAccessNote: residualAccessNote(accessTtlMs),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Honest hybrid-session delay copy from the configured access TTL.
|
|
369
|
+
*
|
|
370
|
+
* @param accessTtlMs - Access-token TTL from config
|
|
371
|
+
*/
|
|
372
|
+
export function residualAccessNote(accessTtlMs: number): string {
|
|
373
|
+
const minutes = Math.max(1, Math.ceil(accessTtlMs / 60_000));
|
|
374
|
+
return `Existing access may continue up to ${minutes} minute${minutes === 1 ? "" : "s"}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Input for creating a key through Access. */
|
|
378
|
+
export interface AccessCreateKeyInput {
|
|
379
|
+
readonly plane: "user" | "operator";
|
|
380
|
+
readonly name: string;
|
|
381
|
+
readonly scopes: readonly string[];
|
|
382
|
+
readonly creatorId: string;
|
|
383
|
+
readonly creatorScopes: Iterable<string>;
|
|
384
|
+
readonly catalog: readonly string[];
|
|
385
|
+
readonly expiresAt?: number | null;
|
|
386
|
+
readonly rateLimit?: { max: number; per: string } | null;
|
|
387
|
+
readonly ipAllowlist?: readonly string[];
|
|
388
|
+
readonly now?: () => number;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Create a key — attenuation + plane absence enforced server-side.
|
|
393
|
+
*
|
|
394
|
+
* @param store - API key store
|
|
395
|
+
* @param input - Create payload
|
|
396
|
+
*/
|
|
397
|
+
export async function accessCreateKey(
|
|
398
|
+
store: ApiKeyStore,
|
|
399
|
+
input: AccessCreateKeyInput,
|
|
400
|
+
): Promise<{ readonly row: AccessKeyRow; readonly secret: string }> {
|
|
401
|
+
const planeOf = (scope: string): "user" | "operator" =>
|
|
402
|
+
isApplicationScope(scope) ? "user" : "operator";
|
|
403
|
+
const held = expandAccessCeiling(input.creatorScopes, input.catalog);
|
|
404
|
+
const allowed = new Set(
|
|
405
|
+
grantableScopes({
|
|
406
|
+
held,
|
|
407
|
+
catalog: input.catalog,
|
|
408
|
+
planeOf,
|
|
409
|
+
plane: input.plane,
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
for (const scope of input.scopes) {
|
|
413
|
+
if (!allowed.has(scope)) {
|
|
414
|
+
assertAttenuated(held, [scope], "api key");
|
|
415
|
+
throw new AccessGrantError(
|
|
416
|
+
`scope ${scope} is not grantable on the ${input.plane} plane`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const created = await createApiKey(store, {
|
|
421
|
+
plane: input.plane,
|
|
422
|
+
name: input.name,
|
|
423
|
+
scopes: input.scopes,
|
|
424
|
+
creatorId: input.creatorId,
|
|
425
|
+
creatorScopes: held,
|
|
426
|
+
expiresAt: input.expiresAt,
|
|
427
|
+
rateLimit: input.rateLimit,
|
|
428
|
+
ipAllowlist: input.ipAllowlist,
|
|
429
|
+
now: input.now,
|
|
430
|
+
});
|
|
431
|
+
return {
|
|
432
|
+
row: toKeyRow(created.row, input.now ?? (() => Date.now())),
|
|
433
|
+
secret: created.secret,
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Set role grants — only grantable scopes on the role's plane.
|
|
439
|
+
*
|
|
440
|
+
* @param options - Role + requested scopes + actor ceiling
|
|
441
|
+
*/
|
|
442
|
+
export function accessSetRoleGrants(options: {
|
|
443
|
+
readonly roles: RoleStore;
|
|
444
|
+
readonly roleId: string;
|
|
445
|
+
readonly scopes: readonly string[];
|
|
446
|
+
readonly actorScopes: Iterable<string>;
|
|
447
|
+
readonly catalog: readonly string[];
|
|
448
|
+
}): void {
|
|
449
|
+
const role = options.roles.roles.get(options.roleId);
|
|
450
|
+
if (!role) throw new AccessGrantError(`unknown role: ${options.roleId}`);
|
|
451
|
+
const planeOf = (scope: string): "user" | "operator" =>
|
|
452
|
+
isApplicationScope(scope) ? "user" : "operator";
|
|
453
|
+
const held = expandAccessCeiling(options.actorScopes, options.catalog);
|
|
454
|
+
const allowed = new Set(
|
|
455
|
+
grantableScopes({
|
|
456
|
+
held,
|
|
457
|
+
catalog: options.catalog,
|
|
458
|
+
planeOf,
|
|
459
|
+
plane: role.plane,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
for (const scope of options.scopes) {
|
|
463
|
+
if (!allowed.has(scope)) {
|
|
464
|
+
throw new AccessGrantError(
|
|
465
|
+
`scope ${scope} is not grantable on the ${role.plane} plane`,
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
setRoleGrants(options.roles, options.roleId, options.scopes);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Revoke a key and any session families bound to it.
|
|
474
|
+
*
|
|
475
|
+
* @param options - Stores + key id
|
|
476
|
+
*/
|
|
477
|
+
export function accessRevokeKey(options: {
|
|
478
|
+
readonly apiKeys: ApiKeyStore;
|
|
479
|
+
readonly sessions: SessionStore;
|
|
480
|
+
readonly keyId: string;
|
|
481
|
+
readonly now?: () => number;
|
|
482
|
+
}): AccessKeyRow | null {
|
|
483
|
+
const now = options.now ?? (() => Date.now());
|
|
484
|
+
const row = revokeApiKey(options.apiKeys, options.keyId, now);
|
|
485
|
+
if (!row) return null;
|
|
486
|
+
for (const session of options.sessions.sessions.values()) {
|
|
487
|
+
if (session.principalId === options.keyId && session.revokedAt === null) {
|
|
488
|
+
revokeFamily(options.sessions, session.familyId, now());
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return toKeyRow(row, now);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Rotate a key secret — shown exactly once.
|
|
496
|
+
*
|
|
497
|
+
* @param store - API key store
|
|
498
|
+
* @param keyId - Key id
|
|
499
|
+
*/
|
|
500
|
+
export async function accessRotateKey(
|
|
501
|
+
store: ApiKeyStore,
|
|
502
|
+
keyId: string,
|
|
503
|
+
): Promise<{ readonly row: AccessKeyRow; readonly secret: string } | null> {
|
|
504
|
+
const rotated = await rotateApiKey(store, keyId);
|
|
505
|
+
if (!rotated) return null;
|
|
506
|
+
return {
|
|
507
|
+
row: toKeyRow(rotated.row, () => Date.now()),
|
|
508
|
+
secret: rotated.secret,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Create an operator invitation.
|
|
514
|
+
*
|
|
515
|
+
* @param store - Invite store
|
|
516
|
+
* @param input - Invite fields
|
|
517
|
+
*/
|
|
518
|
+
export function accessCreateInvite(
|
|
519
|
+
store: OperatorInviteStore,
|
|
520
|
+
input: {
|
|
521
|
+
readonly email: string;
|
|
522
|
+
readonly invitedBy: string;
|
|
523
|
+
readonly now?: () => number;
|
|
524
|
+
},
|
|
525
|
+
) {
|
|
526
|
+
return createOperatorInvite(store, input);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Access grant / plane error. */
|
|
530
|
+
export class AccessGrantError extends Error {
|
|
531
|
+
/** @param message - Diagnostic */
|
|
532
|
+
constructor(message: string) {
|
|
533
|
+
super(message);
|
|
534
|
+
this.name = "AccessGrantError";
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── internals ──────────────────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Expand held scopes for Access administration.
|
|
542
|
+
*
|
|
543
|
+
* `console:*` covers every `console:…` pair and, for the Access admin
|
|
544
|
+
* surface, every application scope in the catalog so operators can manage
|
|
545
|
+
* the user plane without holding application scopes on their own principal
|
|
546
|
+
* (which Gates would flag as a plane violation).
|
|
547
|
+
*
|
|
548
|
+
* @param held - Actor scopes
|
|
549
|
+
* @param catalog - Manifest Module:Action pairs
|
|
550
|
+
*/
|
|
551
|
+
export function expandAccessCeiling(
|
|
552
|
+
held: Iterable<string>,
|
|
553
|
+
catalog: readonly string[],
|
|
554
|
+
): Set<string> {
|
|
555
|
+
const out = expandHeldScopes(held, catalog);
|
|
556
|
+
let star = false;
|
|
557
|
+
for (const s of held) {
|
|
558
|
+
if (s === "console:*") star = true;
|
|
559
|
+
}
|
|
560
|
+
if (star) {
|
|
561
|
+
for (const scope of catalog) {
|
|
562
|
+
if (isApplicationScope(scope)) out.add(scope);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return out;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function projectOperators(
|
|
569
|
+
options: ProjectAccessOptions,
|
|
570
|
+
): AccessOperatorRow[] {
|
|
571
|
+
const rows: AccessOperatorRow[] = [];
|
|
572
|
+
for (const op of options.operators.operators.values()) {
|
|
573
|
+
const roleIds = options.operators.roles.get(op.id) ?? [];
|
|
574
|
+
const scopes = [
|
|
575
|
+
...scopesForRoles(options.roles, roleIds, "operator"),
|
|
576
|
+
].sort((a, b) => a.localeCompare(b));
|
|
577
|
+
rows.push({
|
|
578
|
+
id: op.id,
|
|
579
|
+
email: op.email,
|
|
580
|
+
name: op.name,
|
|
581
|
+
status: op.status,
|
|
582
|
+
roles: [...roleIds],
|
|
583
|
+
scopes,
|
|
584
|
+
lastSeenAt: op.lastSeenAt,
|
|
585
|
+
neverSignedIn: op.lastSeenAt === null,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return rows.sort((a, b) => a.email.localeCompare(b.email));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function projectUsers(options: ProjectAccessOptions): AccessUserRow[] {
|
|
592
|
+
return options.identities
|
|
593
|
+
.map((identity) => {
|
|
594
|
+
const roleIds = roleIdsForMember(options.roleMembers, identity.id);
|
|
595
|
+
const fromRoles = [
|
|
596
|
+
...scopesForRoles(options.roles, roleIds, "user"),
|
|
597
|
+
];
|
|
598
|
+
const scopes = [
|
|
599
|
+
...new Set([...fromRoles, ...identity.scopes]),
|
|
600
|
+
].sort((a, b) => a.localeCompare(b));
|
|
601
|
+
return {
|
|
602
|
+
id: identity.id,
|
|
603
|
+
email: identity.email,
|
|
604
|
+
name: identity.name,
|
|
605
|
+
status: identity.status,
|
|
606
|
+
roles: roleIds,
|
|
607
|
+
scopes,
|
|
608
|
+
};
|
|
609
|
+
})
|
|
610
|
+
.sort((a, b) => a.email.localeCompare(b.email));
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function projectRoles(options: ProjectAccessOptions): AccessRoleRow[] {
|
|
614
|
+
const rows: AccessRoleRow[] = [];
|
|
615
|
+
for (const role of options.roles.roles.values()) {
|
|
616
|
+
rows.push({
|
|
617
|
+
id: role.id,
|
|
618
|
+
name: role.name,
|
|
619
|
+
plane: role.plane,
|
|
620
|
+
description: role.description,
|
|
621
|
+
scopes: [...(options.roles.grants.get(role.id) ?? [])].sort((a, b) =>
|
|
622
|
+
a.localeCompare(b),
|
|
623
|
+
),
|
|
624
|
+
memberCount: options.roleMembers.get(role.id)?.length ?? 0,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function projectKeys(
|
|
631
|
+
options: ProjectAccessOptions,
|
|
632
|
+
now: () => number,
|
|
633
|
+
): AccessKeyRow[] {
|
|
634
|
+
const rows: AccessKeyRow[] = [];
|
|
635
|
+
for (const key of options.apiKeys.keys.values()) {
|
|
636
|
+
rows.push(toKeyRow(key, now));
|
|
637
|
+
}
|
|
638
|
+
return rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function projectInvites(
|
|
642
|
+
options: ProjectAccessOptions,
|
|
643
|
+
now: () => number,
|
|
644
|
+
): AccessInviteRow[] {
|
|
645
|
+
const rows: AccessInviteRow[] = [];
|
|
646
|
+
for (const invite of options.invites.invites.values()) {
|
|
647
|
+
rows.push({
|
|
648
|
+
id: invite.id,
|
|
649
|
+
email: invite.email,
|
|
650
|
+
invitedBy: invite.invitedBy,
|
|
651
|
+
createdAt: invite.createdAt,
|
|
652
|
+
expiresAt: invite.expiresAt,
|
|
653
|
+
expired: isInviteExpired(invite, now),
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
return rows.sort((a, b) => b.createdAt - a.createdAt);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function toKeyRow(
|
|
660
|
+
key: {
|
|
661
|
+
readonly id: string;
|
|
662
|
+
readonly name: string;
|
|
663
|
+
readonly plane: "user" | "operator";
|
|
664
|
+
readonly scopes: readonly string[];
|
|
665
|
+
readonly createdAt: number;
|
|
666
|
+
readonly lastUsedAt: number | null;
|
|
667
|
+
readonly expiresAt: number | null;
|
|
668
|
+
readonly revokedAt: number | null;
|
|
669
|
+
readonly rateLimit: { max: number; per: string } | null;
|
|
670
|
+
readonly ipAllowlist: readonly string[];
|
|
671
|
+
},
|
|
672
|
+
now: () => number,
|
|
673
|
+
): AccessKeyRow {
|
|
674
|
+
const unused90d = isKeyUnused90d(key, now);
|
|
675
|
+
return {
|
|
676
|
+
id: key.id,
|
|
677
|
+
name: key.name,
|
|
678
|
+
plane: key.plane,
|
|
679
|
+
scopes: [...key.scopes].sort((a, b) => a.localeCompare(b)),
|
|
680
|
+
createdAt: key.createdAt,
|
|
681
|
+
lastUsedAt: key.lastUsedAt,
|
|
682
|
+
expiresAt: key.expiresAt,
|
|
683
|
+
revokedAt: key.revokedAt,
|
|
684
|
+
rateLimit: key.rateLimit,
|
|
685
|
+
ipAllowlist: [...key.ipAllowlist],
|
|
686
|
+
unused90d,
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Whether a key has been unused for 90+ days (or never used and aged 90d+).
|
|
692
|
+
*
|
|
693
|
+
* @param key - Key timestamps
|
|
694
|
+
* @param now - Clock
|
|
695
|
+
*/
|
|
696
|
+
export function isKeyUnused90d(
|
|
697
|
+
key: {
|
|
698
|
+
readonly createdAt: number;
|
|
699
|
+
readonly lastUsedAt: number | null;
|
|
700
|
+
readonly revokedAt: number | null;
|
|
701
|
+
},
|
|
702
|
+
now: () => number = () => Date.now(),
|
|
703
|
+
): boolean {
|
|
704
|
+
if (key.revokedAt !== null) return false;
|
|
705
|
+
const t = now();
|
|
706
|
+
if (key.lastUsedAt === null) {
|
|
707
|
+
return t - key.createdAt >= KEY_UNUSED_MS;
|
|
708
|
+
}
|
|
709
|
+
return t - key.lastUsedAt >= KEY_UNUSED_MS;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function provenanceForRoles(
|
|
713
|
+
roles: RoleStore,
|
|
714
|
+
roleIds: readonly string[],
|
|
715
|
+
plane: "user" | "operator",
|
|
716
|
+
): AccessScopeProvenance[] {
|
|
717
|
+
const byScope = new Map<
|
|
718
|
+
string,
|
|
719
|
+
{ kind: "role"; id: string; name: string }[]
|
|
720
|
+
>();
|
|
721
|
+
for (const roleId of roleIds) {
|
|
722
|
+
const role = roles.roles.get(roleId);
|
|
723
|
+
if (!role || role.plane !== plane) continue;
|
|
724
|
+
for (const scope of roles.grants.get(roleId) ?? []) {
|
|
725
|
+
const list = byScope.get(scope) ?? [];
|
|
726
|
+
list.push({ kind: "role", id: role.id, name: role.name });
|
|
727
|
+
byScope.set(scope, list);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return [...byScope.entries()]
|
|
731
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
732
|
+
.map(([scope, sources]) => ({ scope, sources }));
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
function roleIdsForMember(
|
|
736
|
+
roleMembers: ReadonlyMap<string, readonly string[]>,
|
|
737
|
+
memberId: string,
|
|
738
|
+
): string[] {
|
|
739
|
+
const ids: string[] = [];
|
|
740
|
+
for (const [roleId, members] of roleMembers) {
|
|
741
|
+
if (members.includes(memberId)) ids.push(roleId);
|
|
742
|
+
}
|
|
743
|
+
return ids.sort((a, b) => a.localeCompare(b));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function runTouchesKey(run: WideEvent, keyId: string): boolean {
|
|
747
|
+
if (run.principal === keyId) return true;
|
|
748
|
+
const dims = run.dimensions;
|
|
749
|
+
if (dims.principal === keyId) return true;
|
|
750
|
+
if (dims.api_key === keyId || dims.apiKey === keyId || dims.key === keyId) {
|
|
751
|
+
return true;
|
|
752
|
+
}
|
|
753
|
+
return false;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function sourceAddressOf(run: WideEvent): string | null {
|
|
757
|
+
const dims = run.dimensions;
|
|
758
|
+
for (const key of ["ip", "source_ip", "sourceIp", "remote_addr", "client_ip"]) {
|
|
759
|
+
const v = dims[key];
|
|
760
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
761
|
+
}
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function collectCatalogFromStores(
|
|
766
|
+
roles: RoleStore,
|
|
767
|
+
apiKeys: ApiKeyStore,
|
|
768
|
+
): string[] {
|
|
769
|
+
const set = new Set<string>();
|
|
770
|
+
for (const grants of roles.grants.values()) {
|
|
771
|
+
for (const g of grants) set.add(g);
|
|
772
|
+
}
|
|
773
|
+
for (const key of apiKeys.keys.values()) {
|
|
774
|
+
for (const s of key.scopes) set.add(s);
|
|
775
|
+
}
|
|
776
|
+
return [...set].sort((a, b) => a.localeCompare(b));
|
|
777
|
+
}
|