modelrig 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/LICENSE +201 -0
- package/README.md +145 -8
- package/data/PROVENANCE.md +19 -0
- package/data/pricing-manual.json +22 -0
- package/data/pricing-map.json +46362 -0
- package/data/pricing-overlay.json +419 -0
- package/dist/adapters/anthropic.d.ts +90 -0
- package/dist/adapters/anthropic.d.ts.map +1 -0
- package/dist/adapters/anthropic.js +296 -0
- package/dist/adapters/build-adapters.d.ts +11 -0
- package/dist/adapters/build-adapters.d.ts.map +1 -0
- package/dist/adapters/build-adapters.js +45 -0
- package/dist/adapters/deepseek.d.ts +47 -0
- package/dist/adapters/deepseek.d.ts.map +1 -0
- package/dist/adapters/deepseek.js +216 -0
- package/dist/adapters/gemini.d.ts +44 -0
- package/dist/adapters/gemini.d.ts.map +1 -0
- package/dist/adapters/gemini.js +262 -0
- package/dist/adapters/grok.d.ts +53 -0
- package/dist/adapters/grok.d.ts.map +1 -0
- package/dist/adapters/grok.js +232 -0
- package/dist/adapters/openai-compat.d.ts +80 -0
- package/dist/adapters/openai-compat.d.ts.map +1 -0
- package/dist/adapters/openai-compat.js +244 -0
- package/dist/adapters/openai-stream.d.ts +58 -0
- package/dist/adapters/openai-stream.d.ts.map +1 -0
- package/dist/adapters/openai-stream.js +120 -0
- package/dist/adapters/openai.d.ts +55 -0
- package/dist/adapters/openai.d.ts.map +1 -0
- package/dist/adapters/openai.js +281 -0
- package/dist/adapters/sampling.d.ts +28 -0
- package/dist/adapters/sampling.d.ts.map +1 -0
- package/dist/adapters/sampling.js +34 -0
- package/dist/adapters/tool-mapping.d.ts +92 -0
- package/dist/adapters/tool-mapping.d.ts.map +1 -0
- package/dist/adapters/tool-mapping.js +143 -0
- package/dist/artifacts/artifact.d.ts +137 -0
- package/dist/artifacts/artifact.d.ts.map +1 -0
- package/dist/artifacts/artifact.js +377 -0
- package/dist/artifacts/content-uploader.d.ts +87 -0
- package/dist/artifacts/content-uploader.d.ts.map +1 -0
- package/dist/artifacts/content-uploader.js +124 -0
- package/dist/artifacts/index.d.ts +63 -0
- package/dist/artifacts/index.d.ts.map +1 -0
- package/dist/artifacts/index.js +80 -0
- package/dist/artifacts/run-context.d.ts +129 -0
- package/dist/artifacts/run-context.d.ts.map +1 -0
- package/dist/artifacts/run-context.js +208 -0
- package/dist/artifacts/scrub.d.ts +60 -0
- package/dist/artifacts/scrub.d.ts.map +1 -0
- package/dist/artifacts/scrub.js +139 -0
- package/dist/artifacts/step.d.ts +26 -0
- package/dist/artifacts/step.d.ts.map +1 -0
- package/dist/artifacts/step.js +37 -0
- package/dist/artifacts/taint.d.ts +63 -0
- package/dist/artifacts/taint.d.ts.map +1 -0
- package/dist/artifacts/taint.js +76 -0
- package/dist/bakeoff/cache-sim.d.ts +99 -0
- package/dist/bakeoff/cache-sim.d.ts.map +1 -0
- package/dist/bakeoff/cache-sim.js +126 -0
- package/dist/bakeoff/engine.d.ts +52 -0
- package/dist/bakeoff/engine.d.ts.map +1 -0
- package/dist/bakeoff/engine.js +163 -0
- package/dist/bakeoff/eval-cases.d.ts +96 -0
- package/dist/bakeoff/eval-cases.d.ts.map +1 -0
- package/dist/bakeoff/eval-cases.js +105 -0
- package/dist/bakeoff/replay.d.ts +52 -0
- package/dist/bakeoff/replay.d.ts.map +1 -0
- package/dist/bakeoff/replay.js +94 -0
- package/dist/bakeoff/report.d.ts +41 -0
- package/dist/bakeoff/report.d.ts.map +1 -0
- package/dist/bakeoff/report.js +142 -0
- package/dist/bakeoff/samples.d.ts +77 -0
- package/dist/bakeoff/samples.d.ts.map +1 -0
- package/dist/bakeoff/samples.js +148 -0
- package/dist/bakeoff/stats.d.ts +13 -0
- package/dist/bakeoff/stats.d.ts.map +1 -0
- package/dist/bakeoff/stats.js +48 -0
- package/dist/bakeoff/types.d.ts +37 -0
- package/dist/bakeoff/types.d.ts.map +1 -0
- package/dist/bakeoff/types.js +10 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +590 -0
- package/dist/config.d.ts +204 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +252 -0
- package/dist/deprecations.d.ts +32 -0
- package/dist/deprecations.d.ts.map +1 -0
- package/dist/deprecations.js +59 -0
- package/dist/feedback/coverage.d.ts +32 -0
- package/dist/feedback/coverage.d.ts.map +1 -0
- package/dist/feedback/coverage.js +95 -0
- package/dist/feedback/feedback.d.ts +35 -0
- package/dist/feedback/feedback.d.ts.map +1 -0
- package/dist/feedback/feedback.js +39 -0
- package/dist/gateway/http-protocol.d.ts +175 -0
- package/dist/gateway/http-protocol.d.ts.map +1 -0
- package/dist/gateway/http-protocol.js +105 -0
- package/dist/index.d.ts +242 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +576 -0
- package/dist/init/finder.d.ts +94 -0
- package/dist/init/finder.d.ts.map +1 -0
- package/dist/init/finder.js +343 -0
- package/dist/init/init-cli.d.ts +10 -0
- package/dist/init/init-cli.d.ts.map +1 -0
- package/dist/init/init-cli.js +86 -0
- package/dist/init/observe-cli.d.ts +19 -0
- package/dist/init/observe-cli.d.ts.map +1 -0
- package/dist/init/observe-cli.js +73 -0
- package/dist/init/observe.d.ts +81 -0
- package/dist/init/observe.d.ts.map +1 -0
- package/dist/init/observe.js +245 -0
- package/dist/init/run.d.ts +99 -0
- package/dist/init/run.d.ts.map +1 -0
- package/dist/init/run.js +281 -0
- package/dist/init/signatures.d.ts +39 -0
- package/dist/init/signatures.d.ts.map +1 -0
- package/dist/init/signatures.js +116 -0
- package/dist/matrix/aggregate.d.ts +89 -0
- package/dist/matrix/aggregate.d.ts.map +1 -0
- package/dist/matrix/aggregate.js +186 -0
- package/dist/query/grammar.d.ts +35 -0
- package/dist/query/grammar.d.ts.map +1 -0
- package/dist/query/grammar.js +279 -0
- package/dist/query/run.d.ts +8 -0
- package/dist/query/run.d.ts.map +1 -0
- package/dist/query/run.js +21 -0
- package/dist/registry/build.d.ts +219 -0
- package/dist/registry/build.d.ts.map +1 -0
- package/dist/registry/build.js +313 -0
- package/dist/registry/cache-policy.d.ts +94 -0
- package/dist/registry/cache-policy.d.ts.map +1 -0
- package/dist/registry/cache-policy.js +238 -0
- package/dist/registry/call-notes.d.ts +11 -0
- package/dist/registry/call-notes.d.ts.map +1 -0
- package/dist/registry/call-notes.js +110 -0
- package/dist/registry/claims.d.ts +63 -0
- package/dist/registry/claims.d.ts.map +1 -0
- package/dist/registry/claims.js +107 -0
- package/dist/registry/effective-rate.d.ts +151 -0
- package/dist/registry/effective-rate.d.ts.map +1 -0
- package/dist/registry/effective-rate.js +156 -0
- package/dist/registry/facts.d.ts +32 -0
- package/dist/registry/facts.d.ts.map +1 -0
- package/dist/registry/facts.js +136 -0
- package/dist/registry/fingerprint.d.ts +20 -0
- package/dist/registry/fingerprint.d.ts.map +1 -0
- package/dist/registry/fingerprint.js +150 -0
- package/dist/registry/parity.d.ts +52 -0
- package/dist/registry/parity.d.ts.map +1 -0
- package/dist/registry/parity.js +82 -0
- package/dist/registry/pricing-shape.d.ts +113 -0
- package/dist/registry/pricing-shape.d.ts.map +1 -0
- package/dist/registry/pricing-shape.js +123 -0
- package/dist/registry/pricing.d.ts +43 -0
- package/dist/registry/pricing.d.ts.map +1 -0
- package/dist/registry/pricing.js +127 -0
- package/dist/registry/rows.d.ts +16 -0
- package/dist/registry/rows.d.ts.map +1 -0
- package/dist/registry/rows.js +57 -0
- package/dist/registry/seed.d.ts +43 -0
- package/dist/registry/seed.d.ts.map +1 -0
- package/dist/registry/seed.js +97 -0
- package/dist/registry/select.d.ts +68 -0
- package/dist/registry/select.d.ts.map +1 -0
- package/dist/registry/select.js +141 -0
- package/dist/registry/staleness.d.ts +18 -0
- package/dist/registry/staleness.d.ts.map +1 -0
- package/dist/registry/staleness.js +39 -0
- package/dist/rigindex/bootstrap.d.ts +36 -0
- package/dist/rigindex/bootstrap.d.ts.map +1 -0
- package/dist/rigindex/bootstrap.js +97 -0
- package/dist/rigindex/fit.d.ts +24 -0
- package/dist/rigindex/fit.d.ts.map +1 -0
- package/dist/rigindex/fit.js +76 -0
- package/dist/rigindex/gates.d.ts +52 -0
- package/dist/rigindex/gates.d.ts.map +1 -0
- package/dist/rigindex/gates.js +109 -0
- package/dist/rigindex/graph.d.ts +31 -0
- package/dist/rigindex/graph.d.ts.map +1 -0
- package/dist/rigindex/graph.js +120 -0
- package/dist/rigindex/index.d.ts +23 -0
- package/dist/rigindex/index.d.ts.map +1 -0
- package/dist/rigindex/index.js +112 -0
- package/dist/rigindex/types.d.ts +76 -0
- package/dist/rigindex/types.d.ts.map +1 -0
- package/dist/rigindex/types.js +8 -0
- package/dist/routes/cache-lint.d.ts +41 -0
- package/dist/routes/cache-lint.d.ts.map +1 -0
- package/dist/routes/cache-lint.js +81 -0
- package/dist/routes/draft.d.ts +83 -0
- package/dist/routes/draft.d.ts.map +1 -0
- package/dist/routes/draft.js +135 -0
- package/dist/routes/loader.d.ts +63 -0
- package/dist/routes/loader.d.ts.map +1 -0
- package/dist/routes/loader.js +574 -0
- package/dist/routes/manifest.d.ts +24 -0
- package/dist/routes/manifest.d.ts.map +1 -0
- package/dist/routes/manifest.js +118 -0
- package/dist/routes/resolved-bundle.d.ts +79 -0
- package/dist/routes/resolved-bundle.d.ts.map +1 -0
- package/dist/routes/resolved-bundle.js +148 -0
- package/dist/routes/sampling-config.d.ts +32 -0
- package/dist/routes/sampling-config.d.ts.map +1 -0
- package/dist/routes/sampling-config.js +91 -0
- package/dist/routes/serveability.d.ts +52 -0
- package/dist/routes/serveability.d.ts.map +1 -0
- package/dist/routes/serveability.js +73 -0
- package/dist/routes/template.d.ts +18 -0
- package/dist/routes/template.d.ts.map +1 -0
- package/dist/routes/template.js +94 -0
- package/dist/run/ajv.d.ts +22 -0
- package/dist/run/ajv.d.ts.map +1 -0
- package/dist/run/ajv.js +32 -0
- package/dist/run/cache-handle.d.ts +58 -0
- package/dist/run/cache-handle.d.ts.map +1 -0
- package/dist/run/cache-handle.js +54 -0
- package/dist/run/cache-stamp.d.ts +65 -0
- package/dist/run/cache-stamp.d.ts.map +1 -0
- package/dist/run/cache-stamp.js +0 -0
- package/dist/run/capture-write.d.ts +33 -0
- package/dist/run/capture-write.d.ts.map +1 -0
- package/dist/run/capture-write.js +55 -0
- package/dist/run/cost.d.ts +26 -0
- package/dist/run/cost.d.ts.map +1 -0
- package/dist/run/cost.js +41 -0
- package/dist/run/dispatch.d.ts +99 -0
- package/dist/run/dispatch.d.ts.map +1 -0
- package/dist/run/dispatch.js +191 -0
- package/dist/run/envelope.d.ts +21 -0
- package/dist/run/envelope.d.ts.map +1 -0
- package/dist/run/envelope.js +45 -0
- package/dist/run/grounding.d.ts +56 -0
- package/dist/run/grounding.d.ts.map +1 -0
- package/dist/run/grounding.js +89 -0
- package/dist/run/raw.d.ts +88 -0
- package/dist/run/raw.d.ts.map +1 -0
- package/dist/run/raw.js +286 -0
- package/dist/run/record-attempt.d.ts +40 -0
- package/dist/run/record-attempt.d.ts.map +1 -0
- package/dist/run/record-attempt.js +99 -0
- package/dist/run/repair.d.ts +36 -0
- package/dist/run/repair.d.ts.map +1 -0
- package/dist/run/repair.js +61 -0
- package/dist/run/resolve.d.ts +99 -0
- package/dist/run/resolve.d.ts.map +1 -0
- package/dist/run/resolve.js +203 -0
- package/dist/run/retry.d.ts +19 -0
- package/dist/run/retry.d.ts.map +1 -0
- package/dist/run/retry.js +50 -0
- package/dist/run/run.d.ts +55 -0
- package/dist/run/run.d.ts.map +1 -0
- package/dist/run/run.js +540 -0
- package/dist/run/stream.d.ts +49 -0
- package/dist/run/stream.d.ts.map +1 -0
- package/dist/run/stream.js +87 -0
- package/dist/run/validate.d.ts +15 -0
- package/dist/run/validate.d.ts.map +1 -0
- package/dist/run/validate.js +55 -0
- package/dist/savings/cache-ledger.d.ts +108 -0
- package/dist/savings/cache-ledger.d.ts.map +1 -0
- package/dist/savings/cache-ledger.js +174 -0
- package/dist/savings/cache-reconcile.d.ts +64 -0
- package/dist/savings/cache-reconcile.d.ts.map +1 -0
- package/dist/savings/cache-reconcile.js +59 -0
- package/dist/savings/cache-stats.d.ts +63 -0
- package/dist/savings/cache-stats.d.ts.map +1 -0
- package/dist/savings/cache-stats.js +108 -0
- package/dist/savings/emit.d.ts +38 -0
- package/dist/savings/emit.d.ts.map +1 -0
- package/dist/savings/emit.js +100 -0
- package/dist/savings/odometer.d.ts +32 -0
- package/dist/savings/odometer.d.ts.map +1 -0
- package/dist/savings/odometer.js +70 -0
- package/dist/savings/swap-execute.d.ts +58 -0
- package/dist/savings/swap-execute.d.ts.map +1 -0
- package/dist/savings/swap-execute.js +130 -0
- package/dist/savings/swaps-client.d.ts +55 -0
- package/dist/savings/swaps-client.d.ts.map +1 -0
- package/dist/savings/swaps-client.js +95 -0
- package/dist/settings/api-fetcher.d.ts +39 -0
- package/dist/settings/api-fetcher.d.ts.map +1 -0
- package/dist/settings/api-fetcher.js +92 -0
- package/dist/settings/index.d.ts +10 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/task-settings.d.ts +91 -0
- package/dist/settings/task-settings.d.ts.map +1 -0
- package/dist/settings/task-settings.js +116 -0
- package/dist/status.d.ts +77 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +113 -0
- package/dist/telemetry/artifact-store.d.ts +48 -0
- package/dist/telemetry/artifact-store.d.ts.map +1 -0
- package/dist/telemetry/artifact-store.js +189 -0
- package/dist/telemetry/cache-miss-streak.d.ts +79 -0
- package/dist/telemetry/cache-miss-streak.d.ts.map +1 -0
- package/dist/telemetry/cache-miss-streak.js +99 -0
- package/dist/telemetry/capture.d.ts +31 -0
- package/dist/telemetry/capture.d.ts.map +1 -0
- package/dist/telemetry/capture.js +81 -0
- package/dist/telemetry/exporter-setup.d.ts +28 -0
- package/dist/telemetry/exporter-setup.d.ts.map +1 -0
- package/dist/telemetry/exporter-setup.js +34 -0
- package/dist/telemetry/exporter.d.ts +137 -0
- package/dist/telemetry/exporter.d.ts.map +1 -0
- package/dist/telemetry/exporter.js +510 -0
- package/dist/telemetry/health.d.ts +76 -0
- package/dist/telemetry/health.d.ts.map +1 -0
- package/dist/telemetry/health.js +92 -0
- package/dist/telemetry/ingest-protocol.d.ts +310 -0
- package/dist/telemetry/ingest-protocol.d.ts.map +1 -0
- package/dist/telemetry/ingest-protocol.js +363 -0
- package/dist/telemetry/load-error-report.d.ts +24 -0
- package/dist/telemetry/load-error-report.d.ts.map +1 -0
- package/dist/telemetry/load-error-report.js +50 -0
- package/dist/telemetry/otel-map.d.ts +121 -0
- package/dist/telemetry/otel-map.d.ts.map +1 -0
- package/dist/telemetry/otel-map.js +224 -0
- package/dist/telemetry/otel-normalize.d.ts +104 -0
- package/dist/telemetry/otel-normalize.d.ts.map +1 -0
- package/dist/telemetry/otel-normalize.js +530 -0
- package/dist/telemetry/otlp-emitter.d.ts +52 -0
- package/dist/telemetry/otlp-emitter.d.ts.map +1 -0
- package/dist/telemetry/otlp-emitter.js +144 -0
- package/dist/telemetry/otlp-proto.d.ts +29 -0
- package/dist/telemetry/otlp-proto.d.ts.map +1 -0
- package/dist/telemetry/otlp-proto.js +266 -0
- package/dist/telemetry/sinks.d.ts +90 -0
- package/dist/telemetry/sinks.d.ts.map +1 -0
- package/dist/telemetry/sinks.js +180 -0
- package/dist/telemetry/store.d.ts +65 -0
- package/dist/telemetry/store.d.ts.map +1 -0
- package/dist/telemetry/store.js +547 -0
- package/dist/tenancy/api-key-format.d.ts +77 -0
- package/dist/tenancy/api-key-format.d.ts.map +1 -0
- package/dist/tenancy/api-key-format.js +110 -0
- package/dist/tenancy/constants.d.ts +23 -0
- package/dist/tenancy/constants.d.ts.map +1 -0
- package/dist/tenancy/constants.js +25 -0
- package/dist/tenancy/scrub.d.ts +47 -0
- package/dist/tenancy/scrub.d.ts.map +1 -0
- package/dist/tenancy/scrub.js +85 -0
- package/dist/types/config-teaching.d.ts +62 -0
- package/dist/types/config-teaching.d.ts.map +1 -0
- package/dist/types/config-teaching.js +183 -0
- package/dist/types/failures-teaching.d.ts +56 -0
- package/dist/types/failures-teaching.d.ts.map +1 -0
- package/dist/types/failures-teaching.js +177 -0
- package/dist/types/failures.d.ts +27 -0
- package/dist/types/failures.d.ts.map +1 -0
- package/dist/types/failures.js +41 -0
- package/dist/types/provider.d.ts +200 -0
- package/dist/types/provider.d.ts.map +1 -0
- package/dist/types/provider.js +40 -0
- package/dist/types/route.d.ts +205 -0
- package/dist/types/route.d.ts.map +1 -0
- package/dist/types/route.js +6 -0
- package/dist/types/telemetry.d.ts +206 -0
- package/dist/types/telemetry.d.ts.map +1 -0
- package/dist/types/telemetry.js +6 -0
- package/dist/validate-cli.d.ts +12 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate-cli.js +57 -0
- package/dist/verify/capture.d.ts +66 -0
- package/dist/verify/capture.d.ts.map +1 -0
- package/dist/verify/capture.js +180 -0
- package/dist/verify/cli-run.d.ts +23 -0
- package/dist/verify/cli-run.d.ts.map +1 -0
- package/dist/verify/cli-run.js +57 -0
- package/dist/verify/diff.d.ts +48 -0
- package/dist/verify/diff.d.ts.map +1 -0
- package/dist/verify/diff.js +157 -0
- package/dist/verify/index.d.ts +17 -0
- package/dist/verify/index.d.ts.map +1 -0
- package/dist/verify/index.js +24 -0
- package/dist/verify/report.d.ts +14 -0
- package/dist/verify/report.d.ts.map +1 -0
- package/dist/verify/report.js +88 -0
- package/dist/verify/types.d.ts +101 -0
- package/dist/verify/types.d.ts.map +1 -0
- package/dist/verify/types.js +17 -0
- package/dist/verify/verify.d.ts +29 -0
- package/dist/verify/verify.d.ts.map +1 -0
- package/dist/verify/verify.js +148 -0
- package/dist/watch/budget.d.ts +19 -0
- package/dist/watch/budget.d.ts.map +1 -0
- package/dist/watch/budget.js +35 -0
- package/dist/watch/evaluate.d.ts +225 -0
- package/dist/watch/evaluate.d.ts.map +1 -0
- package/dist/watch/evaluate.js +155 -0
- package/dist/watch/monitor.d.ts +116 -0
- package/dist/watch/monitor.d.ts.map +1 -0
- package/dist/watch/monitor.js +199 -0
- package/dist/watch/proposals.d.ts +89 -0
- package/dist/watch/proposals.d.ts.map +1 -0
- package/dist/watch/proposals.js +197 -0
- package/dist/watch/run-record.d.ts +64 -0
- package/dist/watch/run-record.d.ts.map +1 -0
- package/dist/watch/run-record.js +86 -0
- package/dist/watch/run-reported.d.ts +20 -0
- package/dist/watch/run-reported.d.ts.map +1 -0
- package/dist/watch/run-reported.js +53 -0
- package/dist/watch/run.d.ts +73 -0
- package/dist/watch/run.d.ts.map +1 -0
- package/dist/watch/run.js +83 -0
- package/dist/watch/state-store.d.ts +48 -0
- package/dist/watch/state-store.d.ts.map +1 -0
- package/dist/watch/state-store.js +86 -0
- package/dist/watch/state.d.ts +21 -0
- package/dist/watch/state.d.ts.map +1 -0
- package/dist/watch/state.js +93 -0
- package/package.json +56 -14
- package/registry/LICENSE +20 -0
- package/registry/leaderboard.json +3639 -0
- package/registry/registry.json +7263 -0
- package/index.js +0 -3
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Proposal lifecycle + control-plane client — Phase 4 WS1 (plan §4).
|
|
4
|
+
*
|
|
5
|
+
* suggested → evidenced (bake-off attached)
|
|
6
|
+
* → approved (human, recorded) → executed (swap machinery)
|
|
7
|
+
* suggested|evidenced → rejected | expired
|
|
8
|
+
*
|
|
9
|
+
* Proposals NEVER auto-execute: "executed" is only reachable through
|
|
10
|
+
* "approved", and approval is a human CLI action (`swap execute --approve`).
|
|
11
|
+
* Transport is PostgREST with the service-role key — proposals are
|
|
12
|
+
* control-plane-only state (cross-run truth, not per-inference telemetry).
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.assertTransition = assertTransition;
|
|
16
|
+
exports.createProposalsClient = createProposalsClient;
|
|
17
|
+
const ALLOWED_TRANSITIONS = {
|
|
18
|
+
suggested: ["evidenced", "rejected", "expired"],
|
|
19
|
+
evidenced: ["approved", "rejected", "expired"],
|
|
20
|
+
approved: ["executed"],
|
|
21
|
+
rejected: [],
|
|
22
|
+
executed: [],
|
|
23
|
+
expired: [],
|
|
24
|
+
};
|
|
25
|
+
function assertTransition(from, to) {
|
|
26
|
+
if (!ALLOWED_TRANSITIONS[from].includes(to)) {
|
|
27
|
+
throw new Error(`illegal proposal transition ${from} → ${to} (allowed from ${from}: ` +
|
|
28
|
+
`[${ALLOWED_TRANSITIONS[from].join(", ") || "none — terminal"}])`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function toRecord(r) {
|
|
32
|
+
return {
|
|
33
|
+
id: r.id,
|
|
34
|
+
route: r.route,
|
|
35
|
+
kind: r.kind,
|
|
36
|
+
trigger: r.trigger,
|
|
37
|
+
triggerHash: r.trigger_hash,
|
|
38
|
+
evidence: r.evidence,
|
|
39
|
+
status: r.status,
|
|
40
|
+
createdAt: r.created_at,
|
|
41
|
+
updatedAt: r.updated_at,
|
|
42
|
+
expiresAt: r.expires_at,
|
|
43
|
+
approvedBy: r.approved_by ?? null,
|
|
44
|
+
decidedAt: r.decided_at ?? null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createProposalsClient(config) {
|
|
48
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
49
|
+
const base = config.supabaseUrl.replace(/\/$/, "");
|
|
50
|
+
async function request(path, init) {
|
|
51
|
+
const response = await fetchFn(`${base}/rest/v1/${path}`, {
|
|
52
|
+
...init,
|
|
53
|
+
headers: {
|
|
54
|
+
"content-type": "application/json",
|
|
55
|
+
apikey: config.serviceRoleKey,
|
|
56
|
+
authorization: `Bearer ${config.serviceRoleKey}`,
|
|
57
|
+
...(init.headers ?? {}),
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`proposals ${init.method ?? "GET"} ${path} → ${response.status} ` +
|
|
62
|
+
`${await response.text().catch(() => "")}`);
|
|
63
|
+
}
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
async submitDrafts(drafts) {
|
|
68
|
+
if (drafts.length === 0)
|
|
69
|
+
return 0;
|
|
70
|
+
// Dedupe on the REAL unique index. 00013 made it tenant-first
|
|
71
|
+
// `(org_id, route, trigger_hash)` and DROPPED the old `(route, trigger_hash)`
|
|
72
|
+
// index — so the on_conflict target is ALWAYS the composite, in both lanes:
|
|
73
|
+
// - hosted cron: org_id is stamped below;
|
|
74
|
+
// - CLI/self-host: org_id is omitted and the row's house-org column DEFAULT
|
|
75
|
+
// applies, and Postgres evaluates ON CONFLICT against the final row, so the
|
|
76
|
+
// composite index still matches. Naming `(route, trigger_hash)` here — a
|
|
77
|
+
// unique index that no longer exists — would 42P10 the CLI's own watch run.
|
|
78
|
+
const onConflict = "org_id,route,trigger_hash";
|
|
79
|
+
const response = await request(`proposals?on_conflict=${encodeURIComponent(onConflict)}`, {
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers: { prefer: "resolution=ignore-duplicates,return=representation" },
|
|
82
|
+
body: JSON.stringify(drafts.map((d) => ({
|
|
83
|
+
...(config.orgId !== undefined ? { org_id: config.orgId } : {}),
|
|
84
|
+
route: d.route,
|
|
85
|
+
kind: d.kind,
|
|
86
|
+
trigger: d.trigger,
|
|
87
|
+
trigger_hash: d.triggerHash,
|
|
88
|
+
status: "suggested",
|
|
89
|
+
expires_at: d.expiresAt,
|
|
90
|
+
// Analyst insights carry their summary as evidence at insert (E6
|
|
91
|
+
// Chunk B). ignore-duplicates means a re-seen trigger_hash is a
|
|
92
|
+
// no-op — an existing row's evidence is never clobbered.
|
|
93
|
+
...(d.evidence !== undefined ? { evidence: d.evidence } : {}),
|
|
94
|
+
}))),
|
|
95
|
+
});
|
|
96
|
+
const inserted = (await response.json().catch(() => []));
|
|
97
|
+
return Array.isArray(inserted) ? inserted.length : 0;
|
|
98
|
+
},
|
|
99
|
+
async list(orgId, status) {
|
|
100
|
+
const filter = status !== undefined ? `&status=eq.${status}` : "";
|
|
101
|
+
const scope = orgId !== null ? `&org_id=eq.${encodeURIComponent(orgId)}` : "";
|
|
102
|
+
const response = await request(`proposals?select=*${filter}${scope}&order=created_at.desc`, {
|
|
103
|
+
method: "GET",
|
|
104
|
+
});
|
|
105
|
+
return (await response.json()).map(toRecord);
|
|
106
|
+
},
|
|
107
|
+
async get(id) {
|
|
108
|
+
const response = await request(`proposals?select=*&id=eq.${encodeURIComponent(id)}`, {
|
|
109
|
+
method: "GET",
|
|
110
|
+
});
|
|
111
|
+
const rows = (await response.json());
|
|
112
|
+
return rows.length > 0 ? toRecord(rows[0]) : null;
|
|
113
|
+
},
|
|
114
|
+
async attachEvidence(id, evidence) {
|
|
115
|
+
// Status-scoped, like setStatus/decide: a terminal proposal matches zero
|
|
116
|
+
// rows, so attaching evidence can never flip an executed/rejected proposal
|
|
117
|
+
// back to `evidenced` (which would make it re-executable — a double-swap /
|
|
118
|
+
// audit-corruption path an oracle-act caller could otherwise reach on their
|
|
119
|
+
// own org's rows). org_id predicate is defense-in-depth on the scoped path.
|
|
120
|
+
const orgPredicate = config.orgId !== undefined ? `&org_id=eq.${encodeURIComponent(config.orgId)}` : "";
|
|
121
|
+
const response = await request(`proposals?id=eq.${encodeURIComponent(id)}` +
|
|
122
|
+
`&status=in.${encodeURIComponent("(suggested,evidenced)")}${orgPredicate}`, {
|
|
123
|
+
method: "PATCH",
|
|
124
|
+
headers: { prefer: "return=representation" },
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
status: "evidenced",
|
|
127
|
+
evidence,
|
|
128
|
+
updated_at: new Date().toISOString(),
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
const rows = (await response.json().catch(() => []));
|
|
132
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
133
|
+
},
|
|
134
|
+
async setStatus(id, from, to) {
|
|
135
|
+
assertTransition(from, to);
|
|
136
|
+
// Status-scoped PATCH: a concurrent transition means zero rows match —
|
|
137
|
+
// never a blind overwrite.
|
|
138
|
+
await request(`proposals?id=eq.${encodeURIComponent(id)}&status=eq.${from}`, {
|
|
139
|
+
method: "PATCH",
|
|
140
|
+
body: JSON.stringify({ status: to, updated_at: new Date().toISOString() }),
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
async orgOf(id) {
|
|
144
|
+
const response = await request(`proposals?select=org_id&id=eq.${encodeURIComponent(id)}`, {
|
|
145
|
+
method: "GET",
|
|
146
|
+
});
|
|
147
|
+
const rows = (await response.json());
|
|
148
|
+
return rows.length > 0 && typeof rows[0].org_id === "string" ? rows[0].org_id : null;
|
|
149
|
+
},
|
|
150
|
+
async decide(id, from, to, decidedBy, decidedByUserId, orgId) {
|
|
151
|
+
assertTransition(from, to);
|
|
152
|
+
const now = new Date().toISOString();
|
|
153
|
+
// Status-scoped, and asks for the row back so we can tell "decided" from
|
|
154
|
+
// "someone else already decided" rather than assuming success.
|
|
155
|
+
// org_id in the WHERE clause is defense in depth, not the primary check.
|
|
156
|
+
// The caller authorizes against this row's org before we get here; this
|
|
157
|
+
// makes a bypass of THAT check unable to cross a tenant boundary anyway,
|
|
158
|
+
// because the row simply will not match. A service-role write with no
|
|
159
|
+
// tenant predicate is how an authorization bug becomes a data breach.
|
|
160
|
+
const orgPredicate = orgId === undefined ? "" : `&org_id=eq.${encodeURIComponent(orgId)}`;
|
|
161
|
+
const response = await request(`proposals?id=eq.${encodeURIComponent(id)}&status=eq.${from}${orgPredicate}`, {
|
|
162
|
+
method: "PATCH",
|
|
163
|
+
headers: { prefer: "return=representation" },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
status: to,
|
|
166
|
+
approved_by: decidedBy,
|
|
167
|
+
// P6 WS1 identity. `approved_by` stays the operator-TYPED display
|
|
168
|
+
// name; `decided_by` is the verified identity. They are separate
|
|
169
|
+
// columns on purpose — merging them would make the pre-identity
|
|
170
|
+
// rows indistinguishable from the identity-bound ones, and half
|
|
171
|
+
// the corpus would silently look more trustworthy than it is.
|
|
172
|
+
// Undefined (self-host, no auth server) leaves the column null,
|
|
173
|
+
// which is the honest record rather than a guess.
|
|
174
|
+
...(decidedByUserId !== undefined && decidedByUserId !== null
|
|
175
|
+
? { decided_by: decidedByUserId }
|
|
176
|
+
: {}),
|
|
177
|
+
decided_at: now,
|
|
178
|
+
updated_at: now,
|
|
179
|
+
}),
|
|
180
|
+
});
|
|
181
|
+
const rows = (await response.json().catch(() => []));
|
|
182
|
+
return Array.isArray(rows) && rows.length > 0;
|
|
183
|
+
},
|
|
184
|
+
async expireOverdue(now) {
|
|
185
|
+
const cutoff = now.toISOString();
|
|
186
|
+
// Org-scoped when this client carries a tenant — one org's cron never
|
|
187
|
+
// sweeps another's rows (the sweep is idempotent, but touching a foreign
|
|
188
|
+
// tenant's proposals is the confused-deputy shape we refuse on principle).
|
|
189
|
+
const orgPredicate = config.orgId !== undefined ? `&org_id=eq.${encodeURIComponent(config.orgId)}` : "";
|
|
190
|
+
await request(`proposals?status=in.${encodeURIComponent("(suggested,evidenced)")}` +
|
|
191
|
+
`&expires_at=lt.${encodeURIComponent(cutoff)}${orgPredicate}`, {
|
|
192
|
+
method: "PATCH",
|
|
193
|
+
body: JSON.stringify({ status: "expired", updated_at: cutoff }),
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scheduled-job self-reporting — Phase 6.
|
|
3
|
+
*
|
|
4
|
+
* A4 gave the watch cron durable state. It did not give anyone a way to tell
|
|
5
|
+
* whether the cron RAN. `control_state` sat empty for a week and the three
|
|
6
|
+
* candidate explanations — never scheduled, running and failing, or running
|
|
7
|
+
* and finding nothing — were indistinguishable from outside. The only
|
|
8
|
+
* evidence lived in Render's log viewer, behind an API key that grants access
|
|
9
|
+
* to every workspace and every environment variable in the account.
|
|
10
|
+
*
|
|
11
|
+
* A job whose only proof of life is a vendor's log viewer is a job you cannot
|
|
12
|
+
* operate, and reaching for that key to read a log is a poor trade: it is the
|
|
13
|
+
* broadest possible credential borrowed for the narrowest possible question.
|
|
14
|
+
*
|
|
15
|
+
* So the job answers the question itself. One row, upserted each cycle, in the
|
|
16
|
+
* store we already own. "Did it run, when, and what did it do" becomes a
|
|
17
|
+
* SELECT — no vendor credential involved.
|
|
18
|
+
*
|
|
19
|
+
* Three properties matter more than the shape of the record:
|
|
20
|
+
*
|
|
21
|
+
* 1. **It records failures.** A report that only appears on success is
|
|
22
|
+
* indistinguishable from no report, which is precisely the ambiguity that
|
|
23
|
+
* made A4 unverifiable.
|
|
24
|
+
* 2. **It never throws.** Observability that can fail the thing it observes is
|
|
25
|
+
* worse than none.
|
|
26
|
+
* 3. **It is a separate key from the state.** A run report must never be able
|
|
27
|
+
* to corrupt the pricing snapshot it reports on.
|
|
28
|
+
*/
|
|
29
|
+
import { scrubSecrets } from "../tenancy/scrub";
|
|
30
|
+
/** Key under which the last run report lives in `control_state`. */
|
|
31
|
+
export declare const WATCH_RUN_KEY = "watch:last-run";
|
|
32
|
+
export interface WatchRunRecord {
|
|
33
|
+
readonly outcome: "ok" | "failed";
|
|
34
|
+
readonly startedAt: string;
|
|
35
|
+
readonly finishedAt: string;
|
|
36
|
+
/** True when this cycle had no previous state and only established a baseline. */
|
|
37
|
+
readonly seeded?: boolean;
|
|
38
|
+
readonly events?: number;
|
|
39
|
+
readonly drafts?: number;
|
|
40
|
+
readonly inserted?: number;
|
|
41
|
+
/** Which store backed this run — the A4 regression is a file-backed cron. */
|
|
42
|
+
readonly stateStore?: string;
|
|
43
|
+
readonly error?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface RunRecordConfig {
|
|
46
|
+
readonly supabaseUrl: string;
|
|
47
|
+
readonly serviceRoleKey: string;
|
|
48
|
+
readonly fetchFn?: typeof fetch;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Re-exported from the shared scrubber (C2's leak gate). This module owned the
|
|
52
|
+
* original copy; WS3 promoted it to `tenancy/scrub.ts` and widened its prefix
|
|
53
|
+
* set to the BYOK provider families, so error text stored here is scrubbed by
|
|
54
|
+
* the same gate the vault and telemetry boundaries use.
|
|
55
|
+
*/
|
|
56
|
+
export { scrubSecrets };
|
|
57
|
+
/**
|
|
58
|
+
* Write the run report. Best-effort by design — see property 2 above.
|
|
59
|
+
*
|
|
60
|
+
* Silent when no control plane is configured: a self-hosted rig with no
|
|
61
|
+
* Supabase has nowhere to write and nothing to prove to us.
|
|
62
|
+
*/
|
|
63
|
+
export declare function recordWatchRun(record: WatchRunRecord, config: RunRecordConfig): Promise<void>;
|
|
64
|
+
//# sourceMappingURL=run-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-record.d.ts","sourceRoot":"","sources":["../../src/watch/run-record.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,oEAAoE;AACpE,eAAO,MAAM,aAAa,mBAAmB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,IAAI,GAAG,QAAQ,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED;;;;;GAKG;AACH,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Scheduled-job self-reporting — Phase 6.
|
|
4
|
+
*
|
|
5
|
+
* A4 gave the watch cron durable state. It did not give anyone a way to tell
|
|
6
|
+
* whether the cron RAN. `control_state` sat empty for a week and the three
|
|
7
|
+
* candidate explanations — never scheduled, running and failing, or running
|
|
8
|
+
* and finding nothing — were indistinguishable from outside. The only
|
|
9
|
+
* evidence lived in Render's log viewer, behind an API key that grants access
|
|
10
|
+
* to every workspace and every environment variable in the account.
|
|
11
|
+
*
|
|
12
|
+
* A job whose only proof of life is a vendor's log viewer is a job you cannot
|
|
13
|
+
* operate, and reaching for that key to read a log is a poor trade: it is the
|
|
14
|
+
* broadest possible credential borrowed for the narrowest possible question.
|
|
15
|
+
*
|
|
16
|
+
* So the job answers the question itself. One row, upserted each cycle, in the
|
|
17
|
+
* store we already own. "Did it run, when, and what did it do" becomes a
|
|
18
|
+
* SELECT — no vendor credential involved.
|
|
19
|
+
*
|
|
20
|
+
* Three properties matter more than the shape of the record:
|
|
21
|
+
*
|
|
22
|
+
* 1. **It records failures.** A report that only appears on success is
|
|
23
|
+
* indistinguishable from no report, which is precisely the ambiguity that
|
|
24
|
+
* made A4 unverifiable.
|
|
25
|
+
* 2. **It never throws.** Observability that can fail the thing it observes is
|
|
26
|
+
* worse than none.
|
|
27
|
+
* 3. **It is a separate key from the state.** A run report must never be able
|
|
28
|
+
* to corrupt the pricing snapshot it reports on.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.scrubSecrets = exports.WATCH_RUN_KEY = void 0;
|
|
32
|
+
exports.recordWatchRun = recordWatchRun;
|
|
33
|
+
const scrub_1 = require("../tenancy/scrub");
|
|
34
|
+
Object.defineProperty(exports, "scrubSecrets", { enumerable: true, get: function () { return scrub_1.scrubSecrets; } });
|
|
35
|
+
/** Key under which the last run report lives in `control_state`. */
|
|
36
|
+
exports.WATCH_RUN_KEY = "watch:last-run";
|
|
37
|
+
/**
|
|
38
|
+
* Write the run report. Best-effort by design — see property 2 above.
|
|
39
|
+
*
|
|
40
|
+
* Silent when no control plane is configured: a self-hosted rig with no
|
|
41
|
+
* Supabase has nowhere to write and nothing to prove to us.
|
|
42
|
+
*/
|
|
43
|
+
async function recordWatchRun(record, config) {
|
|
44
|
+
const { supabaseUrl, serviceRoleKey } = config;
|
|
45
|
+
if (supabaseUrl === "" || serviceRoleKey === "")
|
|
46
|
+
return;
|
|
47
|
+
const fetchFn = config.fetchFn ?? fetch;
|
|
48
|
+
const safe = record.error === undefined ? record : { ...record, error: (0, scrub_1.scrubSecrets)(record.error) };
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetchFn(`${supabaseUrl.replace(/\/$/, "")}/rest/v1/control_state?on_conflict=key`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/json",
|
|
54
|
+
apikey: serviceRoleKey,
|
|
55
|
+
authorization: `Bearer ${serviceRoleKey}`,
|
|
56
|
+
prefer: "resolution=merge-duplicates,return=minimal",
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify([
|
|
59
|
+
{ key: exports.WATCH_RUN_KEY, value: safe, updated_at: new Date().toISOString() },
|
|
60
|
+
]),
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok)
|
|
63
|
+
warnReportFailed(`${response.status} ${await response.text().catch(() => "")}`);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
warnReportFailed(err instanceof Error ? err.message : String(err));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Non-fatal, but never silent — A-C6.3, 2026-08-05.
|
|
71
|
+
*
|
|
72
|
+
* Property 2 above says this must not fail the cycle it observes, and that is
|
|
73
|
+
* still true. But the first version swallowed the outcome entirely, which
|
|
74
|
+
* recreated the very ambiguity this module exists to end one level up: a
|
|
75
|
+
* report that never lands looks exactly like a report that lands fine, and
|
|
76
|
+
* `watch:last-run` being absent would again have three explanations and no way
|
|
77
|
+
* to tell them apart.
|
|
78
|
+
*
|
|
79
|
+
* So: the cycle still succeeds, and the operator still finds out. The warning
|
|
80
|
+
* names the key so a reader who greps the cron log for it lands here.
|
|
81
|
+
*/
|
|
82
|
+
function warnReportFailed(detail) {
|
|
83
|
+
console.warn(`[modelrig watch] could not write the ${exports.WATCH_RUN_KEY} self-report (${(0, scrub_1.scrubSecrets)(detail)}). ` +
|
|
84
|
+
"The cycle itself is unaffected — but until this succeeds, `control_state` cannot tell you " +
|
|
85
|
+
"whether this job ran, so fall back to watch:pricing-universe.updated_at.");
|
|
86
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One watch cycle, with a run report — Phase 6.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the CLI rather than written inline, for two reasons. The
|
|
5
|
+
* obvious one: `cli.ts` crossed the package's 700-LOC invariant and the test
|
|
6
|
+
* suite said so. The better one: reporting-on-failure is the half that
|
|
7
|
+
* matters, and a `catch` branch buried in a CLI command is a branch nobody
|
|
8
|
+
* exercises. Here it has a test.
|
|
9
|
+
*
|
|
10
|
+
* A4 gave this job durable state but no proof of life. `control_state` sat
|
|
11
|
+
* empty for a week and "never scheduled", "running and failing" and "running
|
|
12
|
+
* and finding nothing" were indistinguishable from outside — readable only
|
|
13
|
+
* through Render's log viewer, behind an API key that grants the whole
|
|
14
|
+
* account. Now the job answers for itself, and answering costs no vendor
|
|
15
|
+
* credential.
|
|
16
|
+
*/
|
|
17
|
+
import { type RunRecordConfig } from "./run-record";
|
|
18
|
+
import type { WatchCycleOptions, WatchCycleResult } from "./run";
|
|
19
|
+
export declare function runWatchCycleReported(opts: WatchCycleOptions, report: RunRecordConfig, now?: () => string): Promise<WatchCycleResult>;
|
|
20
|
+
//# sourceMappingURL=run-reported.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-reported.d.ts","sourceRoot":"","sources":["../../src/watch/run-reported.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAEjE,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,eAAe,EACvB,GAAG,GAAE,MAAM,MAAuC,GACjD,OAAO,CAAC,gBAAgB,CAAC,CAsC3B"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One watch cycle, with a run report — Phase 6.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the CLI rather than written inline, for two reasons. The
|
|
6
|
+
* obvious one: `cli.ts` crossed the package's 700-LOC invariant and the test
|
|
7
|
+
* suite said so. The better one: reporting-on-failure is the half that
|
|
8
|
+
* matters, and a `catch` branch buried in a CLI command is a branch nobody
|
|
9
|
+
* exercises. Here it has a test.
|
|
10
|
+
*
|
|
11
|
+
* A4 gave this job durable state but no proof of life. `control_state` sat
|
|
12
|
+
* empty for a week and "never scheduled", "running and failing" and "running
|
|
13
|
+
* and finding nothing" were indistinguishable from outside — readable only
|
|
14
|
+
* through Render's log viewer, behind an API key that grants the whole
|
|
15
|
+
* account. Now the job answers for itself, and answering costs no vendor
|
|
16
|
+
* credential.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.runWatchCycleReported = runWatchCycleReported;
|
|
20
|
+
const run_record_1 = require("./run-record");
|
|
21
|
+
const run_1 = require("./run");
|
|
22
|
+
async function runWatchCycleReported(opts, report, now = () => new Date().toISOString()) {
|
|
23
|
+
const startedAt = now();
|
|
24
|
+
const stateStore = opts.store?.description ?? "local file";
|
|
25
|
+
let result;
|
|
26
|
+
try {
|
|
27
|
+
result = await (0, run_1.runWatchCycle)(opts);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// Report BEFORE rethrowing. A crashed cycle that leaves no trace is the
|
|
31
|
+
// exact state this module exists to end, and rethrowing still fails the
|
|
32
|
+
// cron so the platform's own alerting is unaffected.
|
|
33
|
+
await (0, run_record_1.recordWatchRun)({
|
|
34
|
+
outcome: "failed",
|
|
35
|
+
startedAt,
|
|
36
|
+
finishedAt: now(),
|
|
37
|
+
stateStore,
|
|
38
|
+
error: err instanceof Error ? err.message : String(err),
|
|
39
|
+
}, report);
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
await (0, run_record_1.recordWatchRun)({
|
|
43
|
+
outcome: "ok",
|
|
44
|
+
startedAt,
|
|
45
|
+
finishedAt: now(),
|
|
46
|
+
seeded: result.seeded,
|
|
47
|
+
events: result.events,
|
|
48
|
+
drafts: result.drafts,
|
|
49
|
+
inserted: result.inserted,
|
|
50
|
+
stateStore,
|
|
51
|
+
}, report);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One watch cycle — Phase 4 WS1 (`modelrig watch run`, cron-able):
|
|
3
|
+
* 1. diff the pricing universe against last-seen state (or take injected
|
|
4
|
+
* fixture events — the test/demo path),
|
|
5
|
+
* 2. evaluate events into proposal drafts (pure),
|
|
6
|
+
* 3. sweep overdue proposals to expired,
|
|
7
|
+
* 4. submit drafts (idempotent on (route, trigger_hash)),
|
|
8
|
+
* 5. save state.
|
|
9
|
+
* The cycle itself spends $0 — paid evidence runs happen only through
|
|
10
|
+
* `bakeoff --from-proposal`, under the monthly watch budget.
|
|
11
|
+
*/
|
|
12
|
+
import type { RouteEconomics, WatchEvent } from "./evaluate";
|
|
13
|
+
import type { ProposalsClient } from "./proposals";
|
|
14
|
+
import type { PriceUniverse } from "./state";
|
|
15
|
+
import type { WatchStateStore } from "./state-store";
|
|
16
|
+
import type { CacheStatsRollup } from "../savings/cache-stats";
|
|
17
|
+
import type { CachePredictionDrift, ReconcileOptions } from "../savings/cache-reconcile";
|
|
18
|
+
import type { TelemetryRow } from "../types/telemetry";
|
|
19
|
+
export interface WatchCycleOptions {
|
|
20
|
+
readonly routes: readonly RouteEconomics[];
|
|
21
|
+
/** Local-file state path — the self-host fallback. Ignored when `store`
|
|
22
|
+
* is supplied. One of `store` or `statePath` is required. */
|
|
23
|
+
readonly statePath?: string;
|
|
24
|
+
/** Durable state store (addendum A4). On an ephemeral cron container this
|
|
25
|
+
* MUST be the control-plane store: a file-backed one re-seeds every run,
|
|
26
|
+
* so no price move can ever fire an event. */
|
|
27
|
+
readonly store?: WatchStateStore;
|
|
28
|
+
readonly universe: PriceUniverse;
|
|
29
|
+
readonly client: ProposalsClient;
|
|
30
|
+
readonly thresholdPct: number;
|
|
31
|
+
/** Fixture events (acceptance criterion 2's injected path) — when set,
|
|
32
|
+
* the state diff is skipped and state is NOT advanced. */
|
|
33
|
+
readonly injectedEvents?: readonly WatchEvent[];
|
|
34
|
+
readonly now?: Date;
|
|
35
|
+
}
|
|
36
|
+
export interface WatchCycleResult {
|
|
37
|
+
readonly seeded: boolean;
|
|
38
|
+
readonly events: number;
|
|
39
|
+
readonly drafts: number;
|
|
40
|
+
readonly inserted: number;
|
|
41
|
+
}
|
|
42
|
+
export declare function runWatchCycle(opts: WatchCycleOptions): Promise<WatchCycleResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Predicted cache-read tokens for one (route, candidate) — the ledger/simulator
|
|
45
|
+
* estimate that a reconciliation cycle checks against observed ground truth.
|
|
46
|
+
* Keyed by (route, provider, model) to join to a rollup.
|
|
47
|
+
*/
|
|
48
|
+
export interface CacheReadPrediction {
|
|
49
|
+
readonly route: string;
|
|
50
|
+
readonly provider: string;
|
|
51
|
+
readonly model: string;
|
|
52
|
+
readonly predictedReadTokens: number;
|
|
53
|
+
}
|
|
54
|
+
export interface CacheReconciliationResult {
|
|
55
|
+
readonly rollups: readonly CacheStatsRollup[];
|
|
56
|
+
readonly drift: readonly CachePredictionDrift[];
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The cache half of a watch cycle (cache-aware-routing spec §5.2, step 6):
|
|
60
|
+
* ROLL UP production cache stats from telemetry rows ONCE, then RECONCILE the
|
|
61
|
+
* ledger/simulator predictions against the observed reads, emitting
|
|
62
|
+
* `cache_prediction_drift` events (G-4 pattern). Pure over its inputs — the
|
|
63
|
+
* caller (a cron/CLI) supplies the window's rows and the predictions from the
|
|
64
|
+
* derived ledger/simulator. Deliberately SEPARATE from `runWatchCycle`: the
|
|
65
|
+
* price-move loop is untouched and byte-identical; this is an additive,
|
|
66
|
+
* independently-testable cache concern that a cron runs alongside it.
|
|
67
|
+
*/
|
|
68
|
+
export declare function runCacheReconciliation(rows: readonly TelemetryRow[], predictions: readonly CacheReadPrediction[], opts: {
|
|
69
|
+
now: number;
|
|
70
|
+
windowDays?: number;
|
|
71
|
+
reconcile?: ReconcileOptions;
|
|
72
|
+
}): CacheReconciliationResult;
|
|
73
|
+
//# sourceMappingURL=run.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/watch/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE/D,OAAO,KAAK,EACV,oBAAoB,EAEpB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAC;IAC3C;iEAC6D;IAC7D,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;kDAE8C;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B;8DAC0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAiCtF;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,SAAS,YAAY,EAAE,EAC7B,WAAW,EAAE,SAAS,mBAAmB,EAAE,EAC3C,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,gBAAgB,CAAA;CAAE,GACvE,yBAAyB,CAyB3B"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One watch cycle — Phase 4 WS1 (`modelrig watch run`, cron-able):
|
|
4
|
+
* 1. diff the pricing universe against last-seen state (or take injected
|
|
5
|
+
* fixture events — the test/demo path),
|
|
6
|
+
* 2. evaluate events into proposal drafts (pure),
|
|
7
|
+
* 3. sweep overdue proposals to expired,
|
|
8
|
+
* 4. submit drafts (idempotent on (route, trigger_hash)),
|
|
9
|
+
* 5. save state.
|
|
10
|
+
* The cycle itself spends $0 — paid evidence runs happen only through
|
|
11
|
+
* `bakeoff --from-proposal`, under the monthly watch budget.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.runWatchCycle = runWatchCycle;
|
|
15
|
+
exports.runCacheReconciliation = runCacheReconciliation;
|
|
16
|
+
const evaluate_1 = require("./evaluate");
|
|
17
|
+
const state_1 = require("./state");
|
|
18
|
+
const state_store_1 = require("./state-store");
|
|
19
|
+
const cache_stats_1 = require("../savings/cache-stats");
|
|
20
|
+
const cache_reconcile_1 = require("../savings/cache-reconcile");
|
|
21
|
+
async function runWatchCycle(opts) {
|
|
22
|
+
const now = opts.now ?? new Date();
|
|
23
|
+
const store = opts.store ??
|
|
24
|
+
(opts.statePath !== undefined
|
|
25
|
+
? (0, state_store_1.createFileWatchStateStore)(opts.statePath)
|
|
26
|
+
: (() => {
|
|
27
|
+
throw new Error("runWatchCycle needs either a state store or a statePath");
|
|
28
|
+
})());
|
|
29
|
+
let events;
|
|
30
|
+
let seeded = false;
|
|
31
|
+
if (opts.injectedEvents !== undefined) {
|
|
32
|
+
events = opts.injectedEvents;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const previous = await store.load();
|
|
36
|
+
seeded = previous === null;
|
|
37
|
+
events = (0, state_1.computeWatchEvents)(opts.universe, previous);
|
|
38
|
+
}
|
|
39
|
+
const drafts = (0, evaluate_1.evaluateEvents)(opts.routes, events, { thresholdPct: opts.thresholdPct });
|
|
40
|
+
// Expiry sweep first so a re-fired trigger never races its own stale row.
|
|
41
|
+
await opts.client.expireOverdue(now);
|
|
42
|
+
const inserted = drafts.length > 0 ? await opts.client.submitDrafts(drafts) : 0;
|
|
43
|
+
if (opts.injectedEvents === undefined) {
|
|
44
|
+
await store.save({ ...opts.universe });
|
|
45
|
+
}
|
|
46
|
+
return { seeded, events: events.length, drafts: drafts.length, inserted };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The cache half of a watch cycle (cache-aware-routing spec §5.2, step 6):
|
|
50
|
+
* ROLL UP production cache stats from telemetry rows ONCE, then RECONCILE the
|
|
51
|
+
* ledger/simulator predictions against the observed reads, emitting
|
|
52
|
+
* `cache_prediction_drift` events (G-4 pattern). Pure over its inputs — the
|
|
53
|
+
* caller (a cron/CLI) supplies the window's rows and the predictions from the
|
|
54
|
+
* derived ledger/simulator. Deliberately SEPARATE from `runWatchCycle`: the
|
|
55
|
+
* price-move loop is untouched and byte-identical; this is an additive,
|
|
56
|
+
* independently-testable cache concern that a cron runs alongside it.
|
|
57
|
+
*/
|
|
58
|
+
function runCacheReconciliation(rows, predictions, opts) {
|
|
59
|
+
const rollups = (0, cache_stats_1.rollupCacheStats)(rows, {
|
|
60
|
+
now: opts.now,
|
|
61
|
+
...(opts.windowDays !== undefined ? { windowDays: opts.windowDays } : {}),
|
|
62
|
+
});
|
|
63
|
+
// Join predictions to their rollup (observed ground truth) by candidate key.
|
|
64
|
+
const byKey = new Map();
|
|
65
|
+
for (const r of rollups)
|
|
66
|
+
byKey.set(`${r.route}\n${r.provider}\n${r.model}`, r);
|
|
67
|
+
const samples = [];
|
|
68
|
+
for (const p of predictions) {
|
|
69
|
+
const rollup = byKey.get(`${p.route}\n${p.provider}\n${p.model}`);
|
|
70
|
+
if (rollup === undefined)
|
|
71
|
+
continue; // no observed traffic → nothing to reconcile
|
|
72
|
+
samples.push({
|
|
73
|
+
route: p.route,
|
|
74
|
+
provider: p.provider,
|
|
75
|
+
model: p.model,
|
|
76
|
+
predictedReadTokens: p.predictedReadTokens,
|
|
77
|
+
observedReadTokens: rollup.tokensCached,
|
|
78
|
+
windowInputTokens: rollup.tokensIn,
|
|
79
|
+
sampleN: rollup.sampleN,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return { rollups, drift: (0, cache_reconcile_1.reconcileCacheDrift)(samples, opts.reconcile) };
|
|
83
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch-state persistence — Phase 5 addendum A4.
|
|
3
|
+
*
|
|
4
|
+
* The watch loop's pricing snapshot has to survive between runs, and the
|
|
5
|
+
* runs happen in an EPHEMERAL cron container. A local file cannot do that:
|
|
6
|
+
* every run reads nothing, re-seeds, and reports zero events — the cron was
|
|
7
|
+
* live and structurally incapable of ever firing a proposal.
|
|
8
|
+
*
|
|
9
|
+
* Two implementations behind one interface:
|
|
10
|
+
* - control plane (default when Supabase is configured): a keyed row in
|
|
11
|
+
* `control_state`. Survives the container.
|
|
12
|
+
* - local file (the self-host / no-Supabase fallback): the Phase 4
|
|
13
|
+
* behavior, kept because a single-machine user should not need a
|
|
14
|
+
* control plane to run a watch cycle.
|
|
15
|
+
*
|
|
16
|
+
* A store that cannot load is NOT treated as "no previous state". Seeding on
|
|
17
|
+
* a transient read failure would re-baseline the universe and silently
|
|
18
|
+
* swallow every price move since the last successful run, which is exactly
|
|
19
|
+
* the bug this file exists to fix — so a failed read throws.
|
|
20
|
+
*/
|
|
21
|
+
import type { PriceUniverse } from "./state";
|
|
22
|
+
/** Key under which the pricing universe lives in `control_state`. */
|
|
23
|
+
export declare const WATCH_STATE_KEY = "watch:pricing-universe";
|
|
24
|
+
export interface WatchStateStore {
|
|
25
|
+
/** Previous snapshot, or null when genuinely none has been stored yet. */
|
|
26
|
+
load(): Promise<PriceUniverse | null>;
|
|
27
|
+
save(universe: PriceUniverse): Promise<void>;
|
|
28
|
+
/** For the operator-facing log line: where this state actually lives. */
|
|
29
|
+
readonly description: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function createFileWatchStateStore(path: string): WatchStateStore;
|
|
32
|
+
export interface ControlPlaneStateConfig {
|
|
33
|
+
readonly supabaseUrl: string;
|
|
34
|
+
readonly serviceRoleKey: string;
|
|
35
|
+
readonly key?: string;
|
|
36
|
+
readonly fetchFn?: typeof fetch;
|
|
37
|
+
}
|
|
38
|
+
export declare function createControlPlaneWatchStateStore(config: ControlPlaneStateConfig): WatchStateStore;
|
|
39
|
+
/** Pick the durable store when a control plane exists, else the local file. */
|
|
40
|
+
export declare function createWatchStateStore(opts: {
|
|
41
|
+
readonly filePath: string;
|
|
42
|
+
readonly export?: {
|
|
43
|
+
readonly supabaseUrl: string;
|
|
44
|
+
readonly serviceRoleKey: string;
|
|
45
|
+
} | undefined;
|
|
46
|
+
readonly fetchFn?: typeof fetch;
|
|
47
|
+
}): WatchStateStore;
|
|
48
|
+
//# sourceMappingURL=state-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.d.ts","sourceRoot":"","sources":["../../src/watch/state-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,qEAAqE;AACrE,eAAO,MAAM,eAAe,2BAA2B,CAAC;AAExD,MAAM,WAAW,eAAe;IAC9B,0EAA0E;IAC1E,IAAI,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,yEAAyE;IACzE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAQvE;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAED,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,uBAAuB,GAC9B,eAAe,CA+CjB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE;IAC1C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IAChG,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC,GAAG,eAAe,CAOlB"}
|