@iowarp/clio-coder 0.3.8 → 0.3.9
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 +49 -0
- package/README.md +7 -3
- package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
- package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
- package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
- package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
- package/dist/chunk-3DPEIQKN.js +113 -0
- package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
- package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
- package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
- package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
- package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
- package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
- package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
- package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
- package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
- package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
- package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
- package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
- package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
- package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
- package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
- package/dist/chunk-7C6RYZGQ.js +391 -0
- package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
- package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
- package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
- package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
- package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
- package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
- package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
- package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
- package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
- package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
- package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
- package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
- package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
- package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
- package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
- package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
- package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
- package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
- package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
- package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
- package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
- package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
- package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
- package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
- package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
- package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
- package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
- package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
- package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
- package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
- package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
- package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
- package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
- package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
- package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
- package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
- package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
- package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
- package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
- package/dist/chunk-RKKLTLYB.js +45 -0
- package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
- package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
- package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
- package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
- package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
- package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
- package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
- package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
- package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
- package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
- package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
- package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
- package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
- package/dist/chunk-YSEHGPCT.js +127 -0
- package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
- package/dist/cli/index.js +27 -27
- package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
- package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
- package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
- package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
- package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
- package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
- package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
- package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
- package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
- package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
- package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
- package/dist/doctor-TWBWFK5V.js +165 -0
- package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
- package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
- package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
- package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
- package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
- package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
- package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
- package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
- package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
- package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
- package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
- package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
- package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
- package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
- package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
- package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
- package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
- package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
- package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
- package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
- package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
- package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
- package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
- package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
- package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
- package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
- package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
- package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
- package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
- package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
- package/dist/worker/entry.js +29 -33
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/artifact-versions.md +6 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +23 -2
- package/docs/commands-and-modes.md +1 -1
- package/docs/configuration-and-targets.md +30 -3
- package/docs/context-engine.md +63 -4
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +1 -1
- package/docs/environment-variables.md +2 -0
- package/docs/eval-runner.md +262 -11
- package/docs/evals-internal.md +72 -2
- package/docs/evidence-and-memory.md +11 -10
- package/docs/evolution.md +1 -1
- package/docs/extensions-and-sharing.md +3 -1
- package/docs/fleet-dispatch.md +4 -4
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +53 -2
- package/docs/proactive-memory.md +127 -14
- package/docs/prompt-envelope-and-tools.md +19 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +19 -3
- package/docs/safety-model.md +1 -1
- package/docs/scientific-validation.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +1 -1
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +87 -0
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +2 -1
- package/src/cli/agents.ts +1 -1
- package/src/cli/config-inspect.ts +33 -6
- package/src/cli/config.ts +1 -1
- package/src/cli/doctor-state-size.ts +82 -0
- package/src/cli/doctor.ts +3 -1
- package/src/cli/eval.ts +80 -16
- package/src/cli/extensions.ts +5 -1
- package/src/cli/fleet.ts +32 -3
- package/src/cli/targets.ts +44 -13
- package/src/cli/trace.ts +63 -4
- package/src/cli/usage.ts +63 -14
- package/src/core/bus-events.ts +29 -1
- package/src/core/cache-telemetry.ts +42 -0
- package/src/core/config.ts +18 -0
- package/src/core/defaults.ts +36 -6
- package/src/core/endpoint-key.ts +27 -0
- package/src/core/residency-target-key.ts +25 -0
- package/src/core/response-schema.ts +36 -2
- package/src/domains/config/classify.ts +3 -0
- package/src/domains/context/codewiki/coordinator.ts +12 -4
- package/src/domains/dispatch/admission.ts +40 -3
- package/src/domains/dispatch/capacity-lease.ts +98 -9
- package/src/domains/dispatch/contract.ts +11 -0
- package/src/domains/dispatch/execution-plan.ts +44 -4
- package/src/domains/dispatch/extension.ts +166 -42
- package/src/domains/dispatch/fleet-run.ts +23 -3
- package/src/domains/dispatch/heartbeat.ts +32 -8
- package/src/domains/dispatch/index.ts +3 -0
- package/src/domains/dispatch/orphan-recovery.ts +5 -0
- package/src/domains/dispatch/reservation-store.ts +116 -8
- package/src/domains/dispatch/state.ts +4 -0
- package/src/domains/dispatch/worker-spawn.ts +25 -11
- package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
- package/src/domains/dispatch/write-boundary.ts +62 -1
- package/src/domains/eval/artifacts/store.ts +62 -0
- package/src/domains/eval/compare/behavioral.ts +224 -0
- package/src/domains/eval/compare/compare.ts +355 -2
- package/src/domains/eval/compare/envelope.ts +128 -0
- package/src/domains/eval/compare/gates.ts +24 -6
- package/src/domains/eval/compare/thresholds.ts +30 -3
- package/src/domains/eval/execution-provenance.ts +240 -0
- package/src/domains/eval/metrics/aggregate.ts +136 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
- package/src/domains/eval/metrics/tracked.ts +413 -0
- package/src/domains/eval/provenance.ts +117 -0
- package/src/domains/eval/reports/comparison.ts +128 -0
- package/src/domains/eval/reports/junit.ts +17 -3
- package/src/domains/eval/reports/markdown.ts +3 -3
- package/src/domains/eval/reports/text.ts +14 -0
- package/src/domains/eval/run-compare.ts +20 -0
- package/src/domains/eval/runners/clio-run.ts +127 -0
- package/src/domains/eval/runners/external-command.ts +28 -3
- package/src/domains/eval/schema/adapter.ts +111 -0
- package/src/domains/eval/schema/artifact.ts +20 -0
- package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
- package/src/domains/eval/schema/behavioral.ts +520 -0
- package/src/domains/eval/schema/execution-envelope.ts +194 -0
- package/src/domains/eval/schema/serving.ts +74 -0
- package/src/domains/eval/schema/suite.ts +38 -8
- package/src/domains/eval/schema/validate.ts +58 -3
- package/src/domains/eval/schema/verdict.ts +237 -0
- package/src/domains/eval/suites/resolve.ts +2 -0
- package/src/domains/eval/suites/run.ts +264 -33
- package/src/domains/eval/verifiers/command.ts +2 -1
- package/src/domains/eval/workspaces/temp-copy.ts +145 -13
- package/src/domains/evidence/build.ts +2 -13
- package/src/domains/evidence/eval.ts +2 -12
- package/src/domains/evidence/findings-markdown.ts +33 -0
- package/src/domains/evidence/run-trust.ts +7 -113
- package/src/domains/evidence/trust-projection.ts +2 -2
- package/src/domains/extensions/compatibility.ts +285 -0
- package/src/domains/extensions/discovery.ts +38 -3
- package/src/domains/extensions/resources.ts +1 -1
- package/src/domains/extensions/state.ts +12 -3
- package/src/domains/extensions/types.ts +2 -0
- package/src/domains/lifecycle/doctor.ts +69 -1
- package/src/domains/memory/index.ts +14 -0
- package/src/domains/memory/task-bank-promotion.ts +64 -0
- package/src/domains/memory/task-memory-policy.ts +77 -8
- package/src/domains/memory/task-memory-spend.ts +131 -0
- package/src/domains/memory/task-memory-status.ts +7 -0
- package/src/domains/memory/task-memory-telemetry.ts +2 -0
- package/src/domains/middleware/index.ts +1 -0
- package/src/domains/middleware/memory-intervention.ts +69 -5
- package/src/domains/middleware/memory-step-endpoint.ts +71 -0
- package/src/domains/observability/background-memory-usage.ts +140 -0
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/index.ts +7 -0
- package/src/domains/observability/out-of-turn-usage.ts +51 -2
- package/src/domains/observability/trace-store.ts +192 -2
- package/src/domains/prompts/compiler.ts +100 -13
- package/src/domains/providers/endpoint-capacity.ts +96 -0
- package/src/domains/providers/index.ts +10 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
- package/src/domains/providers/runtime-resolution.ts +8 -1
- package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/target-descriptor.ts +2 -0
- package/src/domains/resources/prompts/loader.ts +95 -33
- package/src/domains/safety/call-target.ts +52 -0
- package/src/domains/safety/run-effects.ts +35 -4
- package/src/domains/session/context-accounting.ts +52 -1
- package/src/domains/session/context-ledger.ts +37 -13
- package/src/domains/session/index.ts +6 -0
- package/src/domains/session/prompt-cache.ts +140 -0
- package/src/domains/session/prompt-manifest.ts +42 -0
- package/src/engine/acp/adapter.ts +18 -3
- package/src/engine/ai.ts +35 -0
- package/src/engine/apis/llamacpp-residency.ts +55 -3
- package/src/engine/apis/lmstudio.ts +25 -5
- package/src/engine/apis/ollama-native.ts +2 -1
- package/src/engine/apis/openai-completions.ts +80 -17
- package/src/engine/apis/residency-lock.ts +3 -1
- package/src/engine/apis/residency.ts +34 -1
- package/src/engine/provider-payload.ts +29 -1
- package/src/entry/orchestrator.ts +176 -30
- package/src/interactive/chat-loop-messages.ts +26 -7
- package/src/interactive/chat-loop.ts +318 -41
- package/src/interactive/chat-panel.ts +62 -8
- package/src/interactive/clio-editor.ts +45 -8
- package/src/interactive/context-activity.ts +5 -1
- package/src/interactive/context-meter.ts +1 -1
- package/src/interactive/context-overlay.ts +40 -10
- package/src/interactive/cost-overlay.ts +64 -6
- package/src/interactive/dispatch-board.ts +84 -12
- package/src/interactive/fleet-run-preview.ts +41 -15
- package/src/interactive/handoff-round.ts +41 -2
- package/src/interactive/interactive-application.ts +24 -1
- package/src/interactive/interactive-input-runtime.ts +8 -0
- package/src/interactive/interactive-presentation.ts +4 -0
- package/src/interactive/interactive-shell.ts +20 -17
- package/src/interactive/interactive-slash-runtime.ts +27 -4
- package/src/interactive/memory-overlay.ts +8 -0
- package/src/interactive/mutation-preview.ts +295 -0
- package/src/interactive/overlay-general-openers.ts +16 -0
- package/src/interactive/overlay-key-routing.ts +38 -0
- package/src/interactive/overlay-lifecycle.ts +38 -5
- package/src/interactive/overlay-permission-lifecycle.ts +22 -2
- package/src/interactive/overlay-session-lifecycle.ts +73 -9
- package/src/interactive/overlays/ask-user.ts +91 -19
- package/src/interactive/overlays/help-reference.ts +4 -0
- package/src/interactive/overlays/prompts.ts +11 -1
- package/src/interactive/overlays/settings.ts +35 -1
- package/src/interactive/permission-hint.ts +34 -2
- package/src/interactive/permission-overlay.ts +159 -9
- package/src/interactive/prewarm.ts +197 -0
- package/src/interactive/render-trace.ts +162 -15
- package/src/interactive/renderers/tool-execution.ts +4 -0
- package/src/interactive/side-question.ts +58 -1
- package/src/interactive/status/controller.ts +11 -0
- package/src/interactive/status/state-machine.ts +54 -2
- package/src/interactive/status/types.ts +7 -0
- package/src/interactive/terminal-lease.ts +2 -0
- package/src/interactive/turn-context.ts +299 -31
- package/src/interactive/turn-persistence.ts +14 -4
- package/src/interactive/turn-prewarm.ts +364 -0
- package/src/interactive/turn-queues.ts +7 -4
- package/src/interactive/turn-runtime.ts +8 -1
- package/src/interactive/turn-state.ts +23 -0
- package/src/interactive/view/view-overlay.ts +28 -3
- package/src/tools/ask-user.ts +43 -2
- package/src/tools/dispatch-plan.ts +17 -9
- package/src/tools/dispatch-scout.ts +1 -1
- package/src/tools/registry.ts +16 -0
- package/dist/chunk-AOCYTWAV.js +0 -449
- package/dist/chunk-HWUFFB6L.js +0 -83
- package/dist/chunk-R346GLFC.js +0 -31
- package/dist/doctor-M7YEDGAE.js +0 -91
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { withStateFileLockSync } from "../../core/state-file-lock.js";
|
|
3
|
+
import { endpointLabel } from "../providers/endpoint-capacity.js";
|
|
3
4
|
import {
|
|
4
5
|
acquireCapacityLease,
|
|
5
6
|
activeCapacityDrainUnsafe,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
type CapacityLimits,
|
|
8
9
|
type CapacityStateFile,
|
|
9
10
|
capacityStateLockPath,
|
|
11
|
+
describeEndpointCapacityHolders,
|
|
10
12
|
readCapacityStateUnsafe,
|
|
11
13
|
writeCapacityStateUnsafe,
|
|
12
14
|
} from "./capacity-lease.js";
|
|
@@ -19,9 +21,10 @@ export type ReservationMemberStatus = "held" | "consumed" | "released";
|
|
|
19
21
|
export type ReservationStatus = "active" | "released" | "rolled_back" | "expired";
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
|
-
* A reservation holds
|
|
23
|
-
* concurrency slot, a per-node slot, and a budget
|
|
24
|
-
*
|
|
24
|
+
* A reservation holds four scarce things and nothing else: a global
|
|
25
|
+
* concurrency slot, a per-node slot, an inference-endpoint slot, and a budget
|
|
26
|
+
* upper bound. Node and endpoint identity matter only because slots bind on
|
|
27
|
+
* those dimensions. Agent, target, model, and runtime
|
|
25
28
|
* identity are route identity, which the failover envelope owns
|
|
26
29
|
* (`assertRouteWithinApprovedEnvelope`); pinning them here made a
|
|
27
30
|
* plan-approved dispatch unable to fail over at all.
|
|
@@ -30,6 +33,7 @@ export interface ReservationPlanTask {
|
|
|
30
33
|
memberId: string;
|
|
31
34
|
wave: number;
|
|
32
35
|
nodeId: string;
|
|
36
|
+
endpointKey?: string;
|
|
33
37
|
costUpperBoundUsd: number;
|
|
34
38
|
}
|
|
35
39
|
|
|
@@ -54,17 +58,31 @@ export interface DispatchReservationRecord {
|
|
|
54
58
|
export interface ReservationCapacitySnapshot {
|
|
55
59
|
global: { active: number; limit: number };
|
|
56
60
|
nodes: Readonly<Record<string, { active: number; limit: number }>>;
|
|
61
|
+
endpoints: Readonly<Record<string, { active: number; limit: number; leases?: number; foregroundStreams?: number }>>;
|
|
57
62
|
budget: { currentUsd: number; ceilingUsd: number };
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
export interface ReservationAllocation {
|
|
61
66
|
globalSlots: number;
|
|
62
67
|
nodeSlots: Readonly<Record<string, number>>;
|
|
68
|
+
endpointSlots: Readonly<Record<string, number>>;
|
|
63
69
|
budgetUsd: number;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
export type ReservationPlanResult = { ok: true; allocation: ReservationAllocation } | { ok: false; reason: string };
|
|
67
73
|
|
|
74
|
+
function endpointCapacityDenial(input: {
|
|
75
|
+
endpointKey: string;
|
|
76
|
+
limit: number;
|
|
77
|
+
used: number;
|
|
78
|
+
requested: number;
|
|
79
|
+
leases: number;
|
|
80
|
+
reservations: number;
|
|
81
|
+
foregroundStreams: number;
|
|
82
|
+
}): string {
|
|
83
|
+
return `dispatch: admission denied: endpoint '${endpointLabel(input.endpointKey)}' capacity exceeded (${input.used + input.requested}/${input.limit} slots): ${describeEndpointCapacityHolders(input)}; reduce the same-wave worker count, collect in-flight runs, or point workers at a second server`;
|
|
84
|
+
}
|
|
85
|
+
|
|
68
86
|
function copyRecord(record: DispatchReservationRecord): DispatchReservationRecord {
|
|
69
87
|
return { ...record, members: record.members.map((member) => ({ ...member })) };
|
|
70
88
|
}
|
|
@@ -96,6 +114,7 @@ function validRecord(value: unknown): value is DispatchReservationRecord {
|
|
|
96
114
|
typeof member.memberId === "string" &&
|
|
97
115
|
Number.isInteger(member.wave) &&
|
|
98
116
|
typeof member.nodeId === "string" &&
|
|
117
|
+
(member.endpointKey === undefined || typeof member.endpointKey === "string") &&
|
|
99
118
|
typeof member.costUpperBoundUsd === "number" &&
|
|
100
119
|
Number.isFinite(member.costUpperBoundUsd) &&
|
|
101
120
|
member.costUpperBoundUsd >= 0 &&
|
|
@@ -145,9 +164,13 @@ function reservationAllocation(tasks: ReadonlyArray<ReservationPlanTask>): Reser
|
|
|
145
164
|
),
|
|
146
165
|
);
|
|
147
166
|
const nodeSlots = peakBy(tasks.map((task) => ({ wave: task.wave, key: task.nodeId })));
|
|
167
|
+
const endpointSlots = peakBy(
|
|
168
|
+
tasks.flatMap((task) => (task.endpointKey === undefined ? [] : [{ wave: task.wave, key: task.endpointKey }])),
|
|
169
|
+
);
|
|
148
170
|
return {
|
|
149
171
|
globalSlots,
|
|
150
172
|
nodeSlots,
|
|
173
|
+
endpointSlots,
|
|
151
174
|
budgetUsd: tasks.reduce((sum, task) => sum + task.costUpperBoundUsd, 0),
|
|
152
175
|
};
|
|
153
176
|
}
|
|
@@ -191,6 +214,26 @@ function allocateReservation(
|
|
|
191
214
|
return { ok: false, reason: `node '${nodeId}' capacity exceeded (${used + slots}/${node.limit})` };
|
|
192
215
|
}
|
|
193
216
|
}
|
|
217
|
+
for (const [endpointKey, slots] of Object.entries(requested.endpointSlots)) {
|
|
218
|
+
const endpoint = capacity.endpoints[endpointKey];
|
|
219
|
+
if (!endpoint || !Number.isFinite(endpoint.limit)) continue;
|
|
220
|
+
const heldSlots = held.reduce((sum, allocation) => sum + (allocation.endpointSlots[endpointKey] ?? 0), 0);
|
|
221
|
+
const used = endpoint.active + heldSlots;
|
|
222
|
+
if (used + slots > endpoint.limit) {
|
|
223
|
+
return {
|
|
224
|
+
ok: false,
|
|
225
|
+
reason: endpointCapacityDenial({
|
|
226
|
+
endpointKey,
|
|
227
|
+
limit: endpoint.limit,
|
|
228
|
+
used,
|
|
229
|
+
requested: slots,
|
|
230
|
+
leases: endpoint.leases ?? endpoint.active - (endpoint.foregroundStreams ?? 0),
|
|
231
|
+
reservations: heldSlots,
|
|
232
|
+
foregroundStreams: endpoint.foregroundStreams ?? 0,
|
|
233
|
+
}),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
194
237
|
const existingBudget = activeRecords.reduce((sum, record) => sum + outstandingBudget(record), 0);
|
|
195
238
|
const projected = capacity.budget.currentUsd + existingBudget + requested.budgetUsd;
|
|
196
239
|
if (projected >= capacity.budget.ceilingUsd) {
|
|
@@ -225,7 +268,9 @@ export function createDispatchReservation(input: {
|
|
|
225
268
|
if (!planned.ok) {
|
|
226
269
|
replaceReservations(state, records);
|
|
227
270
|
writeCapacityStateUnsafe(state);
|
|
228
|
-
throw new Error(
|
|
271
|
+
throw new Error(
|
|
272
|
+
planned.reason.startsWith("dispatch:") ? planned.reason : `dispatch: reservation denied: ${planned.reason}`,
|
|
273
|
+
);
|
|
229
274
|
}
|
|
230
275
|
const createdAt = new Date(nowMs).toISOString();
|
|
231
276
|
const record: DispatchReservationRecord = {
|
|
@@ -250,6 +295,7 @@ export function transferDispatchReservationToLease(input: {
|
|
|
250
295
|
memberId: string;
|
|
251
296
|
assignmentId: string;
|
|
252
297
|
nodeId: string;
|
|
298
|
+
endpointKey?: string;
|
|
253
299
|
limits: CapacityLimits;
|
|
254
300
|
nowMs?: number;
|
|
255
301
|
ttlMs?: number;
|
|
@@ -258,6 +304,7 @@ export function transferDispatchReservationToLease(input: {
|
|
|
258
304
|
return acquireCapacityLease({
|
|
259
305
|
assignmentId: input.assignmentId,
|
|
260
306
|
nodeId: input.nodeId,
|
|
307
|
+
...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
|
|
261
308
|
limits: input.limits,
|
|
262
309
|
nowMs,
|
|
263
310
|
...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
|
|
@@ -290,6 +337,7 @@ export function rebindDispatchReservationMember(input: {
|
|
|
290
337
|
ownerId: string;
|
|
291
338
|
memberId: string;
|
|
292
339
|
nodeId: string;
|
|
340
|
+
endpointKey?: string;
|
|
293
341
|
costUpperBoundUsd: number;
|
|
294
342
|
capacity: ReservationCapacitySnapshot;
|
|
295
343
|
nowMs?: number;
|
|
@@ -304,11 +352,42 @@ export function rebindDispatchReservationMember(input: {
|
|
|
304
352
|
if (member.status === "released") {
|
|
305
353
|
throw new Error(`dispatch: reservation member '${input.memberId}' was already released`);
|
|
306
354
|
}
|
|
307
|
-
if (
|
|
355
|
+
if (
|
|
356
|
+
member.nodeId === input.nodeId &&
|
|
357
|
+
member.endpointKey === input.endpointKey &&
|
|
358
|
+
member.costUpperBoundUsd === input.costUpperBoundUsd
|
|
359
|
+
) {
|
|
308
360
|
return copyRecord(record);
|
|
309
361
|
}
|
|
310
|
-
|
|
311
362
|
const others = records.filter((entry) => entry.status === "active" && entry.ownerId !== input.ownerId);
|
|
363
|
+
if (member.endpointKey !== input.endpointKey && input.endpointKey !== undefined) {
|
|
364
|
+
const endpointKey = input.endpointKey;
|
|
365
|
+
const endpoint = input.capacity.endpoints[endpointKey];
|
|
366
|
+
if (endpoint !== undefined && Number.isFinite(endpoint.limit)) {
|
|
367
|
+
const siblingSlots = record.members.filter(
|
|
368
|
+
(entry) => entry !== member && entry.status !== "released" && entry.endpointKey === endpointKey,
|
|
369
|
+
).length;
|
|
370
|
+
const otherHeld = others.reduce(
|
|
371
|
+
(sum, entry) => sum + (reservationAllocation(outstandingTasks(entry)).endpointSlots[endpointKey] ?? 0),
|
|
372
|
+
0,
|
|
373
|
+
);
|
|
374
|
+
const used = endpoint.active + otherHeld + siblingSlots;
|
|
375
|
+
if (used + 1 > endpoint.limit) {
|
|
376
|
+
throw new Error(
|
|
377
|
+
endpointCapacityDenial({
|
|
378
|
+
endpointKey,
|
|
379
|
+
limit: endpoint.limit,
|
|
380
|
+
used,
|
|
381
|
+
requested: 1,
|
|
382
|
+
leases: endpoint.leases ?? endpoint.active - (endpoint.foregroundStreams ?? 0),
|
|
383
|
+
reservations: otherHeld + siblingSlots,
|
|
384
|
+
foregroundStreams: endpoint.foregroundStreams ?? 0,
|
|
385
|
+
}),
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
312
391
|
if (member.nodeId !== input.nodeId) {
|
|
313
392
|
const node = input.capacity.nodes[input.nodeId];
|
|
314
393
|
if (node !== undefined && Number.isFinite(node.limit)) {
|
|
@@ -342,6 +421,8 @@ export function rebindDispatchReservationMember(input: {
|
|
|
342
421
|
}
|
|
343
422
|
|
|
344
423
|
member.nodeId = input.nodeId;
|
|
424
|
+
if (input.endpointKey === undefined) delete member.endpointKey;
|
|
425
|
+
else member.endpointKey = input.endpointKey;
|
|
345
426
|
member.costUpperBoundUsd = Math.max(0, input.costUpperBoundUsd);
|
|
346
427
|
writeStore(records);
|
|
347
428
|
return copyRecord(record);
|
|
@@ -370,6 +451,26 @@ export function releaseDispatchReservationMember(
|
|
|
370
451
|
});
|
|
371
452
|
}
|
|
372
453
|
|
|
454
|
+
/** Settle every member after the scheduler has finished the reserved plan. */
|
|
455
|
+
export function releaseDispatchReservation(ownerId: string, nowMs = Date.now()): DispatchReservationRecord | null {
|
|
456
|
+
return withStateFileLockSync(capacityStateLockPath(), () => {
|
|
457
|
+
const records = readStore();
|
|
458
|
+
const record = records.find((entry) => entry.ownerId === ownerId);
|
|
459
|
+
if (!record) return null;
|
|
460
|
+
if (record.status !== "active") return copyRecord(record);
|
|
461
|
+
const settledAt = new Date(nowMs).toISOString();
|
|
462
|
+
for (const member of record.members) {
|
|
463
|
+
if (member.status === "released") continue;
|
|
464
|
+
member.status = "released";
|
|
465
|
+
member.releasedAt = settledAt;
|
|
466
|
+
}
|
|
467
|
+
record.status = "released";
|
|
468
|
+
record.settledAt = settledAt;
|
|
469
|
+
writeStore(records);
|
|
470
|
+
return copyRecord(record);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
373
474
|
export function rollbackDispatchReservation(ownerId: string, nowMs = Date.now()): DispatchReservationRecord | null {
|
|
374
475
|
return withStateFileLockSync(capacityStateLockPath(), () => {
|
|
375
476
|
const records = readStore();
|
|
@@ -488,14 +589,21 @@ export function reservedBudgetUsd(): number {
|
|
|
488
589
|
.reduce((sum, record) => sum + outstandingBudget(record), 0);
|
|
489
590
|
}
|
|
490
591
|
|
|
491
|
-
export function reservedCapacity(): {
|
|
592
|
+
export function reservedCapacity(): {
|
|
593
|
+
globalSlots: number;
|
|
594
|
+
nodeSlots: Readonly<Record<string, number>>;
|
|
595
|
+
endpointSlots: Readonly<Record<string, number>>;
|
|
596
|
+
} {
|
|
492
597
|
let globalSlots = 0;
|
|
493
598
|
const nodeSlots: Record<string, number> = {};
|
|
599
|
+
const endpointSlots: Record<string, number> = {};
|
|
494
600
|
for (const record of readStore().filter((entry) => entry.status === "active")) {
|
|
495
601
|
const allocation = reservationAllocation(outstandingTasks(record));
|
|
496
602
|
globalSlots += allocation.globalSlots;
|
|
497
603
|
for (const [nodeId, count] of Object.entries(allocation.nodeSlots))
|
|
498
604
|
nodeSlots[nodeId] = (nodeSlots[nodeId] ?? 0) + count;
|
|
605
|
+
for (const [endpointKey, count] of Object.entries(allocation.endpointSlots))
|
|
606
|
+
endpointSlots[endpointKey] = (endpointSlots[endpointKey] ?? 0) + count;
|
|
499
607
|
}
|
|
500
|
-
return { globalSlots, nodeSlots };
|
|
608
|
+
return { globalSlots, nodeSlots, endpointSlots };
|
|
501
609
|
}
|
|
@@ -107,6 +107,10 @@ export async function writeFleetRun(record: FleetRunRecord): Promise<void> {
|
|
|
107
107
|
await atomicWrite(fleetRunPath(record.id), `${JSON.stringify(record, null, 2)}\n`);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
// `RunEnvelope.heartbeatAt` deliberately persists only the absolute wall-clock
|
|
111
|
+
// anchor used for operator display and crash recovery. The monotonic heartbeat
|
|
112
|
+
// stamp belongs to the live transport in this process; serializing it would
|
|
113
|
+
// give a restarted process a number from an origin it cannot share.
|
|
110
114
|
function readRuns(): RunEnvelope[] {
|
|
111
115
|
const path = runsPath();
|
|
112
116
|
if (!existsSync(path)) return [];
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
|
|
27
27
|
import { type ChildProcess, spawn } from "node:child_process";
|
|
28
28
|
import { join } from "node:path";
|
|
29
|
+
import { performance } from "node:perf_hooks";
|
|
29
30
|
import { withClioAgentEnvironment } from "../../core/agent-environment.js";
|
|
30
31
|
import { enableClioCompileCache, workerCompileCacheEnvironment } from "../../core/compile-cache.js";
|
|
31
32
|
import { resolvePackageRoot } from "../../core/package-root.js";
|
|
32
33
|
import type { WorkerSpec } from "../../worker/spec-contract.js";
|
|
34
|
+
import type { HeartbeatStamp } from "./heartbeat.js";
|
|
33
35
|
import {
|
|
34
36
|
type AgentLedgerBody,
|
|
35
37
|
type ApprovedWorkerIdentity,
|
|
@@ -71,7 +73,7 @@ export interface SpawnedWorker {
|
|
|
71
73
|
promise: Promise<SpawnedWorkerResult>;
|
|
72
74
|
events: AsyncIterableIterator<unknown>;
|
|
73
75
|
abort(): void;
|
|
74
|
-
heartbeatAt:
|
|
76
|
+
heartbeatAt: HeartbeatStamp;
|
|
75
77
|
/**
|
|
76
78
|
* Write one JSON line to the worker's open stdin (the same line protocol
|
|
77
79
|
* that carried the spec). Returns false when the worker has exited, its
|
|
@@ -134,15 +136,14 @@ export interface WorkerProcessOptions {
|
|
|
134
136
|
/** How long bulk output may precede the announce before the peer is refused. */
|
|
135
137
|
attestationGraceMs?: number;
|
|
136
138
|
/**
|
|
137
|
-
*
|
|
138
|
-
* a wall-clock instant
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* two wall-clock reads taken either side of an interval orders them backward
|
|
142
|
-
* on any host that resyncs its clock in between. A test injects the steppable
|
|
143
|
-
* clock from tests/harness/clock.ts here and asserts on that instead.
|
|
139
|
+
* Wall clock behind the heartbeat anchor, defaulting to `Date.now`. The
|
|
140
|
+
* anchor stays a wall-clock instant because the ledger and evidence record
|
|
141
|
+
* serialize the derived heartbeat as ISO-8601 for an operator to read. This
|
|
142
|
+
* clock is read once; later heartbeat instants add a monotonic span to it.
|
|
144
143
|
*/
|
|
145
144
|
now?: () => number;
|
|
145
|
+
/** Monotonic clock used for heartbeat spans, defaulting to `performance.now`. */
|
|
146
|
+
monotonicNow?: () => number;
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
/**
|
|
@@ -192,6 +193,7 @@ export function spawnWorkerProcess(
|
|
|
192
193
|
const shutdownGraceMs = opts?.shutdownGraceMs ?? DEFAULT_SHUTDOWN_GRACE_MS;
|
|
193
194
|
const attestationGraceMs = opts?.attestationGraceMs ?? DEFAULT_ATTESTATION_GRACE_MS;
|
|
194
195
|
const now = opts?.now ?? Date.now;
|
|
196
|
+
const monotonicNow = opts?.monotonicNow ?? (() => performance.now());
|
|
195
197
|
const approved = opts?.approvedIdentity ?? approvedIdentityForSpec(spec);
|
|
196
198
|
|
|
197
199
|
const child: ChildProcess = spawn(command, [...args], {
|
|
@@ -204,7 +206,19 @@ export function spawnWorkerProcess(
|
|
|
204
206
|
});
|
|
205
207
|
const pid = child.pid ?? null;
|
|
206
208
|
|
|
207
|
-
|
|
209
|
+
// One wall anchor plus a monotonic span keeps the persisted/display instant
|
|
210
|
+
// readable without ever asking a later wall-clock read how much time passed.
|
|
211
|
+
const heartbeatWallAnchor = now();
|
|
212
|
+
const heartbeatMonotonicAnchor = monotonicNow();
|
|
213
|
+
const heartbeatAt: HeartbeatStamp = {
|
|
214
|
+
current: heartbeatWallAnchor,
|
|
215
|
+
monotonic: heartbeatMonotonicAnchor,
|
|
216
|
+
};
|
|
217
|
+
const stampHeartbeat = (): void => {
|
|
218
|
+
const stamp = monotonicNow();
|
|
219
|
+
heartbeatAt.monotonic = stamp;
|
|
220
|
+
heartbeatAt.current = heartbeatWallAnchor + (stamp - heartbeatMonotonicAnchor);
|
|
221
|
+
};
|
|
208
222
|
|
|
209
223
|
const queue = createBoundedEventQueue();
|
|
210
224
|
const waiters: Array<(r: IteratorResult<unknown>) => void> = [];
|
|
@@ -233,7 +247,7 @@ export function spawnWorkerProcess(
|
|
|
233
247
|
}
|
|
234
248
|
|
|
235
249
|
function push(value: unknown): void {
|
|
236
|
-
|
|
250
|
+
stampHeartbeat();
|
|
237
251
|
const w = waiters.shift();
|
|
238
252
|
if (w) {
|
|
239
253
|
w({ value, done: false });
|
|
@@ -378,7 +392,7 @@ export function spawnWorkerProcess(
|
|
|
378
392
|
if (!announceAccepted) failAttestation(`Invalid worker attestation: ${frame.reason}`);
|
|
379
393
|
return;
|
|
380
394
|
}
|
|
381
|
-
|
|
395
|
+
stampHeartbeat();
|
|
382
396
|
if (frame.value.kind === "announce") {
|
|
383
397
|
if (announceAccepted || announceFailed) return;
|
|
384
398
|
const verdict = verifyWorkerAttestation(frame.value.attestation, approved);
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
enforceWriteBoundary,
|
|
27
27
|
type WorkspaceSnapshot,
|
|
28
28
|
type WriteBoundaryAttribution,
|
|
29
|
+
type WriteBoundaryAttributionDowngrade,
|
|
29
30
|
type WriteBoundaryVerdict,
|
|
30
31
|
writeWriteBoundaryVerdict,
|
|
31
32
|
} from "./write-boundary.js";
|
|
@@ -45,7 +46,7 @@ export interface WriteBoundaryEnforcerInput {
|
|
|
45
46
|
* argument names. Absent entirely means the caller offers no record at all,
|
|
46
47
|
* and every change outside the declaration is blamed on the window.
|
|
47
48
|
*/
|
|
48
|
-
recordedWritesFor?(stepId: string): ReadonlyArray<string> | null;
|
|
49
|
+
recordedWritesFor?(stepId: string): ReadonlyArray<string> | WriteBoundaryAttribution | null;
|
|
49
50
|
/** Called once per closed window with the sealed verdict and where it landed. */
|
|
50
51
|
onVerdict?(verdict: WriteBoundaryVerdict, path: string): void;
|
|
51
52
|
}
|
|
@@ -91,16 +92,32 @@ export function createWriteBoundaryEnforcer(input: WriteBoundaryEnforcerInput):
|
|
|
91
92
|
const recordedWritesFor = input.recordedWritesFor;
|
|
92
93
|
if (recordedWritesFor === undefined) return undefined;
|
|
93
94
|
const recorded = new Set<string>();
|
|
95
|
+
const downgrades: WriteBoundaryAttributionDowngrade[] = [];
|
|
94
96
|
let complete = true;
|
|
95
97
|
for (const stepId of stepIds) {
|
|
96
98
|
const observed = recordedWritesFor(stepId);
|
|
97
99
|
if (observed === null) {
|
|
98
100
|
complete = false;
|
|
101
|
+
downgrades.push({
|
|
102
|
+
reason: "write_record_unavailable",
|
|
103
|
+
tool: null,
|
|
104
|
+
toolCallId: null,
|
|
105
|
+
runId: null,
|
|
106
|
+
stepId,
|
|
107
|
+
});
|
|
99
108
|
continue;
|
|
100
109
|
}
|
|
101
|
-
|
|
110
|
+
if (!("recorded" in observed)) {
|
|
111
|
+
for (const target of observed) recorded.add(target);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
for (const target of observed.recorded) recorded.add(target);
|
|
115
|
+
if (!observed.complete) complete = false;
|
|
116
|
+
for (const downgrade of observed.downgrades ?? []) {
|
|
117
|
+
downgrades.push({ ...downgrade, stepId: downgrade.stepId ?? stepId });
|
|
118
|
+
}
|
|
102
119
|
}
|
|
103
|
-
return { recorded: [...recorded].sort(), complete };
|
|
120
|
+
return { recorded: [...recorded].sort(), complete, downgrades };
|
|
104
121
|
};
|
|
105
122
|
return {
|
|
106
123
|
begin(window, stepIds) {
|
|
@@ -283,6 +283,49 @@ export interface WriteBoundaryAttribution {
|
|
|
283
283
|
* change outside the allowlist, and the verdict says it did.
|
|
284
284
|
*/
|
|
285
285
|
complete: boolean;
|
|
286
|
+
/** Durable causes for an incomplete record. Empty only when complete is true. */
|
|
287
|
+
downgrades?: ReadonlyArray<WriteBoundaryAttributionDowngrade>;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type WriteBoundaryAttributionDowngradeReason =
|
|
291
|
+
| "opaque_tool_succeeded"
|
|
292
|
+
| "incomplete_tool_telemetry"
|
|
293
|
+
| "code_step_untracked"
|
|
294
|
+
| "write_record_unavailable";
|
|
295
|
+
|
|
296
|
+
/** Why one step could not offer a closed write record. */
|
|
297
|
+
export interface WriteBoundaryAttributionDowngrade {
|
|
298
|
+
reason: WriteBoundaryAttributionDowngradeReason;
|
|
299
|
+
/** Tool whose successful call opened the record, or null for a non-tool cause. */
|
|
300
|
+
tool: string | null;
|
|
301
|
+
/** Exact worker call when telemetry supplied one, or null for a non-tool cause. */
|
|
302
|
+
toolCallId: string | null;
|
|
303
|
+
/** Run that observed the cause, when one reached dispatch. */
|
|
304
|
+
runId: string | null;
|
|
305
|
+
/** Fleet step whose record was downgraded. */
|
|
306
|
+
stepId: string | null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** One operator-facing explanation shared by live and durable surfaces. */
|
|
310
|
+
export function describeWriteBoundaryAttributionDowngrade(downgrade: WriteBoundaryAttributionDowngrade): string {
|
|
311
|
+
if (downgrade.reason === "opaque_tool_succeeded") {
|
|
312
|
+
const tool = downgrade.tool ?? "unknown";
|
|
313
|
+
const call = downgrade.toolCallId === null ? "" : ` (call ${downgrade.toolCallId})`;
|
|
314
|
+
return `a successful '${tool}' call${call} can write paths its arguments do not name`;
|
|
315
|
+
}
|
|
316
|
+
if (downgrade.reason === "incomplete_tool_telemetry") {
|
|
317
|
+
return downgrade.runId === null
|
|
318
|
+
? "tool telemetry was incomplete"
|
|
319
|
+
: `run '${downgrade.runId}' reported incomplete tool telemetry`;
|
|
320
|
+
}
|
|
321
|
+
if (downgrade.reason === "code_step_untracked") {
|
|
322
|
+
return downgrade.stepId === null
|
|
323
|
+
? "a code step ran through the untracked command runner"
|
|
324
|
+
: `code step '${downgrade.stepId}' ran through the untracked command runner`;
|
|
325
|
+
}
|
|
326
|
+
return downgrade.stepId === null
|
|
327
|
+
? "no usable write record was available"
|
|
328
|
+
: `step '${downgrade.stepId}' supplied no usable write record`;
|
|
286
329
|
}
|
|
287
330
|
|
|
288
331
|
export interface WriteBoundaryVerdict {
|
|
@@ -307,6 +350,8 @@ export interface WriteBoundaryVerdict {
|
|
|
307
350
|
unattributed: ReadonlyArray<string>;
|
|
308
351
|
/** Whether every step in the window offered an enumerable write record. */
|
|
309
352
|
attributionComplete: boolean;
|
|
353
|
+
/** Why attribution was incomplete, including the causal tool call when known. */
|
|
354
|
+
attributionDowngrades: ReadonlyArray<WriteBoundaryAttributionDowngrade>;
|
|
310
355
|
rolledBack: ReadonlyArray<WriteBoundaryRollback>;
|
|
311
356
|
unrecoverable: ReadonlyArray<WriteBoundaryUnrecoverable>;
|
|
312
357
|
status: WriteBoundaryStatus;
|
|
@@ -417,6 +462,19 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
|
|
|
417
462
|
const changes = diffWorkspace(input.snapshot);
|
|
418
463
|
const outside = changes.filter((change) => !writeBoundaryCovers(allow, change.path));
|
|
419
464
|
const attributionComplete = input.attribution?.complete === true;
|
|
465
|
+
const attributionDowngrades: ReadonlyArray<WriteBoundaryAttributionDowngrade> = attributionComplete
|
|
466
|
+
? []
|
|
467
|
+
: input.attribution?.downgrades !== undefined && input.attribution.downgrades.length > 0
|
|
468
|
+
? input.attribution.downgrades.map((entry) => ({ ...entry }))
|
|
469
|
+
: [
|
|
470
|
+
{
|
|
471
|
+
reason: "write_record_unavailable",
|
|
472
|
+
tool: null,
|
|
473
|
+
toolCallId: null,
|
|
474
|
+
runId: null,
|
|
475
|
+
stepId: input.stepIds.length === 1 ? (input.stepIds[0] ?? null) : null,
|
|
476
|
+
},
|
|
477
|
+
];
|
|
420
478
|
const recorded = new Set<string>();
|
|
421
479
|
for (const target of input.attribution?.recorded ?? []) {
|
|
422
480
|
const relativeTarget = repoRelative(input.snapshot.root, target);
|
|
@@ -457,7 +515,9 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
|
|
|
457
515
|
.join("; ")}.`,
|
|
458
516
|
attributionComplete
|
|
459
517
|
? "Each was blamed on this window because a run in it recorded writing that path."
|
|
460
|
-
:
|
|
518
|
+
: `Attribution was incomplete because ${attributionDowngrades
|
|
519
|
+
.map(describeWriteBoundaryAttributionDowngrade)
|
|
520
|
+
.join("; ")}, so every change outside the declaration was blamed on this window.`,
|
|
461
521
|
"If the change was legitimate, widen the step's `writes:` declaration to cover it.",
|
|
462
522
|
];
|
|
463
523
|
const concurrentDetail =
|
|
@@ -482,6 +542,7 @@ export function enforceWriteBoundary(input: EnforceWriteBoundaryInput): WriteBou
|
|
|
482
542
|
violations: violations.map((change) => change.path),
|
|
483
543
|
unattributed: unattributed.map((change) => change.path),
|
|
484
544
|
attributionComplete,
|
|
545
|
+
attributionDowngrades,
|
|
485
546
|
rolledBack,
|
|
486
547
|
unrecoverable,
|
|
487
548
|
status,
|
|
@@ -3,6 +3,11 @@ import { join } from "node:path";
|
|
|
3
3
|
import { assertSafeId } from "../../../core/safe-id.js";
|
|
4
4
|
import { safeResourceWrite } from "../../../core/safe-resource-write.js";
|
|
5
5
|
import type { EvalArtifactV4, EvalTokenAccountingV4 } from "../schema/artifact.js";
|
|
6
|
+
import { assertEvalBehaviorReferencesVerdictV1, parseEvalBehaviorVerdictV1 } from "../schema/behavioral.js";
|
|
7
|
+
import { parseEvalBehaviorMetricsV1 } from "../schema/behavioral-metrics.js";
|
|
8
|
+
import { parseEvalExecutionEnvelopeV1, parseEvalExecutionMatrixDimensionsV1 } from "../schema/execution-envelope.js";
|
|
9
|
+
import { parseEvalServingConfigurationV1 } from "../schema/serving.js";
|
|
10
|
+
import { parseEvalVerdictEnvelopeV1 } from "../schema/verdict.js";
|
|
6
11
|
import { evalRoot } from "../store.js";
|
|
7
12
|
import { redactArtifactForStorage } from "./redact.js";
|
|
8
13
|
|
|
@@ -45,6 +50,14 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
|
|
|
45
50
|
const summary = asRecord(value.summary, `${source}.summary`);
|
|
46
51
|
const matrix = asRecord(value.matrix, `${source}.matrix`);
|
|
47
52
|
const suite = asRecord(value.suite, `${source}.suite`);
|
|
53
|
+
const servingConfiguration =
|
|
54
|
+
value.servingConfiguration === undefined
|
|
55
|
+
? undefined
|
|
56
|
+
: parseEvalServingConfigurationV1(value.servingConfiguration, `${source}.servingConfiguration`);
|
|
57
|
+
const aggregates =
|
|
58
|
+
value.aggregates === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: (readArray(value, source, "aggregates") as NonNullable<EvalArtifactV4["aggregates"]>);
|
|
48
61
|
return {
|
|
49
62
|
version: 4,
|
|
50
63
|
evalId: readString(value, source, "evalId"),
|
|
@@ -62,7 +75,11 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
|
|
|
62
75
|
target: readString(matrix, `${source}.matrix`, "target"),
|
|
63
76
|
model: readNullableString(matrix, `${source}.matrix`, "model"),
|
|
64
77
|
thinking: readNullableString(matrix, `${source}.matrix`, "thinking"),
|
|
78
|
+
...(matrix.dimensions === undefined
|
|
79
|
+
? {}
|
|
80
|
+
: { dimensions: parseEvalExecutionMatrixDimensionsV1(matrix.dimensions, `${source}.matrix.dimensions`) }),
|
|
65
81
|
},
|
|
82
|
+
...(servingConfiguration === undefined ? {} : { servingConfiguration }),
|
|
66
83
|
summary: {
|
|
67
84
|
runs: readNumber(summary, `${source}.summary`, "runs"),
|
|
68
85
|
passed: readNumber(summary, `${source}.summary`, "passed"),
|
|
@@ -71,6 +88,7 @@ export function parseEvalArtifactV4(value: unknown, source: string): EvalArtifac
|
|
|
71
88
|
tokens: parseTokenAccounting(summary.tokens, `${source}.summary.tokens`),
|
|
72
89
|
wallTimeMs: readNumber(summary, `${source}.summary`, "wallTimeMs"),
|
|
73
90
|
},
|
|
91
|
+
...(aggregates === undefined ? {} : { aggregates }),
|
|
74
92
|
results: readArray(value, source, "results").map((entry, index) => parseResult(entry, `${source}.results[${index}]`)),
|
|
75
93
|
};
|
|
76
94
|
}
|
|
@@ -108,6 +126,46 @@ function parseTokenAccounting(value: unknown, source: string): EvalTokenAccounti
|
|
|
108
126
|
function parseResult(value: unknown, source: string): EvalArtifactV4["results"][number] {
|
|
109
127
|
const record = asRecord(value, source);
|
|
110
128
|
const target = asRecord(record.target, `${source}.target`);
|
|
129
|
+
const verdict =
|
|
130
|
+
record.verdict === undefined ? undefined : parseEvalVerdictEnvelopeV1(record.verdict, `${source}.verdict`);
|
|
131
|
+
const behavioral =
|
|
132
|
+
record.behavioral === undefined ? undefined : parseEvalBehaviorVerdictV1(record.behavioral, `${source}.behavioral`);
|
|
133
|
+
const behavioralMetrics =
|
|
134
|
+
record.behavioralMetrics === undefined
|
|
135
|
+
? undefined
|
|
136
|
+
: parseEvalBehaviorMetricsV1(record.behavioralMetrics, `${source}.behavioralMetrics`);
|
|
137
|
+
const executionEnvelope =
|
|
138
|
+
record.executionEnvelope === undefined
|
|
139
|
+
? undefined
|
|
140
|
+
: parseEvalExecutionEnvelopeV1(record.executionEnvelope, `${source}.executionEnvelope`);
|
|
141
|
+
if (behavioral !== undefined && verdict === undefined) {
|
|
142
|
+
throw new Error(`${source}.behavioral: sibling document requires a verdict`);
|
|
143
|
+
}
|
|
144
|
+
if (behavioral !== undefined && verdict !== undefined) {
|
|
145
|
+
assertEvalBehaviorReferencesVerdictV1(behavioral, verdict, `${source}.behavioral`);
|
|
146
|
+
}
|
|
147
|
+
if (behavioralMetrics !== undefined) {
|
|
148
|
+
if (behavioral === undefined) throw new Error(`${source}.behavioralMetrics: requires a behavioral verdict`);
|
|
149
|
+
if (behavioralMetrics.scenarioId !== readString(record, source, "taskId")) {
|
|
150
|
+
throw new Error(`${source}.behavioralMetrics.scenarioId: conflicts with result taskId`);
|
|
151
|
+
}
|
|
152
|
+
if (
|
|
153
|
+
behavioralMetrics.target.id !== readString(target, `${source}.target`, "id") ||
|
|
154
|
+
behavioralMetrics.target.model !== readNullableString(target, `${source}.target`, "model")
|
|
155
|
+
) {
|
|
156
|
+
throw new Error(`${source}.behavioralMetrics.target: conflicts with result target`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (executionEnvelope !== undefined) {
|
|
160
|
+
if (behavioral === undefined) throw new Error(`${source}.executionEnvelope: requires a behavioral verdict`);
|
|
161
|
+
if (
|
|
162
|
+
executionEnvelope.target !== readString(target, `${source}.target`, "id") ||
|
|
163
|
+
executionEnvelope.corpus.id !== behavioral.corpus.id ||
|
|
164
|
+
executionEnvelope.corpus.version !== behavioral.corpus.version
|
|
165
|
+
) {
|
|
166
|
+
throw new Error(`${source}.executionEnvelope: conflicts with result target or behavioral corpus`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
111
169
|
return {
|
|
112
170
|
assignmentId: readNullableString(record, source, "assignmentId"),
|
|
113
171
|
terminalReceiptDigest: readNullableDigest(record, source, "terminalReceiptDigest"),
|
|
@@ -122,6 +180,10 @@ function parseResult(value: unknown, source: string): EvalArtifactV4["results"][
|
|
|
122
180
|
failureClass: readNullableString(record, source, "failureClass"),
|
|
123
181
|
metrics: asRecord(record.metrics, `${source}.metrics`) as Record<string, number | string | boolean | null>,
|
|
124
182
|
artifacts: asRecord(record.artifacts, `${source}.artifacts`) as Record<string, string | string[] | null>,
|
|
183
|
+
...(verdict === undefined ? {} : { verdict }),
|
|
184
|
+
...(behavioral === undefined ? {} : { behavioral }),
|
|
185
|
+
...(behavioralMetrics === undefined ? {} : { behavioralMetrics }),
|
|
186
|
+
...(executionEnvelope === undefined ? {} : { executionEnvelope }),
|
|
125
187
|
};
|
|
126
188
|
}
|
|
127
189
|
|