@iislee/opencodex 2.11.0 → 2.31.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/AGENTS_INSTALL.md +109 -0
- package/README.md +114 -19
- package/bin/ocx.mjs +164 -36
- package/bin/package-main.mjs +1 -1
- package/gui/dist/assets/index-BF38heuV.js +104 -0
- package/gui/dist/assets/index-DMiI18Kv.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/alibaba-color.svg +1 -1
- package/gui/dist/provider-icons/antigravity-color.svg +1 -1
- package/gui/dist/provider-icons/claude-color.svg +1 -1
- package/gui/dist/provider-icons/cline-color.svg +16 -0
- package/gui/dist/provider-icons/cloudflare-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/commandcode-color.svg +1 -0
- package/gui/dist/provider-icons/copilot-color.svg +1 -1
- package/gui/dist/provider-icons/cursor-color.svg +1 -1
- package/gui/dist/provider-icons/deepseek-color.svg +1 -1
- package/gui/dist/provider-icons/firepass-color.svg +1 -1
- package/gui/dist/provider-icons/fireworks-color.svg +1 -1
- package/gui/dist/provider-icons/gemini-color.svg +1 -1
- package/gui/dist/provider-icons/github-copilot-color.svg +1 -1
- package/gui/dist/provider-icons/gitlab-duo-color.svg +1 -1
- package/gui/dist/provider-icons/grok.svg +1 -1
- package/gui/dist/provider-icons/groq-color.svg +1 -1
- package/gui/dist/provider-icons/huggingface-color.svg +1 -1
- package/gui/dist/provider-icons/kimi-color.svg +1 -1
- package/gui/dist/provider-icons/kiro-color.svg +2 -2
- package/gui/dist/provider-icons/lm-studio-color.svg +1 -1
- package/gui/dist/provider-icons/mistral-color.svg +1 -1
- package/gui/dist/provider-icons/moonshot-color.svg +1 -1
- package/gui/dist/provider-icons/nvidia-color.svg +1 -1
- package/gui/dist/provider-icons/ollama-color.svg +1 -1
- package/gui/dist/provider-icons/openai.svg +1 -1
- package/gui/dist/provider-icons/opencode.svg +2 -1
- package/gui/dist/provider-icons/openrouter-color.svg +1 -1
- package/gui/dist/provider-icons/pi.svg +2 -2
- package/gui/dist/provider-icons/qianfan-color.svg +1 -1
- package/gui/dist/provider-icons/qwen-portal-color.svg +1 -1
- package/gui/dist/provider-icons/vercel-ai-gateway-color.svg +1 -1
- package/gui/dist/provider-icons/vllm-color.svg +1 -1
- package/gui/dist/provider-icons/xiaomi-color.svg +1 -1
- package/package.json +19 -10
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +376 -52
- package/src/adapters/base.ts +54 -7
- package/src/adapters/client-fingerprint.ts +18 -12
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +69 -0
- package/src/adapters/command-code.ts +601 -0
- package/src/adapters/cursor/checkpoint-store.ts +303 -0
- package/src/adapters/cursor/cursor-errors.ts +129 -6
- package/src/adapters/cursor/discovery.ts +61 -4
- package/src/adapters/cursor/effort-map.ts +27 -3
- package/src/adapters/cursor/framing.ts +39 -0
- package/src/adapters/cursor/h2-pool.ts +123 -0
- package/src/adapters/cursor/http1-bidi.ts +361 -0
- package/src/adapters/cursor/images.ts +704 -0
- package/src/adapters/cursor/live-models.ts +174 -58
- package/src/adapters/cursor/live-transport.ts +609 -170
- package/src/adapters/cursor/native-exec-common.ts +23 -2
- package/src/adapters/cursor/native-exec-fs.ts +10 -7
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +0 -1
- package/src/adapters/cursor/native-exec.ts +101 -14
- package/src/adapters/cursor/protobuf-events.ts +829 -11
- package/src/adapters/cursor/protobuf-request.ts +383 -65
- package/src/adapters/cursor/request-builder.ts +237 -31
- package/src/adapters/cursor/tool-definitions.ts +242 -12
- package/src/adapters/cursor/tool-result-normalize.ts +92 -0
- package/src/adapters/cursor/transport.ts +22 -0
- package/src/adapters/cursor/types.ts +28 -1
- package/src/adapters/cursor.ts +133 -7
- package/src/adapters/google-antigravity-replay.ts +558 -34
- package/src/adapters/google-antigravity-wire.ts +43 -10
- package/src/adapters/google-http.ts +38 -10
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google-truncation.ts +11 -0
- package/src/adapters/google.ts +618 -74
- package/src/adapters/identity.ts +39 -6
- package/src/adapters/kiro-errors.ts +11 -0
- package/src/adapters/kiro-events.ts +19 -1
- package/src/adapters/kiro-thinking.ts +18 -2
- package/src/adapters/kiro-tools.ts +10 -1
- package/src/adapters/kiro.ts +92 -54
- package/src/adapters/mimo-free.ts +17 -0
- package/src/adapters/openai-chat-url.ts +11 -0
- package/src/adapters/openai-chat.ts +1311 -336
- package/src/adapters/openai-responses-url.ts +16 -0
- package/src/adapters/openai-responses.ts +830 -56
- package/src/adapters/registry.ts +175 -0
- package/src/adapters/responses-tool-schema.ts +67 -0
- package/src/adapters/tool-call-id.ts +119 -0
- package/src/adapters/tool-catalog-nudge.ts +104 -21
- package/src/adapters/xai-web-search.ts +185 -0
- package/src/bridge.ts +428 -61
- package/src/chat/inbound.ts +43 -19
- package/src/chat/outbound.ts +82 -26
- package/src/claude/agents-inject.ts +32 -9
- package/src/claude/context-windows.ts +21 -5
- package/src/claude/desktop-3p.ts +243 -9
- package/src/claude/gateway-cache.ts +41 -4
- package/src/claude/inbound.ts +72 -3
- package/src/claude/model-info.ts +38 -15
- package/src/claude/outbound.ts +70 -16
- package/src/cli/account-api.ts +35 -1
- package/src/cli/account-auth.ts +33 -6
- package/src/cli/account-catalog-refresh.ts +14 -0
- package/src/cli/account-extended.ts +389 -2
- package/src/cli/account-main.ts +317 -0
- package/src/cli/account.ts +30 -6
- package/src/cli/agent.ts +45 -1
- package/src/cli/claude-agent-startup-sync.ts +73 -0
- package/src/cli/claude-desktop.ts +29 -4
- package/src/cli/claude.ts +103 -34
- package/src/cli/codex-log-guard-doctor.ts +103 -0
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +76 -0
- package/src/cli/combo.ts +8 -0
- package/src/cli/config-command.ts +74 -10
- package/src/cli/dispatch.ts +593 -0
- package/src/cli/doctor.ts +315 -43
- package/src/cli/ensure-desired-integrations.ts +152 -0
- package/src/cli/export-command.ts +46 -20
- package/src/cli/help.ts +20 -271
- package/src/cli/index.ts +378 -519
- package/src/cli/init.ts +4 -17
- package/src/cli/integrations.ts +120 -2
- package/src/cli/lab.ts +607 -0
- package/src/cli/launcher-context.ts +77 -0
- package/src/cli/minimax.ts +497 -0
- package/src/cli/models-runtime.ts +35 -2
- package/src/cli/models.ts +100 -14
- package/src/cli/observe.ts +92 -3
- package/src/cli/opencode.ts +4 -2
- package/src/cli/provider-runtime.ts +18 -1
- package/src/cli/provider.ts +24 -3
- package/src/cli/ready.ts +301 -0
- package/src/cli/registry.ts +437 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/route-policy.ts +92 -0
- package/src/cli/runtime-api.ts +6 -3
- package/src/cli/star-prompt.ts +71 -15
- package/src/cli/status.ts +10 -3
- package/src/cli/system-restart-client.ts +146 -0
- package/src/cli/tray-proxy.ts +153 -6
- package/src/cli/v2.ts +105 -10
- package/src/cli.ts +1 -1
- package/src/clients/config-export.ts +1358 -21
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-lifecycle.ts +130 -13
- package/src/codex/account-namespaces.ts +49 -3
- package/src/codex/account-priority.ts +83 -0
- package/src/codex/account-store.ts +29 -2
- package/src/codex/account-usability.ts +25 -2
- package/src/codex/admission.ts +256 -0
- package/src/codex/affinity-debug.ts +162 -0
- package/src/codex/app-server-processes.ts +493 -106
- package/src/codex/app-server-restart-service.ts +232 -0
- package/src/codex/auth-api.ts +849 -242
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/auth-context.ts +345 -32
- package/src/codex/autostart-health.ts +8 -1
- package/src/codex/catalog/account-models.ts +67 -0
- package/src/codex/catalog/aggregation.ts +68 -10
- package/src/codex/catalog/bundled.ts +331 -33
- package/src/codex/catalog/effort.ts +121 -30
- package/src/codex/catalog/filesystem-evidence.ts +302 -0
- package/src/codex/catalog/kinds.ts +2 -0
- package/src/codex/catalog/metadata.ts +529 -45
- package/src/codex/catalog/native-models.ts +72 -0
- package/src/codex/catalog/parsing.ts +224 -30
- package/src/codex/catalog/provider-fetch.ts +1460 -134
- package/src/codex/catalog/sync.ts +1449 -186
- package/src/codex/catalog-admission.ts +199 -0
- package/src/codex/catalog-refresh-status.ts +105 -0
- package/src/codex/catalog-write-serialization.ts +242 -0
- package/src/codex/catalog.ts +6 -3
- package/src/codex/codex-write-lock.ts +384 -0
- package/src/codex/convergence-types.ts +614 -0
- package/src/codex/convergence.ts +651 -0
- package/src/codex/coordinator-doctor.ts +332 -0
- package/src/codex/custom-model-catalog-migration.ts +176 -0
- package/src/codex/desired-state.ts +230 -0
- package/src/codex/features.ts +636 -39
- package/src/codex/generation.ts +202 -0
- package/src/codex/history-job.ts +407 -0
- package/src/codex/history-lock.ts +242 -0
- package/src/codex/history-migration-guardian.ts +26 -20
- package/src/codex/history-provider.ts +231 -28
- package/src/codex/history-transition.ts +105 -0
- package/src/codex/history-worker.ts +220 -0
- package/src/codex/inject-coordination.ts +290 -0
- package/src/codex/inject.ts +1073 -152
- package/src/codex/injected-marker.ts +37 -3
- package/src/codex/integration-record.ts +266 -0
- package/src/codex/internal/catalog-writer.ts +203 -0
- package/src/codex/internal/history-writer.ts +80 -0
- package/src/codex/journal.ts +66 -4
- package/src/codex/log-guard/inspect.ts +506 -0
- package/src/codex/log-guard/lock.ts +150 -0
- package/src/codex/log-guard/maintenance.ts +403 -0
- package/src/codex/log-guard/path-safety.ts +88 -0
- package/src/codex/log-guard/policy.ts +44 -0
- package/src/codex/log-guard/processes.ts +205 -0
- package/src/codex/log-guard/protection.ts +489 -0
- package/src/codex/log-guard/sqlite-errors.ts +9 -0
- package/src/codex/main-account-cache.ts +24 -0
- package/src/codex/main-account.ts +29 -1
- package/src/codex/management-convergence.ts +167 -0
- package/src/codex/model-cache.ts +56 -10
- package/src/codex/model-entitlements.ts +353 -0
- package/src/codex/native-main-admission.ts +47 -0
- package/src/codex/native-main-auth-temp.ts +187 -0
- package/src/codex/native-main-claim.ts +178 -0
- package/src/codex/native-main-lock-file.ts +162 -0
- package/src/codex/native-main-owner.ts +329 -0
- package/src/codex/native-profile-api.ts +247 -0
- package/src/codex/native-profile-manager.ts +1531 -0
- package/src/codex/native-profile-processes.ts +121 -0
- package/src/codex/native-profile-recovery.ts +99 -0
- package/src/codex/native-profile-stage-store.ts +387 -0
- package/src/codex/native-profile-startup.ts +492 -0
- package/src/codex/native-profile-store.ts +855 -0
- package/src/codex/native-profile-types.ts +120 -0
- package/src/codex/native-residue.ts +682 -0
- package/src/codex/paths.ts +80 -1
- package/src/codex/plan-from-token.ts +140 -0
- package/src/codex/plan.ts +40 -0
- package/src/codex/plugins-doctor.ts +1 -1
- package/src/codex/pool-rotation.ts +74 -4
- package/src/codex/project-config-warnings.ts +20 -6
- package/src/codex/prompt-journal.ts +352 -0
- package/src/codex/prompt-layers.ts +967 -0
- package/src/codex/prompt-lock.ts +143 -0
- package/src/codex/quota-rejection.ts +298 -0
- package/src/codex/quota.ts +175 -13
- package/src/codex/refresh.ts +11 -2
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/routing.ts +505 -94
- package/src/codex/runtime.ts +159 -38
- package/src/codex/shim.ts +1009 -28
- package/src/codex/subagent-model-fallback.ts +350 -35
- package/src/codex/sync.ts +191 -2
- package/src/codex/transition-state.ts +612 -0
- package/src/codex/upstream-host-health.ts +368 -0
- package/src/codex/user-identity.ts +557 -0
- package/src/codex/warmup.ts +187 -81
- package/src/codex/write-coordination.ts +114 -0
- package/src/combos/failover.ts +20 -0
- package/src/combos/index.ts +4 -0
- package/src/combos/request.ts +32 -0
- package/src/combos/types.ts +81 -9
- package/src/config/provider-name.ts +24 -0
- package/src/config.ts +1762 -140
- package/src/generated/compatibility-version.json +3164 -0
- package/src/generated/{jawcode-model-metadata.ts → model-metadata.ts} +19 -17
- package/src/grok/inject.ts +16 -5
- package/src/grok/inspect.ts +45 -0
- package/src/grok/sync.ts +2 -2
- package/src/images/loop.ts +152 -29
- package/src/images/plan.ts +23 -13
- package/src/integrations/config-io.ts +269 -0
- package/src/integrations/journal.ts +315 -0
- package/src/integrations/merge.ts +135 -0
- package/src/integrations/mutation-flight.ts +71 -0
- package/src/integrations/native/ownership-preflight.ts +202 -0
- package/src/integrations/omp-yaml-source.ts +358 -0
- package/src/integrations/owned-refresh.ts +74 -0
- package/src/integrations/ownership.ts +111 -0
- package/src/integrations/registry.ts +159 -0
- package/src/integrations/serialize.ts +314 -0
- package/src/integrations/state.ts +361 -0
- package/src/integrations/store.ts +103 -0
- package/src/integrations/writer-lock.ts +98 -0
- package/src/integrations/writer.ts +691 -0
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +499 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +64 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +164 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +68 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +214 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +229 -0
- package/src/lab/events/validate.ts +781 -0
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +489 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +54 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +310 -0
- package/src/lab/ledger/store.ts +532 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +336 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +118 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +301 -0
- package/src/lab/observe/from-live.ts +117 -0
- package/src/lab/paths.ts +153 -0
- package/src/lab/projection/rebuild.ts +495 -0
- package/src/lab/projection/schema.ts +135 -0
- package/src/lab/projection/verdicts.ts +474 -0
- package/src/lab/projection/verification.ts +412 -0
- package/src/lab/public/bundle.ts +217 -0
- package/src/lab/public/community-authority.ts +175 -0
- package/src/lab/public/community-files.ts +29 -0
- package/src/lab/public/community.ts +479 -0
- package/src/lab/public/file-safety.ts +155 -0
- package/src/lab/public/ids.ts +26 -0
- package/src/lab/public/index.ts +16 -0
- package/src/lab/public/mutation-lock.ts +424 -0
- package/src/lab/public/operator.ts +353 -0
- package/src/lab/public/origin-purge.ts +79 -0
- package/src/lab/public/origin.ts +203 -0
- package/src/lab/public/privacy.ts +143 -0
- package/src/lab/public/private-file.ts +261 -0
- package/src/lab/public/project.ts +124 -0
- package/src/lab/public/purge-test-fault.ts +21 -0
- package/src/lab/public/purge.ts +223 -0
- package/src/lab/public/registry.ts +44 -0
- package/src/lab/public/revocation.ts +252 -0
- package/src/lab/public/signature.ts +243 -0
- package/src/lab/public/storage.ts +105 -0
- package/src/lab/public/strict-json.ts +206 -0
- package/src/lab/public/time.ts +26 -0
- package/src/lab/public/types.ts +172 -0
- package/src/lab/public/validate.ts +391 -0
- package/src/lab/query/catalog.ts +101 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +45 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +112 -0
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +74 -0
- package/src/lib/app-owned-memory-stores.ts +22 -0
- package/src/lib/bounded-body.ts +153 -9
- package/src/lib/bun-runtime.ts +125 -12
- package/src/lib/bun-stream-caps.ts +13 -9
- package/src/lib/codex-restart-contract.ts +120 -0
- package/src/lib/config-ownership.ts +6 -2
- package/src/lib/destination-policy.ts +65 -1
- package/src/lib/errors.ts +44 -2
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-activation.ts +223 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +56 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/lab-passive-linker-registration.ts +26 -0
- package/src/lib/local-management-attestation.ts +51 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/local-provider-reload-contract.ts +100 -0
- package/src/lib/optional-shutdown-hooks.ts +57 -0
- package/src/lib/pinned-http.ts +145 -26
- package/src/lib/process-control.ts +4 -1
- package/src/lib/provider-outbound.ts +49 -9
- package/src/lib/redact.ts +419 -3
- package/src/lib/self-launch-argv.ts +15 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/shadow-call.ts +35 -4
- package/src/lib/sse-decoder.ts +41 -0
- package/src/lib/state-store-registrations.ts +10 -2
- package/src/lib/system-restart-contract.ts +73 -0
- package/src/lib/token-estimate.ts +19 -2
- package/src/lib/tool-argument-integers.ts +202 -0
- package/src/lib/translator-budget.ts +44 -0
- package/src/lib/upstream-http-version.ts +57 -0
- package/src/lib/upstream-reachability.ts +95 -0
- package/src/lib/upstream-retry.ts +156 -3
- package/src/lib/windows-atomic-replace.ts +155 -0
- package/src/lib/windows-elevation.ts +70 -2
- package/src/lib/windows-secret-acl.ts +409 -69
- package/src/lib/windows-service-wrappers.ts +72 -0
- package/src/lib/windows-text.ts +106 -0
- package/src/lib/windows-user-principal.ts +341 -0
- package/src/lib/winsw.ts +33 -5
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/command-code.ts +239 -0
- package/src/oauth/cursor.ts +46 -5
- package/src/oauth/google-antigravity.ts +35 -3
- package/src/oauth/health.ts +20 -12
- package/src/oauth/index.ts +398 -66
- package/src/oauth/key-providers.ts +16 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/kiro.ts +50 -6
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/log.ts +3 -1
- package/src/oauth/login-cli.ts +88 -28
- package/src/oauth/nous.ts +798 -0
- package/src/oauth/store.ts +119 -21
- package/src/oauth/token-guardian.ts +9 -3
- package/src/pi/models.ts +2 -2
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/antigravity-models.ts +521 -31
- package/src/providers/base-url-choices.ts +10 -0
- package/src/providers/codex-capacity.ts +292 -0
- package/src/providers/command-code-efforts.ts +144 -0
- package/src/providers/context-cap.ts +22 -5
- package/src/providers/cursor-pool.ts +72 -0
- package/src/providers/derive.ts +253 -6
- package/src/providers/fastwire.ts +501 -0
- package/src/providers/free-directory.ts +10 -7
- package/src/providers/google-vertex-location.ts +14 -0
- package/src/providers/key-failover.ts +71 -3
- package/src/providers/label.ts +1 -1
- package/src/providers/model-discovery-limits.ts +16 -0
- package/src/providers/model-discovery.ts +115 -22
- package/src/providers/model-rename-migration.ts +255 -0
- package/src/providers/model-rename-startup.ts +28 -0
- package/src/providers/openai-sidecar.ts +72 -4
- package/src/providers/openai-tier-startup.ts +31 -2
- package/src/providers/openai-tiers.ts +119 -4
- package/src/providers/openai-virtual-models.ts +1 -0
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/provider-id-rewrite.ts +29 -0
- package/src/providers/quota.ts +1319 -38
- package/src/providers/registry.ts +1429 -111
- package/src/providers/request-pacing.ts +310 -0
- package/src/providers/service-tier.ts +277 -0
- package/src/providers/slug-codec.ts +42 -6
- package/src/providers/static-model-discovery.ts +86 -0
- package/src/providers/xai-responses-opt-in.ts +15 -0
- package/src/providers/xai-transport.ts +11 -4
- package/src/reasoning-effort.ts +49 -1
- package/src/responses/compaction.ts +26 -1
- package/src/responses/custom-tool-compat.ts +266 -0
- package/src/responses/hosted-tool-policy.ts +9 -0
- package/src/responses/namespace-tool-compat.ts +355 -0
- package/src/responses/parser.ts +220 -38
- package/src/responses/provider-continuation.ts +98 -0
- package/src/responses/provider-opaque-metadata.ts +73 -0
- package/src/responses/reasoning-envelope.ts +9 -1
- package/src/responses/reasoning-replay-cache.ts +426 -0
- package/src/responses/schema.ts +7 -1
- package/src/responses/spill-store.ts +75 -10
- package/src/responses/state.ts +565 -27
- package/src/responses/thought-signature-replay.ts +347 -0
- package/src/responses/tool-search-compat.ts +301 -0
- package/src/responses/truncated-stop-reason.ts +60 -0
- package/src/router.ts +366 -30
- package/src/routing/analytics.ts +378 -0
- package/src/routing/capability.ts +244 -0
- package/src/routing/compatibility/assemble.ts +73 -0
- package/src/routing/compatibility/behavior.ts +278 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/lab-evidence-provider.ts +130 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/provider-slot.ts +56 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/cost.ts +77 -0
- package/src/routing/evaluator.ts +495 -0
- package/src/routing/health.ts +412 -0
- package/src/routing/history/cursor.ts +43 -0
- package/src/routing/history/indexer.ts +605 -0
- package/src/routing/history/schema.ts +72 -0
- package/src/routing/profile-namespace.ts +15 -0
- package/src/routing/profile.ts +547 -0
- package/src/routing/quota.ts +145 -0
- package/src/routing/request-evidence.ts +45 -0
- package/src/routing/trace.ts +776 -0
- package/src/server/adapter-resolve.ts +2 -29
- package/src/server/auth-cors.ts +267 -46
- package/src/server/background-lifecycle.ts +182 -0
- package/src/server/chat-completions.ts +130 -56
- package/src/server/chat-native-sse.ts +331 -0
- package/src/server/chat-native.ts +426 -0
- package/src/server/claude-messages.ts +159 -43
- package/src/server/direct-local-http.ts +347 -0
- package/src/server/effort-policy.ts +18 -0
- package/src/server/github-copilot-responses-repair.ts +338 -0
- package/src/server/gui-static.ts +39 -10
- package/src/server/images.ts +94 -12
- package/src/server/index.ts +865 -181
- package/src/server/lifecycle.ts +284 -13
- package/src/server/live.ts +136 -17
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/local-provider-reload-client.ts +137 -0
- package/src/server/management/agent-settings-routes.ts +398 -103
- package/src/server/management/api-key-usage.ts +31 -5
- package/src/server/management/body.ts +6 -0
- package/src/server/management/combo-routes.ts +62 -24
- package/src/server/management/config-routes.ts +464 -51
- package/src/server/management/context.ts +80 -2
- package/src/server/management/integration-routes.ts +498 -0
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +563 -0
- package/src/server/management/logs-usage-routes.ts +101 -32
- package/src/server/management/model-routes.ts +189 -131
- package/src/server/management/model-rows.ts +163 -0
- package/src/server/management/native-integration-routes.ts +769 -0
- package/src/server/management/oauth-account-routes.ts +80 -4
- package/src/server/management/provider-capability-config.ts +48 -0
- package/src/server/management/provider-routes.ts +764 -157
- package/src/server/management/request-history-routes.ts +191 -0
- package/src/server/management/routing-analytics-routes.ts +74 -0
- package/src/server/management/routing-profile-routes.ts +380 -0
- package/src/server/management/shared.ts +27 -11
- package/src/server/management/sidebar-routes.ts +47 -31
- package/src/server/management/storage-log-guard-routes.ts +186 -0
- package/src/server/management/sync-response.ts +69 -0
- package/src/server/management/system-restart.ts +288 -32
- package/src/server/management/system-routes.ts +77 -0
- package/src/server/management/usage-summary-cache.ts +9 -1
- package/src/server/management/vision-sidecar-options.ts +167 -0
- package/src/server/management/web-search-sidecar-options.ts +120 -0
- package/src/server/management-api.ts +115 -14
- package/src/server/management-auth.ts +220 -5
- package/src/server/passive-route-linker.ts +66 -0
- package/src/server/ports.ts +41 -1
- package/src/server/proxy-liveness.ts +132 -5
- package/src/server/readiness.ts +99 -0
- package/src/server/relay-eager.ts +82 -42
- package/src/server/relay.ts +236 -76
- package/src/server/request-decompress.ts +113 -6
- package/src/server/request-log.ts +235 -22
- package/src/server/responses/agent-task-recovery-cache.ts +143 -0
- package/src/server/responses/agent-task-recovery.ts +465 -0
- package/src/server/responses/collaboration.ts +204 -35
- package/src/server/responses/compact.ts +442 -55
- package/src/server/responses/core.ts +2872 -331
- package/src/server/responses/empty-completion-guard.ts +276 -0
- package/src/server/responses/encrypted-payload.ts +62 -39
- package/src/server/responses/fetch-helpers.ts +79 -4
- package/src/server/responses/input-admission.ts +185 -0
- package/src/server/responses/pacing-overload.ts +13 -0
- package/src/server/responses/policy-fallback.ts +178 -0
- package/src/server/responses/responses-field-backfill.ts +251 -0
- package/src/server/responses/terminal-guard.ts +26 -5
- package/src/server/responses/upstream-error.ts +5 -0
- package/src/server/responses/ws-upstream.ts +308 -0
- package/src/server/responses-custom-tool-repair.ts +282 -0
- package/src/server/responses-item-id-repair.ts +54 -6
- package/src/server/responses-json-events.ts +90 -0
- package/src/server/responses-model-rewrite.ts +29 -0
- package/src/server/responses-reasoning-summary-rewrite.ts +178 -0
- package/src/server/responses-snapshot-repair.ts +621 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/responses-tool-search-repair.ts +267 -0
- package/src/server/responses-undeclared-tool-guard.ts +153 -0
- package/src/server/responses.ts +18 -2
- package/src/server/search.ts +78 -13
- package/src/server/sse-frame-buffer.ts +292 -0
- package/src/server/sse-payload-rewrite.ts +110 -22
- package/src/server/startup-action-control.ts +8 -1
- package/src/server/startup-health-cache.ts +19 -1
- package/src/server/system-env.ts +80 -9
- package/src/server/ws-bridge.ts +39 -38
- package/src/service-manager-probe.ts +892 -0
- package/src/service.ts +1111 -90
- package/src/sidecar/auth.ts +92 -0
- package/src/sidecar/candidates.ts +83 -0
- package/src/storage/cleanup.ts +2 -2
- package/src/storage/scanner.ts +1 -1
- package/src/storage/worker-lifecycle.ts +14 -14
- package/src/tray/windows-tray.ps1 +83 -9
- package/src/tray/windows.ts +43 -16
- package/src/types/accounts.ts +37 -0
- package/src/types/config.ts +845 -0
- package/src/types/provider.ts +545 -0
- package/src/types/request.ts +384 -0
- package/src/types/tools.ts +131 -0
- package/src/types/wire.ts +80 -0
- package/src/types.ts +104 -1236
- package/src/update/index.ts +32 -19
- package/src/update/job.ts +442 -67
- package/src/update/notify.ts +12 -6
- package/src/update/npm-cache-preflight.d.mts +47 -0
- package/src/update/npm-cache-preflight.mjs +201 -0
- package/src/update/transactional-install.d.mts +22 -0
- package/src/update/transactional-install.mjs +259 -0
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +268 -16
- package/src/usage/log.ts +606 -41
- package/src/usage/summary.ts +177 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/anthropic-describe.ts +10 -6
- package/src/vision/backends.ts +97 -0
- package/src/vision/describe.ts +9 -3
- package/src/vision/eligibility.ts +250 -0
- package/src/vision/index.ts +238 -24
- package/src/vision/reasoning.ts +55 -0
- package/src/vision/routed-describe.ts +175 -0
- package/src/vision/timeout-bounds.ts +9 -0
- package/src/web-search/anthropic-executor.ts +13 -7
- package/src/web-search/backends.ts +108 -0
- package/src/web-search/exa-executor.ts +88 -0
- package/src/web-search/executor.ts +11 -3
- package/src/web-search/gemini-executor.ts +141 -0
- package/src/web-search/index.ts +150 -15
- package/src/web-search/loop.ts +279 -50
- package/src/web-search/parse.ts +125 -30
- package/src/web-search/sources.ts +60 -0
- package/src/web-search/xai-executor.ts +219 -0
- package/gui/dist/assets/index-DTpMHS4F.js +0 -67
- package/gui/dist/assets/index-ZNVDE3C7.css +0 -1
- package/gui/dist/provider-icons/antigravity.svg +0 -1
- package/gui/dist/provider-icons/claude.svg +0 -1
- package/gui/dist/provider-icons/copilot.svg +0 -1
- package/gui/dist/provider-icons/cursor.svg +0 -2
- package/gui/dist/provider-icons/gemini.svg +0 -1
- package/gui/dist/provider-icons/grok-color.svg +0 -1
- package/gui/dist/provider-icons/kiro.svg +0 -14
- package/src/cli/internal-dispatch.ts +0 -20
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { loadCaseAuthority } from "../conformance/manifest";
|
|
2
|
+
import {
|
|
3
|
+
FABRIC_COMPATIBILITY_VERSION,
|
|
4
|
+
FABRIC_SCENARIO_ID,
|
|
5
|
+
FABRIC_SCENARIO_VERSION,
|
|
6
|
+
FABRIC_SUITE_ID,
|
|
7
|
+
FABRIC_SUITE_VERSION,
|
|
8
|
+
FABRIC_TASK_CLASS_ID,
|
|
9
|
+
FABRIC_TASK_CLASS_VERSION,
|
|
10
|
+
} from "../fabric/constants";
|
|
11
|
+
import { loadFabricCaseAuthority } from "../fabric/manifest";
|
|
12
|
+
import { verifierManifestDigest } from "../fabric/subject";
|
|
13
|
+
import { findPublicRouteRegistryEntry } from "./registry";
|
|
14
|
+
import type { PublicEvidenceBundleV1, PublicEvidenceRecordV1, PublicRouteSubjectV1 } from "./types";
|
|
15
|
+
import { PublicEvidenceValidationError } from "./validate";
|
|
16
|
+
|
|
17
|
+
type ProtocolCaseAuthority = ReturnType<typeof loadCaseAuthority>;
|
|
18
|
+
|
|
19
|
+
interface ProtocolAuthoritySnapshot {
|
|
20
|
+
scenarioVersion: string;
|
|
21
|
+
suiteVersion: string;
|
|
22
|
+
sourceCommit: string;
|
|
23
|
+
load: () => ProtocolCaseAuthority;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Public records are historical evidence. Never replace an authority entry when a
|
|
27
|
+
// protocol version advances: retain the old loader and append a new snapshot.
|
|
28
|
+
const PROTOCOL_AUTHORITY_SNAPSHOTS: readonly ProtocolAuthoritySnapshot[] = Object.freeze([
|
|
29
|
+
Object.freeze({
|
|
30
|
+
scenarioVersion: "1.0.0",
|
|
31
|
+
suiteVersion: "1.0.0",
|
|
32
|
+
sourceCommit: "3ad5bb6bd3f76f6879d84b78ea39edd3e01ec296",
|
|
33
|
+
load: loadCaseAuthority,
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const cachedCaseAuthorities = new Map<string, ProtocolCaseAuthority>();
|
|
38
|
+
let cachedFabricCaseAuthority: ReturnType<typeof loadFabricCaseAuthority> | null = null;
|
|
39
|
+
let cachedVerifierManifestDigest: string | null = null;
|
|
40
|
+
|
|
41
|
+
function protocolAuthorityKey(snapshot: ProtocolAuthoritySnapshot): string {
|
|
42
|
+
return `${snapshot.suiteVersion}\0${snapshot.scenarioVersion}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function caseAuthorityFor(record: PublicEvidenceRecordV1): ProtocolCaseAuthority {
|
|
46
|
+
const snapshot = PROTOCOL_AUTHORITY_SNAPSHOTS.find((candidate) =>
|
|
47
|
+
candidate.scenarioVersion === record.scenarioVersion
|
|
48
|
+
&& candidate.suiteVersion === record.suiteVersion
|
|
49
|
+
);
|
|
50
|
+
if (!snapshot) {
|
|
51
|
+
throw new PublicEvidenceValidationError(
|
|
52
|
+
"public_authority",
|
|
53
|
+
"scenario/suite authority version is not retained",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const key = protocolAuthorityKey(snapshot);
|
|
58
|
+
const cached = cachedCaseAuthorities.get(key);
|
|
59
|
+
if (cached) return cached;
|
|
60
|
+
|
|
61
|
+
const authority = snapshot.load();
|
|
62
|
+
if (
|
|
63
|
+
String(authority.manifestDefaults.version) !== snapshot.scenarioVersion
|
|
64
|
+
|| String(authority.manifestDefaults.suiteVersion) !== snapshot.suiteVersion
|
|
65
|
+
|| authority.sourceCommit !== snapshot.sourceCommit
|
|
66
|
+
) {
|
|
67
|
+
throw new Error("public protocol authority snapshot drift");
|
|
68
|
+
}
|
|
69
|
+
cachedCaseAuthorities.set(key, authority);
|
|
70
|
+
return authority;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function fabricCaseAuthority(): ReturnType<typeof loadFabricCaseAuthority> {
|
|
74
|
+
cachedFabricCaseAuthority ??= loadFabricCaseAuthority();
|
|
75
|
+
return cachedFabricCaseAuthority;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function reviewedVerifierManifestDigest(): string {
|
|
79
|
+
cachedVerifierManifestDigest ??= verifierManifestDigest();
|
|
80
|
+
return cachedVerifierManifestDigest;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function validateRouteAuthority(subject: PublicRouteSubjectV1): void {
|
|
84
|
+
const entry = findPublicRouteRegistryEntry(subject.providerId, subject.modelId);
|
|
85
|
+
if (!entry || !entry.adapterFamilies.includes(subject.adapterFamily)) {
|
|
86
|
+
throw new PublicEvidenceValidationError("public_authority", "public route is not in reviewed registry authority");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function validateAssertionAuthority(
|
|
91
|
+
record: PublicEvidenceRecordV1,
|
|
92
|
+
assertions: readonly { id: string; required: boolean }[],
|
|
93
|
+
): void {
|
|
94
|
+
const allowed = new Map(assertions.map((assertion) => [assertion.id, assertion.required] as const));
|
|
95
|
+
if (allowed.size !== assertions.length) {
|
|
96
|
+
throw new PublicEvidenceValidationError("public_authority", "reviewed scenario assertion authority contains duplicates");
|
|
97
|
+
}
|
|
98
|
+
if (record.assertions.length !== allowed.size) {
|
|
99
|
+
throw new PublicEvidenceValidationError(
|
|
100
|
+
"public_authority",
|
|
101
|
+
"public assertion set does not exactly match reviewed scenario authority",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const seen = new Set<string>();
|
|
105
|
+
for (const assertion of record.assertions) {
|
|
106
|
+
if (seen.has(assertion.id)) {
|
|
107
|
+
throw new PublicEvidenceValidationError("public_authority", "public assertion set contains duplicate assertion ids");
|
|
108
|
+
}
|
|
109
|
+
seen.add(assertion.id);
|
|
110
|
+
if (!allowed.has(assertion.id) || allowed.get(assertion.id) !== assertion.required) {
|
|
111
|
+
throw new PublicEvidenceValidationError(
|
|
112
|
+
"public_authority",
|
|
113
|
+
"public assertion id/required flag is not in reviewed scenario authority",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const assertionId of allowed.keys()) {
|
|
118
|
+
if (!seen.has(assertionId)) {
|
|
119
|
+
throw new PublicEvidenceValidationError("public_authority", "public assertion set is missing reviewed scenario authority");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function validateTaskAuthority(record: PublicEvidenceRecordV1): void {
|
|
125
|
+
const fabricAuthority = fabricCaseAuthority();
|
|
126
|
+
const caseRecord = fabricAuthority.cases.find((candidate) => candidate.id === FABRIC_SCENARIO_ID);
|
|
127
|
+
if (
|
|
128
|
+
!caseRecord
|
|
129
|
+
|| record.suiteId !== FABRIC_SUITE_ID
|
|
130
|
+
|| record.suiteVersion !== FABRIC_SUITE_VERSION
|
|
131
|
+
|| record.scenarioId !== FABRIC_SCENARIO_ID
|
|
132
|
+
|| record.scenarioVersion !== FABRIC_SCENARIO_VERSION
|
|
133
|
+
|| record.subject.subjectKind !== "task"
|
|
134
|
+
|| record.subject.taskClassId !== FABRIC_TASK_CLASS_ID
|
|
135
|
+
|| record.subject.taskClassVersion !== FABRIC_TASK_CLASS_VERSION
|
|
136
|
+
|| record.subject.taskFixtureDigest !== caseRecord.fixture.digest
|
|
137
|
+
|| record.subject.verifierManifestDigest !== reviewedVerifierManifestDigest()
|
|
138
|
+
|| record.subject.fabricCompatibilityVersion !== FABRIC_COMPATIBILITY_VERSION
|
|
139
|
+
) {
|
|
140
|
+
throw new PublicEvidenceValidationError("public_authority", "task scenario/verifier authority mismatch");
|
|
141
|
+
}
|
|
142
|
+
validateAssertionAuthority(record, caseRecord.assertions);
|
|
143
|
+
validateRouteAuthority(record.subject.route);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function validateScenarioAuthority(record: PublicEvidenceRecordV1): void {
|
|
147
|
+
if (record.evidenceLayer === "task_effectiveness") {
|
|
148
|
+
validateTaskAuthority(record);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const authority = caseAuthorityFor(record);
|
|
153
|
+
const caseRecord = authority.cases.find((candidate) => candidate.id === record.scenarioId);
|
|
154
|
+
if (!caseRecord || caseRecord.suite !== record.suiteId) {
|
|
155
|
+
throw new PublicEvidenceValidationError("public_authority", "scenario/suite authority mismatch");
|
|
156
|
+
}
|
|
157
|
+
validateAssertionAuthority(record, caseRecord.assertions);
|
|
158
|
+
|
|
159
|
+
if (record.evidenceLayer === "live_route_compatibility") {
|
|
160
|
+
if (record.subject.subjectKind !== "route") {
|
|
161
|
+
throw new PublicEvidenceValidationError("public_authority", "live route subject mismatch");
|
|
162
|
+
}
|
|
163
|
+
validateRouteAuthority(record.subject);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Repository-owned authority gate used by both local signing and community imports. */
|
|
168
|
+
export function validatePublicEvidenceAuthorities(records: readonly PublicEvidenceRecordV1[]): void {
|
|
169
|
+
for (const record of records) validateScenarioAuthority(record);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function validateCommunityEvidenceAuthorities(bundle: PublicEvidenceBundleV1): PublicEvidenceBundleV1 {
|
|
173
|
+
validatePublicEvidenceAuthorities(bundle.records);
|
|
174
|
+
return bundle;
|
|
175
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const COMMUNITY_ID_FRAGMENT = "[0-9a-f]{64}";
|
|
2
|
+
const COMMUNITY_BUNDLE_FILE_RE = new RegExp(
|
|
3
|
+
`^bundle-(${COMMUNITY_ID_FRAGMENT})-(${COMMUNITY_ID_FRAGMENT})\\.json$`,
|
|
4
|
+
);
|
|
5
|
+
const COMMUNITY_REVOCATION_FILE_RE = new RegExp(
|
|
6
|
+
`^revocation-(${COMMUNITY_ID_FRAGMENT})\\.json$`,
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
export interface CommunityBundleFileIdentity {
|
|
10
|
+
publisherKeyId: string;
|
|
11
|
+
bundleId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function communityBundleFileName(publisherKeyId: string, bundleId: string): string {
|
|
15
|
+
return `bundle-${publisherKeyId}-${bundleId}.json`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function communityRevocationFileName(revocationId: string): string {
|
|
19
|
+
return `revocation-${revocationId}.json`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function parseCommunityBundleFileName(name: string): CommunityBundleFileIdentity | null {
|
|
23
|
+
const match = COMMUNITY_BUNDLE_FILE_RE.exec(name);
|
|
24
|
+
return match ? { publisherKeyId: match[1]!, bundleId: match[2]! } : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isCommunityRevocationFileName(name: string): boolean {
|
|
28
|
+
return COMMUNITY_REVOCATION_FILE_RE.test(name);
|
|
29
|
+
}
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { jcsStringify } from "../digest";
|
|
4
|
+
import { ensureLabDirs, labCommunityDir } from "../paths";
|
|
5
|
+
import { validateCommunityEvidenceAuthorities } from "./community-authority";
|
|
6
|
+
import { communityBundleFileName } from "./community-files";
|
|
7
|
+
import { privateRegularFileSize, readPrivateRegularFile } from "./file-safety";
|
|
8
|
+
import { withPublicEvidenceMutationLock } from "./mutation-lock";
|
|
9
|
+
import { recordLocalPublicOrigin } from "./origin";
|
|
10
|
+
import {
|
|
11
|
+
cleanupStalePrivateFileStages,
|
|
12
|
+
cleanupStalePrivateFileStagesInDir,
|
|
13
|
+
isPrivateFileStageName,
|
|
14
|
+
publishPrivateFileExclusive,
|
|
15
|
+
} from "./private-file";
|
|
16
|
+
import { validatePublicEvidencePrivacy } from "./privacy";
|
|
17
|
+
import { verifyPublicEvidenceRevocation } from "./revocation";
|
|
18
|
+
import { loadExistingPublicPublisher, verifyPublicEvidenceBundle } from "./signature";
|
|
19
|
+
import { parseStrictPublicJson } from "./strict-json";
|
|
20
|
+
import type {
|
|
21
|
+
CommunityEvidenceSummaryV1,
|
|
22
|
+
PublicEvidenceBundleV1,
|
|
23
|
+
PublicEvidenceRevocationV1,
|
|
24
|
+
} from "./types";
|
|
25
|
+
import { PublicEvidenceValidationError } from "./validate";
|
|
26
|
+
|
|
27
|
+
const MAX_IMPORT_BYTES = 2 * 1024 * 1024;
|
|
28
|
+
const MAX_CACHE_FILES = 512;
|
|
29
|
+
const MAX_CACHE_BYTES = 64 * 1024 * 1024;
|
|
30
|
+
const MAX_DEPTH = 8;
|
|
31
|
+
const MAX_OBJECT_KEYS = 64;
|
|
32
|
+
const MAX_ARRAY_ELEMENTS = 512;
|
|
33
|
+
const MAX_GENERIC_STRING_BYTES = 384 * 1024;
|
|
34
|
+
const COMMUNITY_MUTATION_LOCK_NAME = ".mutation-lock";
|
|
35
|
+
const COMMUNITY_BUNDLE_FILE_RE = /^bundle-([0-9a-f]{64})-([0-9a-f]{64})\.json$/;
|
|
36
|
+
const COMMUNITY_REVOCATION_FILE_RE = /^revocation-([0-9a-f]{64})\.json$/;
|
|
37
|
+
|
|
38
|
+
const COMMUNITY_FILE_OPTIONS = {
|
|
39
|
+
maxBytes: MAX_IMPORT_BYTES,
|
|
40
|
+
errorCode: "community_unsafe_target",
|
|
41
|
+
errorMessage: "community object is not a bounded private regular file",
|
|
42
|
+
sizeErrorCode: "community_size",
|
|
43
|
+
sizeErrorMessage: "community file exceeds bound",
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
type CommunitySummaryCache = {
|
|
47
|
+
directory: string;
|
|
48
|
+
fingerprint: string;
|
|
49
|
+
evidence: CommunityEvidenceSummaryV1[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
let communitySummaryCache: CommunitySummaryCache | null = null;
|
|
53
|
+
|
|
54
|
+
function assertId(value: string): string {
|
|
55
|
+
if (!/^[0-9a-f]{64}$/.test(value)) {
|
|
56
|
+
throw new PublicEvidenceValidationError("community_id", "community object id invalid");
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function scanStructure(value: unknown, depth = 0): void {
|
|
62
|
+
if (depth > MAX_DEPTH) {
|
|
63
|
+
throw new PublicEvidenceValidationError("community_depth", "community JSON nesting depth exceeded");
|
|
64
|
+
}
|
|
65
|
+
if (typeof value === "string") {
|
|
66
|
+
if (new TextEncoder().encode(value).byteLength > MAX_GENERIC_STRING_BYTES || value.includes("\0")) {
|
|
67
|
+
throw new PublicEvidenceValidationError("community_string", "community string invalid or oversized");
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
if (value.length > MAX_ARRAY_ELEMENTS) {
|
|
73
|
+
throw new PublicEvidenceValidationError("community_array", "community array bound exceeded");
|
|
74
|
+
}
|
|
75
|
+
for (const item of value) scanStructure(item, depth + 1);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (value && typeof value === "object") {
|
|
79
|
+
const keys = Object.keys(value);
|
|
80
|
+
if (keys.length > MAX_OBJECT_KEYS) {
|
|
81
|
+
throw new PublicEvidenceValidationError("community_object", "community object key bound exceeded");
|
|
82
|
+
}
|
|
83
|
+
for (const key of keys) {
|
|
84
|
+
if (new TextEncoder().encode(key).byteLength > 4096) {
|
|
85
|
+
throw new PublicEvidenceValidationError("community_key", "community key oversized");
|
|
86
|
+
}
|
|
87
|
+
scanStructure((value as Record<string, unknown>)[key], depth + 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function boundedInput(raw: unknown): unknown {
|
|
93
|
+
let bytes: Buffer;
|
|
94
|
+
if (raw instanceof Uint8Array) {
|
|
95
|
+
bytes = Buffer.from(raw);
|
|
96
|
+
} else if (typeof raw === "string") {
|
|
97
|
+
bytes = Buffer.from(raw, "utf8");
|
|
98
|
+
} else {
|
|
99
|
+
scanStructure(raw);
|
|
100
|
+
bytes = Buffer.from(jcsStringify(raw), "utf8");
|
|
101
|
+
}
|
|
102
|
+
if (bytes.byteLength > MAX_IMPORT_BYTES) {
|
|
103
|
+
throw new PublicEvidenceValidationError("community_size", "community import exceeds 2 MiB");
|
|
104
|
+
}
|
|
105
|
+
const parsed = parseStrictPublicJson(bytes, "community import");
|
|
106
|
+
scanStructure(parsed);
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function assertCommunityArtifactAuthority(bundle: PublicEvidenceBundleV1): void {
|
|
111
|
+
if (bundle.artifacts.length !== 0) {
|
|
112
|
+
throw new PublicEvidenceValidationError(
|
|
113
|
+
"public_artifact_authority_required",
|
|
114
|
+
"community artifact bytes require reviewed public_export policy authority",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function verifiedBundle(raw: unknown): PublicEvidenceBundleV1 {
|
|
120
|
+
const result = verifyPublicEvidenceBundle(raw as PublicEvidenceBundleV1);
|
|
121
|
+
if (result.status !== "cryptographically_valid") {
|
|
122
|
+
throw new PublicEvidenceValidationError(result.status, "community bundle verification failed");
|
|
123
|
+
}
|
|
124
|
+
const bundle = validateCommunityEvidenceAuthorities(raw as PublicEvidenceBundleV1);
|
|
125
|
+
assertCommunityArtifactAuthority(bundle);
|
|
126
|
+
validatePublicEvidencePrivacy(bundle);
|
|
127
|
+
return bundle;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function bundleObjectPath(publisherKeyId: string, bundleId: string, configDir?: string): string {
|
|
131
|
+
return join(
|
|
132
|
+
labCommunityDir(configDir),
|
|
133
|
+
communityBundleFileName(assertId(publisherKeyId), assertId(bundleId)),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function revocationObjectPath(revocationId: string, configDir?: string): string {
|
|
138
|
+
return join(labCommunityDir(configDir), `revocation-${assertId(revocationId)}.json`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function readBounded(path: string): Buffer {
|
|
142
|
+
cleanupStalePrivateFileStages(path);
|
|
143
|
+
return readPrivateRegularFile(path, COMMUNITY_FILE_OPTIONS);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function cacheUsage(configDir?: string): { names: string[]; bytes: number } {
|
|
147
|
+
ensureLabDirs(configDir);
|
|
148
|
+
const dir = labCommunityDir(configDir);
|
|
149
|
+
cleanupStalePrivateFileStagesInDir(dir);
|
|
150
|
+
const names = readdirSync(dir)
|
|
151
|
+
.filter((name) => name !== COMMUNITY_MUTATION_LOCK_NAME && !isPrivateFileStageName(name))
|
|
152
|
+
.sort();
|
|
153
|
+
if (names.length > MAX_CACHE_FILES) {
|
|
154
|
+
throw new PublicEvidenceValidationError("community_cache_bound", "community cache file bound exceeded");
|
|
155
|
+
}
|
|
156
|
+
let bytes = 0;
|
|
157
|
+
for (const name of names) {
|
|
158
|
+
bytes += privateRegularFileSize(join(dir, name), COMMUNITY_FILE_OPTIONS);
|
|
159
|
+
if (bytes > MAX_CACHE_BYTES) {
|
|
160
|
+
throw new PublicEvidenceValidationError("community_cache_bound", "community cache byte bound exceeded");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { names, bytes };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function assertCacheCanAdd(byteCount: number, configDir?: string): void {
|
|
167
|
+
const usage = cacheUsage(configDir);
|
|
168
|
+
if (usage.names.length >= MAX_CACHE_FILES || usage.bytes + byteCount > MAX_CACHE_BYTES) {
|
|
169
|
+
throw new PublicEvidenceValidationError("community_cache_bound", "community cache capacity exceeded");
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function persistAtLocked(
|
|
174
|
+
path: string,
|
|
175
|
+
kind: "bundle" | "revocation",
|
|
176
|
+
value: unknown,
|
|
177
|
+
configDir?: string,
|
|
178
|
+
onCommit?: () => void,
|
|
179
|
+
): { path: string; created: boolean } {
|
|
180
|
+
const bytes = Buffer.from(jcsStringify(value), "utf8");
|
|
181
|
+
if (bytes.byteLength > MAX_IMPORT_BYTES) {
|
|
182
|
+
throw new PublicEvidenceValidationError("community_size", "community object exceeds bound");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let created = false;
|
|
186
|
+
try {
|
|
187
|
+
try {
|
|
188
|
+
const existing = readBounded(path);
|
|
189
|
+
if (!existing.equals(bytes)) {
|
|
190
|
+
throw new PublicEvidenceValidationError("community_conflict", `${kind} identity already exists with different bytes`);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error instanceof PublicEvidenceValidationError) throw error;
|
|
194
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
195
|
+
assertCacheCanAdd(bytes.byteLength, configDir);
|
|
196
|
+
const published = publishPrivateFileExclusive(path, bytes);
|
|
197
|
+
if (!published.created) {
|
|
198
|
+
const raced = readBounded(path);
|
|
199
|
+
if (!raced.equals(bytes)) {
|
|
200
|
+
throw new PublicEvidenceValidationError("community_conflict", `${kind} identity already exists with different bytes`);
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
created = true;
|
|
204
|
+
cacheUsage(configDir);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
onCommit?.();
|
|
209
|
+
if (created) communitySummaryCache = null;
|
|
210
|
+
return { path, created };
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (created) {
|
|
213
|
+
try { unlinkSync(path); } catch { /* preserve commit error */ }
|
|
214
|
+
communitySummaryCache = null;
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function persistAt(
|
|
221
|
+
path: string,
|
|
222
|
+
kind: "bundle" | "revocation",
|
|
223
|
+
value: unknown,
|
|
224
|
+
configDir?: string,
|
|
225
|
+
onCommit?: () => void,
|
|
226
|
+
): { path: string; created: boolean } {
|
|
227
|
+
return withPublicEvidenceMutationLock(
|
|
228
|
+
configDir,
|
|
229
|
+
() => persistAtLocked(path, kind, value, configDir, onCommit),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function readJson(path: string): unknown {
|
|
234
|
+
const parsed = parseStrictPublicJson(readBounded(path), "stored community object");
|
|
235
|
+
scanStructure(parsed);
|
|
236
|
+
return parsed;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function files(configDir?: string): string[] {
|
|
240
|
+
return cacheUsage(configDir).names;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function readVerifiedBundleAt(path: string): PublicEvidenceBundleV1 {
|
|
244
|
+
return verifiedBundle(readJson(path));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function bundleFromName(name: string, configDir?: string): PublicEvidenceBundleV1 | null {
|
|
248
|
+
const match = COMMUNITY_BUNDLE_FILE_RE.exec(name);
|
|
249
|
+
if (!match) return null;
|
|
250
|
+
const publisherKeyId = match[1]!;
|
|
251
|
+
const bundleId = match[2]!;
|
|
252
|
+
const bundle = readVerifiedBundleAt(bundleObjectPath(publisherKeyId, bundleId, configDir));
|
|
253
|
+
if (bundle.bundleId !== bundleId || bundle.publisher.keyId !== publisherKeyId) {
|
|
254
|
+
throw new PublicEvidenceValidationError("community_identity_mismatch", "stored community bundle does not match filename identity");
|
|
255
|
+
}
|
|
256
|
+
return bundle;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function bundlesFromNames(names: readonly string[], configDir?: string): PublicEvidenceBundleV1[] {
|
|
260
|
+
const bundles: PublicEvidenceBundleV1[] = [];
|
|
261
|
+
for (const name of names) {
|
|
262
|
+
const bundle = bundleFromName(name, configDir);
|
|
263
|
+
if (bundle) bundles.push(bundle);
|
|
264
|
+
}
|
|
265
|
+
return bundles;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function restoreOwnPublisherOrigin(bundle: PublicEvidenceBundleV1, configDir?: string): void {
|
|
269
|
+
const local = loadExistingPublicPublisher(configDir);
|
|
270
|
+
if (!local) return;
|
|
271
|
+
if (
|
|
272
|
+
local.publisher.algorithm !== bundle.publisher.algorithm
|
|
273
|
+
|| local.publisher.keyId !== bundle.publisher.keyId
|
|
274
|
+
|| local.publisher.publicKey !== bundle.publisher.publicKey
|
|
275
|
+
) return;
|
|
276
|
+
recordLocalPublicOrigin({ publisherKeyId: bundle.publisher.keyId, bundleId: bundle.bundleId }, configDir);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function importCommunityEvidenceBundle(
|
|
280
|
+
raw: unknown,
|
|
281
|
+
configDir?: string,
|
|
282
|
+
): { created: boolean; status: "cryptographically_valid"; bundleId: string; publisherKeyId: string; path: string } {
|
|
283
|
+
const bundle = verifiedBundle(boundedInput(raw));
|
|
284
|
+
ensureLabDirs(configDir);
|
|
285
|
+
const stored = persistAt(
|
|
286
|
+
bundleObjectPath(bundle.publisher.keyId, bundle.bundleId, configDir),
|
|
287
|
+
"bundle",
|
|
288
|
+
bundle,
|
|
289
|
+
configDir,
|
|
290
|
+
() => restoreOwnPublisherOrigin(bundle, configDir),
|
|
291
|
+
);
|
|
292
|
+
return { ...stored, status: "cryptographically_valid", bundleId: bundle.bundleId, publisherKeyId: bundle.publisher.keyId };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function readCommunityEvidenceBundleForPublisherLocked(
|
|
296
|
+
bundleId: string,
|
|
297
|
+
publisherKeyId: string,
|
|
298
|
+
configDir?: string,
|
|
299
|
+
): PublicEvidenceBundleV1 {
|
|
300
|
+
const bundle = readVerifiedBundleAt(bundleObjectPath(publisherKeyId, bundleId, configDir));
|
|
301
|
+
if (bundle.bundleId !== bundleId || bundle.publisher.keyId !== publisherKeyId) {
|
|
302
|
+
throw new PublicEvidenceValidationError("community_identity_mismatch", "stored community bundle does not match filename identity");
|
|
303
|
+
}
|
|
304
|
+
return bundle;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function readCommunityEvidenceBundleForPublisher(
|
|
308
|
+
bundleId: string,
|
|
309
|
+
publisherKeyId: string,
|
|
310
|
+
configDir?: string,
|
|
311
|
+
): PublicEvidenceBundleV1 {
|
|
312
|
+
return withPublicEvidenceMutationLock(
|
|
313
|
+
configDir,
|
|
314
|
+
() => readCommunityEvidenceBundleForPublisherLocked(bundleId, publisherKeyId, configDir),
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
type RevocationMetadata = {
|
|
319
|
+
publisher?: { keyId?: unknown };
|
|
320
|
+
targets?: Array<{ kind?: unknown; id?: unknown }>;
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
function resolveTargetBundle(
|
|
324
|
+
revocation: unknown,
|
|
325
|
+
bundles: readonly PublicEvidenceBundleV1[],
|
|
326
|
+
): PublicEvidenceBundleV1 {
|
|
327
|
+
if (!revocation || typeof revocation !== "object") {
|
|
328
|
+
throw new PublicEvidenceValidationError("revocation_target", "revocation target metadata unavailable");
|
|
329
|
+
}
|
|
330
|
+
const raw = revocation as RevocationMetadata;
|
|
331
|
+
if (!Array.isArray(raw.targets) || typeof raw.publisher?.keyId !== "string") {
|
|
332
|
+
throw new PublicEvidenceValidationError("revocation_target", "revocation targets or publisher unavailable");
|
|
333
|
+
}
|
|
334
|
+
const publisherKeyId = assertId(raw.publisher.keyId);
|
|
335
|
+
const publisherBundles = bundles
|
|
336
|
+
.filter((bundle) => bundle.publisher.keyId === publisherKeyId)
|
|
337
|
+
.sort((a, b) => a.bundleId.localeCompare(b.bundleId));
|
|
338
|
+
const bundleTargets = raw.targets.filter((target) => target.kind === "bundle" && typeof target.id === "string");
|
|
339
|
+
if (bundleTargets.length > 0) {
|
|
340
|
+
const targetIds = new Set(bundleTargets.map((target) => target.id));
|
|
341
|
+
if (targetIds.size !== 1) {
|
|
342
|
+
throw new PublicEvidenceValidationError("revocation_target", "revocation bundle targets are ambiguous");
|
|
343
|
+
}
|
|
344
|
+
const id = [...targetIds][0]!;
|
|
345
|
+
const candidate = publisherBundles.find((bundle) => bundle.bundleId === id);
|
|
346
|
+
if (!candidate) throw new PublicEvidenceValidationError("revocation_target", "revocation target bundle not found");
|
|
347
|
+
return candidate;
|
|
348
|
+
}
|
|
349
|
+
const fullyMatching = publisherBundles.filter((bundle) => raw.targets!.every((target) =>
|
|
350
|
+
target.kind === "record" && typeof target.id === "string"
|
|
351
|
+
&& bundle.records.some((record) => record.recordId === target.id),
|
|
352
|
+
));
|
|
353
|
+
if (fullyMatching.length === 0) {
|
|
354
|
+
throw new PublicEvidenceValidationError(
|
|
355
|
+
"revocation_target",
|
|
356
|
+
"revocation targets do not resolve to a verified bundle for the same publisher",
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return fullyMatching[0]!;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function findTargetBundleLocked(revocation: unknown, configDir?: string): PublicEvidenceBundleV1 {
|
|
363
|
+
const names = files(configDir);
|
|
364
|
+
const raw = revocation as RevocationMetadata;
|
|
365
|
+
const publisherKeyId = typeof raw?.publisher?.keyId === "string" ? assertId(raw.publisher.keyId) : null;
|
|
366
|
+
const directBundleIds = Array.isArray(raw?.targets)
|
|
367
|
+
? [...new Set(raw.targets.filter((target) => target.kind === "bundle" && typeof target.id === "string").map((target) => target.id as string))]
|
|
368
|
+
: [];
|
|
369
|
+
if (publisherKeyId && directBundleIds.length === 1) {
|
|
370
|
+
try {
|
|
371
|
+
return readCommunityEvidenceBundleForPublisherLocked(
|
|
372
|
+
assertId(directBundleIds[0]!),
|
|
373
|
+
publisherKeyId,
|
|
374
|
+
configDir,
|
|
375
|
+
);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
378
|
+
throw new PublicEvidenceValidationError(
|
|
379
|
+
"revocation_target",
|
|
380
|
+
"revocation target bundle not found",
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return resolveTargetBundle(revocation, bundlesFromNames(names, configDir));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function importCommunityEvidenceRevocation(
|
|
390
|
+
raw: unknown,
|
|
391
|
+
configDir?: string,
|
|
392
|
+
): { created: boolean; status: "cryptographically_valid"; revocationId: string; path: string } {
|
|
393
|
+
const parsed = boundedInput(raw);
|
|
394
|
+
ensureLabDirs(configDir);
|
|
395
|
+
return withPublicEvidenceMutationLock(configDir, () => {
|
|
396
|
+
const targetBundle = findTargetBundleLocked(parsed, configDir);
|
|
397
|
+
const verified = verifyPublicEvidenceRevocation(parsed, targetBundle);
|
|
398
|
+
if (verified.status !== "cryptographically_valid") {
|
|
399
|
+
throw new PublicEvidenceValidationError(verified.status, verified.detail ?? "community revocation verification failed");
|
|
400
|
+
}
|
|
401
|
+
const stored = persistAtLocked(
|
|
402
|
+
revocationObjectPath(verified.revocation.revocationId, configDir),
|
|
403
|
+
"revocation",
|
|
404
|
+
verified.revocation,
|
|
405
|
+
configDir,
|
|
406
|
+
);
|
|
407
|
+
return { ...stored, status: "cryptographically_valid", revocationId: verified.revocation.revocationId };
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function communityFingerprint(names: readonly string[], configDir?: string): string {
|
|
412
|
+
const dir = labCommunityDir(configDir);
|
|
413
|
+
return names.map((name) => {
|
|
414
|
+
const stat = lstatSync(join(dir, name));
|
|
415
|
+
return [name, stat.dev, stat.ino, stat.mode, stat.nlink, stat.size, stat.mtimeMs, stat.ctimeMs].join(":");
|
|
416
|
+
}).join("\n");
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function copySummaries(evidence: readonly CommunityEvidenceSummaryV1[]): CommunityEvidenceSummaryV1[] {
|
|
420
|
+
return evidence.map((row) => ({ ...row }));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function listCommunityEvidenceLocked(configDir?: string): CommunityEvidenceSummaryV1[] {
|
|
424
|
+
const names = files(configDir);
|
|
425
|
+
const directory = labCommunityDir(configDir);
|
|
426
|
+
const fingerprint = communityFingerprint(names, configDir);
|
|
427
|
+
if (communitySummaryCache?.directory === directory && communitySummaryCache.fingerprint === fingerprint) {
|
|
428
|
+
return copySummaries(communitySummaryCache.evidence);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const bundles = bundlesFromNames(names, configDir);
|
|
432
|
+
const revocations: PublicEvidenceRevocationV1[] = [];
|
|
433
|
+
|
|
434
|
+
for (const name of names) {
|
|
435
|
+
if (!COMMUNITY_REVOCATION_FILE_RE.test(name)) continue;
|
|
436
|
+
const raw = readJson(join(directory, name));
|
|
437
|
+
let targetBundle: PublicEvidenceBundleV1;
|
|
438
|
+
try {
|
|
439
|
+
targetBundle = resolveTargetBundle(raw, bundles);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (error instanceof PublicEvidenceValidationError) continue;
|
|
442
|
+
throw error;
|
|
443
|
+
}
|
|
444
|
+
const verified = verifyPublicEvidenceRevocation(raw, targetBundle);
|
|
445
|
+
if (verified.status === "cryptographically_valid") revocations.push(verified.revocation);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const evidence = bundles.map((bundle) => {
|
|
449
|
+
const revoked = new Set<string>();
|
|
450
|
+
const bundleRecordIds = new Set(bundle.records.map((record) => record.recordId));
|
|
451
|
+
for (const revocation of revocations) {
|
|
452
|
+
if (revocation.publisher.keyId !== bundle.publisher.keyId
|
|
453
|
+
|| revocation.publisher.publicKey !== bundle.publisher.publicKey) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (revocation.targets.some((target) => target.kind === "bundle" && target.id === bundle.bundleId)) {
|
|
457
|
+
for (const record of bundle.records) revoked.add(record.recordId);
|
|
458
|
+
}
|
|
459
|
+
for (const target of revocation.targets) {
|
|
460
|
+
if (target.kind === "record" && bundleRecordIds.has(target.id)) revoked.add(target.id);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
trustClass: "community_untrusted_v1" as const,
|
|
465
|
+
status: "cryptographically_valid" as const,
|
|
466
|
+
bundleId: bundle.bundleId,
|
|
467
|
+
publisherKeyId: bundle.publisher.keyId,
|
|
468
|
+
activeRecordCount: bundle.records.filter((record) => !revoked.has(record.recordId)).length,
|
|
469
|
+
revokedRecordCount: bundle.records.filter((record) => revoked.has(record.recordId)).length,
|
|
470
|
+
};
|
|
471
|
+
}).sort((a, b) => a.bundleId.localeCompare(b.bundleId) || a.publisherKeyId.localeCompare(b.publisherKeyId));
|
|
472
|
+
|
|
473
|
+
communitySummaryCache = { directory, fingerprint, evidence: copySummaries(evidence) };
|
|
474
|
+
return copySummaries(evidence);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export function listCommunityEvidence(configDir?: string): CommunityEvidenceSummaryV1[] {
|
|
478
|
+
return withPublicEvidenceMutationLock(configDir, () => listCommunityEvidenceLocked(configDir));
|
|
479
|
+
}
|