@aiwg/cli 0.0.0-bootstrap.0 → 2026.7.17
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/README.md +21 -1
- package/bin/aiwg.mjs +349 -0
- package/dist/src/a2a/agent-card.js +145 -0
- package/dist/src/a2a/client.js +411 -0
- package/dist/src/a2a/hitl-cli.js +171 -0
- package/dist/src/a2a/hitl-driver.js +370 -0
- package/dist/src/a2a/hitl.js +181 -0
- package/dist/src/a2a/http.js +193 -0
- package/dist/src/a2a/jcs.js +128 -0
- package/dist/src/a2a/jws.js +187 -0
- package/dist/src/a2a/types.js +24 -0
- package/dist/src/a2a/webhook.js +240 -0
- package/dist/src/activity-log/cli.js +348 -0
- package/dist/src/activity-log/parser.js +81 -0
- package/dist/src/activity-log/types.js +50 -0
- package/dist/src/agents/agent-deployer.js +367 -0
- package/dist/src/agents/agent-packager.js +271 -0
- package/dist/src/agents/agent-validator.js +271 -0
- package/dist/src/agents/grounding/knowledge-base.js +161 -0
- package/dist/src/agents/grounding/types.js +12 -0
- package/dist/src/agents/index.js +10 -0
- package/dist/src/agents/packaged-agent-inventory.js +75 -0
- package/dist/src/agents/types.js +7 -0
- package/dist/src/api/index.d.ts +10 -0
- package/dist/src/api/index.js +10 -0
- package/dist/src/artifacts/address-parser.js +73 -0
- package/dist/src/artifacts/audit/cli.js +183 -0
- package/dist/src/artifacts/audit/drift.js +122 -0
- package/dist/src/artifacts/audit/types.js +12 -0
- package/dist/src/artifacts/backends/graphology-backend.js +160 -0
- package/dist/src/artifacts/backends/json-backend.js +129 -0
- package/dist/src/artifacts/backends/sqlite-backend.js +199 -0
- package/dist/src/artifacts/browser-export.js +622 -0
- package/dist/src/artifacts/capability-resolver.js +94 -0
- package/dist/src/artifacts/checksum-manifest.js +91 -0
- package/dist/src/artifacts/citation-parser.js +162 -0
- package/dist/src/artifacts/cli.js +1462 -0
- package/dist/src/artifacts/corpus-tools/citation-densify.js +311 -0
- package/dist/src/artifacts/corpus-tools/cli.js +393 -0
- package/dist/src/artifacts/corpus-tools/corpus-graph.js +225 -0
- package/dist/src/artifacts/corpus-tools/curator.js +186 -0
- package/dist/src/artifacts/corpus-tools/discovery-log.js +70 -0
- package/dist/src/artifacts/corpus-tools/funder-network.js +141 -0
- package/dist/src/artifacts/corpus-tools/induction-audit.js +233 -0
- package/dist/src/artifacts/corpus-tools/integrity-scan.js +202 -0
- package/dist/src/artifacts/corpus-tools/profile-communities.js +166 -0
- package/dist/src/artifacts/corpus-tools/profile-edges.js +72 -0
- package/dist/src/artifacts/corpus-tools/profile-embed.js +113 -0
- package/dist/src/artifacts/corpus-tools/profile-generate-fm.js +167 -0
- package/dist/src/artifacts/corpus-tools/profile-generate.js +181 -0
- package/dist/src/artifacts/corpus-tools/profile-metrics.js +98 -0
- package/dist/src/artifacts/corpus-tools/profile-status.js +86 -0
- package/dist/src/artifacts/corpus-tools/profile-temporal.js +139 -0
- package/dist/src/artifacts/corpus-tools/radar-init.js +93 -0
- package/dist/src/artifacts/corpus-tools/radar-report.js +118 -0
- package/dist/src/artifacts/corpus-tools/radar-shared.js +189 -0
- package/dist/src/artifacts/corpus-tools/radar-status.js +68 -0
- package/dist/src/artifacts/corpus-tools/sidecar-lint.js +233 -0
- package/dist/src/artifacts/corpus-tools/sidecar-repair.js +277 -0
- package/dist/src/artifacts/corpus-tools/snapshot.js +591 -0
- package/dist/src/artifacts/corpus-tools/source-types.js +164 -0
- package/dist/src/artifacts/corpus-tools/vision-extract.js +243 -0
- package/dist/src/artifacts/corpus-views/build.js +126 -0
- package/dist/src/artifacts/corpus-views/corpus-config.js +124 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +437 -0
- package/dist/src/artifacts/corpus-views/renderers.js +353 -0
- package/dist/src/artifacts/corpus-views/taxonomies.js +131 -0
- package/dist/src/artifacts/dep-graph.js +173 -0
- package/dist/src/artifacts/discover-facets.js +375 -0
- package/dist/src/artifacts/embedding-index.js +354 -0
- package/dist/src/artifacts/enrichment/cli.js +240 -0
- package/dist/src/artifacts/enrichment/prompt.js +47 -0
- package/dist/src/artifacts/enrichment/store.js +90 -0
- package/dist/src/artifacts/enrichment/types.js +12 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +475 -0
- package/dist/src/artifacts/fortemi-core-sync.js +251 -0
- package/dist/src/artifacts/fortemi-shard-export.js +44 -0
- package/dist/src/artifacts/fulltext.js +108 -0
- package/dist/src/artifacts/graph-backend.js +51 -0
- package/dist/src/artifacts/graph-query.js +247 -0
- package/dist/src/artifacts/hybrid-query.js +225 -0
- package/dist/src/artifacts/index-builder.js +1089 -0
- package/dist/src/artifacts/index-reader.js +112 -0
- package/dist/src/artifacts/index-status.js +193 -0
- package/dist/src/artifacts/legacy-index-migration.js +169 -0
- package/dist/src/artifacts/move.js +154 -0
- package/dist/src/artifacts/operational-state.js +239 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +49 -0
- package/dist/src/artifacts/query-engine.js +1955 -0
- package/dist/src/artifacts/source-graph.js +529 -0
- package/dist/src/artifacts/stats.js +169 -0
- package/dist/src/artifacts/types.js +606 -0
- package/dist/src/artifacts/views/cli.js +293 -0
- package/dist/src/artifacts/views/definition.js +170 -0
- package/dist/src/artifacts/views/store.js +149 -0
- package/dist/src/artifacts/views/types.js +8 -0
- package/dist/src/artifacts/watcher.js +255 -0
- package/dist/src/catalog/builtin-models.json +636 -0
- package/dist/src/catalog/cli.js +228 -0
- package/dist/src/catalog/cli.mjs +289 -0
- package/dist/src/catalog/index.js +15 -0
- package/dist/src/catalog/loader.js +217 -0
- package/dist/src/catalog/loader.mjs +251 -0
- package/dist/src/catalog/sources.json +41 -0
- package/dist/src/catalog/types.js +8 -0
- package/dist/src/channel/manager.mjs +504 -0
- package/dist/src/cli/agent-spawn.js +178 -0
- package/dist/src/cli/cli-extension-loader.js +245 -0
- package/dist/src/cli/command-log.js +275 -0
- package/dist/src/cli/config-loader.js +232 -0
- package/dist/src/cli/env.js +82 -0
- package/dist/src/cli/errors.js +150 -0
- package/dist/src/cli/find-package-root.js +40 -0
- package/dist/src/cli/git-hooks.js +250 -0
- package/dist/src/cli/handlers/agentcard.js +206 -0
- package/dist/src/cli/handlers/artifacts.js +76 -0
- package/dist/src/cli/handlers/best-practices-audit.js +280 -0
- package/dist/src/cli/handlers/cockpit.js +137 -0
- package/dist/src/cli/handlers/command-log.js +37 -0
- package/dist/src/cli/handlers/daemon.js +59 -0
- package/dist/src/cli/handlers/diagnose.js +300 -0
- package/dist/src/cli/handlers/execution-mode.js +105 -0
- package/dist/src/cli/handlers/feedback.js +341 -0
- package/dist/src/cli/handlers/help.js +147 -0
- package/dist/src/cli/handlers/index.js +302 -0
- package/dist/src/cli/handlers/init.js +179 -0
- package/dist/src/cli/handlers/install.js +129 -0
- package/dist/src/cli/handlers/issues.js +46 -0
- package/dist/src/cli/handlers/lint.js +42 -0
- package/dist/src/cli/handlers/local-executor.js +315 -0
- package/dist/src/cli/handlers/marketplace.js +168 -0
- package/dist/src/cli/handlers/mc.js +1010 -0
- package/dist/src/cli/handlers/models.js +352 -0
- package/dist/src/cli/handlers/packages.js +155 -0
- package/dist/src/cli/handlers/ralph-launcher.js +717 -0
- package/dist/src/cli/handlers/ralph.js +575 -0
- package/dist/src/cli/handlers/refresh.js +432 -0
- package/dist/src/cli/handlers/regenerate.js +336 -0
- package/dist/src/cli/handlers/repo-access.js +109 -0
- package/dist/src/cli/handlers/run.js +165 -0
- package/dist/src/cli/handlers/runtime-info.js +301 -0
- package/dist/src/cli/handlers/sandbox.js +197 -0
- package/dist/src/cli/handlers/scaffolding.js +149 -0
- package/dist/src/cli/handlers/script-runner.js +121 -0
- package/dist/src/cli/handlers/sdlc-accelerate.js +148 -0
- package/dist/src/cli/handlers/serve.js +1835 -0
- package/dist/src/cli/handlers/session.js +348 -0
- package/dist/src/cli/handlers/setup.js +440 -0
- package/dist/src/cli/handlers/skill-lint.js +367 -0
- package/dist/src/cli/handlers/skill-usage.js +74 -0
- package/dist/src/cli/handlers/steward.js +567 -0
- package/dist/src/cli/handlers/subcommands.js +1702 -0
- package/dist/src/cli/handlers/team.js +337 -0
- package/dist/src/cli/handlers/types.js +11 -0
- package/dist/src/cli/handlers/use.js +2772 -0
- package/dist/src/cli/handlers/utilities.js +703 -0
- package/dist/src/cli/handlers/version.js +134 -0
- package/dist/src/cli/handlers/workspace-context.js +93 -0
- package/dist/src/cli/handlers/workspace.js +126 -0
- package/dist/src/cli/help-generator.js +178 -0
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +126 -0
- package/dist/src/cli/hooks/executor.js +96 -0
- package/dist/src/cli/hooks/index.js +15 -0
- package/dist/src/cli/hooks/registry.js +143 -0
- package/dist/src/cli/hooks/types.js +13 -0
- package/dist/src/cli/log.js +492 -0
- package/dist/src/cli/project-isolation/detect.js +70 -0
- package/dist/src/cli/project-isolation/index.js +7 -0
- package/dist/src/cli/project-isolation/signals.js +17 -0
- package/dist/src/cli/project-isolation/warning.js +102 -0
- package/dist/src/cli/prompt-utils.js +155 -0
- package/dist/src/cli/provider-deployment-plan.js +126 -0
- package/dist/src/cli/provider-resolution.js +119 -0
- package/dist/src/cli/router.js +305 -0
- package/dist/src/cli/scope-resolver.js +392 -0
- package/dist/src/cli/skill-usage.js +616 -0
- package/dist/src/cli/ui.js +224 -0
- package/dist/src/cli/watch-service.js +203 -0
- package/dist/src/cli/workflow-orchestrator.js +478 -0
- package/dist/src/cli/workspace-signals.js +321 -0
- package/dist/src/community/footer.js +14 -0
- package/dist/src/community/links.js +123 -0
- package/dist/src/community/milestones.js +46 -0
- package/dist/src/community/nudge-policy.js +93 -0
- package/dist/src/config/aiwg-config.js +883 -0
- package/dist/src/config/cli.js +703 -0
- package/dist/src/config/gitignore.js +156 -0
- package/dist/src/config/project-artifacts.js +76 -0
- package/dist/src/config/user-config.js +428 -0
- package/dist/src/config/user-registry.js +127 -0
- package/dist/src/config/workspace.js +276 -0
- package/dist/src/contributors/detect.js +39 -0
- package/dist/src/contributors/discover.js +172 -0
- package/dist/src/contributors/heuristic.js +186 -0
- package/dist/src/contributors/index.js +11 -0
- package/dist/src/contributors/types.js +13 -0
- package/dist/src/contributors/validation.js +116 -0
- package/dist/src/data/community.schema.json +43 -0
- package/dist/src/data/community.yaml +16 -0
- package/dist/src/extensions/capability-index.js +270 -0
- package/dist/src/extensions/claude-hooks-installer.js +268 -0
- package/dist/src/extensions/commands/definitions.js +3485 -0
- package/dist/src/extensions/deployment-registration.js +463 -0
- package/dist/src/extensions/index.js +22 -0
- package/dist/src/extensions/loader.js +132 -0
- package/dist/src/extensions/managed-marker.js +100 -0
- package/dist/src/extensions/manifest.js +196 -0
- package/dist/src/extensions/project-local-activity.js +114 -0
- package/dist/src/extensions/project-local-discovery.js +261 -0
- package/dist/src/extensions/project-local-doctor.js +281 -0
- package/dist/src/extensions/project-local-gitignore.js +176 -0
- package/dist/src/extensions/project-local-paths.js +121 -0
- package/dist/src/extensions/project-local-promote.js +218 -0
- package/dist/src/extensions/project-local-remove.js +373 -0
- package/dist/src/extensions/project-local-scaffold.js +252 -0
- package/dist/src/extensions/project-quickref.js +291 -0
- package/dist/src/extensions/registry.js +368 -0
- package/dist/src/extensions/shadow-resolver.js +271 -0
- package/dist/src/extensions/types.js +86 -0
- package/dist/src/extensions/upstream-registry.js +195 -0
- package/dist/src/extensions/validation.js +631 -0
- package/dist/src/features/catalog.js +88 -0
- package/dist/src/features/cli.js +197 -0
- package/dist/src/features/installer.js +57 -0
- package/dist/src/features/paths.js +14 -0
- package/dist/src/features/runtime.js +21 -0
- package/dist/src/features/status.js +134 -0
- package/dist/src/hooks/laziness-detection.js +532 -0
- package/dist/src/intake/git-history-analyzer.js +486 -0
- package/dist/src/intake/index.js +9 -0
- package/dist/src/issues/cli.js +327 -0
- package/dist/src/issues/index.js +5 -0
- package/dist/src/issues/live.js +154 -0
- package/dist/src/issues/local.js +666 -0
- package/dist/src/issues/sync.js +143 -0
- package/dist/src/issues/types.js +2 -0
- package/dist/src/issues/workflows.js +164 -0
- package/dist/src/kb/cli.js +173 -0
- package/dist/src/lint/cli.js +171 -0
- package/dist/src/lint/loader.js +225 -0
- package/dist/src/lint/reporters.js +141 -0
- package/dist/src/lint/runner.js +364 -0
- package/dist/src/lint/types.js +10 -0
- package/dist/src/marketplace/cache.js +110 -0
- package/dist/src/marketplace/identifier.js +63 -0
- package/dist/src/marketplace/registry.js +36 -0
- package/dist/src/marketplace/sources/clawhub.js +53 -0
- package/dist/src/marketplace/sources/git.js +45 -0
- package/dist/src/marketplace/types.js +9 -0
- package/dist/src/mcp/adapters/codex-runtime.js +224 -0
- package/dist/src/mcp/cli.mjs +1197 -0
- package/dist/src/mcp/elicitation.mjs +149 -0
- package/dist/src/mcp/helpers.mjs +287 -0
- package/dist/src/mcp/index.mjs +11 -0
- package/dist/src/mcp/profiles.js +263 -0
- package/dist/src/mcp/profiles.mjs +360 -0
- package/dist/src/mcp/registry.js +376 -0
- package/dist/src/mcp/registry.mjs +387 -0
- package/dist/src/mcp/server.mjs +633 -0
- package/dist/src/mcp/test-simple.mjs +271 -0
- package/dist/src/mcp/tools/command-run.mjs +101 -0
- package/dist/src/mcp/tools/discovery.mjs +291 -0
- package/dist/src/mcp/tools/interaction.mjs +87 -0
- package/dist/src/mcp/tools/orchestration.mjs +320 -0
- package/dist/src/mcp/tools/subsystems.mjs +640 -0
- package/dist/src/memory/cli.js +166 -0
- package/dist/src/memory/project-registry.js +282 -0
- package/dist/src/metrics/artifact-metrics.js +184 -0
- package/dist/src/metrics/context-budget.js +174 -0
- package/dist/src/metrics/token-counter.js +133 -0
- package/dist/src/models/config-loader.js +228 -0
- package/dist/src/models/index.js +22 -0
- package/dist/src/models/model-capabilities.v1.json +120 -0
- package/dist/src/models/model-catalog.v1.json +96 -0
- package/dist/src/models/model-discovery.js +521 -0
- package/dist/src/models/provider-models.js +73 -0
- package/dist/src/models/provider-policy.js +273 -0
- package/dist/src/models/resolver.js +245 -0
- package/dist/src/models/router.js +70 -0
- package/dist/src/models/types.js +8 -0
- package/dist/src/models/wrapper-deployment.js +178 -0
- package/dist/src/models/wrapper-route.js +54 -0
- package/dist/src/ops/cli.js +268 -0
- package/dist/src/ops/registry.js +623 -0
- package/dist/src/packages/adapters/clawhub.js +113 -0
- package/dist/src/packages/adapters/git.js +153 -0
- package/dist/src/packages/adapters/gitea.js +93 -0
- package/dist/src/packages/adapters/github.js +69 -0
- package/dist/src/packages/adapters/local-cache.js +89 -0
- package/dist/src/packages/package-registry.js +143 -0
- package/dist/src/packages/registry.js +238 -0
- package/dist/src/packages/types.js +11 -0
- package/dist/src/plugin/cross-framework-ops.js +475 -0
- package/dist/src/plugin/framework-config-loader.js +235 -0
- package/dist/src/plugin/framework-detector.js +186 -0
- package/dist/src/plugin/framework-isolator.js +341 -0
- package/dist/src/plugin/framework-migration.js +371 -0
- package/dist/src/plugin/metadata-validator.js +906 -0
- package/dist/src/plugin/plugin-installer.js +436 -0
- package/dist/src/plugin/plugin-status.js +369 -0
- package/dist/src/plugin/plugin-uninstaller.js +473 -0
- package/dist/src/plugin/registry-validator.js +344 -0
- package/dist/src/plugin/skill-command-translator.js +415 -0
- package/dist/src/plugin/workspace-creator.js +189 -0
- package/dist/src/plugin/workspace-migrator.js +821 -0
- package/dist/src/policy/authorization.js +390 -0
- package/dist/src/policy/repo-access.js +215 -0
- package/dist/src/provenance/cli.js +16 -0
- package/dist/src/providers/capability-matrix.js +300 -0
- package/dist/src/providers/capability-matrix.yaml +514 -0
- package/dist/src/providers/provider-definitions.js +977 -0
- package/dist/src/providers/provider-definitions.mjs +159 -0
- package/dist/src/providers/provider-inventory.js +169 -0
- package/dist/src/quality/feedback-ab.js +174 -0
- package/dist/src/quality/feedback-collector.js +176 -0
- package/dist/src/quality/feedback-tracker.js +123 -0
- package/dist/src/quality/patterns/adr.json +24 -0
- package/dist/src/quality/patterns/sad.json +24 -0
- package/dist/src/quality/patterns/test-plan.json +24 -0
- package/dist/src/quality/patterns/use-case.json +24 -0
- package/dist/src/quality/scoring.js +157 -0
- package/dist/src/reflections/cli.js +15 -0
- package/dist/src/release/plan.js +139 -0
- package/dist/src/research/cache/manager.js +227 -0
- package/dist/src/research/clients/arxiv.js +156 -0
- package/dist/src/research/clients/base.js +197 -0
- package/dist/src/research/clients/crossref.js +112 -0
- package/dist/src/research/clients/semantic-scholar.js +126 -0
- package/dist/src/research/clients/unpaywall.js +87 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/query-cli.js +336 -0
- package/dist/src/research/services/acquisition.js +313 -0
- package/dist/src/research/services/archival.js +225 -0
- package/dist/src/research/services/citation.js +279 -0
- package/dist/src/research/services/discovery.js +243 -0
- package/dist/src/research/services/documentation.js +269 -0
- package/dist/src/research/services/index.js +14 -0
- package/dist/src/research/services/provenance.js +279 -0
- package/dist/src/research/services/quality.js +370 -0
- package/dist/src/research/services/types.js +7 -0
- package/dist/src/research/storage-cli.js +20 -0
- package/dist/src/research/types.js +47 -0
- package/dist/src/resources/index.d.ts +2 -0
- package/dist/src/resources/index.js +2 -0
- package/dist/src/resources/web-release.d.ts +89 -0
- package/dist/src/resources/web-release.js +882 -0
- package/dist/src/rlm/cache/cli.js +162 -0
- package/dist/src/rlm/cache/hash.js +35 -0
- package/dist/src/rlm/cache/store.js +170 -0
- package/dist/src/rlm/cache/types.js +8 -0
- package/dist/src/rlm/cli.js +650 -0
- package/dist/src/serve/a2a-terminal-observer.js +120 -0
- package/dist/src/serve/agent-router.js +205 -0
- package/dist/src/serve/dispatch-router.js +171 -0
- package/dist/src/serve/executor-registry.js +715 -0
- package/dist/src/serve/mission-conductor.js +177 -0
- package/dist/src/serve/orchestrator-adapter.js +113 -0
- package/dist/src/serve/orchestrator-override.js +94 -0
- package/dist/src/serve/orchestrator-pty.js +133 -0
- package/dist/src/serve/pty-bridge.js +799 -0
- package/dist/src/serve/sandbox-registry.js +832 -0
- package/dist/src/serve/screen-reader.js +228 -0
- package/dist/src/serve/stack-adapters.js +68 -0
- package/dist/src/serve/telemetry.js +143 -0
- package/dist/src/skills/adapters/clawhub.js +250 -0
- package/dist/src/skills/adapters/local.js +335 -0
- package/dist/src/skills/adapters/openclaw.js +316 -0
- package/dist/src/skills/cli.js +334 -0
- package/dist/src/skills/registry.js +117 -0
- package/dist/src/skills/run.js +259 -0
- package/dist/src/skills/runtime.js +94 -0
- package/dist/src/skills/types.js +10 -0
- package/dist/src/smiths/agentsmith/demo.js +116 -0
- package/dist/src/smiths/agentsmith/examples.js +300 -0
- package/dist/src/smiths/agentsmith/generator.js +503 -0
- package/dist/src/smiths/agentsmith/index.js +11 -0
- package/dist/src/smiths/agentsmith/types.js +8 -0
- package/dist/src/smiths/commandsmith/example.js +220 -0
- package/dist/src/smiths/commandsmith/generator.js +288 -0
- package/dist/src/smiths/commandsmith/index.js +41 -0
- package/dist/src/smiths/commandsmith/templates.js +296 -0
- package/dist/src/smiths/commandsmith/types.js +7 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +104 -0
- package/dist/src/smiths/context-pipeline/allowlist.js +195 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +127 -0
- package/dist/src/smiths/context-pipeline/discovery.js +187 -0
- package/dist/src/smiths/context-pipeline/external-links-section.js +49 -0
- package/dist/src/smiths/context-pipeline/finalization.js +147 -0
- package/dist/src/smiths/context-pipeline/generator.js +477 -0
- package/dist/src/smiths/context-pipeline/index.js +30 -0
- package/dist/src/smiths/context-pipeline/legacy-inject.js +103 -0
- package/dist/src/smiths/context-pipeline/managed-hook.js +95 -0
- package/dist/src/smiths/context-pipeline/overflow.js +212 -0
- package/dist/src/smiths/context-pipeline/parallelism-section.js +94 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +64 -0
- package/dist/src/smiths/context-pipeline/sanitizer.js +93 -0
- package/dist/src/smiths/context-pipeline/types.js +23 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +988 -0
- package/dist/src/smiths/hook-bridge/codex-translator.js +129 -0
- package/dist/src/smiths/hook-bridge/copilot-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/factory-translator.js +43 -0
- package/dist/src/smiths/hook-bridge/hermes-translator.js +88 -0
- package/dist/src/smiths/hook-bridge/index.js +79 -0
- package/dist/src/smiths/hook-bridge/loader.js +116 -0
- package/dist/src/smiths/hook-bridge/shim.js +89 -0
- package/dist/src/smiths/hook-bridge/types.js +31 -0
- package/dist/src/smiths/index.js +39 -0
- package/dist/src/smiths/mcpsmith/analyzers/cli-analyzer.js +390 -0
- package/dist/src/smiths/mcpsmith/example.js +119 -0
- package/dist/src/smiths/mcpsmith/generator.js +236 -0
- package/dist/src/smiths/mcpsmith/index.js +17 -0
- package/dist/src/smiths/mcpsmith/types.js +10 -0
- package/dist/src/smiths/platform-paths.js +101 -0
- package/dist/src/smiths/skillsmith/codex-sidecar.js +85 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +249 -0
- package/dist/src/smiths/skillsmith/demo.js +83 -0
- package/dist/src/smiths/skillsmith/examples.js +161 -0
- package/dist/src/smiths/skillsmith/generator.js +316 -0
- package/dist/src/smiths/skillsmith/index.js +12 -0
- package/dist/src/smiths/skillsmith/namespace-adapter.js +180 -0
- package/dist/src/smiths/skillsmith/platform-resolver.js +157 -0
- package/dist/src/smiths/skillsmith/types.js +9 -0
- package/dist/src/smiths/toolsmith/index.js +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +710 -0
- package/dist/src/smiths/toolsmith/types.js +8 -0
- package/dist/src/storage/backends/fortemi.js +227 -0
- package/dist/src/storage/backends/fs.js +134 -0
- package/dist/src/storage/backends/logseq.js +309 -0
- package/dist/src/storage/backends/obsidian.js +233 -0
- package/dist/src/storage/cli.js +468 -0
- package/dist/src/storage/config.js +272 -0
- package/dist/src/storage/index.js +108 -0
- package/dist/src/storage/subsystem-cli.js +201 -0
- package/dist/src/storage/types.js +33 -0
- package/dist/src/testing/corpus/corpus-builder.js +383 -0
- package/dist/src/testing/corpus/ground-truth-manager.js +460 -0
- package/dist/src/testing/corpus/index.js +10 -0
- package/dist/src/testing/fixtures/index.js +9 -0
- package/dist/src/testing/fixtures/test-data-factory.js +472 -0
- package/dist/src/testing/generators/index.js +11 -0
- package/dist/src/testing/generators/test-case-generator.js +393 -0
- package/dist/src/testing/generators/test-code-generator.js +384 -0
- package/dist/src/testing/generators/use-case-parser.js +325 -0
- package/dist/src/testing/index.js +11 -0
- package/dist/src/tracker/capability-protocol.js +118 -0
- package/dist/src/update/checker.mjs +396 -0
- package/dist/src/update/notifier.mjs +235 -0
- package/dist/src/writing/content-diversifier.js +804 -0
- package/dist/src/writing/example-generator.js +959 -0
- package/dist/src/writing/example-templates.js +99 -0
- package/dist/src/writing/pattern-library.js +486 -0
- package/dist/src/writing/patterns/banned-phrases.json +4413 -0
- package/dist/src/writing/patterns/formulaic-structures.json +1300 -0
- package/dist/src/writing/patterns/generic-adjectives.json +1510 -0
- package/dist/src/writing/patterns/hedging-language.json +2096 -0
- package/dist/src/writing/patterns/transition-words.json +1285 -0
- package/dist/src/writing/patterns/weak-verbs.json +1112 -0
- package/dist/src/writing/prompt-optimizer.js +660 -0
- package/dist/src/writing/prompt-templates.js +583 -0
- package/dist/src/writing/scoring-config-loader.js +168 -0
- package/dist/src/writing/validation-engine.js +595 -0
- package/dist/src/writing/validation-rules.js +380 -0
- package/dist/src/writing/voice-analyzer.js +439 -0
- package/dist/src/writing/voice-calibration.js +661 -0
- package/dist/src/writing/voice-profiles.json +588 -0
- package/package.json +64 -9
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
import { createHash, createPublicKey, randomUUID, verify } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
export const DEFAULT_RESOURCE_BASE_URL = "https://releases.aiwg.io";
|
|
6
|
+
export const AIWG_RELEASE_PUBLIC_KEY_PEM = `-----BEGIN PUBLIC KEY-----
|
|
7
|
+
MCowBQYDK2VwAyEA8BsJ2vjuHBReexz328sknfL7MKUtxynX6MGfqFVMD38=
|
|
8
|
+
-----END PUBLIC KEY-----`;
|
|
9
|
+
const EXACT_VERSION_PATTERN = /^(?:19|20)\d{2}\.(?:[1-9]|1[0-2])\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?$/;
|
|
10
|
+
const CHANNEL_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
|
11
|
+
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
12
|
+
const SIGNATURE_PATTERN = /^(?:[A-Za-z0-9+/]{4}){21}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)$/;
|
|
13
|
+
const RELEASE_MANIFEST_SCHEMAS = new Set([
|
|
14
|
+
"aiwg.resource-manifest/v1",
|
|
15
|
+
"aiwg.resource-manifest/v2",
|
|
16
|
+
]);
|
|
17
|
+
const FORTEMI_MANIFEST_PATH = "raw/prebuilt/fortemi-core/framework/manifest.json";
|
|
18
|
+
const FORTEMI_EXPORT_PATH = "raw/prebuilt/fortemi-core/framework/aiwg-fortemi-index-v2.json";
|
|
19
|
+
const MAX_SIGNED_METADATA_BYTES = 4 * 1024 * 1024;
|
|
20
|
+
const MAX_FORTEMI_EXPORT_BYTES = 64 * 1024 * 1024;
|
|
21
|
+
const MAX_RAW_RESOURCE_BYTES = 16 * 1024 * 1024;
|
|
22
|
+
const MAX_SIGNATURE_BYTES = 64 * 1024;
|
|
23
|
+
const MAX_COMPLETION_MARKER_BYTES = 4 * 1024;
|
|
24
|
+
const MAX_CHANNEL_CACHE_CANDIDATES = 128;
|
|
25
|
+
const MAX_RELEASE_CACHE_CANDIDATES = 32;
|
|
26
|
+
const RESOURCE_FETCH_TIMEOUT_MS = 15_000;
|
|
27
|
+
function isRecord(value) {
|
|
28
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
29
|
+
}
|
|
30
|
+
function sha256(bytes) {
|
|
31
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
32
|
+
}
|
|
33
|
+
function parseJson(bytes, label) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(Buffer.from(bytes).toString("utf8"));
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`${label} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function publicKeyId(publicKey) {
|
|
42
|
+
const der = publicKey.export({ type: "spki", format: "der" });
|
|
43
|
+
return `sha256:${sha256(der)}`;
|
|
44
|
+
}
|
|
45
|
+
function loadTrustRoot(publicKeyPem) {
|
|
46
|
+
const key = createPublicKey(publicKeyPem);
|
|
47
|
+
if (key.asymmetricKeyType !== "ed25519") {
|
|
48
|
+
throw new Error("AIWG web release trust root must be an Ed25519 public key");
|
|
49
|
+
}
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
function validateDetachedSignature(value, label) {
|
|
53
|
+
if (!isRecord(value) || value.schemaVersion !== "aiwg.detached-signature/v1" || value.algorithm !== "Ed25519") {
|
|
54
|
+
throw new Error(`${label} has an unsupported detached-signature schema or algorithm`);
|
|
55
|
+
}
|
|
56
|
+
if (typeof value.keyId !== "string" || !/^sha256:[0-9a-f]{64}$/.test(value.keyId)) {
|
|
57
|
+
throw new Error(`${label} has an invalid keyId`);
|
|
58
|
+
}
|
|
59
|
+
if (typeof value.payloadSha256 !== "string" || !SHA256_PATTERN.test(value.payloadSha256)) {
|
|
60
|
+
throw new Error(`${label} has an invalid payloadSha256`);
|
|
61
|
+
}
|
|
62
|
+
if (typeof value.signature !== "string" || !SIGNATURE_PATTERN.test(value.signature)) {
|
|
63
|
+
throw new Error(`${label} has an invalid Ed25519 signature encoding`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
export function verifySignedResourceBytes(bytes, signatureBytes, publicKeyPem = AIWG_RELEASE_PUBLIC_KEY_PEM, label = "AIWG release metadata") {
|
|
68
|
+
const publicKey = loadTrustRoot(publicKeyPem);
|
|
69
|
+
const envelope = validateDetachedSignature(parseJson(signatureBytes, `${label} signature`), `${label} signature`);
|
|
70
|
+
const digest = sha256(bytes);
|
|
71
|
+
if (envelope.keyId !== publicKeyId(publicKey)) {
|
|
72
|
+
throw new Error(`${label} signature keyId does not match the configured trust root`);
|
|
73
|
+
}
|
|
74
|
+
if (envelope.payloadSha256 !== digest) {
|
|
75
|
+
throw new Error(`${label} signature payload digest does not match the exact bytes`);
|
|
76
|
+
}
|
|
77
|
+
const signature = Buffer.from(envelope.signature, "base64");
|
|
78
|
+
if (signature.length !== 64 || !verify(null, bytes, publicKey, signature)) {
|
|
79
|
+
throw new Error(`${label} signature verification failed`);
|
|
80
|
+
}
|
|
81
|
+
return digest;
|
|
82
|
+
}
|
|
83
|
+
export function parseResourceSelector(selector) {
|
|
84
|
+
if (EXACT_VERSION_PATTERN.test(selector))
|
|
85
|
+
return { kind: "exact", value: selector };
|
|
86
|
+
if (CHANNEL_PATTERN.test(selector) && !/^v\d/.test(selector)) {
|
|
87
|
+
return { kind: "channel", value: selector };
|
|
88
|
+
}
|
|
89
|
+
throw new Error(`Unsupported AIWG resource selector '${selector}'. This release supports exact calendar-semver versions or channel names only.`);
|
|
90
|
+
}
|
|
91
|
+
function assertSafeRelativePath(value, label) {
|
|
92
|
+
if (typeof value !== "string" ||
|
|
93
|
+
value.length === 0 ||
|
|
94
|
+
value.startsWith("/") ||
|
|
95
|
+
value.includes("\\") ||
|
|
96
|
+
value.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
97
|
+
throw new Error(`${label} is not a safe relative path`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function descriptorFrom(value, descriptorPath) {
|
|
101
|
+
if (!isRecord(value))
|
|
102
|
+
throw new Error(`release descriptor for ${descriptorPath} must be an object`);
|
|
103
|
+
assertSafeRelativePath(value.path, `release descriptor path for ${descriptorPath}`);
|
|
104
|
+
if (!Number.isSafeInteger(value.size) || value.size < 0) {
|
|
105
|
+
throw new Error(`release descriptor size for ${value.path} is invalid`);
|
|
106
|
+
}
|
|
107
|
+
if (typeof value.sha256 !== "string" || !SHA256_PATTERN.test(value.sha256)) {
|
|
108
|
+
throw new Error(`release descriptor digest for ${value.path} is invalid`);
|
|
109
|
+
}
|
|
110
|
+
return { path: value.path, size: value.size, sha256: value.sha256 };
|
|
111
|
+
}
|
|
112
|
+
function validateReleaseManifest(value, version) {
|
|
113
|
+
if (!isRecord(value) || typeof value.schemaVersion !== "string" || !RELEASE_MANIFEST_SCHEMAS.has(value.schemaVersion)) {
|
|
114
|
+
throw new Error("release manifest has an unsupported schemaVersion");
|
|
115
|
+
}
|
|
116
|
+
if (value.version !== version)
|
|
117
|
+
throw new Error("release manifest version does not match the resolved version");
|
|
118
|
+
if (value.schemaVersion === "aiwg.resource-manifest/v2") {
|
|
119
|
+
const compatibility = value.compatibility;
|
|
120
|
+
if (!isRecord(compatibility) ||
|
|
121
|
+
compatibility.schemaVersion !== "aiwg.resource-compatibility/v1" ||
|
|
122
|
+
compatibility.resourceSchema !== "aiwg.resource-manifest/v2" ||
|
|
123
|
+
!isRecord(compatibility.cli) ||
|
|
124
|
+
typeof compatibility.cli.minimumVersion !== "string" ||
|
|
125
|
+
!EXACT_VERSION_PATTERN.test(compatibility.cli.minimumVersion) ||
|
|
126
|
+
!Array.isArray(compatibility.cli.knownIncompatibleRanges) ||
|
|
127
|
+
!compatibility.cli.knownIncompatibleRanges.every((range) => typeof range === "string")) {
|
|
128
|
+
throw new Error("release manifest v2 compatibility metadata is invalid");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!Array.isArray(value.bundles))
|
|
132
|
+
throw new Error("release manifest bundles must be an array");
|
|
133
|
+
if (!Array.isArray(value.files))
|
|
134
|
+
throw new Error("release manifest files must be an array");
|
|
135
|
+
const descriptors = new Map();
|
|
136
|
+
const add = (descriptor) => {
|
|
137
|
+
if (descriptors.has(descriptor.path))
|
|
138
|
+
throw new Error(`duplicate release descriptor path: ${descriptor.path}`);
|
|
139
|
+
descriptors.set(descriptor.path, descriptor);
|
|
140
|
+
};
|
|
141
|
+
for (const bundle of value.bundles) {
|
|
142
|
+
if (!isRecord(bundle) || typeof bundle.filename !== "string" || !/^[a-z0-9-]+\.tar\.zst$/.test(bundle.filename)) {
|
|
143
|
+
throw new Error("release manifest contains an unsafe bundle filename");
|
|
144
|
+
}
|
|
145
|
+
add(descriptorFrom({ path: `bundles/${bundle.filename}`, size: bundle.size, sha256: bundle.sha256 }, bundle.filename));
|
|
146
|
+
}
|
|
147
|
+
for (const file of value.files)
|
|
148
|
+
add(descriptorFrom(file, "file"));
|
|
149
|
+
return { manifest: value, descriptors };
|
|
150
|
+
}
|
|
151
|
+
function validateChannelManifest(value, channel) {
|
|
152
|
+
if (!isRecord(value) || value.schemaVersion !== "aiwg.channel-manifest/v1") {
|
|
153
|
+
throw new Error(`channel ${channel} has an unsupported schemaVersion`);
|
|
154
|
+
}
|
|
155
|
+
if (value.channel !== channel)
|
|
156
|
+
throw new Error(`channel metadata name does not match '${channel}'`);
|
|
157
|
+
if (!Number.isSafeInteger(value.sequence) || value.sequence < 1) {
|
|
158
|
+
throw new Error(`channel ${channel} has an invalid sequence`);
|
|
159
|
+
}
|
|
160
|
+
if (typeof value.version !== "string" || !EXACT_VERSION_PATTERN.test(value.version)) {
|
|
161
|
+
throw new Error(`channel ${channel} has an invalid exact version`);
|
|
162
|
+
}
|
|
163
|
+
const expectedPath = `/resources/${value.version}/manifest.json`;
|
|
164
|
+
if (value.releaseManifest !== expectedPath) {
|
|
165
|
+
throw new Error(`channel ${channel} has an invalid release manifest path`);
|
|
166
|
+
}
|
|
167
|
+
if (typeof value.releaseManifestSha256 !== "string" || !SHA256_PATTERN.test(value.releaseManifestSha256)) {
|
|
168
|
+
throw new Error(`channel ${channel} has an invalid release manifest digest`);
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
function validateFortemiFiles(manifestBytes, exportBytes) {
|
|
173
|
+
const manifest = parseJson(manifestBytes, "Fortemi Core manifest");
|
|
174
|
+
if (!isRecord(manifest) || manifest.schema_version !== "aiwg.fortemi.prebuilt.v1") {
|
|
175
|
+
throw new Error("Fortemi Core manifest has an unsupported schema_version");
|
|
176
|
+
}
|
|
177
|
+
if (manifest.backend !== "fortemi-core" || manifest.graph !== "framework") {
|
|
178
|
+
throw new Error("Fortemi Core manifest backend or graph is invalid");
|
|
179
|
+
}
|
|
180
|
+
if (manifest.export_path !== "aiwg-fortemi-index-v2.json" || manifest.export_schema_version !== "aiwg.fortemi.index.export.v2") {
|
|
181
|
+
throw new Error("Fortemi Core manifest export path or schema is invalid");
|
|
182
|
+
}
|
|
183
|
+
if (typeof manifest.export_checksum !== "string" || !SHA256_PATTERN.test(manifest.export_checksum)) {
|
|
184
|
+
throw new Error("Fortemi Core manifest export checksum is invalid");
|
|
185
|
+
}
|
|
186
|
+
if (!Number.isSafeInteger(manifest.item_count) || manifest.item_count < 1) {
|
|
187
|
+
throw new Error("Fortemi Core manifest item_count is invalid");
|
|
188
|
+
}
|
|
189
|
+
if (sha256(exportBytes) !== manifest.export_checksum) {
|
|
190
|
+
throw new Error("Fortemi Core export checksum does not match its nested manifest");
|
|
191
|
+
}
|
|
192
|
+
const exported = parseJson(exportBytes, "Fortemi Core index export");
|
|
193
|
+
if (!isRecord(exported) || exported.schema_version !== "aiwg.fortemi.index.export.v2") {
|
|
194
|
+
throw new Error("Fortemi Core index export has an unsupported schema_version");
|
|
195
|
+
}
|
|
196
|
+
if (!isRecord(exported.source) || exported.source.graph !== "framework") {
|
|
197
|
+
throw new Error("Fortemi Core index export graph is invalid");
|
|
198
|
+
}
|
|
199
|
+
if (!Array.isArray(exported.items) || exported.items.length !== manifest.item_count) {
|
|
200
|
+
throw new Error("Fortemi Core index export item count does not match its nested manifest");
|
|
201
|
+
}
|
|
202
|
+
if (exported.items.some((item) => isRecord(item) && Array.isArray(item.chunks) && item.chunks.length > 0)) {
|
|
203
|
+
throw new Error("Fortemi Core CLI index must not contain discovery/search chunks");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function platformCacheRoot() {
|
|
207
|
+
if (process.env.XDG_CACHE_HOME)
|
|
208
|
+
return process.env.XDG_CACHE_HOME;
|
|
209
|
+
if (process.platform === "win32") {
|
|
210
|
+
return process.env.LOCALAPPDATA ?? path.join(os.homedir(), "AppData", "Local");
|
|
211
|
+
}
|
|
212
|
+
if (process.platform === "darwin")
|
|
213
|
+
return path.join(os.homedir(), "Library", "Caches");
|
|
214
|
+
return path.join(os.homedir(), ".cache");
|
|
215
|
+
}
|
|
216
|
+
export function getResourceCacheRoot(cacheRoot) {
|
|
217
|
+
return path.resolve(cacheRoot ?? path.join(platformCacheRoot(), "aiwg", "resources"));
|
|
218
|
+
}
|
|
219
|
+
function normalizeBaseUrl(baseUrl, allowInsecureLoopbackHttp) {
|
|
220
|
+
let url;
|
|
221
|
+
try {
|
|
222
|
+
url = new URL(baseUrl);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
throw new Error(`Invalid AIWG resource base URL: ${baseUrl}`);
|
|
226
|
+
}
|
|
227
|
+
const loopback = url.hostname === "127.0.0.1" || url.hostname === "[::1]" || url.hostname === "::1" || url.hostname === "localhost";
|
|
228
|
+
if (url.protocol !== "https:" && !(url.protocol === "http:" && loopback && allowInsecureLoopbackHttp)) {
|
|
229
|
+
throw new Error("AIWG web resources require HTTPS; insecure HTTP is allowed only for loopback with the explicit test/development option");
|
|
230
|
+
}
|
|
231
|
+
if (url.username || url.password || url.search || url.hash)
|
|
232
|
+
throw new Error("AIWG resource base URL must be a clean origin");
|
|
233
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
234
|
+
return url;
|
|
235
|
+
}
|
|
236
|
+
function resourceUrl(base, relativePath) {
|
|
237
|
+
assertSafeRelativePath(relativePath, "resource URL path");
|
|
238
|
+
const prefix = base.pathname.replace(/\/$/, "");
|
|
239
|
+
const url = new URL(base.toString());
|
|
240
|
+
url.pathname = `${prefix}/${relativePath}`;
|
|
241
|
+
return url.toString();
|
|
242
|
+
}
|
|
243
|
+
async function fetchBytes(fetcher, url, label, maxBytes) {
|
|
244
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0)
|
|
245
|
+
throw new Error(`${label} has an invalid network size limit`);
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
let timedOut = false;
|
|
248
|
+
let timeout;
|
|
249
|
+
const timeoutFailure = new Promise((_resolve, reject) => {
|
|
250
|
+
timeout = setTimeout(() => {
|
|
251
|
+
timedOut = true;
|
|
252
|
+
const error = new Error(`${label} request timed out after ${RESOURCE_FETCH_TIMEOUT_MS}ms`);
|
|
253
|
+
controller.abort(error);
|
|
254
|
+
reject(error);
|
|
255
|
+
}, RESOURCE_FETCH_TIMEOUT_MS);
|
|
256
|
+
});
|
|
257
|
+
try {
|
|
258
|
+
const response = await Promise.race([
|
|
259
|
+
fetcher(url, {
|
|
260
|
+
redirect: "error",
|
|
261
|
+
headers: { "accept-encoding": "identity" },
|
|
262
|
+
signal: controller.signal,
|
|
263
|
+
}),
|
|
264
|
+
timeoutFailure,
|
|
265
|
+
]);
|
|
266
|
+
if (!response.ok)
|
|
267
|
+
throw new Error(`${label} fetch failed (${response.status}): ${url}`);
|
|
268
|
+
const contentLength = response.headers.get("content-length");
|
|
269
|
+
let declaredLength;
|
|
270
|
+
if (contentLength !== null) {
|
|
271
|
+
declaredLength = Number(contentLength);
|
|
272
|
+
if (!Number.isSafeInteger(declaredLength) || declaredLength < 0 || declaredLength > maxBytes) {
|
|
273
|
+
controller.abort();
|
|
274
|
+
throw new Error(`${label} response size is invalid`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (response.body && typeof response.body.getReader === "function") {
|
|
278
|
+
const reader = response.body.getReader();
|
|
279
|
+
const chunks = [];
|
|
280
|
+
let total = 0;
|
|
281
|
+
try {
|
|
282
|
+
for (;;) {
|
|
283
|
+
const { done, value } = await Promise.race([reader.read(), timeoutFailure]);
|
|
284
|
+
if (done)
|
|
285
|
+
break;
|
|
286
|
+
if (!(value instanceof Uint8Array))
|
|
287
|
+
throw new Error(`${label} returned a non-byte stream chunk`);
|
|
288
|
+
total += value.byteLength;
|
|
289
|
+
if (total > maxBytes) {
|
|
290
|
+
controller.abort();
|
|
291
|
+
if (reader.cancel)
|
|
292
|
+
await reader.cancel(`${label} exceeded its fixed size limit`).catch(() => { });
|
|
293
|
+
throw new Error(`${label} exceeds the maximum permitted size`);
|
|
294
|
+
}
|
|
295
|
+
chunks.push(Buffer.from(value));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
if (controller.signal.aborted && reader.cancel)
|
|
300
|
+
await reader.cancel().catch(() => { });
|
|
301
|
+
}
|
|
302
|
+
if (declaredLength !== undefined && total !== declaredLength) {
|
|
303
|
+
throw new Error(`${label} response length does not match content-length`);
|
|
304
|
+
}
|
|
305
|
+
return Buffer.concat(chunks, total);
|
|
306
|
+
}
|
|
307
|
+
// Compatibility for controlled test fetchers that expose only
|
|
308
|
+
// arrayBuffer(). Requiring a bounded content-length prevents an
|
|
309
|
+
// unbounded fallback for ordinary network responses.
|
|
310
|
+
if (declaredLength === undefined) {
|
|
311
|
+
throw new Error(`${label} response has no bounded stream or content-length`);
|
|
312
|
+
}
|
|
313
|
+
const bytes = Buffer.from(await Promise.race([response.arrayBuffer(), timeoutFailure]));
|
|
314
|
+
if (bytes.length > maxBytes || bytes.length !== declaredLength) {
|
|
315
|
+
controller.abort();
|
|
316
|
+
throw new Error(`${label} response size is invalid`);
|
|
317
|
+
}
|
|
318
|
+
return bytes;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
if (timedOut)
|
|
322
|
+
throw new Error(`${label} request timed out after ${RESOURCE_FETCH_TIMEOUT_MS}ms`);
|
|
323
|
+
throw error;
|
|
324
|
+
}
|
|
325
|
+
finally {
|
|
326
|
+
clearTimeout(timeout);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function verifyDescriptor(bytes, descriptor, label = descriptor.path) {
|
|
330
|
+
if (bytes.byteLength !== descriptor.size || sha256(bytes) !== descriptor.sha256) {
|
|
331
|
+
throw new Error(`release descriptor size or digest verification failed: ${label}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function generationDir(cacheRoot, version, digest) {
|
|
335
|
+
return path.join(cacheRoot, "releases", version, digest);
|
|
336
|
+
}
|
|
337
|
+
function assertCacheDirectory(pathname, label) {
|
|
338
|
+
try {
|
|
339
|
+
const stat = fs.lstatSync(pathname);
|
|
340
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
341
|
+
throw new Error(`${label} must be a real directory`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
throw new Error(`${label} is missing or unsafe: ${error instanceof Error ? error.message : String(error)}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Read a bounded regular file through one descriptor. O_NOFOLLOW closes the
|
|
350
|
+
* final-component symlink race on platforms that support it; lstat/fstat and
|
|
351
|
+
* digest checks retain fail-closed behavior elsewhere.
|
|
352
|
+
*/
|
|
353
|
+
export function readVerifiedRegularFile(pathname, options) {
|
|
354
|
+
const { label, maxBytes, expectedSize, expectedSha256 } = options;
|
|
355
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
356
|
+
throw new Error(`${label} has an invalid read limit`);
|
|
357
|
+
}
|
|
358
|
+
let initial;
|
|
359
|
+
try {
|
|
360
|
+
initial = fs.lstatSync(pathname);
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
throw new Error(`${label} is missing or unreadable: ${error instanceof Error ? error.message : String(error)}`);
|
|
364
|
+
}
|
|
365
|
+
if (initial.isSymbolicLink() || !initial.isFile()) {
|
|
366
|
+
throw new Error(`${label} must be a non-symlink regular file`);
|
|
367
|
+
}
|
|
368
|
+
if (initial.size > maxBytes || (expectedSize !== undefined && initial.size !== expectedSize)) {
|
|
369
|
+
throw new Error(`${label} has an invalid size`);
|
|
370
|
+
}
|
|
371
|
+
const noFollow = typeof fs.constants.O_NOFOLLOW === "number" ? fs.constants.O_NOFOLLOW : 0;
|
|
372
|
+
const nonBlocking = typeof fs.constants.O_NONBLOCK === "number" ? fs.constants.O_NONBLOCK : 0;
|
|
373
|
+
let descriptor;
|
|
374
|
+
try {
|
|
375
|
+
descriptor = fs.openSync(pathname, fs.constants.O_RDONLY | nonBlocking | noFollow);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
throw new Error(`${label} could not be opened safely: ${error instanceof Error ? error.message : String(error)}`);
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const stat = fs.fstatSync(descriptor);
|
|
382
|
+
if (!stat.isFile() || stat.size > maxBytes || (expectedSize !== undefined && stat.size !== expectedSize)) {
|
|
383
|
+
throw new Error(`${label} has an invalid descriptor type or size`);
|
|
384
|
+
}
|
|
385
|
+
const bytes = Buffer.alloc(stat.size);
|
|
386
|
+
let offset = 0;
|
|
387
|
+
while (offset < bytes.length) {
|
|
388
|
+
const count = fs.readSync(descriptor, bytes, offset, bytes.length - offset, offset);
|
|
389
|
+
if (count === 0)
|
|
390
|
+
throw new Error(`${label} was truncated while being read`);
|
|
391
|
+
offset += count;
|
|
392
|
+
}
|
|
393
|
+
const trailing = Buffer.alloc(1);
|
|
394
|
+
if (fs.readSync(descriptor, trailing, 0, 1, offset) !== 0) {
|
|
395
|
+
throw new Error(`${label} grew while being read`);
|
|
396
|
+
}
|
|
397
|
+
if (expectedSha256 !== undefined && sha256(bytes) !== expectedSha256) {
|
|
398
|
+
throw new Error(`${label} digest does not match the signed release descriptor`);
|
|
399
|
+
}
|
|
400
|
+
return bytes;
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
fs.closeSync(descriptor);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
export function loadResourceTrustRootFile(pathname) {
|
|
407
|
+
const bytes = readVerifiedRegularFile(path.resolve(pathname), {
|
|
408
|
+
label: "AIWG resource trust root file",
|
|
409
|
+
maxBytes: MAX_SIGNATURE_BYTES,
|
|
410
|
+
});
|
|
411
|
+
loadTrustRoot(bytes);
|
|
412
|
+
return bytes;
|
|
413
|
+
}
|
|
414
|
+
function verifyCachedGeneration(cacheRoot, version, digest, selector, publicKeyPem, baseUrl, expectedDigest, channelSequence) {
|
|
415
|
+
if (!SHA256_PATTERN.test(digest))
|
|
416
|
+
throw new Error("cached release generation has an invalid digest directory");
|
|
417
|
+
const cacheDir = generationDir(cacheRoot, version, digest);
|
|
418
|
+
const releaseManifestPath = path.join(cacheDir, "manifest.json");
|
|
419
|
+
const releaseSignaturePath = path.join(cacheDir, "manifest.sig");
|
|
420
|
+
const fortemiManifestPath = path.join(cacheDir, FORTEMI_MANIFEST_PATH);
|
|
421
|
+
const fortemiExportPath = path.join(cacheDir, FORTEMI_EXPORT_PATH);
|
|
422
|
+
assertCacheDirectory(cacheDir, "cached release generation");
|
|
423
|
+
const completionBytes = readVerifiedRegularFile(path.join(cacheDir, "complete.json"), {
|
|
424
|
+
label: "cached release completion marker",
|
|
425
|
+
maxBytes: MAX_COMPLETION_MARKER_BYTES,
|
|
426
|
+
});
|
|
427
|
+
const completion = parseJson(completionBytes, "cached release completion marker");
|
|
428
|
+
if (!isRecord(completion) ||
|
|
429
|
+
completion.schemaVersion !== "aiwg.resource-cache-generation/v1" ||
|
|
430
|
+
completion.version !== version ||
|
|
431
|
+
completion.manifestSha256 !== digest) {
|
|
432
|
+
throw new Error("cached release completion marker does not match its generation");
|
|
433
|
+
}
|
|
434
|
+
const manifestBytes = readVerifiedRegularFile(releaseManifestPath, {
|
|
435
|
+
label: "cached release manifest",
|
|
436
|
+
maxBytes: MAX_SIGNED_METADATA_BYTES,
|
|
437
|
+
expectedSha256: digest,
|
|
438
|
+
});
|
|
439
|
+
const signatureBytes = readVerifiedRegularFile(releaseSignaturePath, {
|
|
440
|
+
label: "cached release signature",
|
|
441
|
+
maxBytes: MAX_SIGNATURE_BYTES,
|
|
442
|
+
});
|
|
443
|
+
const verifiedDigest = verifySignedResourceBytes(manifestBytes, signatureBytes, publicKeyPem, "cached release manifest");
|
|
444
|
+
if (verifiedDigest !== digest || (expectedDigest && verifiedDigest !== expectedDigest)) {
|
|
445
|
+
throw new Error("cached release manifest digest does not match the selected generation");
|
|
446
|
+
}
|
|
447
|
+
const { descriptors } = validateReleaseManifest(parseJson(manifestBytes, "cached release manifest"), version);
|
|
448
|
+
const fortemiManifestDescriptor = descriptors.get(FORTEMI_MANIFEST_PATH);
|
|
449
|
+
const fortemiExportDescriptor = descriptors.get(FORTEMI_EXPORT_PATH);
|
|
450
|
+
if (!fortemiManifestDescriptor || !fortemiExportDescriptor) {
|
|
451
|
+
throw new Error("cached release manifest does not commit to the required Fortemi Core files");
|
|
452
|
+
}
|
|
453
|
+
const nestedManifestBytes = readVerifiedRegularFile(fortemiManifestPath, {
|
|
454
|
+
label: "cached Fortemi Core manifest",
|
|
455
|
+
maxBytes: MAX_SIGNED_METADATA_BYTES,
|
|
456
|
+
expectedSize: fortemiManifestDescriptor.size,
|
|
457
|
+
expectedSha256: fortemiManifestDescriptor.sha256,
|
|
458
|
+
});
|
|
459
|
+
const exportBytes = readVerifiedRegularFile(fortemiExportPath, {
|
|
460
|
+
label: "cached Fortemi Core export",
|
|
461
|
+
maxBytes: MAX_FORTEMI_EXPORT_BYTES,
|
|
462
|
+
expectedSize: fortemiExportDescriptor.size,
|
|
463
|
+
expectedSha256: fortemiExportDescriptor.sha256,
|
|
464
|
+
});
|
|
465
|
+
validateFortemiFiles(nestedManifestBytes, exportBytes);
|
|
466
|
+
return {
|
|
467
|
+
selector: selector.value,
|
|
468
|
+
selectorKind: selector.kind,
|
|
469
|
+
version,
|
|
470
|
+
manifestDigest: verifiedDigest,
|
|
471
|
+
baseUrl: baseUrl.toString().replace(/\/$/, ""),
|
|
472
|
+
manifestUrl: resourceUrl(baseUrl, `resources/${version}/manifest.json`),
|
|
473
|
+
cacheDir,
|
|
474
|
+
releaseManifestPath,
|
|
475
|
+
releaseSignaturePath,
|
|
476
|
+
fortemiManifestPath,
|
|
477
|
+
fortemiExportPath,
|
|
478
|
+
fortemiManifestSha256: fortemiManifestDescriptor.sha256,
|
|
479
|
+
fortemiManifestSize: fortemiManifestDescriptor.size,
|
|
480
|
+
fortemiExportSha256: fortemiExportDescriptor.sha256,
|
|
481
|
+
fortemiExportSize: fortemiExportDescriptor.size,
|
|
482
|
+
...(channelSequence === undefined ? {} : { channelSequence }),
|
|
483
|
+
descriptors,
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
function cachedDigests(cacheRoot, version) {
|
|
487
|
+
const root = path.join(cacheRoot, "releases", version);
|
|
488
|
+
if (!fs.existsSync(root))
|
|
489
|
+
return [];
|
|
490
|
+
assertCacheDirectory(root, `cached release ${version} directory`);
|
|
491
|
+
const digests = fs.readdirSync(root, { withFileTypes: true })
|
|
492
|
+
.filter((entry) => entry.isDirectory() && SHA256_PATTERN.test(entry.name))
|
|
493
|
+
.map((entry) => entry.name)
|
|
494
|
+
.sort();
|
|
495
|
+
if (digests.length > MAX_RELEASE_CACHE_CANDIDATES) {
|
|
496
|
+
throw new Error(`cached release ${version} exceeds ${MAX_RELEASE_CACHE_CANDIDATES} candidate generations`);
|
|
497
|
+
}
|
|
498
|
+
return digests;
|
|
499
|
+
}
|
|
500
|
+
function channelGenerationRoot(cacheRoot, channel) {
|
|
501
|
+
return path.join(cacheRoot, "channels", channel);
|
|
502
|
+
}
|
|
503
|
+
function cachedChannelCandidates(cacheRoot, channel) {
|
|
504
|
+
const root = channelGenerationRoot(cacheRoot, channel);
|
|
505
|
+
if (!fs.existsSync(root))
|
|
506
|
+
return [];
|
|
507
|
+
assertCacheDirectory(root, `cached channel ${channel} directory`);
|
|
508
|
+
const candidates = [];
|
|
509
|
+
let directory;
|
|
510
|
+
try {
|
|
511
|
+
directory = fs.opendirSync(root);
|
|
512
|
+
for (;;) {
|
|
513
|
+
const entry = directory.readSync();
|
|
514
|
+
if (!entry)
|
|
515
|
+
break;
|
|
516
|
+
if (!entry.isDirectory())
|
|
517
|
+
continue;
|
|
518
|
+
const match = /^([1-9]\d*)-([0-9a-f]{64})$/.exec(entry.name);
|
|
519
|
+
if (!match)
|
|
520
|
+
continue;
|
|
521
|
+
const sequence = Number(match[1]);
|
|
522
|
+
if (!Number.isSafeInteger(sequence) || sequence < 1 || String(sequence) !== match[1])
|
|
523
|
+
continue;
|
|
524
|
+
candidates.push({ name: entry.name, sequence, digest: match[2] });
|
|
525
|
+
if (candidates.length > MAX_CHANNEL_CACHE_CANDIDATES) {
|
|
526
|
+
throw new Error(`cached channel ${channel} exceeds ${MAX_CHANNEL_CACHE_CANDIDATES} candidate generations`);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
finally {
|
|
531
|
+
directory?.closeSync();
|
|
532
|
+
}
|
|
533
|
+
return candidates.sort((left, right) => right.sequence - left.sequence || left.digest.localeCompare(right.digest));
|
|
534
|
+
}
|
|
535
|
+
function readCachedChannel(cacheRoot, channel, publicKeyPem) {
|
|
536
|
+
const valid = [];
|
|
537
|
+
let corrupt = false;
|
|
538
|
+
for (const candidate of cachedChannelCandidates(cacheRoot, channel)) {
|
|
539
|
+
const dir = path.join(channelGenerationRoot(cacheRoot, channel), candidate.name);
|
|
540
|
+
try {
|
|
541
|
+
assertCacheDirectory(dir, `cached channel ${channel} generation`);
|
|
542
|
+
const bytes = readVerifiedRegularFile(path.join(dir, "channel.json"), {
|
|
543
|
+
label: `cached channel ${channel}`,
|
|
544
|
+
maxBytes: MAX_SIGNED_METADATA_BYTES,
|
|
545
|
+
expectedSha256: candidate.digest,
|
|
546
|
+
});
|
|
547
|
+
const signatureBytes = readVerifiedRegularFile(path.join(dir, "channel.sig"), {
|
|
548
|
+
label: `cached channel ${channel} signature`,
|
|
549
|
+
maxBytes: MAX_SIGNATURE_BYTES,
|
|
550
|
+
});
|
|
551
|
+
const digest = verifySignedResourceBytes(bytes, signatureBytes, publicKeyPem, `cached channel ${channel}`);
|
|
552
|
+
const manifest = validateChannelManifest(parseJson(bytes, `cached channel ${channel}`), channel);
|
|
553
|
+
if (candidate.name !== `${manifest.sequence}-${digest}`) {
|
|
554
|
+
throw new Error(`cached channel ${channel} generation name does not match its signed metadata`);
|
|
555
|
+
}
|
|
556
|
+
valid.push({ manifest, bytes, signatureBytes, digest });
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
corrupt = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
valid.sort((left, right) => right.manifest.sequence - left.manifest.sequence ||
|
|
563
|
+
left.digest.localeCompare(right.digest));
|
|
564
|
+
if (valid.length > 1 &&
|
|
565
|
+
valid[0].manifest.sequence === valid[1].manifest.sequence &&
|
|
566
|
+
valid[0].digest !== valid[1].digest) {
|
|
567
|
+
throw new Error(`cached channel ${channel} sequence ${valid[0].manifest.sequence} has conflicting signed metadata`);
|
|
568
|
+
}
|
|
569
|
+
if (valid.length > 0)
|
|
570
|
+
return valid[0];
|
|
571
|
+
if (corrupt)
|
|
572
|
+
throw new Error(`cached channel ${channel} is corrupt and cannot be used offline`);
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
function installGeneration(stageDir, targetDir) {
|
|
576
|
+
fsyncTree(stageDir);
|
|
577
|
+
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
|
578
|
+
try {
|
|
579
|
+
fs.renameSync(stageDir, targetDir);
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
const code = error.code;
|
|
583
|
+
if ((code === "EEXIST" || code === "ENOTEMPTY") && fs.existsSync(targetDir)) {
|
|
584
|
+
fs.rmSync(stageDir, { recursive: true, force: true });
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
throw error;
|
|
588
|
+
}
|
|
589
|
+
fsyncDirectory(path.dirname(targetDir));
|
|
590
|
+
}
|
|
591
|
+
function fsyncDirectory(directory) {
|
|
592
|
+
let descriptor;
|
|
593
|
+
try {
|
|
594
|
+
descriptor = fs.openSync(directory, "r");
|
|
595
|
+
fs.fsyncSync(descriptor);
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
// Some platforms/filesystems do not support directory fsync.
|
|
599
|
+
}
|
|
600
|
+
finally {
|
|
601
|
+
if (descriptor !== undefined)
|
|
602
|
+
fs.closeSync(descriptor);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function fsyncTree(root) {
|
|
606
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
607
|
+
const pathname = path.join(root, entry.name);
|
|
608
|
+
if (entry.isDirectory()) {
|
|
609
|
+
fsyncTree(pathname);
|
|
610
|
+
fsyncDirectory(pathname);
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (!entry.isFile())
|
|
614
|
+
continue;
|
|
615
|
+
const descriptor = fs.openSync(pathname, "r");
|
|
616
|
+
try {
|
|
617
|
+
fs.fsyncSync(descriptor);
|
|
618
|
+
}
|
|
619
|
+
finally {
|
|
620
|
+
fs.closeSync(descriptor);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
fsyncDirectory(root);
|
|
624
|
+
}
|
|
625
|
+
function cacheChannel(cacheRoot, manifest, bytes, signatureBytes, digest) {
|
|
626
|
+
const root = channelGenerationRoot(cacheRoot, manifest.channel);
|
|
627
|
+
const stagingRoot = path.join(cacheRoot, ".staging", "channels");
|
|
628
|
+
fs.mkdirSync(stagingRoot, { recursive: true });
|
|
629
|
+
const stage = fs.mkdtempSync(path.join(stagingRoot, `${manifest.channel}-`));
|
|
630
|
+
try {
|
|
631
|
+
fs.writeFileSync(path.join(stage, "channel.json"), bytes, { flag: "wx" });
|
|
632
|
+
fs.writeFileSync(path.join(stage, "channel.sig"), signatureBytes, { flag: "wx" });
|
|
633
|
+
const target = path.join(root, `${manifest.sequence}-${digest}`);
|
|
634
|
+
if (fs.existsSync(target)) {
|
|
635
|
+
let matches = false;
|
|
636
|
+
try {
|
|
637
|
+
assertCacheDirectory(target, `cached channel ${manifest.channel} generation`);
|
|
638
|
+
matches =
|
|
639
|
+
readVerifiedRegularFile(path.join(target, "channel.json"), {
|
|
640
|
+
label: `cached channel ${manifest.channel}`,
|
|
641
|
+
maxBytes: MAX_SIGNED_METADATA_BYTES,
|
|
642
|
+
}).equals(Buffer.from(bytes)) &&
|
|
643
|
+
readVerifiedRegularFile(path.join(target, "channel.sig"), {
|
|
644
|
+
label: `cached channel ${manifest.channel} signature`,
|
|
645
|
+
maxBytes: MAX_SIGNATURE_BYTES,
|
|
646
|
+
}).equals(Buffer.from(signatureBytes));
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
matches = false;
|
|
650
|
+
}
|
|
651
|
+
if (!matches)
|
|
652
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
653
|
+
}
|
|
654
|
+
installGeneration(stage, target);
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
658
|
+
throw error;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function fetchAndCacheRelease(base, fetcher, cacheRoot, selector, version, publicKeyPem, expectedDigest, channelSequence) {
|
|
662
|
+
if (expectedDigest) {
|
|
663
|
+
try {
|
|
664
|
+
return verifyCachedGeneration(cacheRoot, version, expectedDigest, selector, publicKeyPem, base, expectedDigest, channelSequence);
|
|
665
|
+
}
|
|
666
|
+
catch {
|
|
667
|
+
// Re-fetch a damaged or absent generation into a fresh stage.
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
for (const digest of cachedDigests(cacheRoot, version)) {
|
|
672
|
+
try {
|
|
673
|
+
return verifyCachedGeneration(cacheRoot, version, digest, selector, publicKeyPem, base, undefined, channelSequence);
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
// Try another complete signed generation before using the network.
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
const prefix = `resources/${version}`;
|
|
681
|
+
const manifestUrl = resourceUrl(base, `${prefix}/manifest.json`);
|
|
682
|
+
const manifestBytes = await fetchBytes(fetcher, manifestUrl, "release manifest", MAX_SIGNED_METADATA_BYTES);
|
|
683
|
+
const signatureBytes = await fetchBytes(fetcher, resourceUrl(base, `${prefix}/manifest.sig`), "release manifest signature", MAX_SIGNATURE_BYTES);
|
|
684
|
+
const manifestDigest = verifySignedResourceBytes(manifestBytes, signatureBytes, publicKeyPem, "release manifest");
|
|
685
|
+
if (expectedDigest && manifestDigest !== expectedDigest) {
|
|
686
|
+
throw new Error("signed channel release manifest digest does not match the fetched release manifest");
|
|
687
|
+
}
|
|
688
|
+
const { descriptors } = validateReleaseManifest(parseJson(manifestBytes, "release manifest"), version);
|
|
689
|
+
const fortemiManifestDescriptor = descriptors.get(FORTEMI_MANIFEST_PATH);
|
|
690
|
+
const fortemiExportDescriptor = descriptors.get(FORTEMI_EXPORT_PATH);
|
|
691
|
+
if (!fortemiManifestDescriptor || !fortemiExportDescriptor) {
|
|
692
|
+
throw new Error("release manifest does not commit to the required Fortemi Core files");
|
|
693
|
+
}
|
|
694
|
+
if (fortemiManifestDescriptor.size > MAX_SIGNED_METADATA_BYTES || fortemiExportDescriptor.size > MAX_FORTEMI_EXPORT_BYTES) {
|
|
695
|
+
throw new Error("release manifest commits to an oversized Fortemi Core file");
|
|
696
|
+
}
|
|
697
|
+
const nestedManifestBytes = await fetchBytes(fetcher, resourceUrl(base, `${prefix}/${FORTEMI_MANIFEST_PATH}`), "Fortemi Core manifest", fortemiManifestDescriptor.size);
|
|
698
|
+
const exportBytes = await fetchBytes(fetcher, resourceUrl(base, `${prefix}/${FORTEMI_EXPORT_PATH}`), "Fortemi Core index export", fortemiExportDescriptor.size);
|
|
699
|
+
verifyDescriptor(nestedManifestBytes, fortemiManifestDescriptor);
|
|
700
|
+
verifyDescriptor(exportBytes, fortemiExportDescriptor);
|
|
701
|
+
validateFortemiFiles(nestedManifestBytes, exportBytes);
|
|
702
|
+
const stagingRoot = path.join(cacheRoot, ".staging", "releases");
|
|
703
|
+
fs.mkdirSync(stagingRoot, { recursive: true });
|
|
704
|
+
const stage = fs.mkdtempSync(path.join(stagingRoot, `${version}-`));
|
|
705
|
+
try {
|
|
706
|
+
fs.mkdirSync(path.join(stage, path.dirname(FORTEMI_MANIFEST_PATH)), { recursive: true });
|
|
707
|
+
fs.writeFileSync(path.join(stage, "manifest.json"), manifestBytes, { flag: "wx" });
|
|
708
|
+
fs.writeFileSync(path.join(stage, "manifest.sig"), signatureBytes, { flag: "wx" });
|
|
709
|
+
fs.writeFileSync(path.join(stage, FORTEMI_MANIFEST_PATH), nestedManifestBytes, { flag: "wx" });
|
|
710
|
+
fs.writeFileSync(path.join(stage, FORTEMI_EXPORT_PATH), exportBytes, { flag: "wx" });
|
|
711
|
+
fs.writeFileSync(path.join(stage, "complete.json"), JSON.stringify({
|
|
712
|
+
schemaVersion: "aiwg.resource-cache-generation/v1",
|
|
713
|
+
version,
|
|
714
|
+
manifestSha256: manifestDigest,
|
|
715
|
+
}) + "\n", { flag: "wx" });
|
|
716
|
+
const target = generationDir(cacheRoot, version, manifestDigest);
|
|
717
|
+
if (fs.existsSync(target)) {
|
|
718
|
+
try {
|
|
719
|
+
verifyCachedGeneration(cacheRoot, version, manifestDigest, selector, publicKeyPem, base, expectedDigest, channelSequence);
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
installGeneration(stage, target);
|
|
726
|
+
}
|
|
727
|
+
catch (error) {
|
|
728
|
+
fs.rmSync(stage, { recursive: true, force: true });
|
|
729
|
+
throw error;
|
|
730
|
+
}
|
|
731
|
+
const verified = verifyCachedGeneration(cacheRoot, version, manifestDigest, selector, publicKeyPem, base, expectedDigest, channelSequence);
|
|
732
|
+
return { ...verified, manifestUrl };
|
|
733
|
+
}
|
|
734
|
+
function resolveOfflineExact(cacheRoot, selector, version, publicKeyPem, baseUrl, expectedDigest, channelSequence) {
|
|
735
|
+
const digests = expectedDigest ? [expectedDigest] : cachedDigests(cacheRoot, version);
|
|
736
|
+
let corrupt = false;
|
|
737
|
+
for (const digest of digests) {
|
|
738
|
+
try {
|
|
739
|
+
return verifyCachedGeneration(cacheRoot, version, digest, selector, publicKeyPem, baseUrl, expectedDigest, channelSequence);
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
corrupt = true;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
if (corrupt)
|
|
746
|
+
throw new Error(`Cached AIWG resource release ${version} is corrupt; offline mode fails closed`);
|
|
747
|
+
throw new Error(`AIWG resource release ${version} is not cached; offline mode cannot fetch it`);
|
|
748
|
+
}
|
|
749
|
+
export async function resolveWebRelease(options = {}) {
|
|
750
|
+
const selector = parseResourceSelector(options.selector ?? "stable");
|
|
751
|
+
const cacheRoot = getResourceCacheRoot(options.cacheRoot);
|
|
752
|
+
const publicKeyPem = options.publicKeyPem ?? AIWG_RELEASE_PUBLIC_KEY_PEM;
|
|
753
|
+
const base = normalizeBaseUrl(options.baseUrl ?? DEFAULT_RESOURCE_BASE_URL, options.allowInsecureLoopbackHttp === true);
|
|
754
|
+
// Validate injected trust material before reading cache or making requests.
|
|
755
|
+
loadTrustRoot(publicKeyPem);
|
|
756
|
+
if (selector.kind === "exact") {
|
|
757
|
+
if (options.offline)
|
|
758
|
+
return resolveOfflineExact(cacheRoot, selector, selector.value, publicKeyPem, base);
|
|
759
|
+
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
760
|
+
if (!fetcher)
|
|
761
|
+
throw new Error("No fetch implementation is available for AIWG web resources");
|
|
762
|
+
return fetchAndCacheRelease(base, fetcher, cacheRoot, selector, selector.value, publicKeyPem);
|
|
763
|
+
}
|
|
764
|
+
if (options.offline) {
|
|
765
|
+
const cached = readCachedChannel(cacheRoot, selector.value, publicKeyPem);
|
|
766
|
+
if (!cached)
|
|
767
|
+
throw new Error(`AIWG resource channel ${selector.value} is not cached; offline mode cannot fetch it`);
|
|
768
|
+
return resolveOfflineExact(cacheRoot, selector, cached.manifest.version, publicKeyPem, base, cached.manifest.releaseManifestSha256, cached.manifest.sequence);
|
|
769
|
+
}
|
|
770
|
+
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
771
|
+
if (!fetcher)
|
|
772
|
+
throw new Error("No fetch implementation is available for AIWG web resources");
|
|
773
|
+
const channelPrefix = `resources/channels/${selector.value}`;
|
|
774
|
+
const channelBytes = await fetchBytes(fetcher, resourceUrl(base, `${channelPrefix}.json`), `channel ${selector.value}`, MAX_SIGNED_METADATA_BYTES);
|
|
775
|
+
const channelSignatureBytes = await fetchBytes(fetcher, resourceUrl(base, `${channelPrefix}.sig`), `channel ${selector.value} signature`, MAX_SIGNATURE_BYTES);
|
|
776
|
+
const channelDigest = verifySignedResourceBytes(channelBytes, channelSignatureBytes, publicKeyPem, `channel ${selector.value}`);
|
|
777
|
+
const channel = validateChannelManifest(parseJson(channelBytes, `channel ${selector.value}`), selector.value);
|
|
778
|
+
const prior = readCachedChannel(cacheRoot, selector.value, publicKeyPem);
|
|
779
|
+
if (prior && channel.sequence < prior.manifest.sequence) {
|
|
780
|
+
throw new Error(`channel ${selector.value} sequence rollback detected (${channel.sequence} < ${prior.manifest.sequence})`);
|
|
781
|
+
}
|
|
782
|
+
if (prior &&
|
|
783
|
+
channel.sequence === prior.manifest.sequence &&
|
|
784
|
+
(channelDigest !== prior.digest ||
|
|
785
|
+
channel.version !== prior.manifest.version ||
|
|
786
|
+
channel.releaseManifestSha256 !== prior.manifest.releaseManifestSha256)) {
|
|
787
|
+
throw new Error(`channel ${selector.value} sequence ${channel.sequence} has conflicting signed metadata`);
|
|
788
|
+
}
|
|
789
|
+
const release = await fetchAndCacheRelease(base, fetcher, cacheRoot, selector, channel.version, publicKeyPem, channel.releaseManifestSha256, channel.sequence);
|
|
790
|
+
cacheChannel(cacheRoot, channel, channelBytes, channelSignatureBytes, channelDigest);
|
|
791
|
+
return release;
|
|
792
|
+
}
|
|
793
|
+
export async function fetchVerifiedRawResource(release, resourcePath, options = {}) {
|
|
794
|
+
assertSafeRelativePath(resourcePath, "raw resource path");
|
|
795
|
+
if (!resourcePath.startsWith("raw/"))
|
|
796
|
+
throw new Error("web show may fetch only immutable raw resource paths");
|
|
797
|
+
const descriptor = release.descriptors.get(resourcePath);
|
|
798
|
+
if (!descriptor)
|
|
799
|
+
throw new Error(`release manifest does not commit to raw resource '${resourcePath}'`);
|
|
800
|
+
if (descriptor.size > MAX_RAW_RESOURCE_BYTES) {
|
|
801
|
+
throw new Error(`raw resource '${resourcePath}' exceeds the fixed ${MAX_RAW_RESOURCE_BYTES}-byte limit`);
|
|
802
|
+
}
|
|
803
|
+
const rawCacheDir = path.join(release.cacheDir, "raw-bodies");
|
|
804
|
+
const rawCachePath = path.join(rawCacheDir, descriptor.sha256);
|
|
805
|
+
if (fs.existsSync(rawCacheDir))
|
|
806
|
+
assertCacheDirectory(rawCacheDir, "cached raw resource directory");
|
|
807
|
+
if (fs.existsSync(rawCachePath)) {
|
|
808
|
+
try {
|
|
809
|
+
const cached = readVerifiedRegularFile(rawCachePath, {
|
|
810
|
+
label: `cached raw resource ${resourcePath}`,
|
|
811
|
+
maxBytes: MAX_RAW_RESOURCE_BYTES,
|
|
812
|
+
expectedSize: descriptor.size,
|
|
813
|
+
expectedSha256: descriptor.sha256,
|
|
814
|
+
});
|
|
815
|
+
return cached;
|
|
816
|
+
}
|
|
817
|
+
catch (error) {
|
|
818
|
+
if (options.offline) {
|
|
819
|
+
throw new Error(`Cached raw resource '${resourcePath}' is corrupt; offline mode fails closed`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
else if (options.offline) {
|
|
824
|
+
throw new Error(`Raw resource '${resourcePath}' is not cached; offline mode cannot fetch it`);
|
|
825
|
+
}
|
|
826
|
+
const base = normalizeBaseUrl(options.baseUrl ?? release.baseUrl, options.allowInsecureLoopbackHttp === true);
|
|
827
|
+
const fetcher = options.fetcher ?? globalThis.fetch;
|
|
828
|
+
if (!fetcher)
|
|
829
|
+
throw new Error("No fetch implementation is available for AIWG web resources");
|
|
830
|
+
const bytes = await fetchBytes(fetcher, resourceUrl(base, `resources/${release.version}/${resourcePath}`), `raw resource ${resourcePath}`, Math.min(descriptor.size, MAX_RAW_RESOURCE_BYTES));
|
|
831
|
+
verifyDescriptor(bytes, descriptor);
|
|
832
|
+
const stagingRoot = path.join(release.cacheDir, ".raw-staging");
|
|
833
|
+
fs.mkdirSync(stagingRoot, { recursive: true });
|
|
834
|
+
const stage = path.join(stagingRoot, `${descriptor.sha256}-${process.pid}-${randomUUID()}`);
|
|
835
|
+
try {
|
|
836
|
+
fs.writeFileSync(stage, bytes, { flag: "wx" });
|
|
837
|
+
const descriptorFd = fs.openSync(stage, "r");
|
|
838
|
+
try {
|
|
839
|
+
fs.fsyncSync(descriptorFd);
|
|
840
|
+
}
|
|
841
|
+
finally {
|
|
842
|
+
fs.closeSync(descriptorFd);
|
|
843
|
+
}
|
|
844
|
+
fs.mkdirSync(rawCacheDir, { recursive: true });
|
|
845
|
+
if (fs.existsSync(rawCachePath)) {
|
|
846
|
+
try {
|
|
847
|
+
readVerifiedRegularFile(rawCachePath, {
|
|
848
|
+
label: `cached raw resource ${resourcePath}`,
|
|
849
|
+
maxBytes: MAX_RAW_RESOURCE_BYTES,
|
|
850
|
+
expectedSize: descriptor.size,
|
|
851
|
+
expectedSha256: descriptor.sha256,
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
catch {
|
|
855
|
+
fs.rmSync(rawCachePath, { force: true });
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
try {
|
|
859
|
+
fs.renameSync(stage, rawCachePath);
|
|
860
|
+
}
|
|
861
|
+
catch (error) {
|
|
862
|
+
const code = error.code;
|
|
863
|
+
if (code !== "EEXIST" && code !== "ENOTEMPTY")
|
|
864
|
+
throw error;
|
|
865
|
+
fs.rmSync(stage, { force: true });
|
|
866
|
+
}
|
|
867
|
+
fsyncDirectory(rawCacheDir);
|
|
868
|
+
}
|
|
869
|
+
catch (error) {
|
|
870
|
+
fs.rmSync(stage, { force: true });
|
|
871
|
+
throw error;
|
|
872
|
+
}
|
|
873
|
+
return bytes;
|
|
874
|
+
}
|
|
875
|
+
export function createWebReleaseTestOptions(baseUrl, overrides = {}) {
|
|
876
|
+
return {
|
|
877
|
+
...overrides,
|
|
878
|
+
baseUrl,
|
|
879
|
+
allowInsecureLoopbackHttp: true,
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
//# sourceMappingURL=web-release.js.map
|