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,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API keys are first-class principals with attenuation.
|
|
3
|
+
*
|
|
4
|
+
* A key can never exceed the permissions of whoever created it.
|
|
5
|
+
* The raw secret is returned exactly once on create / rotate.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/spec/console.md §3.3 · §9.14
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { assertAttenuated } from "./attenuation.ts";
|
|
11
|
+
import type { AuthPlane } from "./planes.ts";
|
|
12
|
+
import type { ApiKeyRow } from "./tables.ts";
|
|
13
|
+
|
|
14
|
+
/** Options when creating an API key. */
|
|
15
|
+
export interface CreateApiKeyOptions {
|
|
16
|
+
readonly plane: AuthPlane;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly scopes: readonly string[];
|
|
19
|
+
readonly creatorId: string;
|
|
20
|
+
/** Creator's full scope set — attenuation ceiling. */
|
|
21
|
+
readonly creatorScopes: ReadonlySet<string> | Iterable<string>;
|
|
22
|
+
readonly expiresAt?: number | null;
|
|
23
|
+
readonly rateLimit?: { max: number; per: string } | null;
|
|
24
|
+
readonly ipAllowlist?: readonly string[];
|
|
25
|
+
/** Injectable id / secret / hash (tests). */
|
|
26
|
+
readonly id?: string;
|
|
27
|
+
readonly secret?: string;
|
|
28
|
+
readonly hash?: string;
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Result of key creation — secret shown exactly once. */
|
|
33
|
+
export interface CreatedApiKey {
|
|
34
|
+
readonly row: ApiKeyRow;
|
|
35
|
+
/** Raw secret — shown exactly once. */
|
|
36
|
+
readonly secret: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** In-memory API key store. */
|
|
40
|
+
export interface ApiKeyStore {
|
|
41
|
+
keys: Map<string, ApiKeyRow>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Create an empty API key store.
|
|
46
|
+
*/
|
|
47
|
+
export function createApiKeyStore(): ApiKeyStore {
|
|
48
|
+
return { keys: new Map() };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Hash an API key secret (SHA-256 hex).
|
|
53
|
+
*
|
|
54
|
+
* @param secret - Raw secret
|
|
55
|
+
*/
|
|
56
|
+
export async function hashApiKeySecret(secret: string): Promise<string> {
|
|
57
|
+
const bytes = new TextEncoder().encode(secret);
|
|
58
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
59
|
+
return [...new Uint8Array(digest)]
|
|
60
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
61
|
+
.join("");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create an API key attenuated to the creator's scopes.
|
|
66
|
+
*
|
|
67
|
+
* @param store - Key store
|
|
68
|
+
* @param options - Key fields + creator ceiling
|
|
69
|
+
*/
|
|
70
|
+
export async function createApiKey(
|
|
71
|
+
store: ApiKeyStore,
|
|
72
|
+
options: CreateApiKeyOptions,
|
|
73
|
+
): Promise<CreatedApiKey> {
|
|
74
|
+
assertAttenuated(options.creatorScopes, options.scopes, "api key");
|
|
75
|
+
|
|
76
|
+
const now = options.now ?? (() => Date.now());
|
|
77
|
+
const id = options.id ?? crypto.randomUUID();
|
|
78
|
+
const secret = options.secret ?? `oke_${id.replace(/-/g, "")}_${randomSecret()}`;
|
|
79
|
+
const hash = options.hash ?? (await hashApiKeySecret(secret));
|
|
80
|
+
const creatorScopes = [...options.creatorScopes];
|
|
81
|
+
|
|
82
|
+
const row: ApiKeyRow = {
|
|
83
|
+
id,
|
|
84
|
+
plane: options.plane,
|
|
85
|
+
hash,
|
|
86
|
+
name: options.name,
|
|
87
|
+
scopes: [...options.scopes],
|
|
88
|
+
expiresAt: options.expiresAt ?? null,
|
|
89
|
+
rateLimit: options.rateLimit ?? null,
|
|
90
|
+
ipAllowlist: [...(options.ipAllowlist ?? [])],
|
|
91
|
+
creatorId: options.creatorId,
|
|
92
|
+
creatorScopes,
|
|
93
|
+
createdAt: now(),
|
|
94
|
+
lastUsedAt: null,
|
|
95
|
+
revokedAt: null,
|
|
96
|
+
};
|
|
97
|
+
store.keys.set(id, row);
|
|
98
|
+
return { row, secret };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Resolve a key by raw secret; enforces expiry and revocation.
|
|
103
|
+
*
|
|
104
|
+
* @param store - Key store
|
|
105
|
+
* @param secret - Raw secret
|
|
106
|
+
* @param now - Clock
|
|
107
|
+
*/
|
|
108
|
+
export async function authenticateApiKey(
|
|
109
|
+
store: ApiKeyStore,
|
|
110
|
+
secret: string,
|
|
111
|
+
now: () => number = () => Date.now(),
|
|
112
|
+
): Promise<ApiKeyRow | null> {
|
|
113
|
+
const hash = await hashApiKeySecret(secret);
|
|
114
|
+
for (const row of store.keys.values()) {
|
|
115
|
+
if (row.hash !== hash) continue;
|
|
116
|
+
if (row.revokedAt !== null) return null;
|
|
117
|
+
if (row.expiresAt !== null && row.expiresAt <= now()) return null;
|
|
118
|
+
row.lastUsedAt = now();
|
|
119
|
+
return row;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Revoke an API key — irreversible. The row remains for audit / hygiene.
|
|
126
|
+
*
|
|
127
|
+
* @param store - Key store
|
|
128
|
+
* @param id - Key id
|
|
129
|
+
* @param now - Clock
|
|
130
|
+
*/
|
|
131
|
+
export function revokeApiKey(
|
|
132
|
+
store: ApiKeyStore,
|
|
133
|
+
id: string,
|
|
134
|
+
now: () => number = () => Date.now(),
|
|
135
|
+
): ApiKeyRow | null {
|
|
136
|
+
const row = store.keys.get(id);
|
|
137
|
+
if (!row || row.revokedAt !== null) return null;
|
|
138
|
+
row.revokedAt = now();
|
|
139
|
+
return row;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Rotate an API key secret — new value shown exactly once; old hash dies.
|
|
144
|
+
*
|
|
145
|
+
* @param store - Key store
|
|
146
|
+
* @param id - Key id
|
|
147
|
+
* @param options - Optional secret override / clock
|
|
148
|
+
*/
|
|
149
|
+
export async function rotateApiKey(
|
|
150
|
+
store: ApiKeyStore,
|
|
151
|
+
id: string,
|
|
152
|
+
options: {
|
|
153
|
+
readonly secret?: string;
|
|
154
|
+
readonly now?: () => number;
|
|
155
|
+
} = {},
|
|
156
|
+
): Promise<CreatedApiKey | null> {
|
|
157
|
+
const row = store.keys.get(id);
|
|
158
|
+
if (!row || row.revokedAt !== null) return null;
|
|
159
|
+
const secret =
|
|
160
|
+
options.secret ?? `oke_${id.replace(/-/g, "")}_${randomSecret()}`;
|
|
161
|
+
row.hash = await hashApiKeySecret(secret);
|
|
162
|
+
return { row, secret };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function randomSecret(): string {
|
|
166
|
+
const bytes = crypto.getRandomValues(new Uint8Array(24));
|
|
167
|
+
return [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
168
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attenuation — a derived principal can never exceed its creator.
|
|
3
|
+
*
|
|
4
|
+
* Applies to API keys and `invoke-as` exactly the same way.
|
|
5
|
+
*
|
|
6
|
+
* @see docs/spec/console.md §3.3 · §10.4
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Result of an attenuation check. */
|
|
10
|
+
export interface AttenuationResult {
|
|
11
|
+
readonly ok: boolean;
|
|
12
|
+
/** Scopes the creator lacks (empty when ok). */
|
|
13
|
+
readonly excess: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Expand held scopes against a catalog — `console:*` covers every
|
|
18
|
+
* `console:…` pair in the catalog (Access · MCP inheritance).
|
|
19
|
+
*
|
|
20
|
+
* @param held - Raw scopes from the principal / session
|
|
21
|
+
* @param catalog - Manifest-derived Module:Action pairs
|
|
22
|
+
*/
|
|
23
|
+
export function expandHeldScopes(
|
|
24
|
+
held: Iterable<string>,
|
|
25
|
+
catalog: readonly string[],
|
|
26
|
+
): Set<string> {
|
|
27
|
+
const out = new Set<string>();
|
|
28
|
+
let star = false;
|
|
29
|
+
for (const s of held) {
|
|
30
|
+
out.add(s);
|
|
31
|
+
if (s === "console:*") star = true;
|
|
32
|
+
}
|
|
33
|
+
if (star) {
|
|
34
|
+
for (const scope of catalog) {
|
|
35
|
+
if (scope.startsWith("console:")) out.add(scope);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Whether `requested` is a subset of `creator` scopes.
|
|
43
|
+
*
|
|
44
|
+
* @param creatorScopes - Creator's Module:Action set
|
|
45
|
+
* @param requestedScopes - Requested scopes for the derived principal
|
|
46
|
+
*/
|
|
47
|
+
export function attenuateScopes(
|
|
48
|
+
creatorScopes: ReadonlySet<string> | Iterable<string>,
|
|
49
|
+
requestedScopes: Iterable<string>,
|
|
50
|
+
): AttenuationResult {
|
|
51
|
+
const creator =
|
|
52
|
+
creatorScopes instanceof Set ? creatorScopes : new Set(creatorScopes);
|
|
53
|
+
const excess: string[] = [];
|
|
54
|
+
for (const scope of requestedScopes) {
|
|
55
|
+
if (!creator.has(scope)) excess.push(scope);
|
|
56
|
+
}
|
|
57
|
+
return { ok: excess.length === 0, excess };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Scopes a principal may grant on a plane — intersection of catalog,
|
|
62
|
+
* plane membership, and {@link attenuateScopes} against the expanded
|
|
63
|
+
* ceiling. Impossibility is taught by absence (console §9.14).
|
|
64
|
+
*
|
|
65
|
+
* @param options - Held scopes, catalog, plane predicate
|
|
66
|
+
*/
|
|
67
|
+
export function grantableScopes(options: {
|
|
68
|
+
readonly held: Iterable<string>;
|
|
69
|
+
readonly catalog: readonly string[];
|
|
70
|
+
readonly planeOf: (scope: string) => "user" | "operator";
|
|
71
|
+
readonly plane: "user" | "operator";
|
|
72
|
+
}): string[] {
|
|
73
|
+
const ceiling = expandHeldScopes(options.held, options.catalog);
|
|
74
|
+
return options.catalog
|
|
75
|
+
.filter((scope) => {
|
|
76
|
+
if (options.planeOf(scope) !== options.plane) return false;
|
|
77
|
+
return attenuateScopes(ceiling, [scope]).ok;
|
|
78
|
+
})
|
|
79
|
+
.sort((a, b) => a.localeCompare(b));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Assert attenuation; throw when the key/invoke-as would escalate.
|
|
84
|
+
*
|
|
85
|
+
* @param creatorScopes - Creator scopes
|
|
86
|
+
* @param requestedScopes - Requested scopes
|
|
87
|
+
* @param label - Error label (`api key` / `invoke-as`)
|
|
88
|
+
*/
|
|
89
|
+
export function assertAttenuated(
|
|
90
|
+
creatorScopes: ReadonlySet<string> | Iterable<string>,
|
|
91
|
+
requestedScopes: Iterable<string>,
|
|
92
|
+
label = "principal",
|
|
93
|
+
): void {
|
|
94
|
+
const result = attenuateScopes(creatorScopes, requestedScopes);
|
|
95
|
+
if (!result.ok) {
|
|
96
|
+
throw new AttenuationError(
|
|
97
|
+
`${label} cannot exceed creator scopes; missing: ${result.excess.join(", ")}`,
|
|
98
|
+
result.excess,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Privilege-escalation attempt via key or invoke-as. */
|
|
104
|
+
export class AttenuationError extends Error {
|
|
105
|
+
readonly excess: readonly string[];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param message - Diagnostic
|
|
109
|
+
* @param excess - Scopes the creator lacks
|
|
110
|
+
*/
|
|
111
|
+
constructor(message: string, excess: readonly string[]) {
|
|
112
|
+
super(message);
|
|
113
|
+
this.name = "AttenuationError";
|
|
114
|
+
this.excess = excess;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth acceptance:
|
|
3
|
+
* - API key cannot exceed creator scopes
|
|
4
|
+
* - cross-plane: application principal → console flow is a compile error
|
|
5
|
+
* - hybrid sessions rotate refresh tokens with reuse detection
|
|
6
|
+
* - operator local credential cannot be removed; SSO is additive
|
|
7
|
+
* - invoke-as attenuated like an API key
|
|
8
|
+
* - roles are data
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import type { Manifest } from "../manifest/types.ts";
|
|
13
|
+
import {
|
|
14
|
+
assertAttenuated,
|
|
15
|
+
assertCrossPlane,
|
|
16
|
+
AttenuationError,
|
|
17
|
+
authenticateOperator,
|
|
18
|
+
auth,
|
|
19
|
+
checkCrossPlane,
|
|
20
|
+
createApiKey,
|
|
21
|
+
createApiKeyStore,
|
|
22
|
+
createOperator,
|
|
23
|
+
createOperatorStore,
|
|
24
|
+
createRoleStore,
|
|
25
|
+
createSessionStore,
|
|
26
|
+
CrossPlaneError,
|
|
27
|
+
invokeAs,
|
|
28
|
+
issueSessionWithScopes,
|
|
29
|
+
linkOperatorSso,
|
|
30
|
+
removeOperatorCredential,
|
|
31
|
+
rotateRefresh,
|
|
32
|
+
setRoleGrants,
|
|
33
|
+
scopesForRoles,
|
|
34
|
+
SessionError,
|
|
35
|
+
upsertRole,
|
|
36
|
+
userPrincipal,
|
|
37
|
+
assertPlaneAccess,
|
|
38
|
+
} from "./index.ts";
|
|
39
|
+
|
|
40
|
+
describe("auth plugin", () => {
|
|
41
|
+
test("registers oke_ tables on both planes", () => {
|
|
42
|
+
const tables: string[] = [];
|
|
43
|
+
auth({ secret: "test" }).register({
|
|
44
|
+
hook() { return this; },
|
|
45
|
+
decorate() { return this; },
|
|
46
|
+
element() { return this; },
|
|
47
|
+
needs() { return this; },
|
|
48
|
+
errors() { return this; },
|
|
49
|
+
consolePanel() { return this; },
|
|
50
|
+
cli() { return this; },
|
|
51
|
+
driver() { return this; },
|
|
52
|
+
image() { return this; },
|
|
53
|
+
table(name) {
|
|
54
|
+
tables.push(name);
|
|
55
|
+
return this;
|
|
56
|
+
},
|
|
57
|
+
flow() { return this; },
|
|
58
|
+
client() { return this; },
|
|
59
|
+
config() { return this; },
|
|
60
|
+
});
|
|
61
|
+
expect(tables).toContain("oke_operators");
|
|
62
|
+
expect(tables).toContain("oke_operator_credentials");
|
|
63
|
+
expect(tables).toContain("oke_identities");
|
|
64
|
+
expect(tables).toContain("oke_api_keys");
|
|
65
|
+
expect(tables).toContain("oke_refresh_tokens");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("API key attenuation", () => {
|
|
70
|
+
test("key cannot be created with a scope its creator lacks", async () => {
|
|
71
|
+
const store = createApiKeyStore();
|
|
72
|
+
const creatorScopes = new Set(["bookings:create", "bookings:list"]);
|
|
73
|
+
|
|
74
|
+
await expect(
|
|
75
|
+
createApiKey(store, {
|
|
76
|
+
plane: "user",
|
|
77
|
+
name: "escalated",
|
|
78
|
+
scopes: ["bookings:create", "admin:all"],
|
|
79
|
+
creatorId: "u1",
|
|
80
|
+
creatorScopes,
|
|
81
|
+
}),
|
|
82
|
+
).rejects.toBeInstanceOf(AttenuationError);
|
|
83
|
+
|
|
84
|
+
const ok = await createApiKey(store, {
|
|
85
|
+
plane: "user",
|
|
86
|
+
name: "ok",
|
|
87
|
+
scopes: ["bookings:list"],
|
|
88
|
+
creatorId: "u1",
|
|
89
|
+
creatorScopes,
|
|
90
|
+
});
|
|
91
|
+
expect(ok.row.scopes).toEqual(["bookings:list"]);
|
|
92
|
+
expect(ok.secret.startsWith("oke_")).toBe(true);
|
|
93
|
+
expect(ok.row.createdAt).toBeGreaterThan(0);
|
|
94
|
+
expect(ok.row.revokedAt).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("revoke and rotate — secret once on rotate", async () => {
|
|
98
|
+
const { revokeApiKey, rotateApiKey } = await import("./api-keys.ts");
|
|
99
|
+
const store = createApiKeyStore();
|
|
100
|
+
const created = await createApiKey(store, {
|
|
101
|
+
plane: "user",
|
|
102
|
+
name: "rot",
|
|
103
|
+
scopes: ["bookings:list"],
|
|
104
|
+
creatorId: "u1",
|
|
105
|
+
creatorScopes: ["bookings:list"],
|
|
106
|
+
});
|
|
107
|
+
const rotated = await rotateApiKey(store, created.row.id);
|
|
108
|
+
expect(rotated?.secret).toBeTruthy();
|
|
109
|
+
expect(rotated?.secret).not.toBe(created.secret);
|
|
110
|
+
const revoked = revokeApiKey(store, created.row.id);
|
|
111
|
+
expect(revoked?.revokedAt).not.toBeNull();
|
|
112
|
+
expect(await rotateApiKey(store, created.row.id)).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("cross-plane compile error", () => {
|
|
117
|
+
test("application principal reaching a console flow is a compile error", () => {
|
|
118
|
+
const manifest: Manifest = {
|
|
119
|
+
oke: "1.0",
|
|
120
|
+
app: "skyport",
|
|
121
|
+
flows: {
|
|
122
|
+
"bookings.create": { plane: "user", effects: { calls: ["console.store.query"] } },
|
|
123
|
+
"console.store.query": { plane: "operator" },
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const diags = checkCrossPlane(manifest, [
|
|
128
|
+
{
|
|
129
|
+
path: "src/flows/console/store.ts",
|
|
130
|
+
flow: "console.store.query",
|
|
131
|
+
source: `
|
|
132
|
+
export const query = flow({
|
|
133
|
+
name: "console.store.query",
|
|
134
|
+
plane: "operator",
|
|
135
|
+
do: (_i, fx) => fx.auth.userId,
|
|
136
|
+
});
|
|
137
|
+
`,
|
|
138
|
+
},
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
expect(diags.some((d) => d.message.includes("fx.auth"))).toBe(true);
|
|
142
|
+
expect(diags.some((d) => d.message.includes("cross-plane call"))).toBe(true);
|
|
143
|
+
|
|
144
|
+
expect(() => assertCrossPlane(manifest, [
|
|
145
|
+
{
|
|
146
|
+
path: "x.ts",
|
|
147
|
+
flow: "console.store.query",
|
|
148
|
+
source: `plane: "operator"; fx.auth.userId`,
|
|
149
|
+
},
|
|
150
|
+
])).toThrow(CrossPlaneError);
|
|
151
|
+
|
|
152
|
+
const user = userPrincipal({ userId: "u1", scopes: ["bookings:create"] });
|
|
153
|
+
expect(() => assertPlaneAccess(user, "operator")).toThrow(CrossPlaneError);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
describe("hybrid sessions", () => {
|
|
158
|
+
test("rotate refresh tokens with reuse detection", async () => {
|
|
159
|
+
const store = createSessionStore();
|
|
160
|
+
let now = 1_000_000;
|
|
161
|
+
const crypto = { secret: "test-secret", now: () => now };
|
|
162
|
+
|
|
163
|
+
const issued = await issueSessionWithScopes(store, crypto, {
|
|
164
|
+
id: "user-1",
|
|
165
|
+
plane: "user",
|
|
166
|
+
scopes: ["bookings:create"],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
now += 1_000;
|
|
170
|
+
const rotated = await rotateRefresh(store, crypto, issued.refreshToken);
|
|
171
|
+
expect(rotated.refreshToken).not.toBe(issued.refreshToken);
|
|
172
|
+
expect(rotated.accessToken).not.toBe(issued.accessToken);
|
|
173
|
+
|
|
174
|
+
// Reuse of the old refresh token revokes the family.
|
|
175
|
+
await expect(
|
|
176
|
+
rotateRefresh(store, crypto, issued.refreshToken),
|
|
177
|
+
).rejects.toMatchObject({ name: "SessionError" });
|
|
178
|
+
|
|
179
|
+
// Family revoked — the rotated refresh is also dead.
|
|
180
|
+
await expect(
|
|
181
|
+
rotateRefresh(store, crypto, rotated.refreshToken),
|
|
182
|
+
).rejects.toBeInstanceOf(SessionError);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe("operator plane", () => {
|
|
187
|
+
test("local credential cannot be removed; SSO is additive", async () => {
|
|
188
|
+
const store = createOperatorStore();
|
|
189
|
+
const op = await createOperator(store, {
|
|
190
|
+
email: "ops@example.com",
|
|
191
|
+
name: "Ops",
|
|
192
|
+
password: "correct horse battery staple",
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
expect(store.credentials.has(op.id)).toBe(true);
|
|
196
|
+
expect(() => removeOperatorCredential(store, op.id)).toThrow(
|
|
197
|
+
/cannot be removed/,
|
|
198
|
+
);
|
|
199
|
+
expect(store.credentials.has(op.id)).toBe(true);
|
|
200
|
+
|
|
201
|
+
linkOperatorSso(store, op.id, "oidc", "sub-123");
|
|
202
|
+
expect(store.ssoLinks.get(op.id)).toHaveLength(1);
|
|
203
|
+
// Local login still works after SSO link.
|
|
204
|
+
const authed = await authenticateOperator(
|
|
205
|
+
store,
|
|
206
|
+
"ops@example.com",
|
|
207
|
+
"correct horse battery staple",
|
|
208
|
+
);
|
|
209
|
+
expect(authed?.id).toBe(op.id);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("invoke-as attenuation", () => {
|
|
214
|
+
test("attenuated exactly like an API key", () => {
|
|
215
|
+
const operatorScopes = new Set(["bookings:create", "bookings:list"]);
|
|
216
|
+
|
|
217
|
+
expect(() =>
|
|
218
|
+
invokeAs({
|
|
219
|
+
operatorScopes,
|
|
220
|
+
scopes: ["bookings:create", "admin:all"],
|
|
221
|
+
userId: "synthetic-1",
|
|
222
|
+
}),
|
|
223
|
+
).toThrow(AttenuationError);
|
|
224
|
+
|
|
225
|
+
const principal = invokeAs({
|
|
226
|
+
operatorScopes,
|
|
227
|
+
scopes: ["bookings:create"],
|
|
228
|
+
userId: "synthetic-1",
|
|
229
|
+
});
|
|
230
|
+
expect(principal.plane).toBe("user");
|
|
231
|
+
expect(principal.scopes.has("bookings:create")).toBe(true);
|
|
232
|
+
|
|
233
|
+
expect(() =>
|
|
234
|
+
invokeAs({
|
|
235
|
+
operatorScopes,
|
|
236
|
+
scopes: ["bookings:create"],
|
|
237
|
+
userId: "real-user",
|
|
238
|
+
impersonateRealUser: true,
|
|
239
|
+
development: false,
|
|
240
|
+
}),
|
|
241
|
+
).toThrow(/development-only/);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe("roles are data", () => {
|
|
246
|
+
test("grants assignable without redeploy", () => {
|
|
247
|
+
const store = createRoleStore();
|
|
248
|
+
upsertRole(store, {
|
|
249
|
+
id: "r1",
|
|
250
|
+
name: "agent",
|
|
251
|
+
plane: "user",
|
|
252
|
+
description: "support agent",
|
|
253
|
+
});
|
|
254
|
+
setRoleGrants(store, "r1", ["bookings:list", "bookings:create"]);
|
|
255
|
+
const scopes = scopesForRoles(store, ["r1"], "user");
|
|
256
|
+
expect(scopes.has("bookings:list")).toBe(true);
|
|
257
|
+
// Plane isolation: operator roles ignored for user resolution.
|
|
258
|
+
upsertRole(store, {
|
|
259
|
+
id: "r-ops",
|
|
260
|
+
name: "admin",
|
|
261
|
+
plane: "operator",
|
|
262
|
+
description: "ops",
|
|
263
|
+
});
|
|
264
|
+
setRoleGrants(store, "r-ops", ["console:store.sql:write"]);
|
|
265
|
+
expect(scopesForRoles(store, ["r-ops"], "user").size).toBe(0);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("assertAttenuated helper", () => {
|
|
269
|
+
expect(() =>
|
|
270
|
+
assertAttenuated(new Set(["a"]), ["a", "b"], "api key"),
|
|
271
|
+
).toThrow(/missing: b/);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-plane compile checks — application principals never reach console flows.
|
|
3
|
+
*
|
|
4
|
+
* @see docs/spec/console.md §2.2
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Manifest } from "../manifest/types.ts";
|
|
8
|
+
import { CrossPlaneError } from "./planes.ts";
|
|
9
|
+
|
|
10
|
+
/** One cross-plane diagnostic. */
|
|
11
|
+
export interface CrossPlaneDiagnostic {
|
|
12
|
+
readonly flow: string;
|
|
13
|
+
readonly message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Source snippet used for principal-usage analysis. */
|
|
17
|
+
export interface PlaneSourceFile {
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly source: string;
|
|
20
|
+
/** Flow id this file contributes to (when known). */
|
|
21
|
+
readonly flow?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compile-time check: application principal (`fx.auth`) must not reach
|
|
26
|
+
* operator-plane (console) flows; user-plane flows must not call operator flows.
|
|
27
|
+
*
|
|
28
|
+
* @param manifest - Extracted Manifest
|
|
29
|
+
* @param sources - Optional source files for `fx.auth` / `fx.operator` usage
|
|
30
|
+
* @returns Diagnostics (empty when clean)
|
|
31
|
+
*/
|
|
32
|
+
export function checkCrossPlane(
|
|
33
|
+
manifest: Manifest,
|
|
34
|
+
sources: readonly PlaneSourceFile[] = [],
|
|
35
|
+
): CrossPlaneDiagnostic[] {
|
|
36
|
+
const diagnostics: CrossPlaneDiagnostic[] = [];
|
|
37
|
+
const planes = new Map<string, "user" | "operator">();
|
|
38
|
+
|
|
39
|
+
for (const [id, flow] of Object.entries(manifest.flows ?? {})) {
|
|
40
|
+
planes.set(id, flow.plane ?? "user");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const [id, flow] of Object.entries(manifest.flows ?? {})) {
|
|
44
|
+
const plane = flow.plane ?? "user";
|
|
45
|
+
for (const callee of flow.effects?.calls ?? []) {
|
|
46
|
+
const calleePlane = planes.get(callee);
|
|
47
|
+
if (calleePlane === "operator" && plane === "user") {
|
|
48
|
+
diagnostics.push({
|
|
49
|
+
flow: id,
|
|
50
|
+
message: `cross-plane call: user flow "${id}" calls operator flow "${callee}"`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
for (const file of sources) {
|
|
57
|
+
const flowId = file.flow ?? inferFlowFromPath(file.path);
|
|
58
|
+
if (!flowId) continue;
|
|
59
|
+
const plane = planes.get(flowId) ?? inferPlaneFromSource(file.source);
|
|
60
|
+
if (plane === "operator" && /\bfx\.auth\b/.test(file.source)) {
|
|
61
|
+
diagnostics.push({
|
|
62
|
+
flow: flowId,
|
|
63
|
+
message: `application principal (fx.auth) reaches console flow "${flowId}"`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return diagnostics;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Assert cross-plane isolation; throw {@link CrossPlaneError} on violation.
|
|
73
|
+
*
|
|
74
|
+
* @param manifest - Manifest
|
|
75
|
+
* @param sources - Sources
|
|
76
|
+
*/
|
|
77
|
+
export function assertCrossPlane(
|
|
78
|
+
manifest: Manifest,
|
|
79
|
+
sources: readonly PlaneSourceFile[] = [],
|
|
80
|
+
): void {
|
|
81
|
+
const diagnostics = checkCrossPlane(manifest, sources);
|
|
82
|
+
if (diagnostics.length > 0) {
|
|
83
|
+
throw new CrossPlaneError(diagnostics.map((d) => d.message).join("; "));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function inferFlowFromPath(path: string): string | undefined {
|
|
88
|
+
// …/flows/bookings/create.ts → bookings.create
|
|
89
|
+
const m = /flows\/([^/]+)\/([^/]+)\.(ts|tsx|js)$/.exec(path.replace(/\\/g, "/"));
|
|
90
|
+
if (m) return `${m[1]}.${m[2]}`;
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function inferPlaneFromSource(source: string): "user" | "operator" | undefined {
|
|
95
|
+
if (/plane\s*:\s*["']operator["']/.test(source)) return "operator";
|
|
96
|
+
if (/plane\s*:\s*["']user["']/.test(source)) return "user";
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|