@aiwg/cli 2026.9.20 → 2026.9.24
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/agentic/code/providers/capability-matrix.yaml +58 -6
- package/agentic/code/providers/model-capabilities.v1.json +34 -0
- package/agentic/code/providers/model-catalog.v1.json +55 -13
- package/bin/aiwg.mjs +2 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/cli.js +23 -18
- package/dist/src/artifacts/enrichment/cli.js +1 -1
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
- package/dist/src/artifacts/query-engine.js +14 -4
- package/dist/src/artifacts/views/cli.js +1 -1
- package/dist/src/audit/operator-decision.js +10 -0
- package/dist/src/auth/credential-store.js +127 -27
- package/dist/src/catalog/builtin-models.json +272 -40
- package/dist/src/catalog/cli.js +1 -1
- package/dist/src/catalog/cli.mjs +1 -1
- package/dist/src/cli/handlers/effect.js +872 -0
- package/dist/src/cli/handlers/help.js +2 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/models.js +41 -10
- package/dist/src/cli/handlers/ralph.js +1 -1
- package/dist/src/cli/handlers/sessions.js +113 -37
- package/dist/src/cli/handlers/subcommands.js +31 -0
- package/dist/src/cli/handlers/use.js +46 -9
- package/dist/src/cli/handlers/utilities.js +10 -0
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/scope-resolver.js +112 -9
- package/dist/src/cli/services/deployment-verification.js +5 -0
- package/dist/src/config/aiwg-config.js +3 -0
- package/dist/src/decision/acceptance-rollout.js +93 -0
- package/dist/src/decision/acceptance.js +175 -0
- package/dist/src/decision/adapters/jev.js +234 -13
- package/dist/src/decision/adapters/llm-subagent.js +60 -12
- package/dist/src/decision/admission.js +550 -0
- package/dist/src/decision/batch-benchmark.js +47 -0
- package/dist/src/decision/batch-receipts/accounting.js +75 -0
- package/dist/src/decision/batch-receipts/export.js +40 -0
- package/dist/src/decision/batch-receipts/index.js +9 -0
- package/dist/src/decision/batch-receipts/protection.js +40 -0
- package/dist/src/decision/batch-receipts/receipt.js +44 -0
- package/dist/src/decision/batch-receipts/result-store.js +341 -0
- package/dist/src/decision/batch-receipts/store.js +362 -0
- package/dist/src/decision/batch-receipts/types.js +2 -0
- package/dist/src/decision/batch-receipts/validate.js +127 -0
- package/dist/src/decision/batch.js +68 -0
- package/dist/src/decision/calibration/index.js +4 -0
- package/dist/src/decision/calibration/receipts.js +130 -0
- package/dist/src/decision/calibration/registry.js +258 -0
- package/dist/src/decision/calibration/types.js +2 -0
- package/dist/src/decision/compile-cache/benchmark.js +62 -0
- package/dist/src/decision/compile-cache/compatibility.js +65 -0
- package/dist/src/decision/compile-cache/file-store.js +280 -0
- package/dist/src/decision/compile-cache/identity.js +26 -0
- package/dist/src/decision/compile-cache/index.js +9 -0
- package/dist/src/decision/compile-cache/prefix.js +21 -0
- package/dist/src/decision/compile-cache/records.js +86 -0
- package/dist/src/decision/compile-cache/runtime.js +68 -0
- package/dist/src/decision/compile-cache/store.js +108 -0
- package/dist/src/decision/compile-cache/types.js +2 -0
- package/dist/src/decision/context-plan.js +234 -0
- package/dist/src/decision/context-qualification.js +60 -0
- package/dist/src/decision/ensemble/aggregate.js +215 -0
- package/dist/src/decision/ensemble/contract.js +316 -0
- package/dist/src/decision/ensemble/drift.js +51 -0
- package/dist/src/decision/ensemble/index.js +6 -0
- package/dist/src/decision/ensemble/report.js +71 -0
- package/dist/src/decision/ensemble/types.js +2 -0
- package/dist/src/decision/evaluate.js +1024 -78
- package/dist/src/decision/file-lifecycle-store.js +168 -0
- package/dist/src/decision/graph-beam.js +40 -0
- package/dist/src/decision/graph-budget.js +110 -0
- package/dist/src/decision/graph-decision-bridge.js +93 -0
- package/dist/src/decision/graph-evidence.js +123 -0
- package/dist/src/decision/graph-flow-adapter.js +56 -0
- package/dist/src/decision/graph-flow.js +105 -0
- package/dist/src/decision/graph-parallel.js +79 -0
- package/dist/src/decision/graph-patterns.js +20 -0
- package/dist/src/decision/graph-run-store.js +66 -0
- package/dist/src/decision/graph-run.js +166 -0
- package/dist/src/decision/graph-skill-bridge.js +91 -0
- package/dist/src/decision/graph-templates.js +56 -0
- package/dist/src/decision/graph.js +115 -0
- package/dist/src/decision/index.js +35 -0
- package/dist/src/decision/job-accounting.js +93 -0
- package/dist/src/decision/job-contract.js +186 -0
- package/dist/src/decision/job-effects.js +95 -0
- package/dist/src/decision/job-evaluate.js +80 -0
- package/dist/src/decision/job-gateway.js +121 -0
- package/dist/src/decision/job-http.js +194 -0
- package/dist/src/decision/job-payload-store.js +258 -0
- package/dist/src/decision/job-poll-limiter.js +92 -0
- package/dist/src/decision/job-quota.js +158 -0
- package/dist/src/decision/job-runtime.js +240 -0
- package/dist/src/decision/job-scheduler.js +26 -0
- package/dist/src/decision/job-service.js +117 -0
- package/dist/src/decision/job-store.js +264 -0
- package/dist/src/decision/job-worker.js +141 -0
- package/dist/src/decision/lifecycle.js +148 -0
- package/dist/src/decision/patterns/artifacts.js +173 -0
- package/dist/src/decision/patterns/catalog.js +108 -0
- package/dist/src/decision/patterns/durable-review.js +237 -0
- package/dist/src/decision/patterns/index.js +8 -0
- package/dist/src/decision/patterns/playground.js +401 -0
- package/dist/src/decision/patterns/recorded.js +43 -0
- package/dist/src/decision/patterns/specs.js +174 -0
- package/dist/src/decision/patterns/types.js +2 -0
- package/dist/src/decision/portable-secrets.js +53 -0
- package/dist/src/decision/projection.js +210 -0
- package/dist/src/decision/qualification/capture.js +67 -0
- package/dist/src/decision/qualification/drift.js +68 -0
- package/dist/src/decision/qualification/gate-evidence.js +172 -0
- package/dist/src/decision/qualification/gates.js +83 -0
- package/dist/src/decision/qualification/index.js +12 -0
- package/dist/src/decision/qualification/load.js +282 -0
- package/dist/src/decision/qualification/manifest.js +37 -0
- package/dist/src/decision/qualification/privacy.js +41 -0
- package/dist/src/decision/qualification/quality.js +213 -0
- package/dist/src/decision/qualification/release.js +116 -0
- package/dist/src/decision/qualification/runner.js +408 -0
- package/dist/src/decision/qualification/types.js +2 -0
- package/dist/src/decision/receipts.js +30 -7
- package/dist/src/decision/result-cache/index.js +6 -0
- package/dist/src/decision/result-cache/integrity.js +28 -0
- package/dist/src/decision/result-cache/key.js +28 -0
- package/dist/src/decision/result-cache/service.js +92 -0
- package/dist/src/decision/result-cache/store.js +327 -0
- package/dist/src/decision/result-cache/types.js +3 -0
- package/dist/src/decision/review/authorization.js +110 -0
- package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
- package/dist/src/decision/review/index.js +12 -0
- package/dist/src/decision/review/migrate.js +34 -0
- package/dist/src/decision/review/operator-audit.js +59 -0
- package/dist/src/decision/review/recovery-adapters.js +252 -0
- package/dist/src/decision/review/recovery.js +33 -0
- package/dist/src/decision/review/service.js +498 -0
- package/dist/src/decision/review/store.js +219 -0
- package/dist/src/decision/review/types.js +2 -0
- package/dist/src/decision/review/validate.js +205 -0
- package/dist/src/decision/scheduler.js +115 -0
- package/dist/src/decision/telemetry/context.js +42 -0
- package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
- package/dist/src/decision/telemetry/exporter.js +77 -0
- package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
- package/dist/src/decision/telemetry/index.js +13 -0
- package/dist/src/decision/telemetry/mapping.js +146 -0
- package/dist/src/decision/telemetry/metrics.js +164 -0
- package/dist/src/decision/telemetry/otlp-http.js +183 -0
- package/dist/src/decision/telemetry/redaction.js +93 -0
- package/dist/src/decision/telemetry/retention.js +109 -0
- package/dist/src/decision/telemetry/runtime.js +310 -0
- package/dist/src/decision/telemetry/trace.js +158 -0
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/validate.js +135 -5
- package/dist/src/effects/checkpoint-sinks.js +94 -0
- package/dist/src/effects/checkpoint.js +76 -0
- package/dist/src/effects/decision-links.js +54 -0
- package/dist/src/effects/errors.js +48 -0
- package/dist/src/effects/identity.js +178 -0
- package/dist/src/effects/index.js +25 -0
- package/dist/src/effects/keyring.js +160 -0
- package/dist/src/effects/keys.js +130 -0
- package/dist/src/effects/ledger.js +435 -0
- package/dist/src/effects/lifecycle.js +110 -0
- package/dist/src/effects/lock-recovery.js +269 -0
- package/dist/src/effects/reader.js +103 -0
- package/dist/src/effects/records.js +114 -0
- package/dist/src/effects/redaction.js +51 -0
- package/dist/src/effects/schema.js +73 -0
- package/dist/src/effects/store.js +239 -0
- package/dist/src/effects/types.js +49 -0
- package/dist/src/effects/verifiers/decision.js +168 -0
- package/dist/src/effects/verifiers/file.js +106 -0
- package/dist/src/effects/verifiers/git.js +206 -0
- package/dist/src/effects/verifiers/index.js +90 -0
- package/dist/src/effects/verifiers/registry.js +148 -0
- package/dist/src/effects/verifiers/review.js +123 -0
- package/dist/src/effects/verifiers/tracker.js +299 -0
- package/dist/src/effects/verifiers/types.js +21 -0
- package/dist/src/effects/verify.js +295 -0
- package/dist/src/evidence/bundle.js +2 -0
- package/dist/src/extensions/commands/definitions.js +28 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/marketplace/provenance.js +25 -27
- package/dist/src/mcp/cli.mjs +11 -1
- package/dist/src/mcp/grok-build-config.mjs +189 -0
- package/dist/src/mcp/registry.js +21 -1
- package/dist/src/mcp/registry.mjs +20 -1
- package/dist/src/models/grok-build-models.js +356 -0
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +34 -0
- package/dist/src/models/model-catalog.v1.json +55 -13
- package/dist/src/models/model-discovery.js +41 -5
- package/dist/src/models/provider-policy.js +3 -2
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +58 -6
- package/dist/src/providers/grok-build-acp.js +192 -0
- package/dist/src/providers/grok-build-dispatch.js +251 -0
- package/dist/src/providers/grok-build-headless.js +170 -0
- package/dist/src/providers/muse-paths.js +105 -0
- package/dist/src/providers/provider-definitions.js +93 -7
- package/dist/src/providers/provider-definitions.mjs +21 -1
- package/dist/src/providers/provider-inventory.js +3 -0
- package/dist/src/security/artifact-attestation.js +4 -8
- package/dist/src/security/signing.js +119 -0
- package/dist/src/sessions/adapters/grok-build.js +187 -0
- package/dist/src/sessions/adapters/muse.js +412 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/grok-build-cli.js +196 -0
- package/dist/src/sessions/index.js +3 -0
- package/dist/src/sessions/workspace-discovery.js +9 -5
- package/dist/src/skills/deployer.js +33 -5
- package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
- package/dist/src/smiths/context-pipeline/finalization.js +1 -32
- package/dist/src/smiths/context-pipeline/generator.js +12 -0
- package/dist/src/smiths/context-pipeline/index.js +1 -1
- package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
- package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/index.js +3 -0
- package/dist/src/smiths/hook-bridge/shim.js +2 -0
- package/dist/src/smiths/hook-bridge/types.js +5 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/dist/src/storage/protected-files.js +102 -0
- package/dist/src/tracker/forge-http.js +371 -0
- package/dist/src/tracker/project-inputs.js +41 -0
- package/package.json +2 -1
- package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
- package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
- package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
- package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
- package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
- package/schemas/decision/DecisionBinding.schema.json +309 -0
- package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
- package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
- package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
- package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
- package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
- package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
- package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
- package/schemas/decision/DecisionDefinition.schema.json +159 -0
- package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
- package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
- package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
- package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
- package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
- package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
- package/schemas/decision/DecisionJob.v1.schema.json +71 -0
- package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
- package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
- package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
- package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
- package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
- package/schemas/decision/DecisionResult.schema.json +524 -0
- package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
- package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
- package/schemas/decision/DecisionReview.schema.json +29 -0
- package/schemas/decision/DecisionRuleset.schema.json +313 -0
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
- package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
- package/schemas/decision/RulesetResult.schema.json +721 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
- package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
- package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
- package/tools/agents/deploy-agents.mjs +21 -6
- package/tools/agents/providers/base.mjs +7 -4
- package/tools/agents/providers/codex.mjs +1 -1
- package/tools/agents/providers/grok-build.d.mts +7 -0
- package/tools/agents/providers/grok-build.mjs +249 -9
- package/tools/agents/providers/model-role.mjs +6 -3
- package/tools/agents/providers/muse-hooks.mjs +785 -0
- package/tools/agents/providers/muse-paths.mjs +112 -0
- package/tools/agents/providers/muse.mjs +336 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/** Bounded Grok Build headless transport. The caller owns task authorization. */
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
const MAX_TIMEOUT_MS = 10 * 60_000;
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 2 * 60_000;
|
|
5
|
+
const DEFAULT_MAX_OUTPUT = 8 * 1024 * 1024;
|
|
6
|
+
export function redactGrokOutput(value, env = process.env) {
|
|
7
|
+
let redacted = value
|
|
8
|
+
.replace(/\b(Bearer\s+)[A-Za-z0-9._~+/-]+/gi, '$1[REDACTED]')
|
|
9
|
+
.replace(/\b(xai-[A-Za-z0-9_-]{8,})\b/g, '[REDACTED]')
|
|
10
|
+
.replace(/\b((?:api[_-]?key|access[_-]?token|authorization)\s*[:=]\s*)[^\s,}"']+/gi, '$1[REDACTED]');
|
|
11
|
+
for (const key of ['XAI_API_KEY', 'GROK_CODE_XAI_API_KEY']) {
|
|
12
|
+
const secret = env[key];
|
|
13
|
+
if (secret && secret.length >= 4)
|
|
14
|
+
redacted = redacted.split(secret).join('[REDACTED]');
|
|
15
|
+
}
|
|
16
|
+
return redacted;
|
|
17
|
+
}
|
|
18
|
+
function scrubEvent(event, env) {
|
|
19
|
+
// JSON roundtrip scrubs nested provider event payloads before any callback.
|
|
20
|
+
return JSON.parse(redactGrokOutput(JSON.stringify(event), env));
|
|
21
|
+
}
|
|
22
|
+
function isTerminal(event) {
|
|
23
|
+
return ['result', 'error', 'complete', 'completed'].includes(String(event.type ?? event.event ?? ''));
|
|
24
|
+
}
|
|
25
|
+
function isError(event) {
|
|
26
|
+
return event.is_error === true || event.error !== undefined || ['error', 'failed'].includes(String(event.type ?? event.event ?? ''));
|
|
27
|
+
}
|
|
28
|
+
function stopProcess(child) {
|
|
29
|
+
if (child.pid && process.platform !== 'win32') {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(-child.pid, 'SIGTERM');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
catch { /* process already exited */ }
|
|
35
|
+
}
|
|
36
|
+
child.kill('SIGTERM');
|
|
37
|
+
}
|
|
38
|
+
export async function runGrokHeadless(options) {
|
|
39
|
+
if (!options.prompt.trim())
|
|
40
|
+
throw new Error('Grok Build prompt is required');
|
|
41
|
+
const format = options.format ?? 'streaming-json';
|
|
42
|
+
if (!['plain', 'json', 'streaming-json'].includes(format))
|
|
43
|
+
throw new Error(`Unsupported Grok output format: ${format}`);
|
|
44
|
+
const timeoutMs = Math.min(Math.max(options.timeoutMs ?? DEFAULT_TIMEOUT_MS, 1), MAX_TIMEOUT_MS);
|
|
45
|
+
const maxBytes = Math.min(Math.max(options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT, 1024), DEFAULT_MAX_OUTPUT);
|
|
46
|
+
const env = options.env ?? process.env;
|
|
47
|
+
const args = [...(options.prefixArgs ?? []), '--no-auto-update', ...(options.disableSubagents ? ['--no-subagents'] : []), '-p', options.prompt, '--output-format', format];
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
const child = spawn(options.command ?? 'grok', args, {
|
|
50
|
+
cwd: options.cwd,
|
|
51
|
+
env,
|
|
52
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
53
|
+
detached: process.platform !== 'win32',
|
|
54
|
+
shell: false,
|
|
55
|
+
});
|
|
56
|
+
let stdout = '';
|
|
57
|
+
let stderr = '';
|
|
58
|
+
let pending = '';
|
|
59
|
+
let bytes = 0;
|
|
60
|
+
let settled = false;
|
|
61
|
+
let terminalSeen = false;
|
|
62
|
+
let terminalError = false;
|
|
63
|
+
const events = [];
|
|
64
|
+
let drainTimer;
|
|
65
|
+
const finish = (error, code = 0) => {
|
|
66
|
+
if (settled)
|
|
67
|
+
return;
|
|
68
|
+
settled = true;
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
if (drainTimer)
|
|
71
|
+
clearTimeout(drainTimer);
|
|
72
|
+
options.signal?.removeEventListener('abort', abort);
|
|
73
|
+
if (error) {
|
|
74
|
+
stopProcess(child);
|
|
75
|
+
reject(error);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const cleanStdout = redactGrokOutput(stdout, env);
|
|
79
|
+
const cleanStderr = redactGrokOutput(stderr, env);
|
|
80
|
+
if (code !== 0 || terminalError) {
|
|
81
|
+
reject(new Error(`Grok Build headless failed (exit ${code}): ${cleanStderr || 'provider error event'}`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (format === 'streaming-json' && !terminalSeen) {
|
|
85
|
+
reject(new Error('Grok Build streaming-json ended without a terminal event'));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (format === 'json') {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = JSON.parse(cleanStdout);
|
|
91
|
+
if (isError(parsed))
|
|
92
|
+
throw new Error('Grok Build returned a JSON error result');
|
|
93
|
+
events.push(parsed);
|
|
94
|
+
}
|
|
95
|
+
catch (cause) {
|
|
96
|
+
reject(cause instanceof Error ? cause : new Error('Invalid Grok Build JSON result'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
resolve({ format, text: cleanStdout, events, exitCode: code, stderr: cleanStderr });
|
|
101
|
+
};
|
|
102
|
+
const abort = () => finish(new Error('Grok Build headless aborted'));
|
|
103
|
+
const timer = setTimeout(() => finish(new Error(`Grok Build headless timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
104
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
105
|
+
if (options.signal?.aborted)
|
|
106
|
+
abort();
|
|
107
|
+
child.on('error', error => finish(new Error(`Grok Build headless launch failed: ${error.message}`)));
|
|
108
|
+
child.stdout.on('data', (chunk) => {
|
|
109
|
+
bytes += chunk.length;
|
|
110
|
+
if (bytes > maxBytes) {
|
|
111
|
+
finish(new Error(`Grok Build headless output exceeded ${maxBytes} bytes`));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const part = chunk.toString('utf8');
|
|
115
|
+
stdout += part;
|
|
116
|
+
if (format !== 'streaming-json')
|
|
117
|
+
return;
|
|
118
|
+
pending += part;
|
|
119
|
+
let end;
|
|
120
|
+
while ((end = pending.indexOf('\n')) >= 0) {
|
|
121
|
+
const line = pending.slice(0, end).trim();
|
|
122
|
+
pending = pending.slice(end + 1);
|
|
123
|
+
if (!line)
|
|
124
|
+
continue;
|
|
125
|
+
let event;
|
|
126
|
+
try {
|
|
127
|
+
event = JSON.parse(line);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
finish(new Error('Invalid Grok Build streaming-json event'));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const safe = scrubEvent(event, env);
|
|
134
|
+
events.push(safe);
|
|
135
|
+
options.onEvent?.(safe);
|
|
136
|
+
if (isError(safe))
|
|
137
|
+
terminalError = true;
|
|
138
|
+
if (isTerminal(safe)) {
|
|
139
|
+
terminalSeen = true;
|
|
140
|
+
// Some provider builds leave background work attached after the
|
|
141
|
+
// result. Allow a short drain, then terminate the process group.
|
|
142
|
+
drainTimer ??= setTimeout(() => { stopProcess(child); finish(undefined, terminalError ? 1 : 0); }, 300);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
child.stderr.on('data', (chunk) => {
|
|
147
|
+
stderr += chunk.toString('utf8');
|
|
148
|
+
if (Buffer.byteLength(stderr) > maxBytes)
|
|
149
|
+
finish(new Error(`Grok Build stderr exceeded ${maxBytes} bytes`));
|
|
150
|
+
});
|
|
151
|
+
child.on('close', code => {
|
|
152
|
+
if (format === 'streaming-json' && pending.trim()) {
|
|
153
|
+
try {
|
|
154
|
+
const event = scrubEvent(JSON.parse(pending), env);
|
|
155
|
+
events.push(event);
|
|
156
|
+
if (isError(event))
|
|
157
|
+
terminalError = true;
|
|
158
|
+
if (isTerminal(event))
|
|
159
|
+
terminalSeen = true;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
finish(new Error('Invalid trailing Grok Build streaming-json event'));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finish(undefined, code ?? 1);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=grok-build-headless.js.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Muse Code skill-root resolution — documented roots only, fail-closed on bad metadata.
|
|
3
|
+
*
|
|
4
|
+
* Muse Code's documented user skill root is `$XDG_CONFIG_HOME/muse/skills`
|
|
5
|
+
* (default `~/.config/muse/skills` when XDG_CONFIG_HOME is unset). The
|
|
6
|
+
* project skill root (`<repo>/.agents/skills`) is static provider metadata
|
|
7
|
+
* on the `muse` skillNamespace; this module resolves only the XDG user root
|
|
8
|
+
* at deploy time.
|
|
9
|
+
*
|
|
10
|
+
* Unlike Grok Bot (AIWG_GROKBOT_SKILLS_DIR, fail-closed until configured),
|
|
11
|
+
* the Muse user root is documented, so an unset XDG_CONFIG_HOME falls back
|
|
12
|
+
* to the documented default — but malformed metadata (relative
|
|
13
|
+
* XDG_CONFIG_HOME, bare `~`, NUL bytes, root-only resolution) fails closed
|
|
14
|
+
* so the deployer never invents a bogus tree.
|
|
15
|
+
*
|
|
16
|
+
* Never invent `~/.muse`, `~/.config/muse` siblings outside `muse/skills`,
|
|
17
|
+
* or `~/.agents/skills` as a write target.
|
|
18
|
+
*
|
|
19
|
+
* @see docs/architecture/adr-muse-provider-target.md
|
|
20
|
+
* @issue #234
|
|
21
|
+
*/
|
|
22
|
+
import { homedir } from 'node:os';
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
/** Muse's documented user skills subtree below the XDG config root. */
|
|
25
|
+
export const MUSE_XDG_SKILLS_SUBDIR = path.join('muse', 'skills');
|
|
26
|
+
/**
|
|
27
|
+
* Expand a leading `~/` against the operator home directory.
|
|
28
|
+
* Bare `~` is rejected — it would invent a home skill root.
|
|
29
|
+
*/
|
|
30
|
+
function expandHomePrefix(raw, userHome = homedir()) {
|
|
31
|
+
if (raw === '~')
|
|
32
|
+
return raw;
|
|
33
|
+
if (raw.startsWith('~/'))
|
|
34
|
+
return path.join(userHome, raw.slice(2));
|
|
35
|
+
return raw;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate and resolve the Muse XDG user skills directory.
|
|
39
|
+
* Returns a structured result so the deployer can fail closed on bad metadata.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveMuseXdgSkillsDirResult(env = process.env, userHome = homedir()) {
|
|
42
|
+
const raw = (env.XDG_CONFIG_HOME || '').trim();
|
|
43
|
+
if (!raw) {
|
|
44
|
+
// Documented default: ~/.config/muse/skills when XDG_CONFIG_HOME is unset.
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
path: path.join(userHome, '.config', MUSE_XDG_SKILLS_SUBDIR),
|
|
48
|
+
source: 'default',
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
const expanded = expandHomePrefix(raw, userHome);
|
|
52
|
+
if (expanded === '~' || expanded.startsWith('~')) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
reason: 'not-absolute',
|
|
56
|
+
message: `XDG_CONFIG_HOME must be an absolute path (got '${raw}'). Bare '~' is rejected so AIWG never invents a Muse home layout.`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (!path.isAbsolute(expanded)) {
|
|
60
|
+
return {
|
|
61
|
+
ok: false,
|
|
62
|
+
reason: 'not-absolute',
|
|
63
|
+
message: `XDG_CONFIG_HOME must be an absolute path (got '${raw}'). Relative values are rejected.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (raw.includes('\0') || expanded.includes('\0')) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
reason: 'traversal',
|
|
70
|
+
message: 'XDG_CONFIG_HOME contains an invalid path character.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
// Defensive: refuse a config root that collapses to the filesystem root;
|
|
74
|
+
// deploying skills to /muse/skills is never the operator's intent.
|
|
75
|
+
const normalized = path.normalize(expanded);
|
|
76
|
+
if (normalized === path.sep) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
reason: 'empty',
|
|
80
|
+
message: 'XDG_CONFIG_HOME resolves to the filesystem root; refuse to deploy skills there.',
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
ok: true,
|
|
85
|
+
path: path.join(normalized, MUSE_XDG_SKILLS_SUBDIR),
|
|
86
|
+
source: 'env',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Absolute Muse XDG user skills dir when the metadata is sane; otherwise
|
|
91
|
+
* null (fail closed). Never invents ~/.muse.
|
|
92
|
+
*/
|
|
93
|
+
export function resolveMuseXdgSkillsDir(env = process.env, userHome = homedir()) {
|
|
94
|
+
const resolution = resolveMuseXdgSkillsDirResult(env, userHome);
|
|
95
|
+
return resolution.ok ? resolution.path : null;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* User-facing remediation for bad Muse XDG metadata.
|
|
99
|
+
* Shared by the deployer so a blocked deploy explains the fix.
|
|
100
|
+
*/
|
|
101
|
+
export function museXdgSkillsDirRemediation(env = process.env, userHome = homedir()) {
|
|
102
|
+
const resolution = resolveMuseXdgSkillsDirResult(env, userHome);
|
|
103
|
+
return resolution.ok ? '' : resolution.message;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=muse-paths.js.map
|
|
@@ -23,6 +23,7 @@ const ProviderDefinitionSchema = z.object({
|
|
|
23
23
|
'grokbot',
|
|
24
24
|
'grok-build',
|
|
25
25
|
'hermes',
|
|
26
|
+
'muse',
|
|
26
27
|
'opencode',
|
|
27
28
|
'openclaw',
|
|
28
29
|
'openhuman',
|
|
@@ -146,6 +147,7 @@ export const PROVIDER_IDS = [
|
|
|
146
147
|
'grokbot',
|
|
147
148
|
'grok-build',
|
|
148
149
|
'hermes',
|
|
150
|
+
'muse',
|
|
149
151
|
'opencode',
|
|
150
152
|
'openclaw',
|
|
151
153
|
'openhuman',
|
|
@@ -244,6 +246,20 @@ const CONTEXT_CONTRACTS = {
|
|
|
244
246
|
bootstrapTargets: ['.hermes.md', 'AGENTS.md'], maxContextBytes: null, recommendedMaxLines: 30, nestedContext: false, support: 'degraded',
|
|
245
247
|
verification: { method: 'AIWG Hermes adapter contract; on-demand artifact-read required', source: 'agentic/code/providers/capability-matrix.yaml', lastVerified: VERIFIED_ON },
|
|
246
248
|
},
|
|
249
|
+
// #225: Muse Code prefers AGENTS.md over CLAUDE.md at each directory level;
|
|
250
|
+
// project AGENTS.md loads only after the workspace is explicitly trusted
|
|
251
|
+
// (first-run trust prompt). AIWG deploys a discover-first AGENTS.md bridge.
|
|
252
|
+
muse: {
|
|
253
|
+
startupFiles: ['AGENTS.md'],
|
|
254
|
+
precedence: ['provider/system', 'project AGENTS.md once the workspace is trusted', 'explicit aiwg discover/show'],
|
|
255
|
+
loadMode: 'prose-directive', includeSyntax: null, configRegistration: null,
|
|
256
|
+
bootstrapTargets: ['AGENTS.md'], maxContextBytes: null, recommendedMaxLines: null, nestedContext: false, support: 'supported',
|
|
257
|
+
verification: {
|
|
258
|
+
method: 'ADR-locked surfaces; discover-first AGENTS.md bridge per official Muse Code docs (trust-gated load)',
|
|
259
|
+
source: 'docs/architecture/adr-muse-provider-target.md',
|
|
260
|
+
lastVerified: '2026-09-24',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
247
263
|
opencode: {
|
|
248
264
|
startupFiles: ['AGENTS.md', 'CLAUDE.md', 'opencode.json#instructions'], precedence: ['provider/system', 'nearest AGENTS.md', 'CLAUDE compatibility', 'registered instructions'],
|
|
249
265
|
loadMode: 'config-registration', includeSyntax: null, configRegistration: { file: 'opencode.json', key: 'instructions' },
|
|
@@ -669,16 +685,14 @@ const BUILT_IN_SEEDS = [
|
|
|
669
685
|
surfaces: {
|
|
670
686
|
primary: 'grok-build',
|
|
671
687
|
compatibility: [],
|
|
672
|
-
|
|
673
|
-
precedence: ['AGENTS.md', '.grok/skills/', '.grok/rules/ (host; AIWG writer deferred #2577)', '.grok/config.toml (config only)', '$GROK_HOME'],
|
|
688
|
+
precedence: ['AGENTS.md', '.grok/agents/ (qualified model workers)', '.grok/skills/', '.grok/rules/ (host)', '.grok/config.toml (config only)', '$GROK_HOME'],
|
|
674
689
|
related: [],
|
|
675
690
|
},
|
|
676
691
|
detection: { env: ['GROK_HOME'], process: ['grok'], capabilityId: 'grok-build' },
|
|
677
692
|
paths: {
|
|
678
693
|
deployTarget: 'mixed',
|
|
679
694
|
artifacts: {
|
|
680
|
-
|
|
681
|
-
agents: null,
|
|
695
|
+
agents: '.grok/agents',
|
|
682
696
|
commands: null,
|
|
683
697
|
skills: '.grok/skills',
|
|
684
698
|
rules: null,
|
|
@@ -695,7 +709,7 @@ const BUILT_IN_SEEDS = [
|
|
|
695
709
|
contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
|
|
696
710
|
},
|
|
697
711
|
smithPaths: {
|
|
698
|
-
agents:
|
|
712
|
+
agents: '.grok/agents',
|
|
699
713
|
commands: null,
|
|
700
714
|
skills: '.grok/skills',
|
|
701
715
|
rules: null,
|
|
@@ -711,8 +725,8 @@ const BUILT_IN_SEEDS = [
|
|
|
711
725
|
},
|
|
712
726
|
adapters: {
|
|
713
727
|
agentFormat: 'agents-md',
|
|
714
|
-
hookBridge:
|
|
715
|
-
mcpInjection:
|
|
728
|
+
hookBridge: 'grok-build',
|
|
729
|
+
mcpInjection: 'grok-build',
|
|
716
730
|
contextAggregation: 'agents-md',
|
|
717
731
|
ruleFormat: 'agents-md-section',
|
|
718
732
|
},
|
|
@@ -764,6 +778,78 @@ const BUILT_IN_SEEDS = [
|
|
|
764
778
|
},
|
|
765
779
|
matrixRef: 'hermes',
|
|
766
780
|
},
|
|
781
|
+
{
|
|
782
|
+
id: 'muse',
|
|
783
|
+
displayName: 'Muse Code',
|
|
784
|
+
// #225: ADR fixes the canonical id as `muse` with no aliases.
|
|
785
|
+
aliases: [],
|
|
786
|
+
status: 'experimental',
|
|
787
|
+
builtIn: true,
|
|
788
|
+
surfaces: {
|
|
789
|
+
primary: 'muse',
|
|
790
|
+
compatibility: [],
|
|
791
|
+
precedence: ['AGENTS.md', '.agents/skills/'],
|
|
792
|
+
related: [],
|
|
793
|
+
},
|
|
794
|
+
// Fail-closed: no env or process signals. A bare `muse` process name collides
|
|
795
|
+
// with unrelated software, so it is not evidence of Muse Code. The `muse`
|
|
796
|
+
// CLI binary name is a documented fact, not a detection claim
|
|
797
|
+
// (docs/architecture/adr-muse-provider-target.md).
|
|
798
|
+
detection: { env: [], process: [], capabilityId: 'muse' },
|
|
799
|
+
paths: {
|
|
800
|
+
deployTarget: 'mixed',
|
|
801
|
+
artifacts: {
|
|
802
|
+
// Skills are written natively; agents, commands, and rules stay
|
|
803
|
+
// indexed (aiwg discover/show) behind the AGENTS.md bridge.
|
|
804
|
+
agents: null,
|
|
805
|
+
commands: null,
|
|
806
|
+
skills: '.agents/skills',
|
|
807
|
+
rules: null,
|
|
808
|
+
behaviors: null,
|
|
809
|
+
},
|
|
810
|
+
kernelSkills: '.agents/skills',
|
|
811
|
+
contextDiscovery: {
|
|
812
|
+
agents: null,
|
|
813
|
+
skills: '.agents/skills',
|
|
814
|
+
rules: null,
|
|
815
|
+
behaviors: null,
|
|
816
|
+
},
|
|
817
|
+
configFile: null,
|
|
818
|
+
contextFiles: { aiwgMd: true, agentsMd: true, claudeMdHook: false, hookFile: null, contextFile: 'AGENTS.md' },
|
|
819
|
+
},
|
|
820
|
+
smithPaths: {
|
|
821
|
+
agents: null,
|
|
822
|
+
commands: null,
|
|
823
|
+
skills: '.agents/skills',
|
|
824
|
+
rules: null,
|
|
825
|
+
fileExtension: '.md',
|
|
826
|
+
configFile: null,
|
|
827
|
+
aggregated: false,
|
|
828
|
+
},
|
|
829
|
+
// Project skill root only: the namespace default resolves to
|
|
830
|
+
// <repo>/.agents/skills — a project tree, never a home-dir tree — so the
|
|
831
|
+
// deployer cannot invent ~/.muse, ~/.config/muse siblings outside
|
|
832
|
+
// muse/skills, or ~/.agents/skills from this metadata alone. The XDG
|
|
833
|
+
// user root ($XDG_CONFIG_HOME/muse/skills, default ~/.config/muse/skills)
|
|
834
|
+
// is not a static namespace default; it resolves dynamically at deploy
|
|
835
|
+
// time via resolveMuseXdgSkillsDir (src/providers/muse-paths.ts, #234),
|
|
836
|
+
// which fails closed on bad XDG metadata. Never write ~/.muse or
|
|
837
|
+
// silently mirror into ~/.agents/skills (#226).
|
|
838
|
+
skillNamespace: {
|
|
839
|
+
deploymentGroup: 'deep-recursion',
|
|
840
|
+
pathType: 'project',
|
|
841
|
+
skillsBaseDir: '.agents/skills',
|
|
842
|
+
subdirLayout: true,
|
|
843
|
+
},
|
|
844
|
+
adapters: {
|
|
845
|
+
agentFormat: 'agents-md',
|
|
846
|
+
hookBridge: null,
|
|
847
|
+
mcpInjection: null,
|
|
848
|
+
contextAggregation: 'agents-md',
|
|
849
|
+
ruleFormat: 'agents-md-section',
|
|
850
|
+
},
|
|
851
|
+
matrixRef: 'muse',
|
|
852
|
+
},
|
|
767
853
|
{
|
|
768
854
|
id: 'opencode',
|
|
769
855
|
aliases: [],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveOmpPaths } from './omp-paths.mjs';
|
|
2
2
|
import { homedir } from 'os';
|
|
3
|
-
import { resolve } from 'path';
|
|
3
|
+
import { isAbsolute, join, resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
const MCP_INJECTION_DEFINITIONS = [
|
|
6
6
|
{
|
|
@@ -72,6 +72,19 @@ const MCP_INJECTION_DEFINITIONS = [
|
|
|
72
72
|
supportsEphemeral: true,
|
|
73
73
|
},
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
id: 'grok-build', aliases: [],
|
|
77
|
+
mcp: {
|
|
78
|
+
providerId: 'grok-build',
|
|
79
|
+
includeInSupportedProviders: true,
|
|
80
|
+
configFormat: 'toml',
|
|
81
|
+
serverConfigFormat: 'grok-build',
|
|
82
|
+
serversKey: null,
|
|
83
|
+
configPath: { scope: 'project', path: '.grok/config.toml' },
|
|
84
|
+
supportsEphemeral: false,
|
|
85
|
+
unsupportedReason: 'Grok Build project MCP configuration is trust-scoped and must remain in its reviewed config layer.',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
75
88
|
{
|
|
76
89
|
id: 'opencode',
|
|
77
90
|
aliases: [],
|
|
@@ -172,6 +185,13 @@ export function resolveMcpConfigPath(provider, projectDir = '.', options = {}) {
|
|
|
172
185
|
if (normalizeRuntimeProviderId(provider) === 'omp' && options.scope === 'user') {
|
|
173
186
|
return resolve(resolveOmpPaths(options).agentDir, 'mcp.json');
|
|
174
187
|
}
|
|
188
|
+
if (normalizeRuntimeProviderId(provider) === 'grok-build' && options.scope === 'user') {
|
|
189
|
+
const userHome = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
190
|
+
const raw = process.env.GROK_HOME || join(userHome, '.grok');
|
|
191
|
+
const home = raw.startsWith('~/') ? join(userHome, raw.slice(2)) : raw;
|
|
192
|
+
if (!isAbsolute(home) || resolve(home) === resolve(home, '..')) throw new Error('GROK_HOME must be an absolute non-root path for MCP injection');
|
|
193
|
+
return resolve(home, 'config.toml');
|
|
194
|
+
}
|
|
175
195
|
const definition = getMcpInjectionDefinition(provider);
|
|
176
196
|
if (!definition) return '';
|
|
177
197
|
|
|
@@ -13,7 +13,10 @@ const PROVIDER_EXECUTABLES = {
|
|
|
13
13
|
'deepseek-harness': ['dsh'],
|
|
14
14
|
factory: ['droid'],
|
|
15
15
|
// Grok Bot has no verified CLI executable name; omit rather than invent.
|
|
16
|
+
// Muse Code's documented CLI binary is `muse`; the bare process name is
|
|
17
|
+
// deliberately NOT a detection signal (fail-closed per ADR).
|
|
16
18
|
'grok-build': ['grok'],
|
|
19
|
+
muse: ['muse'],
|
|
17
20
|
hermes: ['hermes'],
|
|
18
21
|
opencode: ['opencode'],
|
|
19
22
|
openclaw: ['openclaw'],
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createPrivateKey, createPublicKey,
|
|
2
|
-
import { canonicalJson,
|
|
1
|
+
import { createPrivateKey, createPublicKey, } from 'node:crypto';
|
|
2
|
+
import { canonicalJson, sha256 } from './artifact-trust.js';
|
|
3
|
+
import { signDsseEnvelope } from './signing.js';
|
|
3
4
|
export const ARTIFACT_ATTESTATION_MEDIA_TYPE = 'application/vnd.aiwg.artifact-attestation.v1+json';
|
|
4
5
|
export const ARTIFACT_PROVENANCE_PREDICATE_TYPE = 'https://aiwg.io/attestations/artifact-provenance/v1';
|
|
5
6
|
export const IN_TOTO_STATEMENT_V1 = 'https://in-toto.io/Statement/v1';
|
|
@@ -87,14 +88,9 @@ export function createArtifactAttestation(options) {
|
|
|
87
88
|
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' }).toString();
|
|
88
89
|
const statement = createArtifactProvenanceStatement(options);
|
|
89
90
|
const payload = Buffer.from(canonicalJson(statement), 'utf8');
|
|
90
|
-
const signature = signBytes(null, dssePae(DSSE_IN_TOTO_PAYLOAD_TYPE, payload), privateKey);
|
|
91
91
|
return {
|
|
92
92
|
mediaType: ARTIFACT_ATTESTATION_MEDIA_TYPE,
|
|
93
|
-
envelope:
|
|
94
|
-
payloadType: DSSE_IN_TOTO_PAYLOAD_TYPE,
|
|
95
|
-
payload: payload.toString('base64'),
|
|
96
|
-
signatures: [{ keyid: publicKeyFingerprint(publicKeyPem), sig: signature.toString('base64') }],
|
|
97
|
-
},
|
|
93
|
+
envelope: signDsseEnvelope(DSSE_IN_TOTO_PAYLOAD_TYPE, payload, privateKey),
|
|
98
94
|
verificationMaterial: {
|
|
99
95
|
kind: 'public-key',
|
|
100
96
|
algorithm: 'ed25519',
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ed25519 signing helpers shared across subsystems.
|
|
3
|
+
*
|
|
4
|
+
* Two shapes are provided:
|
|
5
|
+
*
|
|
6
|
+
* - canonical-document signatures: an Ed25519 signature over the canonical JSON
|
|
7
|
+
* of a document, with its SHA-256 and signer SPKI carried alongside;
|
|
8
|
+
* - DSSE envelopes: an Ed25519 signature over the DSSE pre-authentication
|
|
9
|
+
* encoding (`artifact-trust.dssePae`) of exact payload bytes.
|
|
10
|
+
*
|
|
11
|
+
* Canonicalization defaults to the RFC 8785 implementation in
|
|
12
|
+
* `artifact-trust.canonicalJson`. A caller whose stored signatures were made
|
|
13
|
+
* over a different canonical form can bind its own canonicalizer through
|
|
14
|
+
* `createCanonicalSigner`, so existing signatures keep verifying byte for byte.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash, createPrivateKey, createPublicKey, sign as cryptoSign, verify as cryptoVerify, } from 'node:crypto';
|
|
17
|
+
import { canonicalJson, decodeBase64, dssePae, publicKeyFingerprint, publicKeyObject, verifyBytes } from './artifact-trust.js';
|
|
18
|
+
export const DEFAULT_KEY_TYPE_ERROR_MESSAGE = 'Signing keys must use Ed25519';
|
|
19
|
+
function sha256Hex(value) {
|
|
20
|
+
return createHash('sha256').update(value).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
function publicKeyFromBase64(value) {
|
|
23
|
+
return createPublicKey({ key: Buffer.from(value, 'base64'), format: 'der', type: 'spki' });
|
|
24
|
+
}
|
|
25
|
+
/** Bind the canonical-document signing helpers to one canonicalizer and error message. */
|
|
26
|
+
export function createCanonicalSigner(options = {}) {
|
|
27
|
+
const canonicalize = options.canonicalize ?? canonicalJson;
|
|
28
|
+
const keyTypeError = options.keyTypeErrorMessage ?? DEFAULT_KEY_TYPE_ERROR_MESSAGE;
|
|
29
|
+
function publicKeyDer(key) {
|
|
30
|
+
const object = typeof key === 'string'
|
|
31
|
+
? createPublicKey(key)
|
|
32
|
+
: key.type === 'public' ? key : createPublicKey(key);
|
|
33
|
+
if (object.asymmetricKeyType !== 'ed25519')
|
|
34
|
+
throw new Error(keyTypeError);
|
|
35
|
+
return object.export({ format: 'der', type: 'spki' });
|
|
36
|
+
}
|
|
37
|
+
function signingKeyId(publicKey) {
|
|
38
|
+
return `ed25519:${sha256Hex(publicKeyDer(publicKey)).slice(0, 32)}`;
|
|
39
|
+
}
|
|
40
|
+
function signCanonicalDocument(document, privateKeyPem, signOptions = {}) {
|
|
41
|
+
const privateKey = createPrivateKey(privateKeyPem);
|
|
42
|
+
if (privateKey.asymmetricKeyType !== 'ed25519')
|
|
43
|
+
throw new Error(keyTypeError);
|
|
44
|
+
const publicKey = signOptions.publicKeyPem ? createPublicKey(signOptions.publicKeyPem) : createPublicKey(privateKey);
|
|
45
|
+
const payload = canonicalize(document);
|
|
46
|
+
return {
|
|
47
|
+
keyId: signOptions.keyId ?? signingKeyId(publicKey),
|
|
48
|
+
algorithm: 'ed25519',
|
|
49
|
+
publicKey: publicKeyDer(publicKey).toString('base64'),
|
|
50
|
+
signedAt: signOptions.signedAt ?? new Date().toISOString(),
|
|
51
|
+
payloadSha256: sha256Hex(payload),
|
|
52
|
+
signature: cryptoSign(null, Buffer.from(payload), privateKey).toString('base64'),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function verifyCanonicalSignature(document, signature) {
|
|
56
|
+
const payload = canonicalize(document);
|
|
57
|
+
return signature.algorithm === 'ed25519'
|
|
58
|
+
&& signature.payloadSha256 === sha256Hex(payload)
|
|
59
|
+
&& cryptoVerify(null, Buffer.from(payload), publicKeyFromBase64(signature.publicKey), Buffer.from(signature.signature, 'base64'));
|
|
60
|
+
}
|
|
61
|
+
return { signCanonicalDocument, verifyCanonicalSignature, signingKeyId, publicKeyDer };
|
|
62
|
+
}
|
|
63
|
+
const defaultSigner = createCanonicalSigner();
|
|
64
|
+
/** Sign the RFC 8785 canonical JSON of `document` with an Ed25519 private key. */
|
|
65
|
+
export const signCanonicalDocument = defaultSigner.signCanonicalDocument;
|
|
66
|
+
/** Verify a signature made by `signCanonicalDocument` over the RFC 8785 canonical JSON. */
|
|
67
|
+
export const verifyCanonicalSignature = defaultSigner.verifyCanonicalSignature;
|
|
68
|
+
/** Stable identifier for an Ed25519 public key: `ed25519:` plus 32 hex chars of its SPKI SHA-256. */
|
|
69
|
+
export const signingKeyId = defaultSigner.signingKeyId;
|
|
70
|
+
function ed25519PrivateKey(privateKey, message) {
|
|
71
|
+
const key = typeof privateKey === 'string' ? createPrivateKey(privateKey) : privateKey;
|
|
72
|
+
if (key.asymmetricKeyType !== 'ed25519')
|
|
73
|
+
throw new Error(message);
|
|
74
|
+
return key;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Sign exact payload bytes as a single-signature DSSE envelope. The keyid is the
|
|
78
|
+
* SHA-256 of the signer's SPKI DER (`artifact-trust.publicKeyFingerprint`).
|
|
79
|
+
*/
|
|
80
|
+
export function signDsseEnvelope(payloadType, payload, privateKey, options = {}) {
|
|
81
|
+
const key = ed25519PrivateKey(privateKey, options.keyTypeErrorMessage ?? DEFAULT_KEY_TYPE_ERROR_MESSAGE);
|
|
82
|
+
const publicKeyPem = createPublicKey(key).export({ type: 'spki', format: 'pem' }).toString();
|
|
83
|
+
const body = Buffer.from(payload);
|
|
84
|
+
const signature = cryptoSign(null, dssePae(payloadType, body), key);
|
|
85
|
+
return {
|
|
86
|
+
payloadType,
|
|
87
|
+
payload: body.toString('base64'),
|
|
88
|
+
signatures: [{ keyid: publicKeyFingerprint(publicKeyPem), sig: signature.toString('base64') }],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Verify that at least one envelope signature is a valid Ed25519 signature by
|
|
93
|
+
* `publicKey` (PEM or base64 SPKI DER) over the DSSE PAE of the envelope's
|
|
94
|
+
* payload type and payload. Keyids are lookup hints and are not trusted.
|
|
95
|
+
* Malformed input verifies as false rather than throwing.
|
|
96
|
+
*/
|
|
97
|
+
export function verifyDsseEnvelope(envelope, publicKey) {
|
|
98
|
+
let payload;
|
|
99
|
+
try {
|
|
100
|
+
if (typeof envelope?.payloadType !== 'string' || !Array.isArray(envelope.signatures))
|
|
101
|
+
return false;
|
|
102
|
+
if (publicKeyObject(publicKey).asymmetricKeyType !== 'ed25519')
|
|
103
|
+
return false;
|
|
104
|
+
payload = decodeBase64(envelope.payload, 'DSSE payload');
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const pae = dssePae(envelope.payloadType, payload);
|
|
110
|
+
return envelope.signatures.some((signature) => {
|
|
111
|
+
try {
|
|
112
|
+
return verifyBytes('ed25519', publicKey, pae, decodeBase64(signature.sig, 'DSSE signature'));
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=signing.js.map
|