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,861 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console Store projection — Manifest + StoreRuntime over `fx` (console §9.5).
|
|
3
|
+
*
|
|
4
|
+
* The UI must not reimplement cache invalidation, PII masking, or effects graph.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
readSchemaFingerprint,
|
|
9
|
+
schemaFingerprint,
|
|
10
|
+
} from "../../cli/schema.ts";
|
|
11
|
+
import {
|
|
12
|
+
memoryFilesDriver,
|
|
13
|
+
memoryIndexDriver,
|
|
14
|
+
memoryKvDriver,
|
|
15
|
+
memorySqlDriver,
|
|
16
|
+
} from "../../drivers/memory.ts";
|
|
17
|
+
import {
|
|
18
|
+
computedCacheKey,
|
|
19
|
+
createStoreRuntime,
|
|
20
|
+
files as declareFiles,
|
|
21
|
+
index as declareIndex,
|
|
22
|
+
isInvalidatedByWrite,
|
|
23
|
+
kv as declareKv,
|
|
24
|
+
projectFileKeys,
|
|
25
|
+
sql as declareSql,
|
|
26
|
+
type FilesStoreFxHandle,
|
|
27
|
+
type IndexStoreFxHandle,
|
|
28
|
+
type KvStoreFxHandle,
|
|
29
|
+
type SqlStoreHandle,
|
|
30
|
+
type StoreRuntime,
|
|
31
|
+
} from "../../elements/store.ts";
|
|
32
|
+
import {
|
|
33
|
+
DryRunWriteIsolationError,
|
|
34
|
+
withDryRun,
|
|
35
|
+
} from "../../kernel/dry-run.ts";
|
|
36
|
+
import type {
|
|
37
|
+
Effects,
|
|
38
|
+
Manifest,
|
|
39
|
+
ResourceRef,
|
|
40
|
+
StoreFacet,
|
|
41
|
+
} from "../../manifest/types.ts";
|
|
42
|
+
import type { WideEvent } from "../../runs/types.ts";
|
|
43
|
+
|
|
44
|
+
/** Whether multi-tenancy is declared on the Manifest (off by default). */
|
|
45
|
+
export function tenancyDeclared(manifest: Manifest | null): boolean {
|
|
46
|
+
return manifest?.tenancy !== undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Cache projection for one resource. */
|
|
50
|
+
export interface ConsoleStoreCacheView {
|
|
51
|
+
/** Tier-1 key produced by a read of this resource. */
|
|
52
|
+
readonly producedByRead: string;
|
|
53
|
+
/** Write resource refs that invalidate the key. */
|
|
54
|
+
readonly invalidatedByWrites: readonly string[];
|
|
55
|
+
/** Flow ids whose writes touch this resource. */
|
|
56
|
+
readonly invalidatingFlowIds: readonly string[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Migration drift — declared schema fingerprint vs applied on disk. */
|
|
60
|
+
export interface ConsoleMigrationDrift {
|
|
61
|
+
readonly declared: string;
|
|
62
|
+
readonly applied: string | null;
|
|
63
|
+
readonly drifted: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** What a direct edit will NOT fire (Manifest effects of writers). */
|
|
67
|
+
export interface ConsoleWillNotFire {
|
|
68
|
+
readonly writerFlowIds: readonly string[];
|
|
69
|
+
readonly signals: readonly string[];
|
|
70
|
+
readonly channels: readonly string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** One child resource under a store (table / namespace / bucket / index). */
|
|
74
|
+
export interface ConsoleStoreChild {
|
|
75
|
+
readonly name: string;
|
|
76
|
+
/** Effect ref used in Manifest (`sql:bookings`). */
|
|
77
|
+
readonly effectRef: ResourceRef;
|
|
78
|
+
readonly writers: readonly string[];
|
|
79
|
+
readonly readers: readonly string[];
|
|
80
|
+
readonly cache: ConsoleStoreCacheView;
|
|
81
|
+
readonly willNotFire: ConsoleWillNotFire;
|
|
82
|
+
readonly piiColumns: readonly string[];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** One row in `console.store.list`. */
|
|
86
|
+
export interface ConsoleStoreRow {
|
|
87
|
+
readonly ref: ResourceRef;
|
|
88
|
+
readonly facet: StoreFacet;
|
|
89
|
+
readonly name: string;
|
|
90
|
+
readonly children: readonly ConsoleStoreChild[];
|
|
91
|
+
readonly replicaLagMs: number | null;
|
|
92
|
+
readonly migrationDrift: ConsoleMigrationDrift | null;
|
|
93
|
+
readonly contentAddressed: boolean;
|
|
94
|
+
readonly warnings: ReadonlyArray<{
|
|
95
|
+
readonly code: string;
|
|
96
|
+
readonly message: string;
|
|
97
|
+
readonly key: string;
|
|
98
|
+
}>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Options when projecting the store list. */
|
|
102
|
+
export interface ProjectStoresOptions {
|
|
103
|
+
readonly manifest: Manifest | null;
|
|
104
|
+
readonly runtime: StoreRuntime | null;
|
|
105
|
+
readonly runs?: ReadonlyArray<Pick<WideEvent, "flow" | "replicaLagMs" | "effects" | "tenant">>;
|
|
106
|
+
readonly cwd?: string;
|
|
107
|
+
/** Injected fingerprints for tests. */
|
|
108
|
+
readonly declaredFingerprint?: string;
|
|
109
|
+
readonly appliedFingerprint?: string | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Project Manifest stores into operator rows.
|
|
114
|
+
*
|
|
115
|
+
* @param options - Manifest, runtime, runs
|
|
116
|
+
*/
|
|
117
|
+
export async function projectStoresList(
|
|
118
|
+
options: ProjectStoresOptions,
|
|
119
|
+
): Promise<{
|
|
120
|
+
readonly stores: readonly ConsoleStoreRow[];
|
|
121
|
+
readonly tenancyDeclared: boolean;
|
|
122
|
+
readonly tenants: readonly string[];
|
|
123
|
+
}> {
|
|
124
|
+
const manifest = options.manifest;
|
|
125
|
+
const tenancy = tenancyDeclared(manifest);
|
|
126
|
+
const tenants = tenancy ? collectTenants(options.runs ?? []) : [];
|
|
127
|
+
|
|
128
|
+
let drift: ConsoleMigrationDrift | null = null;
|
|
129
|
+
if (manifest) {
|
|
130
|
+
const declared =
|
|
131
|
+
options.declaredFingerprint ??
|
|
132
|
+
(await schemaFingerprint(options.cwd ?? process.cwd(), manifest));
|
|
133
|
+
const applied =
|
|
134
|
+
options.appliedFingerprint !== undefined
|
|
135
|
+
? options.appliedFingerprint
|
|
136
|
+
: await readSchemaFingerprint(options.cwd ?? process.cwd());
|
|
137
|
+
drift = {
|
|
138
|
+
declared,
|
|
139
|
+
applied,
|
|
140
|
+
drifted: applied !== null && applied !== declared,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const stores: ConsoleStoreRow[] = [];
|
|
145
|
+
for (const [name, store] of Object.entries(manifest?.stores ?? {})) {
|
|
146
|
+
const facet = store.facet;
|
|
147
|
+
const ref = `${facet}:${name}` as ResourceRef;
|
|
148
|
+
const children = childrenOf(manifest!, name, facet, store);
|
|
149
|
+
const replicaLagMs =
|
|
150
|
+
facet === "sql" ? latestReplicaLag(options.runs ?? [], children) : null;
|
|
151
|
+
|
|
152
|
+
let warnings: ConsoleStoreRow["warnings"] = [];
|
|
153
|
+
if (facet === "files" && options.runtime) {
|
|
154
|
+
try {
|
|
155
|
+
const handle = (await options.runtime.openRef(ref, {
|
|
156
|
+
effects: { reads: [ref] },
|
|
157
|
+
})) as FilesStoreFxHandle;
|
|
158
|
+
const keys = await handle.list();
|
|
159
|
+
warnings = projectFileKeys(keys).flatMap((k) => k.warnings);
|
|
160
|
+
} catch {
|
|
161
|
+
warnings = [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
stores.push({
|
|
166
|
+
ref,
|
|
167
|
+
facet,
|
|
168
|
+
name,
|
|
169
|
+
children,
|
|
170
|
+
replicaLagMs,
|
|
171
|
+
migrationDrift: facet === "sql" ? drift : null,
|
|
172
|
+
contentAddressed: facet === "files",
|
|
173
|
+
warnings,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
stores.sort((a, b) => {
|
|
178
|
+
const order: StoreFacet[] = ["sql", "kv", "files", "index"];
|
|
179
|
+
const d = order.indexOf(a.facet) - order.indexOf(b.facet);
|
|
180
|
+
return d !== 0 ? d : a.name.localeCompare(b.name);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return { stores, tenancyDeclared: tenancy, tenants };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function childrenOf(
|
|
187
|
+
manifest: Manifest,
|
|
188
|
+
storeName: string,
|
|
189
|
+
facet: StoreFacet,
|
|
190
|
+
store: NonNullable<Manifest["stores"]>[string],
|
|
191
|
+
): ConsoleStoreChild[] {
|
|
192
|
+
const names: string[] = [];
|
|
193
|
+
if (facet === "sql") {
|
|
194
|
+
names.push(...Object.keys(store.tables ?? {}));
|
|
195
|
+
// Also surface table suffixes from effects when Manifest tables are sparse.
|
|
196
|
+
for (const flow of Object.values(manifest.flows ?? {})) {
|
|
197
|
+
for (const ref of [
|
|
198
|
+
...(flow.effects?.reads ?? []),
|
|
199
|
+
...(flow.effects?.writes ?? []),
|
|
200
|
+
]) {
|
|
201
|
+
if (ref.startsWith("sql:")) {
|
|
202
|
+
const table = ref.slice(4);
|
|
203
|
+
if (!names.includes(table)) names.push(table);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} else if (facet === "kv") {
|
|
208
|
+
names.push(...(store.namespaces ?? [storeName]));
|
|
209
|
+
} else if (facet === "files") {
|
|
210
|
+
names.push(...(store.buckets ?? [storeName]));
|
|
211
|
+
} else {
|
|
212
|
+
names.push(...(store.indexes ?? [storeName]));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const unique = [...new Set(names)].sort();
|
|
216
|
+
return unique.map((childName) => {
|
|
217
|
+
const effectRef = (
|
|
218
|
+
facet === "sql" ? `sql:${childName}` : `${facet}:${storeName}`
|
|
219
|
+
) as ResourceRef;
|
|
220
|
+
const writers = flowsTouching(manifest, effectRef, "writes");
|
|
221
|
+
const readers = flowsTouching(manifest, effectRef, "reads");
|
|
222
|
+
const willNotFire = willNotFireFor(manifest, writers);
|
|
223
|
+
const piiColumns = piiColumnsFor(store, childName);
|
|
224
|
+
const producedByRead = computedCacheKey(effectRef);
|
|
225
|
+
const invalidatingFlowIds = writers;
|
|
226
|
+
const invalidatedByWrites = writers.length > 0 ? [effectRef] : [];
|
|
227
|
+
return {
|
|
228
|
+
name: childName,
|
|
229
|
+
effectRef,
|
|
230
|
+
writers,
|
|
231
|
+
readers,
|
|
232
|
+
cache: {
|
|
233
|
+
producedByRead,
|
|
234
|
+
invalidatedByWrites,
|
|
235
|
+
invalidatingFlowIds,
|
|
236
|
+
},
|
|
237
|
+
willNotFire,
|
|
238
|
+
piiColumns,
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function flowsTouching(
|
|
244
|
+
manifest: Manifest,
|
|
245
|
+
ref: ResourceRef,
|
|
246
|
+
kind: "reads" | "writes",
|
|
247
|
+
): string[] {
|
|
248
|
+
const out: string[] = [];
|
|
249
|
+
for (const [flowId, flow] of Object.entries(manifest.flows ?? {})) {
|
|
250
|
+
if ((flow.effects?.[kind] ?? []).includes(ref)) out.push(flowId);
|
|
251
|
+
}
|
|
252
|
+
return out.sort();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Signals / channels that writers of this table would emit/send — skipped on
|
|
257
|
+
* a direct Console edit (not a flow execution).
|
|
258
|
+
*
|
|
259
|
+
* @param manifest - Manifest
|
|
260
|
+
* @param writerFlowIds - Flows that write the resource
|
|
261
|
+
*/
|
|
262
|
+
export function willNotFireFor(
|
|
263
|
+
manifest: Manifest,
|
|
264
|
+
writerFlowIds: readonly string[],
|
|
265
|
+
): ConsoleWillNotFire {
|
|
266
|
+
const signals = new Set<string>();
|
|
267
|
+
const channels = new Set<string>();
|
|
268
|
+
for (const flowId of writerFlowIds) {
|
|
269
|
+
const flow = manifest.flows?.[flowId];
|
|
270
|
+
for (const s of flow?.effects?.emits ?? []) signals.add(s);
|
|
271
|
+
for (const c of flow?.effects?.sends ?? []) channels.add(c);
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
writerFlowIds: [...writerFlowIds],
|
|
275
|
+
signals: [...signals].sort(),
|
|
276
|
+
channels: [...channels].sort(),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function piiColumnsFor(
|
|
281
|
+
store: NonNullable<Manifest["stores"]>[string],
|
|
282
|
+
table: string,
|
|
283
|
+
): string[] {
|
|
284
|
+
const cols: string[] = [];
|
|
285
|
+
const tableMeta = store.tables?.[table];
|
|
286
|
+
for (const [col, tags] of Object.entries(tableMeta?.columns ?? {})) {
|
|
287
|
+
if (tags?.pii) cols.push(col);
|
|
288
|
+
}
|
|
289
|
+
for (const [key, tags] of Object.entries(store.classifications ?? {})) {
|
|
290
|
+
if (
|
|
291
|
+
key.startsWith(`${table}.`) &&
|
|
292
|
+
tags &&
|
|
293
|
+
typeof tags === "object" &&
|
|
294
|
+
!Array.isArray(tags) &&
|
|
295
|
+
"pii" in tags &&
|
|
296
|
+
(tags as { pii?: boolean }).pii
|
|
297
|
+
) {
|
|
298
|
+
cols.push(key.slice(table.length + 1));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return [...new Set(cols)].sort();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function latestReplicaLag(
|
|
305
|
+
runs: ReadonlyArray<Pick<WideEvent, "replicaLagMs" | "effects">>,
|
|
306
|
+
children: readonly ConsoleStoreChild[],
|
|
307
|
+
): number | null {
|
|
308
|
+
const refs = new Set(children.map((c) => c.effectRef));
|
|
309
|
+
let lag: number | null = null;
|
|
310
|
+
for (const run of runs) {
|
|
311
|
+
if (run.replicaLagMs == null) continue;
|
|
312
|
+
const touches = (run.effects ?? []).some(
|
|
313
|
+
(e) => refs.has(e.resource as ResourceRef),
|
|
314
|
+
);
|
|
315
|
+
if (!touches) continue;
|
|
316
|
+
if (lag === null || run.replicaLagMs > lag) lag = run.replicaLagMs;
|
|
317
|
+
}
|
|
318
|
+
return lag;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function collectTenants(
|
|
322
|
+
runs: ReadonlyArray<{ readonly tenant?: string | null }>,
|
|
323
|
+
): string[] {
|
|
324
|
+
const set = new Set<string>();
|
|
325
|
+
for (const r of runs) {
|
|
326
|
+
if (r.tenant) set.add(r.tenant);
|
|
327
|
+
}
|
|
328
|
+
return [...set].sort();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Query / browse input. */
|
|
332
|
+
export interface StoreQueryInput {
|
|
333
|
+
readonly ref: ResourceRef;
|
|
334
|
+
readonly child?: string;
|
|
335
|
+
readonly tenant?: string;
|
|
336
|
+
readonly prefix?: string;
|
|
337
|
+
readonly limit?: number;
|
|
338
|
+
readonly vector?: readonly number[];
|
|
339
|
+
readonly topK?: number;
|
|
340
|
+
readonly revealPii?: boolean;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Query result shape. */
|
|
344
|
+
export interface StoreQueryResult {
|
|
345
|
+
readonly facet: StoreFacet;
|
|
346
|
+
readonly rows?: readonly Record<string, unknown>[];
|
|
347
|
+
readonly keys?: ReadonlyArray<{
|
|
348
|
+
readonly key: string;
|
|
349
|
+
readonly value?: unknown;
|
|
350
|
+
readonly warnings?: ReadonlyArray<{
|
|
351
|
+
readonly code: string;
|
|
352
|
+
readonly message: string;
|
|
353
|
+
}>;
|
|
354
|
+
}>;
|
|
355
|
+
readonly hits?: ReadonlyArray<{
|
|
356
|
+
readonly id: string;
|
|
357
|
+
readonly score: number;
|
|
358
|
+
readonly meta?: Record<string, unknown>;
|
|
359
|
+
}>;
|
|
360
|
+
readonly masked: boolean;
|
|
361
|
+
readonly routedRole?: "primary" | "replica";
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Browse a store facet through the runtime (PII masked unless reveal).
|
|
366
|
+
*
|
|
367
|
+
* @param runtime - Store runtime
|
|
368
|
+
* @param manifest - Manifest (classifications)
|
|
369
|
+
* @param input - Browse input
|
|
370
|
+
*/
|
|
371
|
+
export async function queryStore(
|
|
372
|
+
runtime: StoreRuntime,
|
|
373
|
+
manifest: Manifest | null,
|
|
374
|
+
input: StoreQueryInput,
|
|
375
|
+
): Promise<StoreQueryResult> {
|
|
376
|
+
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
377
|
+
const effects: Effects = { reads: [input.ref] };
|
|
378
|
+
const handle = await runtime.openRef(input.ref, {
|
|
379
|
+
effects,
|
|
380
|
+
revealPii: input.revealPii === true,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (facet === "sql") {
|
|
384
|
+
const sql = handle as SqlStoreHandle;
|
|
385
|
+
const table = input.child;
|
|
386
|
+
if (!table) {
|
|
387
|
+
return { facet, rows: [], masked: !input.revealPii, routedRole: sql.routedRole };
|
|
388
|
+
}
|
|
389
|
+
const limit = input.limit ?? 50;
|
|
390
|
+
let sqlText = `SELECT * FROM "${table}"`;
|
|
391
|
+
const params: unknown[] = [];
|
|
392
|
+
if (input.tenant !== undefined) {
|
|
393
|
+
sqlText += ` WHERE "tenant_id" = ?`;
|
|
394
|
+
params.push(input.tenant);
|
|
395
|
+
}
|
|
396
|
+
sqlText += ` LIMIT ${limit}`;
|
|
397
|
+
try {
|
|
398
|
+
const rows = await sql.raw(sqlText, params);
|
|
399
|
+
return {
|
|
400
|
+
facet,
|
|
401
|
+
rows,
|
|
402
|
+
masked: !input.revealPii,
|
|
403
|
+
routedRole: sql.routedRole,
|
|
404
|
+
};
|
|
405
|
+
} catch {
|
|
406
|
+
// Table may not exist yet — empty browse.
|
|
407
|
+
return {
|
|
408
|
+
facet,
|
|
409
|
+
rows: [],
|
|
410
|
+
masked: !input.revealPii,
|
|
411
|
+
routedRole: sql.routedRole,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (facet === "kv") {
|
|
417
|
+
const kv = handle as KvStoreFxHandle;
|
|
418
|
+
const keys = await kv.list(input.prefix ?? "");
|
|
419
|
+
const limited = keys.slice(0, input.limit ?? 100);
|
|
420
|
+
const entries = await Promise.all(
|
|
421
|
+
limited.map(async (key) => ({
|
|
422
|
+
key,
|
|
423
|
+
value: await kv.get(key),
|
|
424
|
+
})),
|
|
425
|
+
);
|
|
426
|
+
return { facet, keys: entries, masked: false };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (facet === "files") {
|
|
430
|
+
const filesHandle = handle as FilesStoreFxHandle;
|
|
431
|
+
const keys = await filesHandle.list(input.prefix ?? "");
|
|
432
|
+
const projected = projectFileKeys(keys.slice(0, input.limit ?? 100));
|
|
433
|
+
return {
|
|
434
|
+
facet,
|
|
435
|
+
keys: projected.map((k) => ({
|
|
436
|
+
key: k.key,
|
|
437
|
+
warnings: k.warnings.map((w) => ({
|
|
438
|
+
code: w.code,
|
|
439
|
+
message: w.message,
|
|
440
|
+
})),
|
|
441
|
+
})),
|
|
442
|
+
masked: false,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// index
|
|
447
|
+
const idx = handle as IndexStoreFxHandle;
|
|
448
|
+
const vector = input.vector ?? [];
|
|
449
|
+
if (vector.length === 0) {
|
|
450
|
+
return { facet, hits: [], masked: false };
|
|
451
|
+
}
|
|
452
|
+
const hits = await idx.search(vector, input.topK ?? 5);
|
|
453
|
+
void manifest;
|
|
454
|
+
return { facet, hits, masked: false };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/** Options for a direct edit (not a flow execution). */
|
|
458
|
+
export interface StoreEditInput {
|
|
459
|
+
readonly ref: ResourceRef;
|
|
460
|
+
readonly child?: string;
|
|
461
|
+
readonly tenant?: string;
|
|
462
|
+
readonly id?: string;
|
|
463
|
+
readonly key?: string;
|
|
464
|
+
readonly patch: Record<string, unknown>;
|
|
465
|
+
readonly confirmation?: string;
|
|
466
|
+
readonly reason?: string;
|
|
467
|
+
readonly confirmed?: boolean;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Preview or apply a direct row/key edit. Direct edit is NOT a flow execution.
|
|
472
|
+
*
|
|
473
|
+
* @param runtime - Store runtime
|
|
474
|
+
* @param manifest - Manifest
|
|
475
|
+
* @param input - Edit input
|
|
476
|
+
* @param options - Production / dry-run
|
|
477
|
+
*/
|
|
478
|
+
export async function editStore(
|
|
479
|
+
runtime: StoreRuntime,
|
|
480
|
+
manifest: Manifest | null,
|
|
481
|
+
input: StoreEditInput,
|
|
482
|
+
options: {
|
|
483
|
+
readonly production: boolean;
|
|
484
|
+
readonly dryRun?: boolean;
|
|
485
|
+
},
|
|
486
|
+
): Promise<{
|
|
487
|
+
readonly ok: true;
|
|
488
|
+
readonly dryRun: boolean;
|
|
489
|
+
readonly willNotFire: ConsoleWillNotFire;
|
|
490
|
+
readonly applied: boolean;
|
|
491
|
+
readonly wouldHaveFired: ReadonlyArray<{
|
|
492
|
+
readonly kind: "send" | "ask";
|
|
493
|
+
readonly resource: string;
|
|
494
|
+
}>;
|
|
495
|
+
}> {
|
|
496
|
+
const effectRef = (
|
|
497
|
+
input.child && input.ref.startsWith("sql:")
|
|
498
|
+
? `sql:${input.child}`
|
|
499
|
+
: input.ref
|
|
500
|
+
) as ResourceRef;
|
|
501
|
+
const writers = manifest
|
|
502
|
+
? flowsTouching(manifest, effectRef, "writes")
|
|
503
|
+
: [];
|
|
504
|
+
const willNotFire = willNotFireFor(manifest ?? { oke: "1.0", app: "" }, writers);
|
|
505
|
+
|
|
506
|
+
if (options.dryRun) {
|
|
507
|
+
// Dual test: withDryRun stubs send/ask; we also snapshot touched keys/rows
|
|
508
|
+
// and restore after — memory KV/SQL drivers do not auto-register with
|
|
509
|
+
// touchDryRunStore the way stub fx.store maps do.
|
|
510
|
+
const snapshot = await snapshotEditTarget(runtime, input);
|
|
511
|
+
try {
|
|
512
|
+
const { wouldHaveFired } = await withDryRun(async () => {
|
|
513
|
+
await applyEdit(runtime, input, { revealPii: false });
|
|
514
|
+
});
|
|
515
|
+
await restoreEditTarget(runtime, snapshot);
|
|
516
|
+
return {
|
|
517
|
+
ok: true,
|
|
518
|
+
dryRun: true,
|
|
519
|
+
willNotFire,
|
|
520
|
+
applied: false,
|
|
521
|
+
wouldHaveFired,
|
|
522
|
+
};
|
|
523
|
+
} catch (err) {
|
|
524
|
+
await restoreEditTarget(runtime, snapshot);
|
|
525
|
+
if (err instanceof DryRunWriteIsolationError) {
|
|
526
|
+
throw err;
|
|
527
|
+
}
|
|
528
|
+
throw err;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
await applyEdit(runtime, input, { revealPii: false });
|
|
533
|
+
// Direct edit: invalidate cache for written resource (effects-derived).
|
|
534
|
+
runtime.onWriteEffects({ writes: [effectRef] });
|
|
535
|
+
void options.production;
|
|
536
|
+
return {
|
|
537
|
+
ok: true,
|
|
538
|
+
dryRun: false,
|
|
539
|
+
willNotFire,
|
|
540
|
+
applied: true,
|
|
541
|
+
wouldHaveFired: [],
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Snapshot of a direct-edit target for dry-run rollback. */
|
|
546
|
+
type EditSnapshot =
|
|
547
|
+
| { readonly kind: "kv"; readonly ref: ResourceRef; readonly key: string; readonly value: unknown }
|
|
548
|
+
| {
|
|
549
|
+
readonly kind: "sql";
|
|
550
|
+
readonly ref: ResourceRef;
|
|
551
|
+
readonly child: string;
|
|
552
|
+
readonly id: string;
|
|
553
|
+
readonly row: Record<string, unknown> | null;
|
|
554
|
+
}
|
|
555
|
+
| { readonly kind: "none" };
|
|
556
|
+
|
|
557
|
+
async function snapshotEditTarget(
|
|
558
|
+
runtime: StoreRuntime,
|
|
559
|
+
input: StoreEditInput,
|
|
560
|
+
): Promise<EditSnapshot> {
|
|
561
|
+
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
562
|
+
if (facet === "kv" && input.key) {
|
|
563
|
+
const kv = (await runtime.openRef(input.ref, {
|
|
564
|
+
effects: { reads: [input.ref] },
|
|
565
|
+
})) as KvStoreFxHandle;
|
|
566
|
+
return {
|
|
567
|
+
kind: "kv",
|
|
568
|
+
ref: input.ref,
|
|
569
|
+
key: input.key,
|
|
570
|
+
value: await kv.get(input.key),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
if (facet === "sql" && input.child && input.id) {
|
|
574
|
+
const sql = (await runtime.openRef(input.ref, {
|
|
575
|
+
effects: { reads: [input.ref] },
|
|
576
|
+
})) as SqlStoreHandle;
|
|
577
|
+
try {
|
|
578
|
+
const rows = await sql.raw(
|
|
579
|
+
`SELECT * FROM "${input.child}" WHERE "id" = ? LIMIT 1`,
|
|
580
|
+
[input.id],
|
|
581
|
+
);
|
|
582
|
+
return {
|
|
583
|
+
kind: "sql",
|
|
584
|
+
ref: input.ref,
|
|
585
|
+
child: input.child,
|
|
586
|
+
id: input.id,
|
|
587
|
+
row: rows[0] ?? null,
|
|
588
|
+
};
|
|
589
|
+
} catch {
|
|
590
|
+
return {
|
|
591
|
+
kind: "sql",
|
|
592
|
+
ref: input.ref,
|
|
593
|
+
child: input.child,
|
|
594
|
+
id: input.id,
|
|
595
|
+
row: null,
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
return { kind: "none" };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async function restoreEditTarget(
|
|
603
|
+
runtime: StoreRuntime,
|
|
604
|
+
snapshot: EditSnapshot,
|
|
605
|
+
): Promise<void> {
|
|
606
|
+
if (snapshot.kind === "kv") {
|
|
607
|
+
const kv = (await runtime.openRef(snapshot.ref, {
|
|
608
|
+
effects: { writes: [snapshot.ref] },
|
|
609
|
+
})) as KvStoreFxHandle;
|
|
610
|
+
if (snapshot.value === undefined) {
|
|
611
|
+
await kv.delete(snapshot.key);
|
|
612
|
+
} else {
|
|
613
|
+
await kv.set(snapshot.key, snapshot.value);
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (snapshot.kind === "sql") {
|
|
618
|
+
const sql = (await runtime.openRef(snapshot.ref, {
|
|
619
|
+
effects: { writes: [snapshot.ref] },
|
|
620
|
+
})) as SqlStoreHandle;
|
|
621
|
+
if (snapshot.row === null) {
|
|
622
|
+
try {
|
|
623
|
+
await sql.raw(
|
|
624
|
+
`DELETE FROM "${snapshot.child}" WHERE "id" = ?`,
|
|
625
|
+
[snapshot.id],
|
|
626
|
+
);
|
|
627
|
+
} catch {
|
|
628
|
+
/* table may not exist */
|
|
629
|
+
}
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
const cols = Object.keys(snapshot.row);
|
|
633
|
+
const assignments = cols.map((c) => `"${c}" = ?`).join(", ");
|
|
634
|
+
try {
|
|
635
|
+
await sql.raw(
|
|
636
|
+
`UPDATE "${snapshot.child}" SET ${assignments} WHERE "id" = ?`,
|
|
637
|
+
[...cols.map((c) => snapshot.row![c]), snapshot.id],
|
|
638
|
+
);
|
|
639
|
+
} catch {
|
|
640
|
+
/* best-effort restore */
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async function applyEdit(
|
|
646
|
+
runtime: StoreRuntime,
|
|
647
|
+
input: StoreEditInput,
|
|
648
|
+
ctx: { readonly revealPii: boolean },
|
|
649
|
+
): Promise<void> {
|
|
650
|
+
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
651
|
+
const handle = await runtime.openRef(input.ref, {
|
|
652
|
+
effects: { writes: [input.ref] },
|
|
653
|
+
revealPii: ctx.revealPii,
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
if (facet === "kv") {
|
|
657
|
+
const kv = handle as KvStoreFxHandle;
|
|
658
|
+
const key = input.key;
|
|
659
|
+
if (!key) throw new Error("kv edit requires key");
|
|
660
|
+
await kv.set(key, input.patch.value ?? input.patch);
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (facet === "sql") {
|
|
665
|
+
const sql = handle as SqlStoreHandle;
|
|
666
|
+
const table = input.child;
|
|
667
|
+
const id = input.id;
|
|
668
|
+
if (!table || !id) throw new Error("sql edit requires child + id");
|
|
669
|
+
const sets = Object.keys(input.patch);
|
|
670
|
+
if (sets.length === 0) return;
|
|
671
|
+
const assignments = sets.map((c) => `"${c}" = ?`).join(", ");
|
|
672
|
+
const params = [...sets.map((c) => input.patch[c]), id];
|
|
673
|
+
await sql.raw(
|
|
674
|
+
`UPDATE "${table}" SET ${assignments} WHERE "id" = ?`,
|
|
675
|
+
params,
|
|
676
|
+
);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
throw new Error(`direct edit not supported for facet ${facet}`);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** Delete rows/keys. */
|
|
684
|
+
export interface StoreDeleteInput {
|
|
685
|
+
readonly ref: ResourceRef;
|
|
686
|
+
readonly child?: string;
|
|
687
|
+
readonly tenant?: string;
|
|
688
|
+
readonly ids?: readonly string[];
|
|
689
|
+
readonly keys?: readonly string[];
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Delete rows or keys through the runtime.
|
|
694
|
+
*
|
|
695
|
+
* @param runtime - Store runtime
|
|
696
|
+
* @param input - Delete input
|
|
697
|
+
*/
|
|
698
|
+
export async function deleteStore(
|
|
699
|
+
runtime: StoreRuntime,
|
|
700
|
+
input: StoreDeleteInput,
|
|
701
|
+
): Promise<{ readonly deleted: number }> {
|
|
702
|
+
const [facet] = input.ref.split(":") as [StoreFacet, string];
|
|
703
|
+
const handle = await runtime.openRef(input.ref, {
|
|
704
|
+
effects: { writes: [input.ref] },
|
|
705
|
+
});
|
|
706
|
+
let deleted = 0;
|
|
707
|
+
if (facet === "kv") {
|
|
708
|
+
const kv = handle as KvStoreFxHandle;
|
|
709
|
+
for (const key of input.keys ?? []) {
|
|
710
|
+
if (await kv.delete(key)) deleted++;
|
|
711
|
+
}
|
|
712
|
+
} else if (facet === "files") {
|
|
713
|
+
const filesHandle = handle as FilesStoreFxHandle;
|
|
714
|
+
for (const key of input.keys ?? []) {
|
|
715
|
+
if (await filesHandle.delete(key)) deleted++;
|
|
716
|
+
}
|
|
717
|
+
} else if (facet === "sql") {
|
|
718
|
+
const sql = handle as SqlStoreHandle;
|
|
719
|
+
const table = input.child;
|
|
720
|
+
if (!table) return { deleted: 0 };
|
|
721
|
+
for (const id of input.ids ?? []) {
|
|
722
|
+
await sql.raw(`DELETE FROM "${table}" WHERE "id" = ?`, [id]);
|
|
723
|
+
deleted++;
|
|
724
|
+
}
|
|
725
|
+
} else if (facet === "index") {
|
|
726
|
+
const idx = handle as IndexStoreFxHandle;
|
|
727
|
+
for (const id of input.ids ?? []) {
|
|
728
|
+
if (await idx.delete(id)) deleted++;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
const effectRef = (
|
|
732
|
+
input.child && facet === "sql" ? `sql:${input.child}` : input.ref
|
|
733
|
+
) as ResourceRef;
|
|
734
|
+
runtime.onWriteEffects({ writes: [effectRef] });
|
|
735
|
+
return { deleted };
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Purge cache keys for a resource namespace (effects-derived invalidation).
|
|
740
|
+
*
|
|
741
|
+
* @param runtime - Store runtime
|
|
742
|
+
* @param resource - Resource ref
|
|
743
|
+
*/
|
|
744
|
+
export function purgeStoreCache(
|
|
745
|
+
runtime: StoreRuntime,
|
|
746
|
+
resource: ResourceRef,
|
|
747
|
+
): { readonly keys: readonly string[] } {
|
|
748
|
+
const event = runtime.cache.invalidate([resource]);
|
|
749
|
+
// Also drop any live keys matching the computed prefix.
|
|
750
|
+
const prefix = computedCacheKey(resource);
|
|
751
|
+
const extra: string[] = [];
|
|
752
|
+
for (const key of runtime.cache.keys()) {
|
|
753
|
+
if (key === prefix || key.startsWith(`${prefix}/`)) {
|
|
754
|
+
extra.push(key);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
if (extra.length > 0) {
|
|
758
|
+
runtime.cache.invalidate([resource]);
|
|
759
|
+
}
|
|
760
|
+
return { keys: [...new Set([...event.keys, ...extra])] };
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Raw SQL console — read-only by default.
|
|
765
|
+
*
|
|
766
|
+
* @param runtime - Store runtime
|
|
767
|
+
* @param ref - SQL store ref
|
|
768
|
+
* @param sqlText - SQL
|
|
769
|
+
* @param options - Write allow + reveal
|
|
770
|
+
*/
|
|
771
|
+
export async function runStoreSql(
|
|
772
|
+
runtime: StoreRuntime,
|
|
773
|
+
ref: ResourceRef,
|
|
774
|
+
sqlText: string,
|
|
775
|
+
options: {
|
|
776
|
+
readonly allowWrite: boolean;
|
|
777
|
+
readonly revealPii?: boolean;
|
|
778
|
+
readonly tenant?: string;
|
|
779
|
+
},
|
|
780
|
+
): Promise<{
|
|
781
|
+
readonly rows: readonly Record<string, unknown>[];
|
|
782
|
+
readonly masked: boolean;
|
|
783
|
+
readonly routedRole: "primary" | "replica";
|
|
784
|
+
}> {
|
|
785
|
+
const trimmed = sqlText.trim();
|
|
786
|
+
const isWrite =
|
|
787
|
+
/^(INSERT|UPDATE|DELETE|DROP|ALTER|CREATE|TRUNCATE|REPLACE)\b/i.test(
|
|
788
|
+
trimmed,
|
|
789
|
+
);
|
|
790
|
+
if (isWrite && !options.allowWrite) {
|
|
791
|
+
throw new Error(
|
|
792
|
+
"SQL console is read-only by default — write requires console:store.sql:write",
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
const handle = (await runtime.openRef(ref, {
|
|
796
|
+
effects: isWrite ? { writes: [ref] } : { reads: [ref] },
|
|
797
|
+
revealPii: options.revealPii === true,
|
|
798
|
+
})) as SqlStoreHandle;
|
|
799
|
+
const rows = await handle.raw(trimmed);
|
|
800
|
+
return {
|
|
801
|
+
rows,
|
|
802
|
+
masked: !options.revealPii,
|
|
803
|
+
routedRole: handle.routedRole,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* Whether a cache key would be invalidated by writers of a resource — pure
|
|
809
|
+
* projection over {@link isInvalidatedByWrite}.
|
|
810
|
+
*
|
|
811
|
+
* @param key - Cache key
|
|
812
|
+
* @param writeRef - Written resource
|
|
813
|
+
*/
|
|
814
|
+
export function cacheKeyInvalidatedBy(
|
|
815
|
+
key: string,
|
|
816
|
+
writeRef: ResourceRef,
|
|
817
|
+
): boolean {
|
|
818
|
+
return isInvalidatedByWrite(key, { writes: [writeRef] });
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Open a memory StoreRuntime seeded from Manifest stores (Console default).
|
|
823
|
+
*
|
|
824
|
+
* @param manifest - Manifest snapshot
|
|
825
|
+
* @param now - Clock
|
|
826
|
+
*/
|
|
827
|
+
export async function createManifestStoreRuntime(
|
|
828
|
+
manifest: Manifest | null,
|
|
829
|
+
now: () => number = () => Date.now(),
|
|
830
|
+
): Promise<StoreRuntime> {
|
|
831
|
+
const runtime = createStoreRuntime({
|
|
832
|
+
drivers: {
|
|
833
|
+
sql: memorySqlDriver,
|
|
834
|
+
kv: memoryKvDriver,
|
|
835
|
+
files: memoryFilesDriver,
|
|
836
|
+
index: memoryIndexDriver,
|
|
837
|
+
},
|
|
838
|
+
now,
|
|
839
|
+
});
|
|
840
|
+
for (const [name, store] of Object.entries(manifest?.stores ?? {})) {
|
|
841
|
+
switch (store.facet) {
|
|
842
|
+
case "sql":
|
|
843
|
+
runtime.register(
|
|
844
|
+
declareSql(name, {
|
|
845
|
+
classify: store.classifications as never,
|
|
846
|
+
}),
|
|
847
|
+
);
|
|
848
|
+
break;
|
|
849
|
+
case "kv":
|
|
850
|
+
runtime.register(declareKv(name));
|
|
851
|
+
break;
|
|
852
|
+
case "files":
|
|
853
|
+
runtime.register(declareFiles(name));
|
|
854
|
+
break;
|
|
855
|
+
case "index":
|
|
856
|
+
runtime.register(declareIndex(name, { dims: 3 }));
|
|
857
|
+
break;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return runtime;
|
|
861
|
+
}
|