@sentropic/h2a 0.85.25 → 0.87.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/bin.js +51 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli-command-map.d.ts +119 -0
- package/dist/cli-command-map.d.ts.map +1 -0
- package/dist/cli-command-map.js +396 -0
- package/dist/cli-command-map.js.map +1 -0
- package/dist/cli-contract.d.ts.map +1 -1
- package/dist/cli-contract.js +39 -1
- package/dist/cli-contract.js.map +1 -1
- package/dist/cli.d.ts +25 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +356 -7
- package/dist/cli.js.map +1 -1
- package/dist/identity.d.ts +37 -3
- package/dist/identity.d.ts.map +1 -1
- package/dist/identity.js +0 -0
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/runtime/enrollment/ceremony.d.ts +542 -0
- package/dist/runtime/enrollment/ceremony.d.ts.map +1 -0
- package/dist/runtime/enrollment/ceremony.js +777 -0
- package/dist/runtime/enrollment/ceremony.js.map +1 -0
- package/dist/runtime/enrollment/index.d.ts +16 -0
- package/dist/runtime/enrollment/index.d.ts.map +1 -0
- package/dist/runtime/enrollment/index.js +16 -0
- package/dist/runtime/enrollment/index.js.map +1 -0
- package/dist/runtime/feed/descriptors.d.ts +261 -0
- package/dist/runtime/feed/descriptors.d.ts.map +1 -0
- package/dist/runtime/feed/descriptors.js +425 -0
- package/dist/runtime/feed/descriptors.js.map +1 -0
- package/dist/runtime/feed/index.d.ts +12 -0
- package/dist/runtime/feed/index.d.ts.map +1 -0
- package/dist/runtime/feed/index.js +12 -0
- package/dist/runtime/feed/index.js.map +1 -0
- package/dist/runtime/identity/index.d.ts +3 -3
- package/dist/runtime/identity/index.d.ts.map +1 -1
- package/dist/runtime/identity/index.js +2 -2
- package/dist/runtime/identity/index.js.map +1 -1
- package/dist/runtime/identity/live.d.ts +68 -0
- package/dist/runtime/identity/live.d.ts.map +1 -1
- package/dist/runtime/identity/live.js +78 -2
- package/dist/runtime/identity/live.js.map +1 -1
- package/dist/runtime/identity/readers.d.ts +89 -5
- package/dist/runtime/identity/readers.d.ts.map +1 -1
- package/dist/runtime/identity/readers.js +242 -33
- package/dist/runtime/identity/readers.js.map +1 -1
- package/dist/runtime/local-files/presence.d.ts +16 -1
- package/dist/runtime/local-files/presence.d.ts.map +1 -1
- package/dist/runtime/local-files/presence.js +32 -20
- package/dist/runtime/local-files/presence.js.map +1 -1
- package/dist/runtime/loop/index.d.ts +5 -0
- package/dist/runtime/loop/index.d.ts.map +1 -1
- package/dist/runtime/loop/index.js +48 -5
- package/dist/runtime/loop/index.js.map +1 -1
- package/dist/runtime/mcp/agent-launch.d.ts +15 -1
- package/dist/runtime/mcp/agent-launch.d.ts.map +1 -1
- package/dist/runtime/mcp/agent-launch.js +45 -4
- package/dist/runtime/mcp/agent-launch.js.map +1 -1
- package/dist/runtime/mcp/index.d.ts +1 -1
- package/dist/runtime/mcp/index.d.ts.map +1 -1
- package/dist/runtime/mcp/index.js +1 -1
- package/dist/runtime/mcp/index.js.map +1 -1
- package/dist/runtime/mcp/server.d.ts +3 -1
- package/dist/runtime/mcp/server.d.ts.map +1 -1
- package/dist/runtime/mcp/server.js +7 -2
- package/dist/runtime/mcp/server.js.map +1 -1
- package/dist/runtime/mcp/sessions.d.ts +19 -0
- package/dist/runtime/mcp/sessions.d.ts.map +1 -1
- package/dist/runtime/mcp/sessions.js +44 -2
- package/dist/runtime/mcp/sessions.js.map +1 -1
- package/dist/runtime/mcp/stdio.d.ts +8 -0
- package/dist/runtime/mcp/stdio.d.ts.map +1 -1
- package/dist/runtime/mcp/stdio.js +42 -0
- package/dist/runtime/mcp/stdio.js.map +1 -1
- package/dist/runtime/mcp-registry/broker.d.ts +175 -0
- package/dist/runtime/mcp-registry/broker.d.ts.map +1 -0
- package/dist/runtime/mcp-registry/broker.js +203 -0
- package/dist/runtime/mcp-registry/broker.js.map +1 -0
- package/dist/runtime/mcp-registry/broker.typecheck.d.ts +2 -0
- package/dist/runtime/mcp-registry/broker.typecheck.d.ts.map +1 -0
- package/dist/runtime/mcp-registry/broker.typecheck.js +48 -0
- package/dist/runtime/mcp-registry/broker.typecheck.js.map +1 -0
- package/dist/runtime/mirror/accept.d.ts +43 -5
- package/dist/runtime/mirror/accept.d.ts.map +1 -1
- package/dist/runtime/mirror/accept.js +55 -7
- package/dist/runtime/mirror/accept.js.map +1 -1
- package/dist/runtime/mirror/build.d.ts +23 -4
- package/dist/runtime/mirror/build.d.ts.map +1 -1
- package/dist/runtime/mirror/build.js +34 -4
- package/dist/runtime/mirror/build.js.map +1 -1
- package/dist/runtime/mirror/index.d.ts +3 -0
- package/dist/runtime/mirror/index.d.ts.map +1 -1
- package/dist/runtime/mirror/index.js +3 -0
- package/dist/runtime/mirror/index.js.map +1 -1
- package/dist/runtime/mirror/ingest.d.ts +109 -0
- package/dist/runtime/mirror/ingest.d.ts.map +1 -0
- package/dist/runtime/mirror/ingest.js +117 -0
- package/dist/runtime/mirror/ingest.js.map +1 -0
- package/dist/runtime/mirror/push-daemon.d.ts +376 -0
- package/dist/runtime/mirror/push-daemon.d.ts.map +1 -0
- package/dist/runtime/mirror/push-daemon.js +644 -0
- package/dist/runtime/mirror/push-daemon.js.map +1 -0
- package/dist/runtime/mirror/sanitize.d.ts +477 -0
- package/dist/runtime/mirror/sanitize.d.ts.map +1 -0
- package/dist/runtime/mirror/sanitize.js +625 -0
- package/dist/runtime/mirror/sanitize.js.map +1 -0
- package/dist/runtime/mirror/serve.d.ts.map +1 -1
- package/dist/runtime/mirror/serve.js +104 -24
- package/dist/runtime/mirror/serve.js.map +1 -1
- package/dist/runtime/reporting/report-ai.d.ts.map +1 -1
- package/dist/runtime/reporting/report-ai.js +1 -0
- package/dist/runtime/reporting/report-ai.js.map +1 -1
- package/dist/runtime/reporting/stdin.d.ts +2 -0
- package/dist/runtime/reporting/stdin.d.ts.map +1 -0
- package/dist/runtime/reporting/stdin.js +8 -0
- package/dist/runtime/reporting/stdin.js.map +1 -0
- package/dist/status-surface.d.ts +167 -0
- package/dist/status-surface.d.ts.map +1 -0
- package/dist/status-surface.js +670 -0
- package/dist/status-surface.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -1
- package/dist/vendor/harness/skills/manifest.d.ts.map +1 -1
- package/dist/vendor/harness/skills/manifest.js +2 -0
- package/dist/vendor/harness/skills/manifest.js.map +1 -1
- package/focus-app/client/_app/immutable/assets/{0.BdzHFb9q.css → 0.CgEVz9YW.css} +1 -1
- package/focus-app/client/_app/immutable/assets/0.CgEVz9YW.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/0.CgEVz9YW.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css +1 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/2.DaTmt6o7.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css +1 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/3.vWQUAHjz.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css +1 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/AppShell.CDPwj3zU.css.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{Cj6wo28J.js → 1NK_OeRJ.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/1NK_OeRJ.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/1NK_OeRJ.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{Bs3f1UbD.js → B1veta3r.js} +2 -2
- package/focus-app/client/_app/immutable/chunks/B1veta3r.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/B1veta3r.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{DEmooz0s.js → BZ4lcC0v.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/BZ4lcC0v.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/BZ4lcC0v.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js +2 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bu7F9NOv.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js +1 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/C6KhNEX3.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js +1 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DIljKPJN.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js +2 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/iXQate3J.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/{ZxFklh9T.js → kCE9wz0g.js} +1 -1
- package/focus-app/client/_app/immutable/chunks/kCE9wz0g.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/kCE9wz0g.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js +1 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/x-hq5fDG.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js +2 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js.br +0 -0
- package/focus-app/client/_app/immutable/entry/app.C1sDdCg5.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js +1 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js.br +2 -0
- package/focus-app/client/_app/immutable/entry/start.X3j_A5RN.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js +5 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/0.gLl44WzG.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js +1 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/1.DUrghq2x.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js +1 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/2.Bp7JE7Pz.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js +12 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/3.Cml8enOG.js.gz +0 -0
- package/focus-app/client/_app/version.json +1 -1
- package/focus-app/client/_app/version.json.br +0 -0
- package/focus-app/client/_app/version.json.gz +0 -0
- package/focus-app/h2a-focus.json +108 -80
- package/focus-app/handler.js +7 -7
- package/focus-app/index.js +7 -7
- package/focus-app/server/chunks/chunks/AppShell.js-6Pi87dO0.js +1353 -0
- package/focus-app/server/chunks/chunks/AppShell.js-6Pi87dO0.js.map +1 -0
- package/focus-app/server/chunks/chunks/agent-memory-dossier.js-nmFG_rgx.js +1036 -0
- package/focus-app/server/chunks/chunks/agent-memory-dossier.js-nmFG_rgx.js.map +1 -0
- package/focus-app/server/chunks/chunks/{exports.js-DUTkELZq.js → exports.js-CdqPbjm1.js} +3 -3
- package/focus-app/server/chunks/chunks/{exports.js-DUTkELZq.js.map → exports.js-CdqPbjm1.js.map} +1 -1
- package/focus-app/server/chunks/chunks/h2a-bus.js-bLEJ_Adi.js +233 -0
- package/focus-app/server/chunks/chunks/h2a-bus.js-bLEJ_Adi.js.map +1 -0
- package/focus-app/server/chunks/chunks/{index.js-Di21TltP.js → index.js-laGHLarB.js} +5 -2
- package/focus-app/server/chunks/chunks/index.js-laGHLarB.js.map +1 -0
- package/focus-app/server/chunks/chunks/{internal.js-33vbd70h.js → internal.js-C6T8nHkl.js} +4 -4
- package/focus-app/server/chunks/chunks/{internal.js-33vbd70h.js.map → internal.js-C6T8nHkl.js.map} +1 -1
- package/focus-app/server/chunks/chunks/{root.js-DO1m-gJA.js → root.js-CtVQOgDw.js} +2 -2
- package/focus-app/server/chunks/chunks/{root.js-DO1m-gJA.js.map → root.js-CtVQOgDw.js.map} +1 -1
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/{_server.ts.js-j0NNcOSZ.js → _server.ts.js-hrbMnsV0.js} +13 -42
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-hrbMnsV0.js.map +1 -0
- package/focus-app/server/chunks/entries/endpoints/api/dossiers/agent-memory/include/_server.ts.js-Dw4odGdV.js +138 -0
- package/focus-app/server/chunks/entries/endpoints/api/dossiers/agent-memory/include/_server.ts.js-Dw4odGdV.js.map +1 -0
- package/focus-app/server/chunks/entries/endpoints/api/h2a/targets/_server.ts.js-BLjYv7vg.js +41 -0
- package/focus-app/server/chunks/entries/endpoints/api/h2a/targets/_server.ts.js-BLjYv7vg.js.map +1 -0
- package/focus-app/server/chunks/entries/fallbacks/{error.svelte.js-Dj3sETSX.js → error.svelte.js-RbeaNF5a.js} +4 -4
- package/focus-app/server/chunks/entries/fallbacks/{error.svelte.js-Dj3sETSX.js.map → error.svelte.js-RbeaNF5a.js.map} +1 -1
- package/focus-app/server/chunks/entries/pages/{_layout.svelte.js-BoLsbuC2.js → _layout.svelte.js-e1E_I04z.js} +2 -2
- package/focus-app/server/chunks/entries/pages/{_layout.svelte.js-BoLsbuC2.js.map → _layout.svelte.js-e1E_I04z.js.map} +1 -1
- package/focus-app/server/chunks/entries/pages/{_page.svelte.js-QDldMYhk.js → _page.svelte.js-DbaEG23x.js} +31 -1373
- package/focus-app/server/chunks/entries/pages/_page.svelte.js-DbaEG23x.js.map +1 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.server.ts.js-CFC-wn6U.js +88 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.server.ts.js-CFC-wn6U.js.map +1 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.svelte.js-BpR26NMa.js +1574 -0
- package/focus-app/server/chunks/entries/pages/dossier/agent-memory/_page.svelte.js-BpR26NMa.js.map +1 -0
- package/focus-app/server/chunks/{handler-Cp_5hLyY.js → handler-Be3dyNi0.js} +3 -3
- package/focus-app/server/chunks/{handler-Cp_5hLyY.js.map → handler-Be3dyNi0.js.map} +1 -1
- package/focus-app/server/chunks/{index.js-CBh7Wjqp.js → index.js-siUxMMnK.js} +3 -3
- package/focus-app/server/chunks/{index.js-CBh7Wjqp.js.map → index.js-siUxMMnK.js.map} +1 -1
- package/focus-app/server/chunks/manifest.js-CXJqSHHn.js +78 -0
- package/focus-app/server/chunks/manifest.js-CXJqSHHn.js.map +1 -0
- package/focus-app/server/chunks/nodes/0.js-leU6sfRR.js +9 -0
- package/focus-app/server/chunks/nodes/{0.js-4wlmdsk8.js.map → 0.js-leU6sfRR.js.map} +1 -1
- package/focus-app/server/chunks/nodes/1.js-os6QXm6B.js +9 -0
- package/focus-app/server/chunks/nodes/{1.js-B-LpY-lC.js.map → 1.js-os6QXm6B.js.map} +1 -1
- package/focus-app/server/chunks/nodes/2.js-DAaLjh93.js +19 -0
- package/focus-app/server/chunks/nodes/2.js-DAaLjh93.js.map +1 -0
- package/focus-app/server/chunks/nodes/3.js-Cmgi6Uy0.js +18 -0
- package/focus-app/server/chunks/nodes/3.js-Cmgi6Uy0.js.map +1 -0
- package/package.json +5 -5
- package/skills/h2a-run/SKILL.md +100 -0
- package/skills/harness/SKILL.md +3 -1
- package/skills/harness/review/SKILL.md +99 -8
- package/skills/harness/security/SKILL.md +55 -0
- package/skills/harness/track-report/SKILL.md +103 -0
- package/skills/harness/using-harness/SKILL.md +3 -1
- package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.br +0 -2
- package/focus-app/client/_app/immutable/assets/0.BdzHFb9q.css.gz +0 -0
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css +0 -1
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.br +0 -0
- package/focus-app/client/_app/immutable/assets/2.Ddxq02y4.css.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bs3f1UbD.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js +0 -1
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Bv8OL8ib.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js +0 -2
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/CVq_IFFr.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/Cj6wo28J.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DEmooz0s.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js +0 -2
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/DQIRXgdC.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/ZxFklh9T.js.gz +0 -0
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js +0 -1
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.br +0 -0
- package/focus-app/client/_app/immutable/chunks/_wRuVGPs.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js +0 -2
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.br +0 -0
- package/focus-app/client/_app/immutable/entry/app.6DKouWRt.js.gz +0 -0
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js +0 -1
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.br +0 -2
- package/focus-app/client/_app/immutable/entry/start.DlLmkUb7.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js +0 -5
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/0.CuSOF0Of.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js +0 -1
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/1.D39jdnqm.js.gz +0 -0
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js +0 -1
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.br +0 -0
- package/focus-app/client/_app/immutable/nodes/2.CnU8ifRl.js.gz +0 -0
- package/focus-app/server/chunks/chunks/index.js-Di21TltP.js.map +0 -1
- package/focus-app/server/chunks/entries/endpoints/api/decisions/inject/_server.ts.js-j0NNcOSZ.js.map +0 -1
- package/focus-app/server/chunks/entries/pages/_page.svelte.js-QDldMYhk.js.map +0 -1
- package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js +0 -56
- package/focus-app/server/chunks/manifest.js-Cm6cC7mC.js.map +0 -1
- package/focus-app/server/chunks/nodes/0.js-4wlmdsk8.js +0 -9
- package/focus-app/server/chunks/nodes/1.js-B-LpY-lC.js +0 -9
- package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js +0 -19
- package/focus-app/server/chunks/nodes/2.js-F6jp5eD9.js.map +0 -1
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import type { H2ASignature } from "../../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* THE NONCE BRACKET — four bounds, each labelled with what it is FOR.
|
|
4
|
+
* =================================================================
|
|
5
|
+
*
|
|
6
|
+
* The general form, which is the correction to the first draft of this contract:
|
|
7
|
+
* **a positive specification does not mean a single value — it means every
|
|
8
|
+
* accepted-set boundary is stated, and each one says what it is for.** A floor
|
|
9
|
+
* that protects strength and a ceiling that protects against blobs are different
|
|
10
|
+
* parameters and must not be described in the same breath. The first attempt
|
|
11
|
+
* specified `fixed ~43`, which pinned an issuer that DOES NOT EXIST YET to an
|
|
12
|
+
* entropy choice made on its behalf: if the auth lane later picks 384 or 512 bits
|
|
13
|
+
* — the *safer* choice — a fixed verifier turns their improvement into our
|
|
14
|
+
* outage.
|
|
15
|
+
*
|
|
16
|
+
* 1. {@link H2A_ENROLLMENT_NONCE_PATTERN} — alphabet base64url.
|
|
17
|
+
* **POSITIVE, SECURITY-BEARING.**
|
|
18
|
+
* 2. {@link H2A_ENROLLMENT_NONCE_MIN_BITS} / {@link H2A_ENROLLMENT_NONCE_MIN_LENGTH}
|
|
19
|
+
* — minimum 256 bits. **POSITIVE, SECURITY-BEARING.**
|
|
20
|
+
* 3. {@link H2A_ENROLLMENT_NONCE_MAX_BITS} / {@link H2A_ENROLLMENT_NONCE_MAX_LENGTH}
|
|
21
|
+
* — maximum 1024 bits. **SANITY CEILING, EXPLICITLY NOT A SECURITY
|
|
22
|
+
* PARAMETER.** It means *"beyond this it is not a nonce"*. It must NEVER be
|
|
23
|
+
* read as *"this much entropy is enough"* — the floor is the only bound that
|
|
24
|
+
* speaks about strength.
|
|
25
|
+
* 4. {@link H2A_ENROLLMENT_MAX_NONCE_LENGTH} — 4096 chars, pre-parse DoS guard
|
|
26
|
+
* only. Not a definition of anything.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Minimum entropy a gateway nonce must carry. **POSITIVE, SECURITY-BEARING.**
|
|
30
|
+
* Below this a "nonce" is guessable, and a guessable challenge is one an attacker
|
|
31
|
+
* can pre-compute a proof for.
|
|
32
|
+
*/
|
|
33
|
+
export declare const H2A_ENROLLMENT_NONCE_MIN_BITS = 256;
|
|
34
|
+
/**
|
|
35
|
+
* Upper end of the bracket. **SANITY CEILING — NOT A SECURITY PARAMETER.**
|
|
36
|
+
*
|
|
37
|
+
* It says "beyond this it is not a nonce", nothing about sufficiency.
|
|
38
|
+
*
|
|
39
|
+
* Set at 1024 rather than 512 for HEADROOM — and the arithmetic has to be stated
|
|
40
|
+
* correctly, because an earlier draft of this comment said "88 base64url
|
|
41
|
+
* characters" and then drew a conclusion that only the wrong number supports.
|
|
42
|
+
* Base64url packs 6 bits per character, so a perfectly reasonable 64-byte nonce is
|
|
43
|
+
* 512 bits / **86** characters — and a 512-bit ceiling derives to `ceil(512 / 6) =
|
|
44
|
+
* 86` as well. It would therefore land EXACTLY on such a nonce and ACCEPT it, not
|
|
45
|
+
* reject it. So "512 would reject a 64-byte nonce" was never true; the real
|
|
46
|
+
* objection is that it leaves ZERO margin. A bound never meant to bound entropy
|
|
47
|
+
* must not sit flush against a legitimate value, where one more byte of issuer
|
|
48
|
+
* entropy — or base64 padding the issuer does not strip — turns a sanity bound
|
|
49
|
+
* into an outage. 1024 buys that margin. The corrected number does not weaken the
|
|
50
|
+
* choice; it removes a justification that misreported its own arithmetic, which is
|
|
51
|
+
* the defect class this module exists to argue against.
|
|
52
|
+
*/
|
|
53
|
+
export declare const H2A_ENROLLMENT_NONCE_MAX_BITS = 1024;
|
|
54
|
+
/**
|
|
55
|
+
* The nonce's accepted alphabet, stated POSITIVELY: base64url characters only.
|
|
56
|
+
* **POSITIVE, SECURITY-BEARING** — it is what refuses free text, JSON, a URL, or
|
|
57
|
+
* a message borrowed from some other protocol, rather than enumerating those.
|
|
58
|
+
*/
|
|
59
|
+
export declare const H2A_ENROLLMENT_NONCE_PATTERN: RegExp;
|
|
60
|
+
/**
|
|
61
|
+
* Minimum nonce length, DERIVED not asserted: base64url packs 6 bits per
|
|
62
|
+
* character, so `ceil(256 / 6) = 43` characters is the 256-bit floor.
|
|
63
|
+
* **POSITIVE, SECURITY-BEARING.**
|
|
64
|
+
*
|
|
65
|
+
* A MINIMUM, never an exact length — see the bracket note above for why pinning
|
|
66
|
+
* an as-yet-unbuilt issuer to one value is the error this replaces.
|
|
67
|
+
*/
|
|
68
|
+
export declare const H2A_ENROLLMENT_NONCE_MIN_LENGTH: number;
|
|
69
|
+
/**
|
|
70
|
+
* Maximum nonce length, derived the same way: `ceil(1024 / 6) = 171` characters.
|
|
71
|
+
* **SANITY CEILING, NOT A SECURITY PARAMETER** — see
|
|
72
|
+
* {@link H2A_ENROLLMENT_NONCE_MAX_BITS}.
|
|
73
|
+
*/
|
|
74
|
+
export declare const H2A_ENROLLMENT_NONCE_MAX_LENGTH: number;
|
|
75
|
+
/**
|
|
76
|
+
* Cheap PRE-PARSE cap, and nothing more.
|
|
77
|
+
*
|
|
78
|
+
* A DoS sanity bound, not a definition of a nonce — the definition is the
|
|
79
|
+
* alphabet plus the floor, with the ceiling as a separate sanity bound. Kept
|
|
80
|
+
* distinct so none of the four is mistaken for another.
|
|
81
|
+
*/
|
|
82
|
+
export declare const H2A_ENROLLMENT_MAX_NONCE_LENGTH = 4096;
|
|
83
|
+
/**
|
|
84
|
+
* The ONLY keys a challenge document may carry. An ALLOWLIST, not a blocklist.
|
|
85
|
+
*
|
|
86
|
+
* The nonce was specified positively while the challenge *object* was left
|
|
87
|
+
* specified negatively — a blocklist of one key, `principalSub`. That control did
|
|
88
|
+
* not cover the harm it was written for: the harm is *"a principal id reaching an
|
|
89
|
+
* agent process and context window"*, and `{ nonce, meta: { principalSub } }` or
|
|
90
|
+
* `{ nonce, "__proto__": { principalSub } }` both do exactly that while passing a
|
|
91
|
+
* top-level `"principalSub" in challenge` check. A blocklist of one key stops one
|
|
92
|
+
* spelling of one field; nesting is a different spelling.
|
|
93
|
+
*
|
|
94
|
+
* So the same positive-specification move is applied one level up: only `nonce`
|
|
95
|
+
* and `expiresAt` may appear, both must be strings, and anything else is refused
|
|
96
|
+
* without needing to know what it means. No nesting is reachable, so no
|
|
97
|
+
* deep scan for a forbidden name is needed — that would be the blocklist again,
|
|
98
|
+
* one level deeper.
|
|
99
|
+
*/
|
|
100
|
+
export declare const H2A_ENROLLMENT_CHALLENGE_KEYS: readonly ["nonce", "expiresAt"];
|
|
101
|
+
/**
|
|
102
|
+
* The domain-separation tag carried and signed by every proof — see
|
|
103
|
+
* {@link H2AEnrollmentProof.type}.
|
|
104
|
+
*
|
|
105
|
+
* Versioned so a format change is distinguishable rather than silently
|
|
106
|
+
* reinterpreted. Bump this and the verifier stops accepting the old shape, which
|
|
107
|
+
* is the entire point of it being here.
|
|
108
|
+
*/
|
|
109
|
+
export declare const H2A_ENROLLMENT_PROOF_TYPE = "h2a-enrollment-proof-v1";
|
|
110
|
+
/**
|
|
111
|
+
* A gateway-issued enrollment challenge, as the local agent receives it.
|
|
112
|
+
*
|
|
113
|
+
* Part B, flow step 2: *"Gateway issues a challenge: a random nonce, TTL-bound,
|
|
114
|
+
* scoped to that `sub`."* The `sub`-scoping is a fact the GATEWAY holds and
|
|
115
|
+
* re-checks at verification time (flow step 5a) and the agent never sees it —
|
|
116
|
+
* see consequence 2 in the module header.
|
|
117
|
+
*/
|
|
118
|
+
export interface H2AEnrollmentChallenge {
|
|
119
|
+
/**
|
|
120
|
+
* The random nonce: base64url, ≥256 bits. It is one of the three signed
|
|
121
|
+
* fields — see {@link enrollmentProofSignedPayload}.
|
|
122
|
+
*/
|
|
123
|
+
readonly nonce: string;
|
|
124
|
+
/**
|
|
125
|
+
* Optional ISO-8601 expiry, as told to the agent. **Advisory**: the gateway
|
|
126
|
+
* remains the authority on the TTL (flow step 5a). Present, it lets the agent
|
|
127
|
+
* refuse to spend a signature on a challenge it can already see is dead —
|
|
128
|
+
* which can only ever narrow what happens, never widen it.
|
|
129
|
+
*/
|
|
130
|
+
readonly expiresAt?: string;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The proof payload the agent returns to the gateway — Part B, flow step 4:
|
|
134
|
+
* *"The agent returns `{ nonce, signature, publicKeyPem, instance }`."*
|
|
135
|
+
*
|
|
136
|
+
* Exactly those four fields, and nothing else. In particular: no private key
|
|
137
|
+
* material, no filesystem path, no principal identifier, no bearer token, no
|
|
138
|
+
* capability list. What the gateway does with it is Part B flow step 5.
|
|
139
|
+
*/
|
|
140
|
+
export interface H2AEnrollmentProof {
|
|
141
|
+
/**
|
|
142
|
+
* WHAT THIS MESSAGE IS — the domain-separation tag, signed like every other
|
|
143
|
+
* carried field.
|
|
144
|
+
*
|
|
145
|
+
* The rule "a proof must attest to everything it CARRIES" is only
|
|
146
|
+
* content-completeness. The other half: **a proof must also attest WHAT IT
|
|
147
|
+
* IS.** Attesting content while leaving the message type unstated is exactly
|
|
148
|
+
* how cross-protocol attacks work — signature valid, content honest,
|
|
149
|
+
* *interpretation attacker-chosen*.
|
|
150
|
+
*
|
|
151
|
+
* That is precisely the oracle this contract already closed once: moving from a
|
|
152
|
+
* signed string to a signed object fixed it, but bound the type only
|
|
153
|
+
* ACCIDENTALLY, via a key set that happens to be unique among h2a's signing
|
|
154
|
+
* sites today. "Safe because no other site currently signs this shape" is the
|
|
155
|
+
* same negative property — satisfied by absence, expiring when someone adds the
|
|
156
|
+
* colliding site. This field makes it positive.
|
|
157
|
+
*
|
|
158
|
+
* **VERSIONED deliberately.** A future format change must be
|
|
159
|
+
* *distinguishable*, not silently reinterpreted; an unversioned tag only defers
|
|
160
|
+
* the same problem to the next revision. {@link verifyEnrollmentProof} checks
|
|
161
|
+
* this value, so a `-v2` proof cannot pass a v1 verifier.
|
|
162
|
+
*/
|
|
163
|
+
readonly type: typeof H2A_ENROLLMENT_PROOF_TYPE;
|
|
164
|
+
/** The gateway's nonce, echoed verbatim. Signed. */
|
|
165
|
+
readonly nonce: string;
|
|
166
|
+
/**
|
|
167
|
+
* `signCanonical(enrollmentProofSignedPayload(proof), { by: instance,
|
|
168
|
+
* privateKeyPem })` — an `H2ASignature` `{ by, alg: 'ed25519', value }`
|
|
169
|
+
* (`types.ts` `H2ASignature`, reused unchanged per Part B's "Reuse vs. new"
|
|
170
|
+
* table).
|
|
171
|
+
*
|
|
172
|
+
* `signature.by` is NOT part of the signed message; it is a routing label that
|
|
173
|
+
* duplicates the signed `instance`. A verifier must read `instance`, never
|
|
174
|
+
* `by`.
|
|
175
|
+
*/
|
|
176
|
+
readonly signature: H2ASignature;
|
|
177
|
+
/**
|
|
178
|
+
* The agent's CURRENT public key PEM (SPKI). Public material, not a secret.
|
|
179
|
+
* Signed — so a payload cannot be re-pointed at another key and still verify
|
|
180
|
+
* against this signature.
|
|
181
|
+
*/
|
|
182
|
+
readonly publicKeyPem: string;
|
|
183
|
+
/**
|
|
184
|
+
* The agent's CURRENT live instance id. Signed, so its PROVENANCE is
|
|
185
|
+
* trustworthy. That is emphatically not permission: see SIGNED ≠ AUTHORIZED in
|
|
186
|
+
* the module header. It maps to Part B's `agentInstanceIdAtBinding`, which the
|
|
187
|
+
* contract already marks "provenance only — NEVER re-used as authority at read
|
|
188
|
+
* time".
|
|
189
|
+
*/
|
|
190
|
+
readonly instance: string;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* The exact object the signature covers: **every field the proof carries except
|
|
194
|
+
* the signature itself**.
|
|
195
|
+
*
|
|
196
|
+
* Single definition on purpose. The gateway's verification (Part B flow step 5b,
|
|
197
|
+
* as amended) is `verifyCanonical(enrollmentProofSignedPayload(proof),
|
|
198
|
+
* proof.signature, proof.publicKeyPem)`, and field ordering is irrelevant
|
|
199
|
+
* because `canonicalize` normalizes it — so an independent implementation cannot
|
|
200
|
+
* disagree about what was signed by guessing at key order.
|
|
201
|
+
*
|
|
202
|
+
* The rule is STRUCTURAL, not asserted. Coverage is a rest-spread that removes
|
|
203
|
+
* exactly one field, so "every field except the signature" is what the code
|
|
204
|
+
* *does* rather than a list that has to be kept in step — the same shape as
|
|
205
|
+
* `envelope.ts` `envelopeSigningView` (`const { signatures: _omit, ...rest }`).
|
|
206
|
+
* Three things follow, and the third is why this beats an enumeration:
|
|
207
|
+
*
|
|
208
|
+
* 1. `Omit<…, "signature">` on the parameter makes **tsc** reject a caller that
|
|
209
|
+
* does not hold every non-signature field, so a new required field on
|
|
210
|
+
* {@link H2AEnrollmentProof} is a compile error, not a test failure.
|
|
211
|
+
* 2. A new field flows into the signature automatically. There is no second list
|
|
212
|
+
* to forget.
|
|
213
|
+
* 3. A field carried on the proof but NOT signed becomes impossible to emit:
|
|
214
|
+
* signing sees the unsigned view, verification re-derives it from the finished
|
|
215
|
+
* proof, so any extra field makes the two disagree and
|
|
216
|
+
* {@link signEnrollmentChallenge}'s self-verification throws. The rule holds
|
|
217
|
+
* even with every test deleted.
|
|
218
|
+
*
|
|
219
|
+
* The keys test is kept anyway — it documents the intent and costs nothing.
|
|
220
|
+
*/
|
|
221
|
+
export declare function enrollmentProofSignedPayload(proof: Omit<H2AEnrollmentProof, "signature"> & {
|
|
222
|
+
readonly signature?: H2ASignature;
|
|
223
|
+
}): Omit<H2AEnrollmentProof, "signature">;
|
|
224
|
+
/**
|
|
225
|
+
* Verify a proof the way the gateway must: the signature over the whole
|
|
226
|
+
* composite, checked against the key the proof ships.
|
|
227
|
+
*
|
|
228
|
+
* **This answers "did this key produce this payload", and NOTHING else.** It is
|
|
229
|
+
* not an authorization check and must never be used as one: a `true` here on a
|
|
230
|
+
* key with no active binding row still authorizes nothing (Part B, fail-closed
|
|
231
|
+
* item 3 — "verifying the signature must never, by itself, cause any row to be
|
|
232
|
+
* returned"). Provided so the two lanes verify the same bytes, not so a caller
|
|
233
|
+
* can shortcut a binding lookup.
|
|
234
|
+
*/
|
|
235
|
+
export declare function verifyEnrollmentProof(proof: H2AEnrollmentProof): boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Refuse to prove control of a key h2a itself does not list as ACTIVE.
|
|
238
|
+
*
|
|
239
|
+
* h2a-side key validity is *necessary but not sufficient* for exposure — the
|
|
240
|
+
* binding governs that (Part B, fail-closed item 4) — but a key h2a considers
|
|
241
|
+
* revoked must never be offered up for a NEW binding.
|
|
242
|
+
*
|
|
243
|
+
* This is DEFENCE IN DEPTH, and its call site is unreachable in practice: when
|
|
244
|
+
* the live key has been revoked, `resolveLiveIdentity`'s reclaim proof
|
|
245
|
+
* (`provesLocalKey`, which requires at least one active key) fails, so it MINTS
|
|
246
|
+
* a fresh identity instead of returning the revoked one — verified by the
|
|
247
|
+
* "a locally revoked key is never the key proved" test. The guard exists so that
|
|
248
|
+
* remains a CHECKED fact rather than a relied-upon one, and it is exported so it
|
|
249
|
+
* can be exercised directly instead of sitting untested behind a path that
|
|
250
|
+
* cannot reach it.
|
|
251
|
+
*
|
|
252
|
+
* An empty key list is an ERROR here, never a silent pass: "no active keys" is
|
|
253
|
+
* not the fact "this key is fine".
|
|
254
|
+
*/
|
|
255
|
+
export declare function assertKeyIsLocallyActive(input: {
|
|
256
|
+
readonly instance: string;
|
|
257
|
+
readonly publicKeyPem: string;
|
|
258
|
+
readonly activeKeys: readonly string[];
|
|
259
|
+
}): void;
|
|
260
|
+
/** The local key material the ceremony proves control of. */
|
|
261
|
+
export interface H2AEnrollmentIdentity {
|
|
262
|
+
/** The live instance id, resolved at run time — never a recorded value. */
|
|
263
|
+
readonly instance: string;
|
|
264
|
+
/** PKCS#8 PEM. Read from disk, used to sign, and NEVER returned outward. */
|
|
265
|
+
readonly privateKeyPem: string;
|
|
266
|
+
/** SPKI PEM. Public material; this one does go on the wire. */
|
|
267
|
+
readonly publicKeyPem: string;
|
|
268
|
+
/**
|
|
269
|
+
* How live resolution arrived at this identity: `reclaim` re-proved an existing
|
|
270
|
+
* local key, `mint` created a NEW one. Reported so a mint is never silent — a
|
|
271
|
+
* mint the owner did not expect means an earlier keypair was unusable (see
|
|
272
|
+
* {@link listUnusablePrivateKeys}) or the agent re-anchored. Optional so an
|
|
273
|
+
* injected resolver need not synthesize a provenance it does not have.
|
|
274
|
+
*/
|
|
275
|
+
readonly identityAction?: "override" | "reclaim" | "mint";
|
|
276
|
+
}
|
|
277
|
+
/** Where {@link buildEnrollmentProof} looks for the live agent identity. */
|
|
278
|
+
export interface ResolveEnrollmentIdentityInput {
|
|
279
|
+
/** The h2a store root (`<root>/keys/...`). */
|
|
280
|
+
readonly root: string;
|
|
281
|
+
/** The provider host (`claude`, `codex`, ...) — an input to live resolution. */
|
|
282
|
+
readonly host: string;
|
|
283
|
+
/** The working directory, for workspace resolution. */
|
|
284
|
+
readonly cwd: string;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Seam for the identity resolution. Injected by tests; the default
|
|
288
|
+
* ({@link resolveEnrollmentIdentity}) is the only production implementation.
|
|
289
|
+
*/
|
|
290
|
+
export type EnrollmentIdentityResolver = (input: ResolveEnrollmentIdentityInput) => H2AEnrollmentIdentity;
|
|
291
|
+
/**
|
|
292
|
+
* Resolve the CURRENT live identity and load its keypair.
|
|
293
|
+
*
|
|
294
|
+
* Why this resolves live, every single time, and takes no instance id:
|
|
295
|
+
* **a memory that names an instance-id ROTS.** That is a documented failure in
|
|
296
|
+
* this project (a stale recorded mapping sent a consultation to the wrong
|
|
297
|
+
* instance), and it is the same failure the identity re-anchor of 2026-06-07
|
|
298
|
+
* created for enrollment: the stability unit moved from `(host, workspaceId)`
|
|
299
|
+
* to the provider conversation UUID, so an agent enrolled before it now
|
|
300
|
+
* presents a DIFFERENT instance handle and a DIFFERENT keypair (Part B,
|
|
301
|
+
* "Re-enrollment of a post-re-anchor key"). The previously enrolled
|
|
302
|
+
* `agentPubKey` is simply no longer produced by any live agent — which is why a
|
|
303
|
+
* push today is rejected 401.
|
|
304
|
+
*
|
|
305
|
+
* So there is deliberately **no `instance` parameter anywhere in this module's
|
|
306
|
+
* public surface**. The id cannot be passed in, therefore a stale one cannot be
|
|
307
|
+
* used. `resolveLiveIdentity` is called without `explicitInstance` (which would
|
|
308
|
+
* short-circuit before any keypair exists — `identity/live.ts`), and the key is
|
|
309
|
+
* then read from the paths THAT resolution returned.
|
|
310
|
+
*
|
|
311
|
+
* WHAT ACTUALLY HAPPENS WHEN THE LOCAL KEY IS UNUSABLE — stated because the
|
|
312
|
+
* obvious reading ("it fails closed") is wrong, and an overstated guard comment
|
|
313
|
+
* is worse than none:
|
|
314
|
+
*
|
|
315
|
+
* - **Corrupt, truncated or passphrase-protected private key** → `provesLocalKey`
|
|
316
|
+
* catches its own failure and returns `false`, so `resolveLiveIdentity` MINTS a
|
|
317
|
+
* fresh identity. The ceremony then succeeds, with exit 0, proving control of a
|
|
318
|
+
* BRAND-NEW key — the corrupt file is left on disk and the old instance stays
|
|
319
|
+
* listed active. It does not fail, and it does not prove the damaged key. The
|
|
320
|
+
* mint is the same self-healing mechanism as the revoked-key case, but here it
|
|
321
|
+
* would MASK TAMPERING, so silence is the defect: the CLI names any unusable
|
|
322
|
+
* key file on stderr before this runs (see {@link listUnusablePrivateKeys}),
|
|
323
|
+
* and `identityAction` reports the mint. Pinned by an OBSERVED BEHAVIOUR test.
|
|
324
|
+
* - **Unreadable private key (EACCES)** → `readKeypair` reads the same file
|
|
325
|
+
* earlier, inside `resolveLiveIdentity` and outside any `try`, so the raw errno
|
|
326
|
+
* escapes from there rather than from the guard below. That is why the whole
|
|
327
|
+
* resolution call is wrapped: the failure is re-thrown with the root named,
|
|
328
|
+
* instead of surfacing as a bare `EACCES` with no indication of which store it
|
|
329
|
+
* came from.
|
|
330
|
+
* - **The two guards below are NARROWING, not behaviour.** The
|
|
331
|
+
* `privateKeyPath === undefined` branch exists because the type is optional
|
|
332
|
+
* (`explicitInstance` is the only resolution path that omits the paths, and
|
|
333
|
+
* this function never passes one), and the `readFileSync` branch is belt and
|
|
334
|
+
* braces behind the earlier read. Neither is reachable on this path; they are
|
|
335
|
+
* not the reason a broken key is safe. {@link assertKeyIsLocallyActive} is in
|
|
336
|
+
* the same position and says so itself.
|
|
337
|
+
*
|
|
338
|
+
* DOCUMENTED LIMIT on what the returned `instance` contains: an instance id is
|
|
339
|
+
* `<host>:<label>:<uuid>` and its label is the host-native session name or the
|
|
340
|
+
* workspace directory's BASENAME (`identity/live.ts` `deriveInstanceId`). So the
|
|
341
|
+
* proof carries a workspace *label*, which Part A's opacity boundary explicitly
|
|
342
|
+
* permits ("never a filesystem path beyond a human label") — but never a path.
|
|
343
|
+
* It is free text the owner controls, so a consumer must escape it like any
|
|
344
|
+
* user content; see joint plan §9.
|
|
345
|
+
*/
|
|
346
|
+
export declare function resolveEnrollmentIdentity(input: ResolveEnrollmentIdentityInput): H2AEnrollmentIdentity;
|
|
347
|
+
/**
|
|
348
|
+
* Every private key file under `<root>/keys` that exists but cannot be loaded as
|
|
349
|
+
* a private key — corrupt, truncated, or passphrase-protected.
|
|
350
|
+
*
|
|
351
|
+
* This exists because of what does NOT happen when the live key is one of those:
|
|
352
|
+
* live resolution's reclaim proof fails silently and MINTS a fresh identity, so
|
|
353
|
+
* the ceremony succeeds with exit 0 on a brand-new key while a damaged file sits
|
|
354
|
+
* on disk. The mint is defensible; doing it silently is not, because it is
|
|
355
|
+
* indistinguishable from tampering. So the caller can name the file.
|
|
356
|
+
*
|
|
357
|
+
* Read-only and total: it never throws, and a missing `keys` directory yields an
|
|
358
|
+
* empty list from an EXPLICIT branch — "there is no key directory" is an
|
|
359
|
+
* established fact, not a default.
|
|
360
|
+
*/
|
|
361
|
+
export declare function listUnusablePrivateKeys(root: string): string[];
|
|
362
|
+
/** Input to the pure signing step. No I/O, no clock unless injected. */
|
|
363
|
+
export interface SignEnrollmentChallengeInput {
|
|
364
|
+
/** The gateway-issued challenge. */
|
|
365
|
+
readonly challenge: H2AEnrollmentChallenge;
|
|
366
|
+
/** The identity whose control is being proved. */
|
|
367
|
+
readonly identity: H2AEnrollmentIdentity;
|
|
368
|
+
/** Clock, for the advisory expiry check. Defaults to `Date.now`. */
|
|
369
|
+
readonly now?: () => number;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Reject a challenge we must not sign. Narrowing only — this can refuse a
|
|
373
|
+
* signature, never authorize one. Throws with a message naming the reason.
|
|
374
|
+
*
|
|
375
|
+
* Exported so a caller that classifies errors (the CLI, which owes a distinct
|
|
376
|
+
* exit code for "your challenge is bad" vs "my local key state is bad") can
|
|
377
|
+
* check the input it was handed BEFORE touching any identity.
|
|
378
|
+
* {@link signEnrollmentChallenge} validates again regardless: each entry point
|
|
379
|
+
* validates its own input, and an upstream check is never assumed.
|
|
380
|
+
*
|
|
381
|
+
* IMPLEMENTED AS the sanitize path with the result discarded, deliberately. The
|
|
382
|
+
* validator and the sanitizer used to be two independent readers of the caller's
|
|
383
|
+
* object, which is precisely how `expiresAt` came to be validated *through the
|
|
384
|
+
* prototype chain* while the allowlist looked only at own keys. One reader means
|
|
385
|
+
* they cannot disagree.
|
|
386
|
+
*/
|
|
387
|
+
export declare function assertSignableEnrollmentChallenge(challenge: H2AEnrollmentChallenge, nowMs: number): void;
|
|
388
|
+
/**
|
|
389
|
+
* Validate a parsed challenge document and return a FRESH, NULL-PROTOTYPE object
|
|
390
|
+
* carrying only the allowlisted keys.
|
|
391
|
+
*
|
|
392
|
+
* Three distinct jobs, all load-bearing:
|
|
393
|
+
*
|
|
394
|
+
* 1. It takes the OWN-ONLY view first ({@link carriedChallengeFields}), so what is
|
|
395
|
+
* validated and what is returned are the same object. An inherited field is
|
|
396
|
+
* invisible to both.
|
|
397
|
+
* 2. It refuses ({@link assertCarriedChallengeIsSignable}). Refuse-the-rest means
|
|
398
|
+
* refuse, not ignore.
|
|
399
|
+
* 3. What flows onward is a **new object with `null` prototype**, so the
|
|
400
|
+
* `JSON.parse` result — which may carry an own `"__proto__"` key, a
|
|
401
|
+
* prototype-pollution vector the moment anything spreads or assigns it into
|
|
402
|
+
* another object — never propagates past this boundary. Even though such a
|
|
403
|
+
* document is already refused above, nothing downstream has to depend on that
|
|
404
|
+
* having happened.
|
|
405
|
+
*
|
|
406
|
+
* Order matters and is the point: sanitize-then-validate, never validate-then-copy.
|
|
407
|
+
* The copy is what removes the second accessor, so validating the caller's object
|
|
408
|
+
* first would put the bug back.
|
|
409
|
+
*
|
|
410
|
+
* Use this at any boundary where the challenge came from parsed input. The CLI
|
|
411
|
+
* does, and so does {@link signEnrollmentChallenge}.
|
|
412
|
+
*/
|
|
413
|
+
export declare function sanitizeEnrollmentChallenge(challenge: H2AEnrollmentChallenge, nowMs: number): H2AEnrollmentChallenge;
|
|
414
|
+
/**
|
|
415
|
+
* Sign a gateway-issued challenge with the agent's identity key and return the
|
|
416
|
+
* Part B proof payload. PURE apart from the injected clock: no I/O, no network,
|
|
417
|
+
* no store.
|
|
418
|
+
*
|
|
419
|
+
* The signed message is the CANONICAL COMPOSITE
|
|
420
|
+
* {@link enrollmentProofSignedPayload} — `{ type, nonce, instance, publicKeyPem }`
|
|
421
|
+
* — per the signed-composite amendment. The earlier shape signed the bare nonce
|
|
422
|
+
* (Part B flow steps 3 and 5b as originally written), which was safe only
|
|
423
|
+
* because nothing yet consumed `instance`: safety derived from the *absence* of a
|
|
424
|
+
* consumer, which expires the moment someone adds one.
|
|
425
|
+
*
|
|
426
|
+
* The rule, in its amended form: **a proof must attest to everything it carries
|
|
427
|
+
* AND to what it is.** Content-completeness plus context-binding — the first
|
|
428
|
+
* without the second leaves the interpretation attacker-chosen.
|
|
429
|
+
*
|
|
430
|
+
* A structural consequence worth knowing, because it removes a whole finding
|
|
431
|
+
* rather than guarding against it: the reclaim proof-of-possession signs a
|
|
432
|
+
* STRING (`identity-reclaim:<instance>:<fingerprint>`), and this signs an
|
|
433
|
+
* OBJECT. `canonicalize` type-tags the two differently, so no enrollment
|
|
434
|
+
* signature can ever satisfy `verifyReclaimProof` — the signing-oracle collision
|
|
435
|
+
* is impossible by construction, not by refusal. There is deliberately no guard
|
|
436
|
+
* against it: a guard that cannot fire is the defect this PR spent its time
|
|
437
|
+
* finding. The property is pinned by a regression test instead.
|
|
438
|
+
*
|
|
439
|
+
* Note that that separation was ACCIDENTAL — it held because the two payload
|
|
440
|
+
* types differ, not because either said what it was. The signed `type` field is
|
|
441
|
+
* what makes it deliberate, and it generalizes: no h2a signing site can collide
|
|
442
|
+
* with this one now, whatever shape it later adopts.
|
|
443
|
+
*
|
|
444
|
+
* Before returning, the proof is VERIFIED against the public key it ships. A
|
|
445
|
+
* proof we cannot verify ourselves is never emitted: that is what catches a
|
|
446
|
+
* mismatched keypair locally instead of at the gateway, where the failure is
|
|
447
|
+
* indistinguishable from an attack.
|
|
448
|
+
*/
|
|
449
|
+
export declare function signEnrollmentChallenge(input: SignEnrollmentChallengeInput): H2AEnrollmentProof;
|
|
450
|
+
/** Options for the synchronous, network-free proof build. */
|
|
451
|
+
export interface BuildEnrollmentProofOptions {
|
|
452
|
+
/** The h2a store root. */
|
|
453
|
+
readonly root: string;
|
|
454
|
+
/** The provider host (`claude`, `codex`, ...). */
|
|
455
|
+
readonly host: string;
|
|
456
|
+
/** The working directory. */
|
|
457
|
+
readonly cwd: string;
|
|
458
|
+
/** The gateway-issued challenge. */
|
|
459
|
+
readonly challenge: H2AEnrollmentChallenge;
|
|
460
|
+
/** Clock, for the advisory expiry check. */
|
|
461
|
+
readonly now?: () => number;
|
|
462
|
+
/** Identity-resolution seam. Defaults to {@link resolveEnrollmentIdentity}. */
|
|
463
|
+
readonly resolveIdentityImpl?: EnrollmentIdentityResolver;
|
|
464
|
+
}
|
|
465
|
+
/** What the ceremony produced, plus the owner-facing provenance. */
|
|
466
|
+
export interface EnrollmentProofResult {
|
|
467
|
+
/** The payload to hand to the gateway. */
|
|
468
|
+
readonly proof: H2AEnrollmentProof;
|
|
469
|
+
/** The live instance the proof was produced for (same as `proof.instance`). */
|
|
470
|
+
readonly instance: string;
|
|
471
|
+
/**
|
|
472
|
+
* Short sha256 prefix of the proved public key — the same fingerprint the
|
|
473
|
+
* reclaim proof uses. For the OWNER to eyeball which key was proved; it is
|
|
474
|
+
* not part of the proof and carries no authority.
|
|
475
|
+
*/
|
|
476
|
+
readonly publicKeyFingerprint: string;
|
|
477
|
+
/**
|
|
478
|
+
* Provenance of the identity this run proved — see
|
|
479
|
+
* {@link H2AEnrollmentIdentity.identityAction}. Absent when an injected
|
|
480
|
+
* resolver did not report one.
|
|
481
|
+
*/
|
|
482
|
+
readonly identityAction?: "override" | "reclaim" | "mint";
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Resolve the live identity, sign the challenge, return the proof.
|
|
486
|
+
*
|
|
487
|
+
* SYNCHRONOUS and network-free by construction: there is no transport in this
|
|
488
|
+
* function at all, so the CLI path (`h2a keys prove-control`) cannot reach one.
|
|
489
|
+
* Re-enrollment needs no separate entry point — this IS the re-enrollment path,
|
|
490
|
+
* because it resolves the current identity every time and can therefore only
|
|
491
|
+
* ever produce a proof for the key that is live now. A changed controlling key
|
|
492
|
+
* means the owner re-runs this and the gateway mints a NEW binding, revoking the
|
|
493
|
+
* old row (Part B, "Re-enrollment of a post-re-anchor key"); nothing here
|
|
494
|
+
* reuses or rotates anything.
|
|
495
|
+
*/
|
|
496
|
+
export declare function buildEnrollmentProof(options: BuildEnrollmentProofOptions): EnrollmentProofResult;
|
|
497
|
+
/**
|
|
498
|
+
* Seam for handing the proof to the gateway.
|
|
499
|
+
*
|
|
500
|
+
* **There is no default implementation and h2a ships none.** The gateway's
|
|
501
|
+
* challenge/verify endpoint is the auth lane's step 2 and is not built yet; the
|
|
502
|
+
* per-binding push keying is step 4b and carries its own BLOCKING cross-verify
|
|
503
|
+
* requirement (path-identity vs key-identity). Until then a submitter is
|
|
504
|
+
* supplied by the caller — in tests, always a fake — and its absence is
|
|
505
|
+
* REPORTED as a fact, never papered over.
|
|
506
|
+
*/
|
|
507
|
+
export type EnrollmentProofSubmitter = (proof: H2AEnrollmentProof) => Promise<unknown>;
|
|
508
|
+
/**
|
|
509
|
+
* What happened to the proof, from an EXPLICIT branch on whether a transport
|
|
510
|
+
* exists — never a defaulted value. "Not attempted" is a fact the caller can
|
|
511
|
+
* act on; a silent `undefined` would be indistinguishable from a failed send.
|
|
512
|
+
*/
|
|
513
|
+
export type EnrollmentSubmission = {
|
|
514
|
+
readonly attempted: false;
|
|
515
|
+
/** No transport was supplied, so nothing was sent anywhere. */
|
|
516
|
+
readonly reason: "no-transport-configured";
|
|
517
|
+
} | {
|
|
518
|
+
readonly attempted: true;
|
|
519
|
+
/** Whatever the injected submitter returned. Opaque to h2a. */
|
|
520
|
+
readonly response: unknown;
|
|
521
|
+
};
|
|
522
|
+
/** Options for the full ceremony, including the optional injected transport. */
|
|
523
|
+
export interface RunEnrollmentCeremonyOptions extends BuildEnrollmentProofOptions {
|
|
524
|
+
/**
|
|
525
|
+
* Injected transport. Omitted (the production default), the proof is returned
|
|
526
|
+
* for out-of-band delivery and `submission.attempted` is `false`.
|
|
527
|
+
*/
|
|
528
|
+
readonly submitImpl?: EnrollmentProofSubmitter;
|
|
529
|
+
}
|
|
530
|
+
/** The ceremony's outcome: the proof, plus what was done with it. */
|
|
531
|
+
export interface EnrollmentCeremonyResult extends EnrollmentProofResult {
|
|
532
|
+
readonly submission: EnrollmentSubmission;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Run the ceremony end to end: resolve the live identity, sign the challenge,
|
|
536
|
+
* and — only if a transport was injected — hand the proof over.
|
|
537
|
+
*
|
|
538
|
+
* With no `submitImpl` this makes NO network call of any kind. That is not a
|
|
539
|
+
* default-off flag; there is no hosted endpoint in this file to turn on.
|
|
540
|
+
*/
|
|
541
|
+
export declare function runEnrollmentCeremony(options: RunEnrollmentCeremonyOptions): Promise<EnrollmentCeremonyResult>;
|
|
542
|
+
//# sourceMappingURL=ceremony.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ceremony.d.ts","sourceRoot":"","sources":["../../../src/runtime/enrollment/ceremony.ts"],"names":[],"mappings":"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"}
|