@aiwg/cli 2026.10.1 → 2026.10.4
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 +2 -2
- package/agentic/code/providers/capability-matrix.yaml +270 -0
- package/agentic/code/providers/model-capabilities.v1.json +3183 -24
- package/bin/aiwg.mjs +135 -47
- package/dist/src/a2a/codecs.js +9 -0
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +57 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +107 -15
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +285 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +65 -0
- package/dist/src/artifacts/index-refresh.js +576 -0
- package/dist/src/artifacts/index-worker.mjs +9 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/repair.js +9 -4
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +78 -12
- package/dist/src/cli/child-process-result.js +40 -0
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/env.js +38 -16
- package/dist/src/cli/flag-parsing.js +236 -0
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +218 -5
- package/dist/src/cli/handlers/diagnose.js +13 -5
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/feedback.js +14 -5
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +248 -30
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/mc.js +23 -0
- package/dist/src/cli/handlers/models.js +169 -10
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/ralph.js +3 -0
- package/dist/src/cli/handlers/refresh.js +28 -0
- package/dist/src/cli/handlers/regenerate.js +14 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +25 -11
- package/dist/src/cli/handlers/script-runner.js +19 -8
- package/dist/src/cli/handlers/session.js +406 -62
- package/dist/src/cli/handlers/sessions.js +243 -76
- package/dist/src/cli/handlers/setup-manifest.js +474 -79
- package/dist/src/cli/handlers/setup.js +180 -39
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +60 -3
- package/dist/src/cli/handlers/team.js +18 -8
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +307 -55
- package/dist/src/cli/handlers/utilities.js +125 -26
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/prompt-utils.js +72 -0
- package/dist/src/cli/provider-resolution.js +11 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +349 -35
- package/dist/src/config/cli.js +256 -23
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/dispatch-thresholds-runtime.mjs +38 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-health.mjs +14 -3
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +38 -11
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/adapters/jev.js +75 -27
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +14 -7
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +25 -6
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +15 -14
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/index.js +1 -0
- package/dist/src/decision/interop.js +60 -8
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +942 -0
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/decision/validate.js +6 -3
- package/dist/src/effects/dispatch-verifier.js +252 -0
- package/dist/src/effects/index.js +1 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/features/installer.js +2 -2
- package/dist/src/flow/graph-metadata.js +5 -0
- package/dist/src/gates/providers/loader.js +64 -18
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +15 -3
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +95 -5
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +366 -56
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +620 -85
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-file.mjs +279 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/config-layers.mjs +348 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/credentials.mjs +484 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +17 -27
- package/dist/src/mcp/helpers.mjs +109 -4
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/omp-config.mjs +50 -29
- package/dist/src/mcp/profiles.js +97 -23
- package/dist/src/mcp/profiles.mjs +99 -27
- package/dist/src/mcp/registry.js +248 -131
- package/dist/src/mcp/registry.mjs +169 -100
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +127 -82
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-string.mjs +9 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.mjs +135 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tool-filters.mjs +357 -0
- package/dist/src/mcp/tools/agentic-sandbox.mjs +3 -1
- package/dist/src/mcp/tools/decision.mjs +22 -3
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +50 -2
- package/dist/src/mcp/tools/subsystems.mjs +39 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +2856 -0
- package/dist/src/mcp/tools/worker-shell-control.mjs +289 -0
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +79 -0
- package/dist/src/memory/project-registry.js +2 -2
- package/dist/src/mission-protocol/codecs.js +647 -1
- package/dist/src/mission-protocol/types.js +110 -0
- package/dist/src/mission-protocol/worker-status.js +2 -0
- package/dist/src/models/config-loader.js +76 -14
- package/dist/src/models/grok-build-models.js +93 -41
- package/dist/src/models/model-capabilities.v1.json +3183 -24
- package/dist/src/models/model-discovery.js +931 -67
- package/dist/src/models/provider-policy.js +719 -11
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/qualification-digest.mjs +17 -0
- package/dist/src/models/resolver.js +261 -0
- package/dist/src/models/worker-direct-readiness.js +550 -0
- package/dist/src/models/worker-dispatch-offer.js +169 -0
- package/dist/src/models/worker-health-readiness.js +144 -0
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +986 -0
- package/dist/src/models/wrapper-route.js +18 -0
- package/dist/src/ops/registry.js +9 -14
- package/dist/src/plugins/standalone-packager.js +4 -3
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +430 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/cli-command.mjs +75 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/capability-matrix.js +8 -0
- package/dist/src/providers/capability-matrix.yaml +270 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +53 -23
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/provider-definitions.js +9 -1
- package/dist/src/providers/provider-definitions.mjs +9 -2
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/executor-registry.js +16 -24
- package/dist/src/serve/fleet-mission-conductor.js +9 -0
- package/dist/src/serve/mission-conductor.js +47 -12
- package/dist/src/serve/shared-host-scheduler.js +2 -0
- package/dist/src/serve/worker-invocation.js +558 -0
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/serve/worker-stdin-frame.mjs +107 -0
- package/dist/src/sessions/adapter-registry.js +49 -0
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +82 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +43 -9
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot-locator.js +111 -0
- package/dist/src/sessions/adapters/grokbot.js +434 -24
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +128 -0
- package/dist/src/sessions/adapters/muse.js +200 -104
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +30 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +25 -5
- package/dist/src/sessions/batch-import.js +72 -51
- package/dist/src/sessions/candidates.js +47 -11
- package/dist/src/sessions/contracts.js +75 -2
- package/dist/src/sessions/coverage-preflight.js +134 -0
- package/dist/src/sessions/discovery.js +3 -0
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/grok-build-cli.js +67 -5
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +84 -46
- package/dist/src/sessions/index.js +2 -0
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +133 -25
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +106 -0
- package/dist/src/sessions/repository.js +199 -63
- package/dist/src/sessions/workspace-discovery.js +315 -88
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/adapters/clawhub.js +10 -8
- package/dist/src/skills/adapters/openclaw.js +10 -7
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +195 -14
- package/dist/src/smiths/context-pipeline/workspace-context.js +116 -9
- package/dist/src/smiths/hook-bridge/codex-translator.js +6 -5
- package/dist/src/smiths/mcpsmith/example.js +1 -1
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +85 -31
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/logseq.js +68 -7
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/storage/fortemi-qualification-receipt.js +2 -2
- package/dist/src/testing/generators/test-code-generator.js +57 -30
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/uhp/client.js +11 -1
- package/dist/src/uhp/mission.js +5 -0
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/checker.mjs +5 -5
- package/dist/src/update/notifier.mjs +54 -2
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/dist/src/writing/fidelity.js +18 -1
- package/package.json +32 -2
- package/schemas/mission-v1.schema.json +2429 -0
- package/schemas/models/model-catalog.v1.schema.json +301 -0
- package/schemas/models/model-config-compatibility.v1.schema.json +281 -0
- package/schemas/models/model-policy.v1.schema.json +14 -0
- package/schemas/models/provider-model-capabilities.v1.schema.json +1072 -0
- package/tools/_resolve-impl.mjs +39 -2
- package/tools/agents/deploy-agents.mjs +64 -23
- package/tools/agents/providers/base.mjs +153 -166
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/grokbot.mjs +11 -12
- package/tools/agents/providers/hermes.mjs +62 -49
- package/tools/agents/providers/omp.mjs +5 -2
- package/tools/agents/providers/openclaw-translator.mjs +19 -5
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +360 -0
- package/tools/providers/omp-transport.mjs +194 -0
- package/tools/ralph-external/lib/claude-adapter.mjs +389 -0
- package/tools/ralph-external/lib/codex-adapter.mjs +432 -0
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/deepseek-harness-adapter.mjs +397 -0
- package/tools/ralph-external/lib/factory-adapter.mjs +503 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/muse-adapter.mjs +364 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +315 -0
- package/tools/ralph-external/lib/opencode-adapter.mjs +575 -0
- package/tools/ralph-external/lib/openhuman-adapter.mjs +75 -0
- package/tools/ralph-external/lib/pi-adapter.mjs +329 -0
- package/tools/ralph-external/lib/provider-adapter.mjs +748 -0
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +439 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +2916 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worker-dispatch effect verification (#2891, S1 direct/shared checkpoint).
|
|
3
|
+
*
|
|
4
|
+
* Modelled on the ledger lock recovery (`src/effects/lock-recovery.ts`): an
|
|
5
|
+
* extension kind whose verifier is registered only when the evidence it
|
|
6
|
+
* checks actually exists. Here the evidence is Tier 0 supervisor evidence
|
|
7
|
+
* held by the conductor — the dispatch record digest for one invocation,
|
|
8
|
+
* plus optional `file.digest` artifacts. Without supervisor evidence the
|
|
9
|
+
* factory returns null and the kind stays unregistered, so reconciliation
|
|
10
|
+
* through the existing ledger answers `unknown` / `verifier-missing` instead
|
|
11
|
+
* of manufacturing proof.
|
|
12
|
+
*
|
|
13
|
+
* Artifact checks reuse the built-in `file.digest` verifier by delegation;
|
|
14
|
+
* this module owns no second ledger and no file hashing of its own.
|
|
15
|
+
*
|
|
16
|
+
* Managed (S2+) durable dispatch proof and Tier 1 registration are out of
|
|
17
|
+
* scope; this verifier covers direct/shared supervisor evidence only.
|
|
18
|
+
*
|
|
19
|
+
* @see docs/contracts/effect-ledger.v1.md "Verifier interface"
|
|
20
|
+
*/
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
import { constants } from 'node:fs';
|
|
23
|
+
import { open } from 'node:fs/promises';
|
|
24
|
+
import { resolve } from 'node:path';
|
|
25
|
+
import { EffectLedgerError } from './errors.js';
|
|
26
|
+
import { fileDigestVerifier } from './verifiers/file.js';
|
|
27
|
+
import { EffectVerifierError } from './verifiers/types.js';
|
|
28
|
+
export const DISPATCH_KIND = 'x.aiwg.worker-dispatch';
|
|
29
|
+
export const DISPATCH_VERIFIER_VERSION = '1.0.0';
|
|
30
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
31
|
+
/**
|
|
32
|
+
* Canonical invocation-ID shape for dispatch evidence (#2891). This is the
|
|
33
|
+
* SINGLE definition: `tools/ralph-external/lib/dispatch-evidence.mjs`
|
|
34
|
+
* imports this exact binding (through `tools/_resolve-impl.mjs`'s
|
|
35
|
+
* `importImpl`, since `tools/` cannot import `src/*.js` directly) rather
|
|
36
|
+
* than duplicating the literal, so the two can never drift.
|
|
37
|
+
*/
|
|
38
|
+
export const INVOCATION_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/;
|
|
39
|
+
/** Conductor-owned evidence directories and files are owner-only. */
|
|
40
|
+
export const DISPATCH_EVIDENCE_DIR_MODE = 0o700;
|
|
41
|
+
export const DISPATCH_EVIDENCE_FILE_MODE = 0o600;
|
|
42
|
+
export function dispatchEffectTarget(invocationId) {
|
|
43
|
+
return `x-aiwg:dispatch/${invocationId}`;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Test-only race-injection seam (#2891 R1/R7), mirroring the equivalent hook
|
|
47
|
+
* in `tools/ralph-external/lib/dispatch-evidence.mjs`. No-op in production
|
|
48
|
+
* (`afterOpen` defaults to `null`); this package's own conformance suite
|
|
49
|
+
* sets it to a one-shot callback that performs a deterministic TOCTOU swap
|
|
50
|
+
* between `loadSupervisorEvidence`'s two `open()` calls, then clears
|
|
51
|
+
* itself. Never read outside `openTraced` below.
|
|
52
|
+
*/
|
|
53
|
+
export const __raceTestHooks = { afterOpen: null };
|
|
54
|
+
async function openTraced(path, flags) {
|
|
55
|
+
const handle = await open(path, flags);
|
|
56
|
+
if (__raceTestHooks.afterOpen)
|
|
57
|
+
await __raceTestHooks.afterOpen(path);
|
|
58
|
+
return handle;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read supervisor evidence from the conductor-owned evidence location. The
|
|
62
|
+
* evidence directory is opened `O_NOFOLLOW` ONCE and kept open for the
|
|
63
|
+
* whole read: the file `<invocationId>.json` is then opened through that
|
|
64
|
+
* SAME still-open directory descriptor, via `/proc/self/fd/<dirfd>/<name>`,
|
|
65
|
+
* never by re-resolving `<evidenceDir>/<invocationId>.json` as a fresh path
|
|
66
|
+
* string. This means a swap of `evidenceDir`'s path between validating the
|
|
67
|
+
* directory and reading the file cannot redirect the read onto an attacker-
|
|
68
|
+
* controlled location — the descriptor still refers to the original,
|
|
69
|
+
* validated directory regardless of what the path now names.
|
|
70
|
+
*
|
|
71
|
+
* Every check runs against an open fd: the evidence directory must be a
|
|
72
|
+
* real directory owned by us with mode exactly 0700, and the evidence file
|
|
73
|
+
* must be a regular file owned by us with mode exactly 0600 and exactly one
|
|
74
|
+
* link (no surprise hardlink). Supervisor evidence is never read from a
|
|
75
|
+
* worker-writable path: any symlink, wrong mode, wrong owner, extra link or
|
|
76
|
+
* unbound invocationId throws instead of verifying.
|
|
77
|
+
*
|
|
78
|
+
* When `expectedRecordDigest` is supplied (the sha256 of the evidence
|
|
79
|
+
* file's raw bytes, recorded by the conductor at capture time — for
|
|
80
|
+
* example from the effect ledger intent this evidence backs), the freshly
|
|
81
|
+
* read bytes must hash to it or the load throws. Shape and permission
|
|
82
|
+
* checks alone cannot detect in-place mutation of an already-captured
|
|
83
|
+
* evidence file; this closes that gap for any caller that has an earlier
|
|
84
|
+
* recorded digest to check against.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadSupervisorEvidence(options) {
|
|
87
|
+
const invocationId = options?.invocationId;
|
|
88
|
+
if (typeof invocationId !== 'string' || !INVOCATION_PATTERN.test(invocationId)) {
|
|
89
|
+
throw new EffectVerifierError('malformed-response', 'Supervisor evidence needs a valid invocation ID');
|
|
90
|
+
}
|
|
91
|
+
if (!options || typeof options.evidenceDir !== 'string' || !options.evidenceDir) {
|
|
92
|
+
throw new EffectVerifierError('container-unreadable', 'Supervisor evidence needs a conductor-owned evidence directory');
|
|
93
|
+
}
|
|
94
|
+
const dirResolved = resolve(options.evidenceDir);
|
|
95
|
+
let dirHandle;
|
|
96
|
+
try {
|
|
97
|
+
dirHandle = await openTraced(dirResolved, constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | constants.O_NOFOLLOW);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new EffectVerifierError('container-unreadable', `Supervisor evidence directory is not a real directory: ${dirResolved}`);
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const dirStat = await dirHandle.stat();
|
|
104
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
105
|
+
if (!dirStat.isDirectory() || (dirStat.mode & 0o777) !== DISPATCH_EVIDENCE_DIR_MODE
|
|
106
|
+
|| (uid !== null && dirStat.uid !== uid)) {
|
|
107
|
+
throw new EffectVerifierError('container-unreadable', `Supervisor evidence directory is not owner-only 0700: ${dirResolved}`);
|
|
108
|
+
}
|
|
109
|
+
const name = `${invocationId}.json`;
|
|
110
|
+
const ref = `/proc/self/fd/${dirHandle.fd}/${name}`;
|
|
111
|
+
let handle;
|
|
112
|
+
try {
|
|
113
|
+
handle = await openTraced(ref, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
throw new EffectVerifierError('container-unreadable', `Supervisor evidence is unreadable: ${dirResolved}/${name}`);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const stat = await handle.stat();
|
|
120
|
+
const fileUid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
121
|
+
if (!stat.isFile() || (stat.mode & 0o777) !== DISPATCH_EVIDENCE_FILE_MODE
|
|
122
|
+
|| (fileUid !== null && stat.uid !== fileUid) || stat.nlink !== 1) {
|
|
123
|
+
throw new EffectVerifierError('container-unreadable', `Supervisor evidence is not an owner-only 0600 regular file with a single link: ${dirResolved}/${name}`);
|
|
124
|
+
}
|
|
125
|
+
const raw = await handle.readFile('utf8');
|
|
126
|
+
if (typeof options.expectedRecordDigest === 'string' && options.expectedRecordDigest) {
|
|
127
|
+
const actual = `sha256:${createHash('sha256').update(raw, 'utf8').digest('hex')}`;
|
|
128
|
+
if (actual !== options.expectedRecordDigest) {
|
|
129
|
+
throw new EffectVerifierError('malformed-response', 'Supervisor evidence does not match its recorded digest; mutation after capture is not trusted');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
let parsed;
|
|
133
|
+
try {
|
|
134
|
+
parsed = JSON.parse(raw);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new EffectVerifierError('malformed-response', 'Supervisor evidence is not valid JSON');
|
|
138
|
+
}
|
|
139
|
+
if (!hasSupervisorEvidence(parsed) || parsed.invocationId !== invocationId) {
|
|
140
|
+
throw new EffectVerifierError('malformed-response', 'Supervisor evidence is not bound to this invocation');
|
|
141
|
+
}
|
|
142
|
+
return parsed;
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await handle.close();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
await dirHandle.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** True only when actual supervisor evidence exists: a bound invocation and digest. */
|
|
153
|
+
export function hasSupervisorEvidence(evidence) {
|
|
154
|
+
if (!evidence || typeof evidence !== 'object')
|
|
155
|
+
return false;
|
|
156
|
+
const record = evidence;
|
|
157
|
+
if (typeof record.invocationId !== 'string' || !INVOCATION_PATTERN.test(record.invocationId))
|
|
158
|
+
return false;
|
|
159
|
+
if (typeof record.evidenceDigest !== 'string' || !DIGEST_PATTERN.test(record.evidenceDigest))
|
|
160
|
+
return false;
|
|
161
|
+
if (record.artifactPath !== undefined || record.artifactDigest !== undefined) {
|
|
162
|
+
if (typeof record.artifactPath !== 'string' || !record.artifactPath)
|
|
163
|
+
return false;
|
|
164
|
+
if (typeof record.artifactDigest !== 'string' || !DIGEST_PATTERN.test(record.artifactDigest))
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
function assertContextShape(context, target) {
|
|
170
|
+
const invocationId = context.invocationId;
|
|
171
|
+
const evidenceDigest = context.evidenceDigest;
|
|
172
|
+
if (typeof invocationId !== 'string' || !INVOCATION_PATTERN.test(invocationId)
|
|
173
|
+
|| target !== dispatchEffectTarget(invocationId)) {
|
|
174
|
+
throw new EffectVerifierError('malformed-response', 'Dispatch effect has an unexpected target or invocation');
|
|
175
|
+
}
|
|
176
|
+
if (typeof evidenceDigest !== 'string' || !DIGEST_PATTERN.test(evidenceDigest)) {
|
|
177
|
+
throw new EffectVerifierError('malformed-response', 'Dispatch effect has no evidence digest');
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
invocationId,
|
|
181
|
+
evidenceDigest,
|
|
182
|
+
...(typeof context.artifactPath === 'string' ? { artifactPath: context.artifactPath } : {}),
|
|
183
|
+
...(typeof context.artifactDigest === 'string' ? { artifactDigest: context.artifactDigest } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Build the dispatch verifier bound to one project's supervisor evidence, or
|
|
188
|
+
* null when no actual supervisor evidence exists. Callers register the
|
|
189
|
+
* returned verifier (for example through the `extensions` of
|
|
190
|
+
* `buildCliVerifierRegistry`) only when it is non-null; a null result means
|
|
191
|
+
* the kind stays unregistered and reconciliation reports unknown.
|
|
192
|
+
*/
|
|
193
|
+
export function createDispatchVerifierIfSupervised(options) {
|
|
194
|
+
if (!options || typeof options.projectDir !== 'string' || !options.projectDir) {
|
|
195
|
+
throw new EffectLedgerError('usage', 'Dispatch verification needs a project directory', 'invalid-project');
|
|
196
|
+
}
|
|
197
|
+
if (!hasSupervisorEvidence(options.evidence))
|
|
198
|
+
return null;
|
|
199
|
+
const supervised = options.evidence;
|
|
200
|
+
// The record never picks its own strictness: artifacts root ONLY at the
|
|
201
|
+
// conductor-owned evidence directory, and the branch below is driven by
|
|
202
|
+
// what the SUPERVISOR evidence names, not by what the ledger context
|
|
203
|
+
// offers. No evidenceDir means no delegation target: `verify()` below
|
|
204
|
+
// reports `evidence-conflict` for an artifact check rather than silently
|
|
205
|
+
// widening the trusted root to `projectDir`.
|
|
206
|
+
const files = options.evidenceDir ? fileDigestVerifier({ root: options.evidenceDir }) : null;
|
|
207
|
+
return {
|
|
208
|
+
kind: DISPATCH_KIND,
|
|
209
|
+
version: DISPATCH_VERIFIER_VERSION,
|
|
210
|
+
canReportAbsent: true,
|
|
211
|
+
async verify(request) {
|
|
212
|
+
const context = assertContextShape((request.context ?? {}), request.target);
|
|
213
|
+
if (context.invocationId !== supervised.invocationId) {
|
|
214
|
+
throw new EffectVerifierError('malformed-response', 'Dispatch evidence is bound to a different invocation');
|
|
215
|
+
}
|
|
216
|
+
const expected = request.expected.digest ?? request.payloadDigest;
|
|
217
|
+
if (expected !== supervised.evidenceDigest || context.evidenceDigest !== supervised.evidenceDigest) {
|
|
218
|
+
return { result: 'unknown', reason: 'evidence-conflict', complete: false };
|
|
219
|
+
}
|
|
220
|
+
// Supervisor-driven: when supervised evidence names an artifact the
|
|
221
|
+
// verifier ALWAYS delegates to file.digest. A context that omits the
|
|
222
|
+
// artifact (or mismatches it), or a missing evidenceDir to delegate
|
|
223
|
+
// into, is evidence-conflict, never a present/complete downgrade and
|
|
224
|
+
// never a projectDir fallback.
|
|
225
|
+
if (supervised.artifactPath !== undefined) {
|
|
226
|
+
if (!files) {
|
|
227
|
+
return { result: 'unknown', reason: 'evidence-conflict', complete: false };
|
|
228
|
+
}
|
|
229
|
+
if (context.artifactPath !== supervised.artifactPath || context.artifactDigest !== supervised.artifactDigest) {
|
|
230
|
+
return { result: 'unknown', reason: 'evidence-conflict', complete: false };
|
|
231
|
+
}
|
|
232
|
+
const observation = await files.verify({
|
|
233
|
+
...request,
|
|
234
|
+
kind: 'file.digest',
|
|
235
|
+
target: `file:${supervised.artifactPath}@${supervised.artifactDigest}`,
|
|
236
|
+
expected: { digest: supervised.artifactDigest },
|
|
237
|
+
});
|
|
238
|
+
if (observation.result !== 'present')
|
|
239
|
+
return observation;
|
|
240
|
+
return {
|
|
241
|
+
result: 'present', reason: 'state-match', complete: true,
|
|
242
|
+
evidence: { invocationId: context.invocationId, evidenceDigest: supervised.evidenceDigest, artifact: supervised.artifactPath },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
result: 'present', reason: 'state-match', complete: true,
|
|
247
|
+
evidence: { invocationId: context.invocationId, evidenceDigest: supervised.evidenceDigest },
|
|
248
|
+
};
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=dispatch-verifier.js.map
|
|
@@ -19,6 +19,7 @@ export { writeCheckpoint } from './checkpoint.js';
|
|
|
19
19
|
export { checkpointDigest, verifyLedger, } from './verify.js';
|
|
20
20
|
export { EFFECT_RETENTION_POLICY, purgeEffect } from './lifecycle.js';
|
|
21
21
|
export { LEDGER_LOCK_NAME_PATTERN, LOCK_RECOVERY_KIND, LOCK_RECOVERY_VERIFIER_VERSION, inspectLedgerLock, inspectLedgerLocks, ledgerLockRecoveryVerifier, lockRecoveryTarget, recoverStaleLedgerLock, } from './lock-recovery.js';
|
|
22
|
+
export { DISPATCH_KIND, DISPATCH_VERIFIER_VERSION, INVOCATION_PATTERN, createDispatchVerifierIfSupervised, dispatchEffectTarget, hasSupervisorEvidence, loadSupervisorEvidence, } from './dispatch-verifier.js';
|
|
22
23
|
export { assertDigestOnly, containsRestrictedMaterial } from './redaction.js';
|
|
23
24
|
export { EFFECT_SCHEMA_IDS, effectSchemaErrors, isEffectSchemaValid } from './schema.js';
|
|
24
25
|
export { decisionLinkOf, verifyDecisionLinks, } from './decision-links.js';
|
|
@@ -41,7 +41,11 @@ export const UNKNOWN_REASONS = [
|
|
|
41
41
|
'container-unreadable',
|
|
42
42
|
'paging-incomplete',
|
|
43
43
|
'consistency-lag',
|
|
44
|
-
'tracker-blocked',
|
|
44
|
+
'tracker-blocked', // Retained for existing ledger records.
|
|
45
|
+
'tracker-provider-unresolved',
|
|
46
|
+
'tracker-repository-mismatch',
|
|
47
|
+
'tracker-credential-missing',
|
|
48
|
+
'tracker-no-access-route',
|
|
45
49
|
'malformed-response',
|
|
46
50
|
'evidence-conflict',
|
|
47
51
|
];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* @see docs/contracts/effect-ledger.v1.md "Built-in verifiers"
|
|
15
15
|
*/
|
|
16
|
+
import { signalChild } from '../../process/signal-child.mjs';
|
|
16
17
|
import { spawn } from 'node:child_process';
|
|
17
18
|
import { EffectVerifierError } from './types.js';
|
|
18
19
|
export const GIT_VERIFIER_VERSION = '1.0.0';
|
|
@@ -31,7 +32,7 @@ function spawnRunner(repoDir, binary) {
|
|
|
31
32
|
const collect = (sink) => (chunk) => {
|
|
32
33
|
size += chunk.length;
|
|
33
34
|
if (size > MAX_OUTPUT)
|
|
34
|
-
child
|
|
35
|
+
signalChild(child);
|
|
35
36
|
else
|
|
36
37
|
sink.push(chunk);
|
|
37
38
|
};
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
* - **Authority.** The tracker comes from `resolveTrackerAuthority` over the
|
|
6
6
|
* project config and git remote URLs. A target whose forge is neither the
|
|
7
7
|
* internal nor the customer tracker, or whose repository differs from that
|
|
8
|
-
* tracker remote, is `unknown`
|
|
9
|
-
* from the selected remote URL; no host is assumed.
|
|
8
|
+
* tracker remote, is `unknown` with a specific precondition reason. The API
|
|
9
|
+
* base is derived from the selected remote URL; no host is assumed.
|
|
10
10
|
* - **Access order.** `chooseTrackerAccess` over the probes a CLI process can
|
|
11
11
|
* offer: the tracker HTTP API (with credentials from the existing tracker
|
|
12
12
|
* token path, `resolveToken`), then the forge CLI (`gh`, GitHub only). MCP
|
|
13
|
-
* is never offered.
|
|
13
|
+
* is never offered. Blockers include a safe configuration or credential hint.
|
|
14
14
|
* - **Matching.** A comment matches exactly when it carries the
|
|
15
15
|
* `<!-- aiwg-effect: <id> -->` marker and was written by the pinned tracker
|
|
16
16
|
* actor; a merged PR matches its merge commit's `Effect-Id:` trailer.
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* older than `minAbsentAgeMs`; a younger intent is `unknown` /
|
|
21
21
|
* `consistency-lag`.
|
|
22
22
|
*
|
|
23
|
-
* Access material stays inside the transport: evidence carries IDs, SHAs
|
|
24
|
-
* digests only, and errors carry fixed messages.
|
|
23
|
+
* Access material stays inside the transport: evidence carries IDs, SHAs,
|
|
24
|
+
* digests and safe repair hints only, and errors carry fixed messages.
|
|
25
25
|
*
|
|
26
26
|
* @see docs/contracts/effect-ledger.v1.md "Tracker verifiers"
|
|
27
27
|
*/
|
|
@@ -92,7 +92,7 @@ function onPath(binary) {
|
|
|
92
92
|
}
|
|
93
93
|
}));
|
|
94
94
|
}
|
|
95
|
-
/** Resolve the tracker lane
|
|
95
|
+
/** Resolve the tracker lane, or return a classified precondition failure with a safe repair hint. */
|
|
96
96
|
function resolveLane(options, target) {
|
|
97
97
|
const parsed = TARGET.exec(target);
|
|
98
98
|
if (!parsed)
|
|
@@ -103,16 +103,16 @@ function resolveLane(options, target) {
|
|
|
103
103
|
const remotes = options.config?.remotes;
|
|
104
104
|
let lane = null;
|
|
105
105
|
if (authority.provider === provider) {
|
|
106
|
-
lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor };
|
|
106
|
+
lane = { remote: authority.issueTrackerRemote, url: authority.issueTrackerUrl, actor: remotes?.tracker_actor, actorConfigKey: 'remotes.tracker_actor.login' };
|
|
107
107
|
}
|
|
108
108
|
else if (authority.customerIssueTrackerRemote && authority.customerProvider === provider) {
|
|
109
|
-
lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor };
|
|
109
|
+
lane = { remote: authority.customerIssueTrackerRemote, url: authority.customerIssueTrackerUrl, actor: remotes?.customer_tracker_actor, actorConfigKey: 'remotes.customer_tracker_actor.login' };
|
|
110
110
|
}
|
|
111
111
|
if (!lane)
|
|
112
|
-
|
|
112
|
+
return unknown('tracker-provider-unresolved', { hint: 'Set remotes.issue_provider (or remotes.customer_issue_provider for the customer tracker) to the target forge.' });
|
|
113
113
|
const endpoint = forgeEndpointFromRemote(provider, lane.url);
|
|
114
114
|
if (!endpoint || endpoint.repository.toLowerCase() !== parsed[2].toLowerCase())
|
|
115
|
-
|
|
115
|
+
return unknown('tracker-repository-mismatch', { hint: 'Match the target repository to the remote selected by remotes.issue_tracker (or remotes.customer_issue_tracker).' });
|
|
116
116
|
const laneAuthority = { ...authority, provider, issueTrackerRemote: lane.remote, issueTrackerUrl: lane.url };
|
|
117
117
|
const token = resolveToken(provider, options.env ?? process.env);
|
|
118
118
|
const cli = options.cli === false ? null : options.cli ?? {};
|
|
@@ -125,13 +125,16 @@ function resolveLane(options, target) {
|
|
|
125
125
|
const readerOptions = options.pageSize ? { pageSize: options.pageSize } : {};
|
|
126
126
|
if (decision.kind === 'http-api' && token) {
|
|
127
127
|
const transport = httpForgeTransport({ provider, apiBaseUrl: endpoint.apiBaseUrl, token, fetchImpl: options.fetchImpl, maxResponseBytes: options.maxResponseBytes });
|
|
128
|
-
return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
|
|
128
|
+
return { provider, repository: endpoint.repository, number, access: 'http-api', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
|
|
129
129
|
}
|
|
130
130
|
if (decision.kind === 'cli' && cli) {
|
|
131
131
|
const transport = cliForgeTransport({ provider, hostname: endpoint.host, runner: cli.runner, binary: cli.binary, maxResponseBytes: options.maxResponseBytes });
|
|
132
|
-
return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor };
|
|
132
|
+
return { provider, repository: endpoint.repository, number, access: 'cli', reader: createForgeReader(provider, transport, readerOptions), actor: lane.actor, actorConfigKey: lane.actorConfigKey };
|
|
133
133
|
}
|
|
134
|
-
|
|
134
|
+
// Gitea has no raw-read CLI fallback. GitHub may use gh when no token is set.
|
|
135
|
+
return provider === 'gitea'
|
|
136
|
+
? unknown('tracker-credential-missing', { hint: 'Set AIWG_GITEA_TOKEN or GITEA_TOKEN for the tracker HTTP API.' })
|
|
137
|
+
: unknown('tracker-no-access-route', { hint: 'Set AIWG_GITHUB_TOKEN or GITHUB_TOKEN, or install and authenticate gh for the tracker host.' });
|
|
135
138
|
}
|
|
136
139
|
const unknown = (reason, evidence) => ({ result: 'unknown', reason, complete: false, ...(evidence ? { evidence } : {}) });
|
|
137
140
|
/** `absent` once the intent is old enough, else `unknown` / `consistency-lag`. */
|
|
@@ -160,6 +163,8 @@ export function trackerCommentVerifier(options) {
|
|
|
160
163
|
canReportAbsent: true,
|
|
161
164
|
async verify(request) {
|
|
162
165
|
const lane = resolveLane(options, request.target);
|
|
166
|
+
if ('result' in lane)
|
|
167
|
+
return lane;
|
|
163
168
|
const expectedDigest = expectedString(request, 'digest');
|
|
164
169
|
if (expectedDigest !== undefined && !DIGEST.test(expectedDigest))
|
|
165
170
|
return unknown('malformed-response');
|
|
@@ -220,7 +225,10 @@ export function trackerCommentVerifier(options) {
|
|
|
220
225
|
const summary = { ...base, pages, scanned };
|
|
221
226
|
// A marker for this effect under another author is never ours; escalate instead of replaying.
|
|
222
227
|
if (foreignMarker)
|
|
223
|
-
return unknown('evidence-conflict', {
|
|
228
|
+
return unknown('evidence-conflict', {
|
|
229
|
+
...summary, foreignMarker: true,
|
|
230
|
+
...(!pinned ? { actorUnpinned: true, hint: `Set ${lane.actorConfigKey} to the trusted tracker author.` } : {}),
|
|
231
|
+
});
|
|
224
232
|
if (digestMatch) {
|
|
225
233
|
return { result: 'present', reason: 'digest-match', complete, evidence: { ...summary, commentId: digestMatch.id, bodyDigest: digestMatch.bodyDigest, confidence: 'exact' } };
|
|
226
234
|
}
|
|
@@ -241,6 +249,8 @@ export function trackerIssueClosedVerifier(options) {
|
|
|
241
249
|
canReportAbsent: true,
|
|
242
250
|
async verify(request) {
|
|
243
251
|
const lane = resolveLane(options, request.target);
|
|
252
|
+
if ('result' in lane)
|
|
253
|
+
return lane;
|
|
244
254
|
const issue = await read(() => lane.reader.getIssue(lane.repository, lane.number, request.signal));
|
|
245
255
|
const evidence = { ...laneEvidence(lane), state: issue.state, closedAt: issue.closedAt };
|
|
246
256
|
if (issue.state === 'closed')
|
|
@@ -263,6 +273,8 @@ export function trackerPrMergedVerifier(options) {
|
|
|
263
273
|
if (expectedSha !== undefined && !OBJECT_ID.test(expectedSha))
|
|
264
274
|
return unknown('malformed-response');
|
|
265
275
|
const lane = resolveLane(options, request.target);
|
|
276
|
+
if ('result' in lane)
|
|
277
|
+
return lane;
|
|
266
278
|
const pr = await read(() => lane.reader.getPullState(lane.repository, lane.number, request.signal));
|
|
267
279
|
const evidence = {
|
|
268
280
|
...laneEvidence(lane), state: pr.state, merged: pr.merged, mergedAt: pr.mergedAt,
|
|
@@ -200,6 +200,24 @@ export const decisionCommand = {
|
|
|
200
200
|
allowedTools: ['Read', 'Write', 'Bash'],
|
|
201
201
|
},
|
|
202
202
|
};
|
|
203
|
+
export const rulesCommand = {
|
|
204
|
+
id: 'rules',
|
|
205
|
+
type: 'command',
|
|
206
|
+
name: 'Rule Pins',
|
|
207
|
+
description: 'Pin installed rules to every request and manage reusable capability groups',
|
|
208
|
+
version: '1.0.0',
|
|
209
|
+
capabilities: ['cli', 'rules', 'configuration', 'activation'],
|
|
210
|
+
keywords: ['rules', 'pin', 'unpin', 'pinned', 'tier', 'capability', 'group'],
|
|
211
|
+
category: 'config',
|
|
212
|
+
platforms: { claude: 'full', generic: 'full' },
|
|
213
|
+
deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
|
|
214
|
+
metadata: {
|
|
215
|
+
type: 'command',
|
|
216
|
+
template: 'utility',
|
|
217
|
+
argumentHint: '<pin|unpin|pinned|group> [rules...] [--tier tiers] [--capability ids] [--group name] [--all] [--dry-run] [--json]',
|
|
218
|
+
allowedTools: ['Read', 'Write', 'Bash'],
|
|
219
|
+
},
|
|
220
|
+
};
|
|
203
221
|
export const gatesCommand = {
|
|
204
222
|
id: 'gates',
|
|
205
223
|
type: 'command',
|
|
@@ -3752,6 +3770,7 @@ export const commandDefinitions = [
|
|
|
3752
3770
|
updateCommand,
|
|
3753
3771
|
installationCommand,
|
|
3754
3772
|
decisionCommand,
|
|
3773
|
+
rulesCommand,
|
|
3755
3774
|
gatesCommand,
|
|
3756
3775
|
refreshCommand,
|
|
3757
3776
|
regenerateCommand,
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
* @design @.aiwg/architecture/design-aiwg-remove-revert.md
|
|
13
13
|
* @implements #1037
|
|
14
14
|
*/
|
|
15
|
-
import { lstat, readdir, stat, unlink, rmdir } from 'fs/promises';
|
|
15
|
+
import { lstat, readdir, readFile, stat, unlink, rmdir } from 'fs/promises';
|
|
16
16
|
import { resolve, join, relative, dirname, isAbsolute } from 'path';
|
|
17
|
+
import { deployedSkillRoots } from '../providers/deployed-skill-roots.js';
|
|
18
|
+
import { releaseSkillOwnership } from '../providers/skill-ownership.mjs';
|
|
17
19
|
import { appendProjectLocalActivity } from './project-local-activity.js';
|
|
18
20
|
import { sha256OfFileNormalized } from './managed-marker.js';
|
|
19
21
|
import { getProviderArtifactPathStrings } from '../providers/provider-definitions.js';
|
|
@@ -257,7 +259,40 @@ export async function removeProjectLocalBundle(config, projectDir, bundleId, opt
|
|
|
257
259
|
for (const provider of providers) {
|
|
258
260
|
const artifactHashes = artifactHashesForProvider(installedEntry, provider);
|
|
259
261
|
let providerHadSkip = false;
|
|
262
|
+
// Receipts cover the complete transformed skill and support tree, including
|
|
263
|
+
// retired skills no longer present in the source inventory.
|
|
264
|
+
const receiptSkills = new Set();
|
|
265
|
+
const sourceRoot = installedEntry.localPath ? resolve(projectDir, installedEntry.localPath) : undefined;
|
|
266
|
+
for (const root of deployedSkillRoots(projectDir, provider)) {
|
|
267
|
+
for (const name of await readdir(root).catch(() => [])) {
|
|
268
|
+
const skillDir = join(root, name);
|
|
269
|
+
try {
|
|
270
|
+
await readFile(join(skillDir, '.aiwg-manifest.json'), 'utf8');
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
receiptSkills.add(name);
|
|
276
|
+
const removed = releaseSkillOwnership(skillDir, {
|
|
277
|
+
frameworkSlug: bundleId,
|
|
278
|
+
skillSource: sourceRoot ? join(sourceRoot, 'skills', name) : undefined,
|
|
279
|
+
sourceRoot,
|
|
280
|
+
}, { dryRun });
|
|
281
|
+
if (removed === null) {
|
|
282
|
+
// Unrelated receipts are outside this bundle's removal inventory.
|
|
283
|
+
if (!Object.keys(artifactHashes).some(sourceRel => sourceRel.startsWith(`skills/${name}/`)))
|
|
284
|
+
continue;
|
|
285
|
+
providerHadSkip = true;
|
|
286
|
+
}
|
|
287
|
+
outcomes.push({ provider, artifactPath: `skills/${name}/SKILL.md`, deployedAbsPath: skillDir,
|
|
288
|
+
case: removed === null ? 'replaced' : 'pristine', reverted: removed !== null && !dryRun,
|
|
289
|
+
message: removed === null ? 'owned by another source — preserved' : dryRun
|
|
290
|
+
? '[dry-run] would release skill ownership' : 'released skill ownership; operator files preserved' });
|
|
291
|
+
}
|
|
292
|
+
}
|
|
260
293
|
for (const sourceRel of Object.keys(artifactHashes)) {
|
|
294
|
+
if (sourceRel.startsWith('skills/') && receiptSkills.has(sourceRel.split('/')[1]))
|
|
295
|
+
continue;
|
|
261
296
|
const owner = resolveOwnership(config, bundleId, provider, sourceRel);
|
|
262
297
|
if (owner) {
|
|
263
298
|
providerHadSkip = true;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { boundedSpawnSync } from '../process/bounded-child-process.mjs';
|
|
2
2
|
import { promises as fs } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { FEATURE_CATALOG, getFeature } from './catalog.js';
|
|
@@ -46,7 +46,7 @@ export async function installFeature(name, options = {}) {
|
|
|
46
46
|
const root = options.root ?? getFeaturesRoot();
|
|
47
47
|
await prepareFeatureManifest(feature, root);
|
|
48
48
|
const npmCommand = options.npmCommand ?? (process.platform === 'win32' ? 'npm.cmd' : 'npm');
|
|
49
|
-
const result =
|
|
49
|
+
const result = boundedSpawnSync(npmCommand, ['install', '--prefix', root, '--no-audit', '--no-fund'], 'long', { stdio: 'inherit', env: process.env });
|
|
50
50
|
if (result.error)
|
|
51
51
|
throw result.error;
|
|
52
52
|
if (result.status !== 0) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertMissionWorkerHandoff } from '../mission-protocol/codecs.js';
|
|
1
2
|
/** A2A/telemetry metadata key reserved for the optional Flow graph profile. */
|
|
2
3
|
export const AIWG_GRAPH_METADATA_KEY = 'aiwg.flow.graph';
|
|
3
4
|
function nonEmpty(value) {
|
|
@@ -15,9 +16,12 @@ export function isGraphExecutionMetadata(value) {
|
|
|
15
16
|
&& nonEmpty(item.nodeRunId);
|
|
16
17
|
}
|
|
17
18
|
export function graphMetadataRecord(value) {
|
|
19
|
+
assertMissionWorkerHandoff(value, 'graph');
|
|
18
20
|
return { [AIWG_GRAPH_METADATA_KEY]: value };
|
|
19
21
|
}
|
|
20
22
|
export function extractGraphMetadata(metadata) {
|
|
23
|
+
if (metadata)
|
|
24
|
+
assertMissionWorkerHandoff({ metadata }, 'graph');
|
|
21
25
|
const value = metadata?.[AIWG_GRAPH_METADATA_KEY];
|
|
22
26
|
return isGraphExecutionMetadata(value) ? value : undefined;
|
|
23
27
|
}
|
|
@@ -28,6 +32,7 @@ export function extractGraphMetadata(metadata) {
|
|
|
28
32
|
* member of this contract and is therefore dropped by construction.
|
|
29
33
|
*/
|
|
30
34
|
export function projectGraphMetadata(value, audience) {
|
|
35
|
+
assertMissionWorkerHandoff(value, 'graph');
|
|
31
36
|
if (!isGraphExecutionMetadata(value))
|
|
32
37
|
throw new Error('Invalid graph execution metadata.');
|
|
33
38
|
if (audience === 'internal')
|