@iowarp/clio-coder 0.3.6 → 0.3.8
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 +59 -0
- package/README.md +22 -6
- package/dist/{acp-2BEHC4DL.js → acp-U67UHUK2.js} +14 -14
- package/dist/{agents-LNNFTM53.js → agents-YU6SGALZ.js} +42 -35
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-KXXFI2VS.js → auth-5ZPJOIVG.js} +32 -24
- package/dist/builtins-C6JMZVV6.js +17 -0
- package/dist/{chunk-E25LMLRW.js → chunk-26LEYJZH.js} +2 -2
- package/dist/{chunk-TSHXZTOQ.js → chunk-2HEJ2F35.js} +5 -5
- package/dist/{chunk-PBTHKCPN.js → chunk-2HFZQUHL.js} +7 -7
- package/dist/{chunk-FO5ZOVUY.js → chunk-3BINW3FP.js} +5 -5
- package/dist/{chunk-4OC57DA6.js → chunk-4DGYLA73.js} +53 -2
- package/dist/{chunk-CKXWIANG.js → chunk-4SPRNWDE.js} +18 -16
- package/dist/{chunk-E2ER4LJF.js → chunk-5C3AQNDW.js} +25 -1
- package/dist/{chunk-XF5N4U5A.js → chunk-5DHKRSMQ.js} +9 -8
- package/dist/{chunk-43AOLP7E.js → chunk-5FR74PWO.js} +2 -1
- package/dist/{chunk-EKY57CSP.js → chunk-5H3GB5BO.js} +68 -772
- package/dist/{chunk-6XXKFVSN.js → chunk-5Q2VVUKB.js} +4 -4
- package/dist/{chunk-DJVECN66.js → chunk-7RFXX52T.js} +295 -3705
- package/dist/{chunk-ZXF4XRKW.js → chunk-7RGZWPB6.js} +158 -8
- package/dist/{chunk-FYYLNIL5.js → chunk-A2NJGIB3.js} +2 -2
- package/dist/{chunk-XXQNGV4M.js → chunk-A3WNZD3P.js} +548 -72
- package/dist/{chunk-VEZEGCGW.js → chunk-B5CSFE7B.js} +23 -21
- package/dist/{chunk-2SFS6XQE.js → chunk-DGSYXYMX.js} +3 -2
- package/dist/chunk-DR52UMZW.js +21 -0
- package/dist/{chunk-ZWLZP4ZT.js → chunk-DYIM5TJT.js} +98 -12
- package/dist/{chunk-QKMUKYO7.js → chunk-E77JEWSD.js} +270 -125
- package/dist/{chunk-LYF7OHWH.js → chunk-EMYUUSFG.js} +12 -467
- package/dist/{chunk-4VP4KH3K.js → chunk-EQ63NRB7.js} +8 -8
- package/dist/chunk-FBVTI2TJ.js +518 -0
- package/dist/{chunk-R46L2BIR.js → chunk-FHJEP5SW.js} +19 -25
- package/dist/{chunk-WR67VIZY.js → chunk-GN57SG4G.js} +66 -8
- package/dist/{chunk-RD5U66HV.js → chunk-GPIEI3LY.js} +9 -9
- package/dist/{chunk-PCZJO5TI.js → chunk-GU2UIAFZ.js} +13 -178
- package/dist/chunk-GWS3VEIW.js +195 -0
- package/dist/chunk-H7IXIC72.js +103 -0
- package/dist/{chunk-3BPUFZDL.js → chunk-HLE42MG7.js} +3 -3
- package/dist/{chunk-24I7BN55.js → chunk-IFBNV6H6.js} +3 -3
- package/dist/{chunk-QNQHSOLF.js → chunk-IGWKHNIQ.js} +114 -55
- package/dist/chunk-IIZWH4XA.js +172 -0
- package/dist/{chunk-AD2SYQYC.js → chunk-IJ7RPIYJ.js} +124 -6
- package/dist/chunk-J3YUBZWY.js +382 -0
- package/dist/{chunk-5JGRAMKL.js → chunk-JOZYP4GM.js} +8 -6
- package/dist/{chunk-OH3TOQTB.js → chunk-K4XHGFR5.js} +751 -15
- package/dist/{chunk-EYPA3EGJ.js → chunk-KTYTFRMB.js} +178 -14
- package/dist/chunk-LU7P4LHA.js +33 -0
- package/dist/chunk-ME6CCNFO.js +108 -0
- package/dist/chunk-MXKJU4JB.js +1100 -0
- package/dist/{chunk-RY3LY4J5.js → chunk-N22QMJKY.js} +21 -18
- package/dist/chunk-NMPKI6XL.js +3006 -0
- package/dist/chunk-NUGM5KR6.js +165 -0
- package/dist/{chunk-22NAGB7X.js → chunk-P43ETTHK.js} +5 -94
- package/dist/{chunk-NILBFAPG.js → chunk-PMDBGQSJ.js} +2 -2
- package/dist/chunk-PT7HYKEM.js +165 -0
- package/dist/chunk-RVG5JXAL.js +41 -0
- package/dist/{chunk-GEYXPTRF.js → chunk-RWSI4YD7.js} +2 -2
- package/dist/{chunk-4BPJXDWC.js → chunk-TANS5ZJS.js} +35 -19
- package/dist/{verifiers-NCBTHHN2.js → chunk-TB5666IT.js} +67 -324
- package/dist/{chunk-QM3F2GKX.js → chunk-TLQJPP24.js} +7927 -8088
- package/dist/{chunk-G7MUEIGA.js → chunk-TT36MB5S.js} +2 -1
- package/dist/chunk-TTHACPOM.js +961 -0
- package/dist/{chunk-6US73PDB.js → chunk-TYPGUK6W.js} +7 -7
- package/dist/{chunk-MFFY33HR.js → chunk-U6MBIEMB.js} +554 -209
- package/dist/chunk-VAWNZU7Z.js +242 -0
- package/dist/{chunk-IR4CFBFN.js → chunk-VCBR6CU7.js} +12 -12
- package/dist/{chunk-WHJYKASB.js → chunk-VHN4MY6O.js} +2 -2
- package/dist/{chunk-XYDYPRZI.js → chunk-VWZOAB7K.js} +10 -10
- package/dist/{chunk-ZRGEBJ4T.js → chunk-WLFILSD5.js} +48 -48
- package/dist/{chunk-K7T3E2SR.js → chunk-WNIJTQQK.js} +12 -11
- package/dist/{chunk-CYQKWTG3.js → chunk-WSB3FPX7.js} +68 -20
- package/dist/{chunk-KHSFENX2.js → chunk-WWCZ5F23.js} +116 -14
- package/dist/{chunk-XE2VEJHX.js → chunk-WXY7KU3G.js} +2 -2
- package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
- package/dist/{chunk-KOHPCX4K.js → chunk-XWSF374K.js} +5 -5
- package/dist/{chunk-CJUB2JJ2.js → chunk-YS5VLNH5.js} +10 -10
- package/dist/{chunk-ZZMN5OM4.js → chunk-ZNLWCMVZ.js} +2 -2
- package/dist/{chunk-WHGPSPT5.js → chunk-ZVJ5BLO2.js} +2 -2
- package/dist/cli/index.js +33 -31
- package/dist/{clio-M2KGYUFZ.js → clio-QVTYJ57A.js} +10 -10
- package/dist/{code-nav-GQNL7XA6.js → code-nav-FGGFIE7L.js} +5 -5
- package/dist/codewiki/build-worker.js +4 -4
- package/dist/{components-5TTYYX6G.js → components-ZFA3SAER.js} +9 -9
- package/dist/{config-XUUYQIWO.js → config-LW5IJFQN.js} +65 -55
- package/dist/{configure-IHJ7YOMV.js → configure-7XIZCOU4.js} +29 -24
- package/dist/{context-75MIWW3U.js → context-L3WL3X7K.js} +54 -43
- package/dist/{context-ZQ7SIFJV.js → context-N52ZA626.js} +30 -14
- package/dist/{context-74JLXAWD.js → context-Y6Y7QPR6.js} +12 -12
- package/dist/{context-clear-GYKWNUML.js → context-clear-MBQRLSDQ.js} +54 -43
- package/dist/{context-index-SSR5ECNE.js → context-index-HVMFQHK3.js} +5 -5
- package/dist/{context-working-set-UX5KEP4J.js → context-working-set-GS6DSO7F.js} +18 -18
- package/dist/{dispatch-runner-GIJBHNFL.js → dispatch-runner-22ZCNOM3.js} +367 -79
- package/dist/{docs-6FZSCG5B.js → docs-7LQ23DLM.js} +9 -9
- package/dist/{doctor-SVJ5BZCW.js → doctor-M7YEDGAE.js} +27 -23
- package/dist/{eval-CG6LLBLD.js → eval-BEC2WHDA.js} +73 -20
- package/dist/{evidence-ZYFIEN42.js → evidence-REJUMSKM.js} +70 -59
- package/dist/{evolve-QGEXEMDW.js → evolve-PY5ZBA5K.js} +50 -39
- package/dist/{extensions-ADGNCJJD.js → extensions-HVKU65YU.js} +7 -7
- package/dist/{fleet-S5R4ZOQY.js → fleet-7WZEWRFA.js} +236 -377
- package/dist/fleet-commands-UVHWM76J.js +70 -0
- package/dist/fleet-graph-6ULH7PES.js +125 -0
- package/dist/fleet-new-RDVJLHHH.js +48 -0
- package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-J53T6CCE.js} +5 -5
- package/dist/fleet-validate-72PC4SLA.js +79 -0
- package/dist/{init-5DRU55YR.js → init-OG3TPGQG.js} +69 -57
- package/dist/library-CNTMPLRF.js +217 -0
- package/dist/{memory-7YKKR6UC.js → memory-6IS7F275.js} +52 -41
- package/dist/{models-ZPOLRU2C.js → models-ENRJDA5W.js} +37 -31
- package/dist/{monitor-US5F5YGZ.js → monitor-XLDVO7TN.js} +79 -42
- package/dist/{orchestrator-E2AL4T5N.js → orchestrator-6KSPYRHA.js} +4126 -762
- package/dist/{paths-E7KYAQWE.js → paths-DBXMZMDU.js} +6 -6
- package/dist/registry-LG64LTF4.js +11 -0
- package/dist/{reset-KZ652EK6.js → reset-RZ4ER727.js} +12 -12
- package/dist/{run-SRNBKDWD.js → run-Y2CNK5RU.js} +89 -76
- package/dist/{share-CGZE33UP.js → share-A55GYP6Z.js} +37 -13
- package/dist/{skills-S2X4DLY5.js → skills-ALC5J6AT.js} +29 -13
- package/dist/{skills-eval-W2GGIC4R.js → skills-eval-JPBEBYQU.js} +60 -48
- package/dist/support-MIETYA5E.js +38 -0
- package/dist/{targets-54SWINWB.js → targets-VGNXIR3S.js} +46 -36
- package/dist/{terminal-lease-SAIF2OGY.js → terminal-lease-WOBR64YA.js} +4 -4
- package/dist/{uninstall-BVLWXKBT.js → uninstall-ZJF5H5ZN.js} +9 -9
- package/dist/{upgrade-JKAR27XC.js → upgrade-FUSUAGHR.js} +31 -28
- package/dist/{usage-MSAWCLX4.js → usage-N4MKVHKD.js} +140 -68
- package/dist/verifiers-YAWOJ3H2.js +336 -0
- package/dist/{verify-X5HDROLA.js → verify-LTDHYBGY.js} +10 -9
- package/dist/{wiki-generate-GUSOQ6ZP.js → wiki-generate-6M7GHTBJ.js} +75 -62
- package/dist/worker/entry.js +67 -57
- package/docs/README.md +3 -2
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +11 -6
- package/docs/built-in-agents.md +26 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +83 -3
- package/docs/configuration-and-targets.md +86 -4
- package/docs/context-engine.md +1 -1
- package/docs/development-pipeline.md +1 -1
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +3 -3
- package/docs/documentation-guide.md +3 -3
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +1 -1
- package/docs/evidence-and-memory.md +74 -10
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +4 -1
- package/docs/extensions-and-sharing.md +6 -2
- package/docs/fleet-demo-runbook.md +2 -2
- package/docs/fleet-dispatch.md +228 -16
- package/docs/git-commit-provenance.md +2 -2
- package/docs/glossary.md +22 -2
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +2 -1
- package/docs/model-catalog.md +1 -1
- package/docs/observability.md +56 -9
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -2
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +83 -65
- package/docs/resource-library.md +59 -0
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +37 -1
- package/docs/skills-marketplace.md +16 -3
- package/docs/tool-usage.md +14 -7
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +1 -2
- package/src/cli/argv.ts +5 -0
- package/src/cli/configure.ts +107 -23
- package/src/cli/doctor.ts +5 -1
- package/src/cli/evidence.ts +30 -25
- package/src/cli/fleet-commands.ts +37 -0
- package/src/cli/fleet-graph.ts +102 -0
- package/src/cli/fleet-new.ts +36 -0
- package/src/cli/fleet-preflight.ts +111 -0
- package/src/cli/fleet-validate.ts +30 -0
- package/src/cli/fleet.ts +173 -335
- package/src/cli/index.ts +3 -1
- package/src/cli/library.ts +190 -0
- package/src/cli/share.ts +13 -1
- package/src/cli/shared.ts +1 -0
- package/src/cli/targets.ts +4 -1
- package/src/cli/usage.ts +111 -19
- package/src/cli/validate-model.ts +60 -5
- package/src/core/bus-events.ts +29 -0
- package/src/core/commit-attribution.ts +4 -4
- package/src/core/config.ts +130 -0
- package/src/core/defaults.ts +81 -0
- package/src/core/path-boundary.ts +100 -0
- package/src/domains/agents/builtins/architect.md +1 -0
- package/src/domains/agents/builtins/oracle.md +33 -0
- package/src/domains/agents/catalog.ts +13 -1
- package/src/domains/agents/extension.ts +2 -11
- package/src/domains/agents/fleet-contract.ts +304 -24
- package/src/domains/agents/index.ts +14 -0
- package/src/domains/agents/recipe.ts +7 -1
- package/src/domains/agents/registry.ts +73 -5
- package/src/domains/agents/result-contract.ts +360 -15
- package/src/domains/agents/write-boundary.ts +15 -50
- package/src/domains/config/classify.ts +4 -0
- package/src/domains/context/project-rules.ts +51 -1
- package/src/domains/dispatch/active-route-planner.ts +14 -0
- package/src/domains/dispatch/assignment-reconcile.ts +22 -5
- package/src/domains/dispatch/assignment-store.ts +151 -14
- package/src/domains/dispatch/backoff.ts +2 -1
- package/src/domains/dispatch/capability-match.ts +1 -0
- package/src/domains/dispatch/checkout-writer-lease.ts +175 -0
- package/src/domains/dispatch/contract.ts +48 -0
- package/src/domains/dispatch/delegation-plan.ts +164 -0
- package/src/domains/dispatch/execution-plan.ts +76 -5
- package/src/domains/dispatch/execution-role.ts +12 -2
- package/src/domains/dispatch/execution-scheduler.ts +183 -67
- package/src/domains/dispatch/extension.ts +412 -74
- package/src/domains/dispatch/fleet-gate.ts +14 -0
- package/src/domains/dispatch/fleet-plan.ts +63 -3
- package/src/domains/dispatch/fleet-run.ts +791 -0
- package/src/domains/dispatch/gate-role-prompts.ts +47 -0
- package/src/domains/dispatch/host-verification.ts +178 -0
- package/src/domains/dispatch/index.ts +41 -1
- package/src/domains/dispatch/intent-requirements.ts +40 -0
- package/src/domains/dispatch/intent.ts +235 -0
- package/src/domains/dispatch/path-scope.ts +370 -0
- package/src/domains/dispatch/receipt-integrity.ts +9 -4
- package/src/domains/dispatch/state.ts +36 -3
- package/src/domains/dispatch/types.ts +64 -12
- package/src/domains/dispatch/validation.ts +69 -6
- package/src/domains/dispatch/write-boundary-enforcer.ts +45 -0
- package/src/domains/dispatch/write-boundary.ts +201 -22
- package/src/domains/eval/metrics/evidence.ts +79 -2
- package/src/domains/eval/runners/clio-run.ts +12 -2
- package/src/domains/evidence/build.ts +69 -11
- package/src/domains/evidence/index.ts +21 -0
- package/src/domains/evidence/provenance.ts +46 -11
- package/src/domains/evidence/trust-projection.ts +274 -0
- package/src/domains/evidence/trust-status.ts +155 -18
- package/src/domains/evidence/types.ts +4 -0
- package/src/domains/extensions/discovery.ts +88 -1
- package/src/domains/extensions/resources.ts +20 -8
- package/src/domains/extensions/state.ts +6 -2
- package/src/domains/extensions/types.ts +4 -1
- package/src/domains/lifecycle/doctor.ts +140 -1
- package/src/domains/middleware/index.ts +15 -0
- package/src/domains/middleware/watchdog.ts +281 -0
- package/src/domains/observability/contract.ts +3 -1
- package/src/domains/observability/cost.ts +12 -1
- package/src/domains/observability/extension.ts +2 -2
- package/src/domains/observability/index.ts +10 -0
- package/src/domains/observability/out-of-turn-usage.ts +223 -0
- package/src/domains/prompts/contract.ts +3 -5
- package/src/domains/providers/extension.ts +30 -2
- package/src/domains/resources/common-loader.ts +3 -0
- package/src/domains/resources/index.ts +20 -0
- package/src/domains/resources/library.ts +326 -0
- package/src/domains/resources/prompts/loader.ts +119 -14
- package/src/domains/resources/skills/marketplace.ts +37 -12
- package/src/domains/safety/policy-engine.ts +5 -5
- package/src/domains/safety/run-effects.ts +64 -1
- package/src/domains/safety/scope.ts +7 -12
- package/src/domains/session/handoff.ts +629 -0
- package/src/domains/share/archive.ts +67 -2
- package/src/engine/acp/server.ts +4 -1
- package/src/engine/prompt-templates.ts +18 -1
- package/src/engine/worker-runtime.ts +6 -3
- package/src/entry/orchestrator.ts +37 -0
- package/src/interactive/bus-notices.ts +26 -0
- package/src/interactive/chat-loop.ts +235 -1
- package/src/interactive/chat-renderer.ts +22 -0
- package/src/interactive/cost-overlay.ts +31 -3
- package/src/interactive/council-dispatch.ts +30 -0
- package/src/interactive/council-grid.ts +213 -0
- package/src/interactive/council.ts +99 -0
- package/src/interactive/dispatch-board.ts +311 -17
- package/src/interactive/fleet-run-preview.ts +307 -0
- package/src/interactive/footer/notifications.ts +219 -0
- package/src/interactive/handoff-round.ts +56 -0
- package/src/interactive/interactive-application.ts +43 -1
- package/src/interactive/interactive-event-projection.ts +23 -1
- package/src/interactive/interactive-slash-runtime.ts +52 -2
- package/src/interactive/interactive-subscriptions.ts +14 -2
- package/src/interactive/oracle.ts +179 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +6 -0
- package/src/interactive/overlay-general-openers.ts +190 -1
- package/src/interactive/overlay-key-routing.ts +17 -1
- package/src/interactive/overlay-lifecycle.ts +41 -1
- package/src/interactive/overlay-permission-lifecycle.ts +10 -0
- package/src/interactive/overlay-resource-openers.ts +11 -3
- package/src/interactive/overlay-session-lifecycle.ts +234 -2
- package/src/interactive/overlays/fleet-run-approval.ts +208 -0
- package/src/interactive/overlays/handoff-review.ts +185 -0
- package/src/interactive/overlays/library-install-confirm.ts +151 -0
- package/src/interactive/overlays/list-overlay.ts +168 -2
- package/src/interactive/overlays/settings.ts +221 -30
- package/src/interactive/overlays/side-question.ts +139 -0
- package/src/interactive/overlays/skills-hub.ts +401 -15
- package/src/interactive/side-question.ts +171 -0
- package/src/interactive/slash-commands.ts +439 -7
- package/src/interactive/slash-spec.ts +19 -6
- package/src/interactive/theme/tokens.ts +30 -0
- package/src/interactive/turn-middleware.ts +15 -1
- package/src/interactive/view/artifacts.ts +42 -9
- package/src/interactive/view/view-overlay.ts +15 -3
- package/src/interactive/watchdog-run.ts +75 -0
- package/src/interactive/worker-receipts.ts +14 -2
- package/src/interactive/worker-share.ts +56 -1
- package/src/interactive/worker-stream.ts +15 -0
- package/src/tools/bootstrap.ts +3 -0
- package/src/tools/compete-worktrees.ts +13 -79
- package/src/tools/dispatch-admission.ts +251 -18
- package/src/tools/dispatch-arguments.ts +84 -1
- package/src/tools/dispatch-plan.ts +165 -6
- package/src/tools/dispatch-runner.ts +364 -23
- package/src/tools/dispatch-types.ts +20 -1
- package/src/tools/dispatch.ts +72 -2
- package/src/tools/monitor.ts +29 -0
- package/src/tools/profiles.ts +18 -4
- package/src/tools/task-worktree.ts +238 -0
- package/src/tools/verify/authoring.ts +61 -1
- package/src/tools/verify/scripts.ts +62 -0
- package/src/tools/worker-evidence.ts +21 -14
- package/src/worker/spec-contract.ts +3 -1
- package/dist/chunk-HC4CLZ2Y.js +0 -68
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { RUN_RECEIPT_INTEGRITY_VERSION } from "../dispatch/receipt-integrity.js";
|
|
2
|
+
import {
|
|
3
|
+
type CanonicalTrustStatus,
|
|
4
|
+
retiredIntegrityVersionOf,
|
|
5
|
+
TRUST_STATUS_AXES,
|
|
6
|
+
type TRUST_STATUS_STATES,
|
|
7
|
+
type TrustArtifactReference,
|
|
8
|
+
type TrustStatusAxis,
|
|
9
|
+
} from "./trust-status.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The one projection of the canonical trust status every operator surface
|
|
13
|
+
* renders. The six-axis model stays the source of truth and is never
|
|
14
|
+
* collapsed into a score here; this module only fixes the words each state is
|
|
15
|
+
* called and the order the axes are read in, so the dispatch line, the
|
|
16
|
+
* monitor block, `evidence inspect`, `findings.md`, the Alt+W board, the
|
|
17
|
+
* receipt view, the eval bridge, and the ACP wire cannot each spell the same
|
|
18
|
+
* fact differently.
|
|
19
|
+
*
|
|
20
|
+
* Two shapes leave this module. `formatTrustSummary` is the compact human
|
|
21
|
+
* line: it answers who claims the result, what was observed, what was
|
|
22
|
+
* independently checked, and what is still unknown, without receipt
|
|
23
|
+
* internals. `summarizeTrustStatus` is the bounded, versioned machine record
|
|
24
|
+
* behind that line, carrying the axis states and typed references back to
|
|
25
|
+
* the artifacts the detailed model cites.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const TRUST_SUMMARY_VERSION = 1 as const;
|
|
29
|
+
|
|
30
|
+
/** Reference cap for the machine projection; the canonical model keeps up to 16 per axis. */
|
|
31
|
+
export const TRUST_SUMMARY_MAX_REFS = 8;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How much the run's result can be leaned on, read off the axes in a fixed
|
|
35
|
+
* order. It is a presentation tier for styling and sorting, never a score:
|
|
36
|
+
* `reviewed` is the only tier that may be styled as independently verified.
|
|
37
|
+
*/
|
|
38
|
+
export type TrustVerdict = "reviewed" | "grounded" | "unverified" | "compromised" | "unknown";
|
|
39
|
+
|
|
40
|
+
export const TRUST_VERDICTS: ReadonlyArray<TrustVerdict> = [
|
|
41
|
+
"reviewed",
|
|
42
|
+
"grounded",
|
|
43
|
+
"unverified",
|
|
44
|
+
"compromised",
|
|
45
|
+
"unknown",
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
/** Bounded, versioned machine projection with references to the detailed artifacts. */
|
|
49
|
+
export interface TrustSummaryProjection {
|
|
50
|
+
version: typeof TRUST_SUMMARY_VERSION;
|
|
51
|
+
verdict: TrustVerdict;
|
|
52
|
+
/** The compact human line, byte-identical to what the text surfaces print. */
|
|
53
|
+
text: string;
|
|
54
|
+
/** Every axis state, in canonical vocabulary, for machine consumers. */
|
|
55
|
+
axes: Record<TrustStatusAxis, string>;
|
|
56
|
+
/** Who asserts the validation fact: `<authority kind>:<id>`, or `worker` when only the run's own prose exists. */
|
|
57
|
+
claimant: string;
|
|
58
|
+
/** Axes that answer nothing, so a consumer knows what remains unknown. */
|
|
59
|
+
unknown: TrustStatusAxis[];
|
|
60
|
+
/** `<artifact kind>:<id>` pointers to the detailed records, sorted, unique, capped. */
|
|
61
|
+
refs: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
type AxisStates = { [Axis in TrustStatusAxis]: (typeof TRUST_STATUS_STATES)[Axis][number] };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The standardized word for every state. A word is what an operator reads;
|
|
68
|
+
* the state id is what a machine reads. `mediated` names the `enforced`
|
|
69
|
+
* autonomy state because that is the receipt grade and the documented
|
|
70
|
+
* meaning: Clio's own safety gate mediated the run. `inferred` names an
|
|
71
|
+
* ungrounded validation claim: the worker said it validated, nothing was
|
|
72
|
+
* observed to have run.
|
|
73
|
+
*/
|
|
74
|
+
export const TRUST_STATE_WORDS: { [Axis in TrustStatusAxis]: Record<AxisStates[Axis], string> } = {
|
|
75
|
+
artifactIntegrity: {
|
|
76
|
+
verified: "sealed",
|
|
77
|
+
failed: "seal broken",
|
|
78
|
+
absent: "no receipt",
|
|
79
|
+
unknown: "seal unchecked",
|
|
80
|
+
not_applicable: "seal not applicable",
|
|
81
|
+
},
|
|
82
|
+
validationGrounding: {
|
|
83
|
+
validated: "grounded",
|
|
84
|
+
failed: "validation failed",
|
|
85
|
+
ungrounded: "inferred: validation claimed, none observed",
|
|
86
|
+
absent: "no validation observed",
|
|
87
|
+
unknown: "validation unknown",
|
|
88
|
+
not_applicable: "validation not applicable",
|
|
89
|
+
},
|
|
90
|
+
independentReview: {
|
|
91
|
+
passed: "independently reviewed: pass",
|
|
92
|
+
failed: "independently reviewed: fail",
|
|
93
|
+
inconclusive: "independent review inconclusive",
|
|
94
|
+
not_independent: "review not independent",
|
|
95
|
+
absent: "not independently reviewed",
|
|
96
|
+
unknown: "independent review unknown",
|
|
97
|
+
not_applicable: "independent review not applicable",
|
|
98
|
+
},
|
|
99
|
+
contextProvenance: {
|
|
100
|
+
recorded: "context recorded",
|
|
101
|
+
invalid: "context record invalid",
|
|
102
|
+
absent: "context not recorded",
|
|
103
|
+
unknown: "context unknown",
|
|
104
|
+
not_applicable: "context not applicable",
|
|
105
|
+
},
|
|
106
|
+
autonomyEnforcement: {
|
|
107
|
+
enforced: "mediated",
|
|
108
|
+
approximated: "approximated",
|
|
109
|
+
bypassed: "bypassed",
|
|
110
|
+
absent: "autonomy not recorded",
|
|
111
|
+
unknown: "autonomy unknown",
|
|
112
|
+
not_applicable: "autonomy not applicable",
|
|
113
|
+
},
|
|
114
|
+
completionEvidence: {
|
|
115
|
+
evidenced: "completion evidenced",
|
|
116
|
+
incomplete: "completion unevidenced",
|
|
117
|
+
limited: "completion limited",
|
|
118
|
+
absent: "completion not recorded",
|
|
119
|
+
unknown: "completion unknown",
|
|
120
|
+
not_applicable: "completion not applicable",
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/** The word for one axis state; unknown input never throws, it reads as unknown. */
|
|
125
|
+
export function trustStateWord<Axis extends TrustStatusAxis>(axis: Axis, state: string): string {
|
|
126
|
+
const words = TRUST_STATE_WORDS[axis] as Record<string, string>;
|
|
127
|
+
return words[state] ?? words.unknown ?? "unknown";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function authorityOf(status: CanonicalTrustStatus, axis: TrustStatusAxis): string | null {
|
|
131
|
+
const entry = status[axis];
|
|
132
|
+
return entry.state === "absent" ? null : `${entry.authority.kind}:${entry.authority.id}`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A seal this build does not verify because its version is retired names
|
|
137
|
+
* that version and the one in force, so an intact receipt from the previous
|
|
138
|
+
* release never reads as "seal broken" or as merely unchecked. The state
|
|
139
|
+
* behind the clause is `unknown`, which is what it is: nothing was checked.
|
|
140
|
+
*/
|
|
141
|
+
function integrityClause(status: CanonicalTrustStatus): string {
|
|
142
|
+
const retired = retiredIntegrityVersionOf(status.artifactIntegrity);
|
|
143
|
+
if (retired !== null) return `seal v${retired} retired (this build verifies v${RUN_RECEIPT_INTEGRITY_VERSION})`;
|
|
144
|
+
return trustStateWord("artifactIntegrity", status.artifactIntegrity.state);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The validation clause names its claimant, because "grounded" alone hides
|
|
149
|
+
* the difference between a host-run check and a self-reported one.
|
|
150
|
+
*/
|
|
151
|
+
function validationClause(status: CanonicalTrustStatus): string {
|
|
152
|
+
const entry = status.validationGrounding;
|
|
153
|
+
const word = trustStateWord("validationGrounding", entry.state);
|
|
154
|
+
if (entry.state === "absent") return word;
|
|
155
|
+
if (entry.state === "validated" || entry.state === "failed") return `${word} by ${entry.authority.id}`;
|
|
156
|
+
// A named external system that could not observe the run is worth naming;
|
|
157
|
+
// the historical-format placeholder is not an authority anyone can ask.
|
|
158
|
+
if (entry.state === "unknown" && entry.authority.kind !== "unknown") return `${word} (${entry.authority.id})`;
|
|
159
|
+
return word;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** External runtimes name themselves on the autonomy clause so approximation is never anonymous. */
|
|
163
|
+
function autonomyClause(status: CanonicalTrustStatus): string {
|
|
164
|
+
const entry = status.autonomyEnforcement;
|
|
165
|
+
const word = trustStateWord("autonomyEnforcement", entry.state);
|
|
166
|
+
if (entry.state !== "approximated" && entry.state !== "bypassed") return word;
|
|
167
|
+
return `${word} (${entry.authority.id})`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The compact human body. Clause order is fixed: integrity, validation with
|
|
172
|
+
* its claimant, independent review, autonomy, context, completion. Every
|
|
173
|
+
* surface prints this body verbatim, so two surfaces can only disagree by
|
|
174
|
+
* reading different canonical input.
|
|
175
|
+
*/
|
|
176
|
+
export function formatTrustSummary(status: CanonicalTrustStatus): string {
|
|
177
|
+
return [
|
|
178
|
+
integrityClause(status),
|
|
179
|
+
validationClause(status),
|
|
180
|
+
trustStateWord("independentReview", status.independentReview.state),
|
|
181
|
+
autonomyClause(status),
|
|
182
|
+
trustStateWord("contextProvenance", status.contextProvenance.state),
|
|
183
|
+
trustStateWord("completionEvidence", status.completionEvidence.state),
|
|
184
|
+
].join("; ");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The body under its versioned label, for surfaces that print one line per run. */
|
|
188
|
+
export function formatTrustSummaryLine(status: CanonicalTrustStatus): string {
|
|
189
|
+
return `trust v${TRUST_SUMMARY_VERSION}: ${formatTrustSummary(status)}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The drill-down token line: every axis in canonical state ids. This is the
|
|
194
|
+
* one machine-readable text form; `evidence inspect` and the dispatch and
|
|
195
|
+
* monitor labels all print it from here.
|
|
196
|
+
*/
|
|
197
|
+
export function formatTrustAxes(status: CanonicalTrustStatus): string {
|
|
198
|
+
return [`trust_status=v${status.version}`, ...TRUST_STATUS_AXES.map((axis) => `${axis}:${status[axis].state}`)].join(
|
|
199
|
+
" ",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isUnanswered(status: CanonicalTrustStatus, axis: TrustStatusAxis): boolean {
|
|
204
|
+
const state = status[axis].state;
|
|
205
|
+
return state === "absent" || state === "unknown";
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Read the tier off the axes in priority order. A broken seal, a bypassed
|
|
210
|
+
* gate, a failed or inferred validation, a failed or correlated review, or a
|
|
211
|
+
* contradictory context record compromises the result whatever else holds.
|
|
212
|
+
* An unchecked or missing seal leaves it unknown. Only an authenticated
|
|
213
|
+
* independent pass is `reviewed`; observed validation without one is
|
|
214
|
+
* `grounded`; a sealed receipt with nothing observed is `unverified`.
|
|
215
|
+
*/
|
|
216
|
+
export function trustVerdict(status: CanonicalTrustStatus): TrustVerdict {
|
|
217
|
+
const integrity = status.artifactIntegrity.state;
|
|
218
|
+
const validation = status.validationGrounding.state;
|
|
219
|
+
const review = status.independentReview.state;
|
|
220
|
+
const autonomy = status.autonomyEnforcement.state;
|
|
221
|
+
if (
|
|
222
|
+
integrity === "failed" ||
|
|
223
|
+
autonomy === "bypassed" ||
|
|
224
|
+
validation === "failed" ||
|
|
225
|
+
validation === "ungrounded" ||
|
|
226
|
+
review === "failed" ||
|
|
227
|
+
review === "not_independent" ||
|
|
228
|
+
status.contextProvenance.state === "invalid"
|
|
229
|
+
) {
|
|
230
|
+
return "compromised";
|
|
231
|
+
}
|
|
232
|
+
if (integrity !== "verified") return "unknown";
|
|
233
|
+
if (review === "passed") return "reviewed";
|
|
234
|
+
if (validation === "validated") return "grounded";
|
|
235
|
+
return "unverified";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function claimantOf(status: CanonicalTrustStatus): string {
|
|
239
|
+
const state = status.validationGrounding.state;
|
|
240
|
+
if (state === "absent" || state === "ungrounded") return "worker";
|
|
241
|
+
return authorityOf(status, "validationGrounding") ?? "worker";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function referenceKey(reference: TrustArtifactReference): string {
|
|
245
|
+
return `${reference.kind}:${reference.id}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Every artifact the axes cite, as bounded pointers a consumer can resolve. */
|
|
249
|
+
export function trustSummaryReferences(status: CanonicalTrustStatus): string[] {
|
|
250
|
+
const keys = new Set<string>();
|
|
251
|
+
for (const axis of TRUST_STATUS_AXES) {
|
|
252
|
+
const entry = status[axis];
|
|
253
|
+
if (entry.state === "absent") continue;
|
|
254
|
+
for (const reference of entry.artifacts) keys.add(referenceKey(reference));
|
|
255
|
+
}
|
|
256
|
+
return [...keys].sort().slice(0, TRUST_SUMMARY_MAX_REFS);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The bounded machine projection. Flat by design so a depth-capped wire keeps it whole. */
|
|
260
|
+
export function summarizeTrustStatus(status: CanonicalTrustStatus): TrustSummaryProjection {
|
|
261
|
+
const axes = Object.fromEntries(TRUST_STATUS_AXES.map((axis) => [axis, status[axis].state])) as Record<
|
|
262
|
+
TrustStatusAxis,
|
|
263
|
+
string
|
|
264
|
+
>;
|
|
265
|
+
return {
|
|
266
|
+
version: TRUST_SUMMARY_VERSION,
|
|
267
|
+
verdict: trustVerdict(status),
|
|
268
|
+
text: formatTrustSummary(status),
|
|
269
|
+
axes,
|
|
270
|
+
claimant: claimantOf(status),
|
|
271
|
+
unknown: TRUST_STATUS_AXES.filter((axis) => isUnanswered(status, axis)),
|
|
272
|
+
refs: trustSummaryReferences(status),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GateDecisionArtifact, GateDecisionVerification } from "../dispatch/gate-decisions.js";
|
|
2
|
-
import {
|
|
2
|
+
import { RUN_RECEIPT_INTEGRITY_VERSION, verifyReceiptIntegrity } from "../dispatch/receipt-integrity.js";
|
|
3
3
|
import type {
|
|
4
4
|
RunBriefingProvenance,
|
|
5
5
|
RunEnvelope,
|
|
@@ -418,6 +418,7 @@ export type PersistedRunReceiptTrustFacts = Pick<RunReceipt, "runId"> &
|
|
|
418
418
|
RunReceipt,
|
|
419
419
|
| "integrity"
|
|
420
420
|
| "verification"
|
|
421
|
+
| "hostVerification"
|
|
421
422
|
| "quality"
|
|
422
423
|
| "validationGrounding"
|
|
423
424
|
| "briefing"
|
|
@@ -458,13 +459,89 @@ function compatibilitySource(receipt: PersistedRunReceiptTrustFacts, field: stri
|
|
|
458
459
|
const DISPATCH_AUTHORITY: TrustStatusAuthority = { kind: "clio", id: "dispatch" };
|
|
459
460
|
const COMPATIBILITY_AUTHORITY: TrustStatusAuthority = { kind: "unknown", id: "historical-persisted-format" };
|
|
460
461
|
|
|
462
|
+
/**
|
|
463
|
+
* A receipt sealed under an integrity version this build no longer verifies.
|
|
464
|
+
* It is not migrated and not read as evidence, on the same terms as every
|
|
465
|
+
* prior integrity bump, but it is not a tampered receipt either: nothing was
|
|
466
|
+
* checked, so nothing failed. The outcome names both versions so an operator
|
|
467
|
+
* with a store full of last release's runs can tell a retired seal from a
|
|
468
|
+
* broken one.
|
|
469
|
+
*/
|
|
470
|
+
export interface RetiredReceiptIntegrity {
|
|
471
|
+
receiptVersion: number;
|
|
472
|
+
supportedVersion: number;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* The dispatch verifier's result, widened with the retired-format diagnosis
|
|
477
|
+
* this module puts in front of it. A failure without `retired` is the
|
|
478
|
+
* verifier's own verdict: a malformed seal, a ledger mismatch, or a digest
|
|
479
|
+
* that does not match the receipt's contents.
|
|
480
|
+
*/
|
|
481
|
+
export type ReceiptIntegrityOutcome = { ok: true } | { ok: false; reason: string; retired?: RetiredReceiptIntegrity };
|
|
482
|
+
|
|
483
|
+
/** The retired-version diagnosis for a receipt's integrity block, or null when the block is current or unreadable. */
|
|
484
|
+
export function retiredReceiptIntegrity(integrity: unknown): RetiredReceiptIntegrity | null {
|
|
485
|
+
if (!isRecord(integrity)) return null;
|
|
486
|
+
const version = integrity.version;
|
|
487
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1) return null;
|
|
488
|
+
if (version >= RUN_RECEIPT_INTEGRITY_VERSION) return null;
|
|
489
|
+
return { receiptVersion: version, supportedVersion: RUN_RECEIPT_INTEGRITY_VERSION };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/** The one sentence every surface prints for a retired seal. It never calls the receipt invalid, broken, or corrupt. */
|
|
493
|
+
export function retiredReceiptIntegrityReason(retired: RetiredReceiptIntegrity): string {
|
|
494
|
+
return `receipt integrity v${retired.receiptVersion} is retired; this build verifies v${retired.supportedVersion}; the receipt is not read as evidence`;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Verify a receipt against its ledger row, diagnosing a retired seal before
|
|
499
|
+
* the verifier can report it as a malformed one. Every trust-bearing
|
|
500
|
+
* verification goes through here so the CLI, the bundle, and the TUI receipt
|
|
501
|
+
* view agree on what a retired receipt is.
|
|
502
|
+
*/
|
|
503
|
+
export function verifyReceiptIntegrityOutcome(receipt: RunReceipt, envelope: RunEnvelope): ReceiptIntegrityOutcome {
|
|
504
|
+
const retired = retiredReceiptIntegrity(receipt.integrity);
|
|
505
|
+
if (retired !== null) return { ok: false, reason: retiredReceiptIntegrityReason(retired), retired };
|
|
506
|
+
return verifyReceiptIntegrity(receipt, envelope);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const RETIRED_INTEGRITY_SOURCE = /:integrity-v(\d+)-retired$/u;
|
|
510
|
+
|
|
511
|
+
function retiredIntegritySourceId(runId: string, receiptVersion: number): string {
|
|
512
|
+
return `run_receipt:${runId}:integrity-v${receiptVersion}-retired`;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* The retired integrity version a projected artifact-integrity axis names, or
|
|
517
|
+
* null when the axis says something else. The compatibility source id is the
|
|
518
|
+
* only place the persisted projection carries the version, so the human clause
|
|
519
|
+
* reads it back from there rather than from the receipt.
|
|
520
|
+
*/
|
|
521
|
+
export function retiredIntegrityVersionOf(status: ArtifactIntegrityStatus): number | null {
|
|
522
|
+
if (status.state !== "unknown" || status.source.kind !== "compatibility") return null;
|
|
523
|
+
const match = RETIRED_INTEGRITY_SOURCE.exec(status.source.id);
|
|
524
|
+
return match === null ? null : Number(match[1]);
|
|
525
|
+
}
|
|
526
|
+
|
|
461
527
|
/** Adapt receipt integrity verification without reading any receipt claim as self-authenticating. */
|
|
462
528
|
export function adaptReceiptIntegrityStatus(
|
|
463
529
|
receipt: PersistedRunReceiptTrustFacts | null | undefined,
|
|
464
|
-
verification?:
|
|
530
|
+
verification?: ReceiptIntegrityOutcome,
|
|
465
531
|
): ArtifactIntegrityStatus {
|
|
466
532
|
if (receipt === null || receipt === undefined) return absentTrustStatus("artifact_missing");
|
|
467
533
|
const artifacts = [receiptReference(receipt)];
|
|
534
|
+
// A retired seal was never checked, so the axis is unknown rather than
|
|
535
|
+
// failed, and the compatibility source names the version it was sealed
|
|
536
|
+
// under. `failed` stays reserved for a seal this build verified and rejected.
|
|
537
|
+
if (verification !== undefined && !verification.ok && verification.retired !== undefined) {
|
|
538
|
+
return attributed(
|
|
539
|
+
"unknown",
|
|
540
|
+
{ kind: "compatibility", id: retiredIntegritySourceId(receipt.runId, verification.retired.receiptVersion) },
|
|
541
|
+
COMPATIBILITY_AUTHORITY,
|
|
542
|
+
artifacts,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
468
545
|
if (verification === undefined || (verification.ok && receipt.integrity === undefined)) {
|
|
469
546
|
return attributed(
|
|
470
547
|
"unknown",
|
|
@@ -536,6 +613,12 @@ export function adaptRunReceiptValidationStatus(
|
|
|
536
613
|
): ValidationGroundingStatus {
|
|
537
614
|
if (receipt === null || receipt === undefined) return absentTrustStatus("artifact_missing");
|
|
538
615
|
const artifacts = validationArtifacts(receipt);
|
|
616
|
+
if (receipt.hostVerification?.status === "rejected") {
|
|
617
|
+
return attributed("failed", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
|
|
618
|
+
}
|
|
619
|
+
if (receipt.hostVerification?.status === "verified") {
|
|
620
|
+
return attributed("validated", receiptSource(receipt), { kind: "validator", id: "host-verification" }, artifacts);
|
|
621
|
+
}
|
|
539
622
|
if (qualityHasFailure(receipt.quality)) {
|
|
540
623
|
return attributed("failed", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
|
|
541
624
|
}
|
|
@@ -549,7 +632,9 @@ export function adaptRunReceiptValidationStatus(
|
|
|
549
632
|
if (qualityHasValidation(receipt.quality)) {
|
|
550
633
|
return attributed("validated", receiptSource(receipt), { kind: "validator", id: "receipt-quality" }, artifacts);
|
|
551
634
|
}
|
|
552
|
-
if (receipt.verification !== undefined)
|
|
635
|
+
if (receipt.verification !== undefined) {
|
|
636
|
+
return verificationStatus(receipt.verification, receipt, artifacts);
|
|
637
|
+
}
|
|
553
638
|
return attributed("unknown", compatibilitySource(receipt, "verification"), COMPATIBILITY_AUTHORITY, artifacts);
|
|
554
639
|
}
|
|
555
640
|
|
|
@@ -567,15 +652,46 @@ function validBoundedProjectContext(context: RunProjectContextProvenance): boole
|
|
|
567
652
|
);
|
|
568
653
|
}
|
|
569
654
|
|
|
655
|
+
/**
|
|
656
|
+
* The one structured message a `none`-tier run can still receive. The tier is
|
|
657
|
+
* the CLIO-CODER.md handbook policy; the workspace-root message is sent
|
|
658
|
+
* regardless of it, and the producer records the characters and hash of
|
|
659
|
+
* every structured message it sent (`projectContextProvenanceFor` in the
|
|
660
|
+
* dispatch extension). A none-tier block naming exactly this section, with
|
|
661
|
+
* a well-formed count and hash, is therefore a consistent record of a real
|
|
662
|
+
* message, not a contradiction. Anything else under a none tier cannot be
|
|
663
|
+
* explained by the policy: a handbook section the policy forbids, a hash
|
|
664
|
+
* with no section to hash, or a malformed count.
|
|
665
|
+
*/
|
|
666
|
+
const NONE_TIER_SECTIONS: ReadonlyArray<string> = ["workspace-root"];
|
|
667
|
+
|
|
668
|
+
function validNoneTierProjectContext(context: RunProjectContextProvenance): boolean {
|
|
669
|
+
return (
|
|
670
|
+
context.tier === "none" &&
|
|
671
|
+
context.sections !== undefined &&
|
|
672
|
+
context.sections.length === NONE_TIER_SECTIONS.length &&
|
|
673
|
+
context.sections.every((section) => NONE_TIER_SECTIONS.includes(section)) &&
|
|
674
|
+
Number.isSafeInteger(context.chars) &&
|
|
675
|
+
(context.chars ?? -1) >= 0 &&
|
|
676
|
+
typeof context.contentHash === "string" &&
|
|
677
|
+
/^[a-f0-9]{64}$/u.test(context.contentHash)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function recordedProjectContextHash(context: RunProjectContextProvenance | undefined): string | undefined {
|
|
682
|
+
if (context === undefined) return undefined;
|
|
683
|
+
if (context.tier === "bounded") return validBoundedProjectContext(context) ? context.contentHash : undefined;
|
|
684
|
+
return validNoneTierProjectContext(context) ? context.contentHash : undefined;
|
|
685
|
+
}
|
|
686
|
+
|
|
570
687
|
function contextArtifacts(receipt: PersistedRunReceiptTrustFacts): TrustArtifactReference[] {
|
|
571
688
|
const artifacts = [receiptReference(receipt)];
|
|
572
689
|
if (receipt.briefing !== undefined && validBriefing(receipt.briefing)) {
|
|
573
690
|
artifacts.push(sha256Reference("briefing", `${receipt.runId}:briefing`, receipt.briefing.contentHash));
|
|
574
691
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
);
|
|
692
|
+
const recordedHash = recordedProjectContextHash(receipt.projectContext);
|
|
693
|
+
if (recordedHash !== undefined) {
|
|
694
|
+
artifacts.push(sha256Reference("project_context", `${receipt.runId}:project-context`, recordedHash));
|
|
579
695
|
}
|
|
580
696
|
return uniqueBoundedReferences(artifacts);
|
|
581
697
|
}
|
|
@@ -595,12 +711,23 @@ export function adaptRunReceiptContextStatus(
|
|
|
595
711
|
return attributed("unknown", compatibilitySource(receipt, "projectContext"), COMPATIBILITY_AUTHORITY, artifacts);
|
|
596
712
|
}
|
|
597
713
|
if (context.tier === "none") {
|
|
598
|
-
|
|
599
|
-
|
|
714
|
+
const bare = context.chars === undefined && context.contentHash === undefined && context.sections === undefined;
|
|
715
|
+
if (bare) {
|
|
716
|
+
// Nothing structured was sent: the axis does not apply unless a
|
|
717
|
+
// briefing was recorded, which is context of its own.
|
|
718
|
+
return receipt.briefing === undefined
|
|
719
|
+
? attributed("not_applicable", source, DISPATCH_AUTHORITY, artifacts)
|
|
720
|
+
: attributed("recorded", source, DISPATCH_AUTHORITY, artifacts);
|
|
600
721
|
}
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
722
|
+
// The workspace-root message is the one thing a none-tier run receives,
|
|
723
|
+
// and its record is valid; every other none-tier content shape is a
|
|
724
|
+
// record the policy cannot account for.
|
|
725
|
+
return attributed(
|
|
726
|
+
validNoneTierProjectContext(context) ? "recorded" : "invalid",
|
|
727
|
+
source,
|
|
728
|
+
DISPATCH_AUTHORITY,
|
|
729
|
+
artifacts,
|
|
730
|
+
);
|
|
604
731
|
}
|
|
605
732
|
return attributed(validBoundedProjectContext(context) ? "recorded" : "invalid", source, DISPATCH_AUTHORITY, artifacts);
|
|
606
733
|
}
|
|
@@ -630,18 +757,19 @@ export function adaptRunReceiptAutonomyStatus(
|
|
|
630
757
|
}
|
|
631
758
|
|
|
632
759
|
export interface AdaptRunReceiptTrustOptions {
|
|
633
|
-
integrity?:
|
|
760
|
+
integrity?: ReceiptIntegrityOutcome;
|
|
634
761
|
}
|
|
635
762
|
|
|
636
763
|
function receiptClaimsAreAuthenticated(
|
|
637
764
|
receipt: PersistedRunReceiptTrustFacts | null | undefined,
|
|
638
|
-
integrity:
|
|
765
|
+
integrity: ReceiptIntegrityOutcome | undefined,
|
|
639
766
|
): boolean {
|
|
640
767
|
return receipt?.integrity !== undefined && integrity?.ok === true;
|
|
641
768
|
}
|
|
642
769
|
|
|
643
770
|
function unauthenticatedReceiptProjection(
|
|
644
771
|
receipt: PersistedRunReceiptTrustFacts | null | undefined,
|
|
772
|
+
integrity: ReceiptIntegrityOutcome | undefined,
|
|
645
773
|
): TrustStatusProjection {
|
|
646
774
|
if (receipt === null || receipt === undefined) {
|
|
647
775
|
return {
|
|
@@ -673,6 +801,15 @@ function unauthenticatedReceiptProjection(
|
|
|
673
801
|
),
|
|
674
802
|
};
|
|
675
803
|
}
|
|
804
|
+
// A retired seal leaves the receipt unread rather than rejected: its axes
|
|
805
|
+
// are absent because the format is historical, not because a check failed.
|
|
806
|
+
if (integrity !== undefined && !integrity.ok && integrity.retired !== undefined) {
|
|
807
|
+
return {
|
|
808
|
+
validationGrounding: absentTrustStatus("historical_format"),
|
|
809
|
+
contextProvenance: absentTrustStatus("historical_format"),
|
|
810
|
+
autonomyEnforcement: absentTrustStatus("historical_format"),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
676
813
|
// The artifact-integrity axis retains the failure diagnostic. No other
|
|
677
814
|
// axis reads a field from a receipt that failed authentication.
|
|
678
815
|
return {
|
|
@@ -694,7 +831,7 @@ export function adaptRunReceiptTrustStatus(
|
|
|
694
831
|
contextProvenance: adaptRunReceiptContextStatus(receipt),
|
|
695
832
|
autonomyEnforcement: adaptRunReceiptAutonomyStatus(receipt),
|
|
696
833
|
}
|
|
697
|
-
: unauthenticatedReceiptProjection(receipt);
|
|
834
|
+
: unauthenticatedReceiptProjection(receipt, options.integrity);
|
|
698
835
|
return composeTrustStatus({
|
|
699
836
|
artifactIntegrity: adaptReceiptIntegrityStatus(receipt, options.integrity),
|
|
700
837
|
...receiptProjection,
|
|
@@ -706,7 +843,7 @@ export function adaptRunReceiptTrustStatus(
|
|
|
706
843
|
}
|
|
707
844
|
|
|
708
845
|
export interface RunReceiptTrustInspection {
|
|
709
|
-
integrity:
|
|
846
|
+
integrity: ReceiptIntegrityOutcome;
|
|
710
847
|
status: CanonicalTrustStatus;
|
|
711
848
|
}
|
|
712
849
|
|
|
@@ -719,14 +856,14 @@ export function inspectRunReceiptTrustStatus(
|
|
|
719
856
|
receipt: RunReceipt | null | undefined,
|
|
720
857
|
envelope: RunEnvelope | null | undefined,
|
|
721
858
|
): RunReceiptTrustInspection {
|
|
722
|
-
let integrity:
|
|
859
|
+
let integrity: ReceiptIntegrityOutcome;
|
|
723
860
|
if (receipt === null || receipt === undefined) {
|
|
724
861
|
integrity = { ok: false, reason: "receipt unavailable" };
|
|
725
862
|
} else if (envelope === null || envelope === undefined) {
|
|
726
863
|
integrity = { ok: false, reason: "run ledger envelope unavailable" };
|
|
727
864
|
} else {
|
|
728
865
|
try {
|
|
729
|
-
integrity =
|
|
866
|
+
integrity = verifyReceiptIntegrityOutcome(receipt, envelope);
|
|
730
867
|
} catch (error) {
|
|
731
868
|
integrity = {
|
|
732
869
|
ok: false,
|
|
@@ -23,7 +23,11 @@ export const EVIDENCE_TAGS = [
|
|
|
23
23
|
"escalation",
|
|
24
24
|
"external-bypass",
|
|
25
25
|
"external-approximation",
|
|
26
|
+
"independent-review",
|
|
27
|
+
"context-provenance",
|
|
28
|
+
"completion-evidence",
|
|
26
29
|
"receipt-integrity",
|
|
30
|
+
"receipt-retired",
|
|
27
31
|
"protected-artifact",
|
|
28
32
|
"tool-loop",
|
|
29
33
|
"test-failure",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
4
4
|
import type {
|
|
@@ -39,13 +39,99 @@ function normalizeResources(value: unknown): ExtensionManifestResources {
|
|
|
39
39
|
const out: ExtensionManifestResources = {};
|
|
40
40
|
const skills = trimString(value.skills);
|
|
41
41
|
const prompts = trimString(value.prompts);
|
|
42
|
+
const agents = trimString(value.agents);
|
|
43
|
+
const fleets = trimString(value.fleets);
|
|
42
44
|
const themes = trimString(value.themes);
|
|
43
45
|
if (skills) out.skills = skills;
|
|
44
46
|
if (prompts) out.prompts = prompts;
|
|
47
|
+
if (agents) out.agents = agents;
|
|
48
|
+
if (fleets) out.fleets = fleets;
|
|
45
49
|
if (themes) out.themes = themes;
|
|
46
50
|
return out;
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
function contained(root: string, candidate: string): boolean {
|
|
54
|
+
const relative = path.relative(root, candidate);
|
|
55
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateResourceTree(
|
|
59
|
+
root: string,
|
|
60
|
+
resourcePath: string,
|
|
61
|
+
kind: string,
|
|
62
|
+
diagnostics: ExtensionDiagnostic[],
|
|
63
|
+
): void {
|
|
64
|
+
if (path.isAbsolute(resourcePath)) {
|
|
65
|
+
diagnostics.push({ type: "error", message: `resources.${kind} must be relative`, path: resourcePath });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const resolvedRoot = path.resolve(root);
|
|
69
|
+
const resolved = path.resolve(resolvedRoot, resourcePath);
|
|
70
|
+
if (!contained(resolvedRoot, resolved) || resolved === resolvedRoot) {
|
|
71
|
+
diagnostics.push({ type: "error", message: `resources.${kind} escapes the extension root`, path: resourcePath });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
let canonicalRoot: string;
|
|
75
|
+
let canonicalResource: string;
|
|
76
|
+
try {
|
|
77
|
+
canonicalRoot = realpathSync(resolvedRoot);
|
|
78
|
+
canonicalResource = realpathSync(resolved);
|
|
79
|
+
if (!lstatSync(resolved).isDirectory()) {
|
|
80
|
+
diagnostics.push({ type: "error", message: `resources.${kind} is not a directory`, path: resolved });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
diagnostics.push({
|
|
85
|
+
type: "error",
|
|
86
|
+
message: `resources.${kind} cannot be resolved: ${error instanceof Error ? error.message : String(error)}`,
|
|
87
|
+
path: resolved,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!contained(canonicalRoot, canonicalResource)) {
|
|
92
|
+
diagnostics.push({ type: "error", message: `resources.${kind} escapes through a symbolic link`, path: resolved });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pending = [resolved];
|
|
97
|
+
while (pending.length > 0) {
|
|
98
|
+
const directory = pending.pop();
|
|
99
|
+
if (!directory) continue;
|
|
100
|
+
for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
101
|
+
const candidate = path.join(directory, entry.name);
|
|
102
|
+
if (entry.isSymbolicLink()) {
|
|
103
|
+
try {
|
|
104
|
+
if (!contained(canonicalRoot, realpathSync(candidate))) {
|
|
105
|
+
diagnostics.push({
|
|
106
|
+
type: "error",
|
|
107
|
+
message: `resources.${kind} contains an escaping symbolic link`,
|
|
108
|
+
path: candidate,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
diagnostics.push({
|
|
113
|
+
type: "error",
|
|
114
|
+
message: `resources.${kind} contains an unresolved symbolic link: ${error instanceof Error ? error.message : String(error)}`,
|
|
115
|
+
path: candidate,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
} else if (entry.isDirectory()) {
|
|
119
|
+
pending.push(candidate);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function validateResourceRoots(
|
|
126
|
+
root: string,
|
|
127
|
+
resources: ExtensionManifestResources,
|
|
128
|
+
diagnostics: ExtensionDiagnostic[],
|
|
129
|
+
): void {
|
|
130
|
+
for (const [kind, resourcePath] of Object.entries(resources)) {
|
|
131
|
+
if (resourcePath) validateResourceTree(root, resourcePath, kind, diagnostics);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
49
135
|
function stringArray(value: unknown): string[] | undefined {
|
|
50
136
|
if (!Array.isArray(value)) return undefined;
|
|
51
137
|
const out = value.map((entry) => trimString(entry)).filter((entry): entry is string => entry !== undefined);
|
|
@@ -123,6 +209,7 @@ export function loadManifestFromRoot(root: string): ExtensionCandidate {
|
|
|
123
209
|
}
|
|
124
210
|
try {
|
|
125
211
|
const parsed = parseExtensionManifest(readJsonOrYaml(manifestPath), manifestPath);
|
|
212
|
+
if (parsed.manifest) validateResourceRoots(root, parsed.manifest.resources, parsed.diagnostics);
|
|
126
213
|
return {
|
|
127
214
|
path: root,
|
|
128
215
|
manifestPath,
|