@aiwg/cli 2026.8.11 → 2026.8.13
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/bin/aiwg.mjs +2 -0
- package/dist/src/api/index.d.ts +6 -0
- package/dist/src/api/index.js +6 -0
- package/dist/src/cli/handlers/artifact-verify.js +171 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/setup-manifest.js +52 -3
- package/dist/src/cli/handlers/setup.js +15 -2
- package/dist/src/cli/handlers/use.js +71 -6
- package/dist/src/cli/scope-resolver.js +6 -1
- package/dist/src/cli/services/deployment-verification.js +65 -7
- package/dist/src/config/aiwg-config.js +4 -3
- package/dist/src/config/cli.js +3 -1
- package/dist/src/config/gitignore.js +67 -21
- package/dist/src/config/workspace.js +8 -1
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-quickref.js +9 -0
- package/dist/src/marketplace/artifact-attestation.js +195 -0
- package/dist/src/marketplace/exchange.js +437 -79
- package/dist/src/marketplace/provenance-types.js +1 -0
- package/dist/src/marketplace/provenance.js +7 -1
- package/dist/src/providers/hermes-home.js +20 -0
- package/dist/src/providers/provider-definitions.js +5 -4
- package/dist/src/providers/transformation-receipt-integration.js +448 -0
- package/dist/src/providers/transformation-receipt.js +215 -0
- package/dist/src/resources/web-release.d.ts +11 -0
- package/dist/src/resources/web-release.js +61 -6
- package/dist/src/security/artifact-attestation.js +117 -0
- package/dist/src/security/artifact-trust.js +557 -0
- package/dist/src/security/artifact-verifier.js +478 -0
- package/dist/src/skills/deployer.js +5 -1
- package/dist/src/tracker/capability-protocol.js +7 -2
- package/package.json +5 -1
- package/schemas/security/aiwg-artifact-attestation.v1.schema.json +106 -0
- package/schemas/security/aiwg-artifact-provenance.v1.schema.json +204 -0
- package/schemas/security/aiwg-artifact-trust-root.v1.schema.json +59 -0
- package/schemas/security/aiwg-artifact-trust-state.v1.schema.json +32 -0
- package/schemas/security/aiwg-artifact-verification-result.v1.schema.json +46 -0
- package/schemas/security/threat-assessment-input.v1.schema.json +57 -0
- package/schemas/security/threat-assessment-report.v1.schema.json +104 -0
- package/tools/agents/deploy-agents.mjs +8 -2
- package/tools/agents/providers/base.mjs +29 -2
- package/tools/agents/providers/hermes.mjs +163 -19
package/bin/aiwg.mjs
CHANGED
|
@@ -124,6 +124,8 @@ const FAST_HELP_TEXT = `
|
|
|
124
124
|
|
|
125
125
|
VALIDATION
|
|
126
126
|
validate-metadata [path] Validate AIWG component metadata (defaults to agentic/code)
|
|
127
|
+
verify <artifact> Verify DSSE provenance using an explicit versioned trust root
|
|
128
|
+
verify trust <action> Bootstrap, update, or inspect artifact trust state
|
|
127
129
|
context-firewall [scan] Audit provider context, trust, drift, poisoning signals, and budget
|
|
128
130
|
context-firewall baseline Plan or explicitly write the reviewed context baseline
|
|
129
131
|
|
package/dist/src/api/index.d.ts
CHANGED
|
@@ -10,4 +10,10 @@ export * from '../resources/index.js';
|
|
|
10
10
|
export * from '../sessions/index.js';
|
|
11
11
|
export * from '../memory/index.js';
|
|
12
12
|
export * from '../security/threat-assessment-config.js';
|
|
13
|
+
export * from '../security/artifact-verifier.js';
|
|
14
|
+
export * from '../security/artifact-attestation.js';
|
|
15
|
+
export * from '../providers/transformation-receipt.js';
|
|
16
|
+
export * from '../providers/transformation-receipt-integration.js';
|
|
17
|
+
export * from '../marketplace/artifact-attestation.js';
|
|
18
|
+
export { ARTIFACT_TRUST_ROOT_MEDIA_TYPE, ARTIFACT_TRUST_ROOT_SCHEMA_VERSION, ARTIFACT_TRUST_STATE_SCHEMA_VERSION, bootstrapTrustRoot, parseTrustRoot, parseTrustState, readTrustState, validateTrustRoot, validateTrustState, verifyRootTransition, writeTrustState, channelStateKey, canonicalJson, dssePae, publicKeyFingerprint, sha256, type ArtifactTrustRoot, type ArtifactTrustState, type RootBootstrapResult, type RootTransitionResult, type ArtifactTrustPolicySettings, type TrustedChannelState, } from '../security/artifact-trust.js';
|
|
13
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/api/index.js
CHANGED
|
@@ -10,4 +10,10 @@ export * from '../resources/index.js';
|
|
|
10
10
|
export * from '../sessions/index.js';
|
|
11
11
|
export * from '../memory/index.js';
|
|
12
12
|
export * from '../security/threat-assessment-config.js';
|
|
13
|
+
export * from '../security/artifact-verifier.js';
|
|
14
|
+
export * from '../security/artifact-attestation.js';
|
|
15
|
+
export * from '../providers/transformation-receipt.js';
|
|
16
|
+
export * from '../providers/transformation-receipt-integration.js';
|
|
17
|
+
export * from '../marketplace/artifact-attestation.js';
|
|
18
|
+
export { ARTIFACT_TRUST_ROOT_MEDIA_TYPE, ARTIFACT_TRUST_ROOT_SCHEMA_VERSION, ARTIFACT_TRUST_STATE_SCHEMA_VERSION, bootstrapTrustRoot, parseTrustRoot, parseTrustState, readTrustState, validateTrustRoot, validateTrustState, verifyRootTransition, writeTrustState, channelStateKey, canonicalJson, dssePae, publicKeyFingerprint, sha256, } from '../security/artifact-trust.js';
|
|
13
19
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { bootstrapTrustRoot, parseTrustState, verifyRootTransition, writeTrustState, } from '../../security/artifact-trust.js';
|
|
4
|
+
import { verifyArtifact } from '../../security/artifact-verifier.js';
|
|
5
|
+
const MAX_BYTES = 32 * 1024 * 1024;
|
|
6
|
+
const DEFAULT_STATE = '.aiwg/security/artifact-trust-state.json';
|
|
7
|
+
function usage() {
|
|
8
|
+
return [
|
|
9
|
+
'aiwg verify — Verify cross-asset provenance using explicit trust policy',
|
|
10
|
+
'',
|
|
11
|
+
'Usage:',
|
|
12
|
+
' aiwg verify <artifact> --attestation <file-or-https-url> --policy <root.json>',
|
|
13
|
+
' [--state <file>] [--root-fingerprint <sha256>] [--material <uri>=<file>]...',
|
|
14
|
+
' [--asset-type <type> --namespace <name> --channel <name>]',
|
|
15
|
+
' [--offline] [--json] [--allow-policy-exempt] [--no-write-state]',
|
|
16
|
+
' aiwg verify trust bootstrap --root <file> --fingerprint <sha256> [--state <file>]',
|
|
17
|
+
' aiwg verify trust update --current <file> --next <file> [--state <file>]',
|
|
18
|
+
' aiwg verify trust status [--state <file>] [--json]',
|
|
19
|
+
'',
|
|
20
|
+
'Trust is never inferred from DSSE keyid or embedded outer public keys.',
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
23
|
+
function valueAfter(args, flag) {
|
|
24
|
+
const index = args.indexOf(flag);
|
|
25
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
26
|
+
}
|
|
27
|
+
function resolve(cwd, file) {
|
|
28
|
+
return path.isAbsolute(file) ? file : path.resolve(cwd, file);
|
|
29
|
+
}
|
|
30
|
+
function checkedRead(file) {
|
|
31
|
+
const bytes = readFileSync(file);
|
|
32
|
+
if (bytes.length > MAX_BYTES)
|
|
33
|
+
throw new Error(`${file} exceeds the ${MAX_BYTES}-byte safety limit`);
|
|
34
|
+
return bytes;
|
|
35
|
+
}
|
|
36
|
+
async function readLocation(location, cwd, offline, signal) {
|
|
37
|
+
if (!/^https?:\/\//i.test(location))
|
|
38
|
+
return checkedRead(resolve(cwd, location));
|
|
39
|
+
if (offline)
|
|
40
|
+
throw new Error('offline mode forbids network locations');
|
|
41
|
+
if (!location.startsWith('https://'))
|
|
42
|
+
throw new Error('remote verification inputs must use HTTPS');
|
|
43
|
+
const response = await fetch(location, { redirect: 'follow', signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(15_000)]) : AbortSignal.timeout(15_000) });
|
|
44
|
+
if (!response.ok || !response.url.startsWith('https://'))
|
|
45
|
+
throw new Error(`HTTPS input failed closed (${response.status})`);
|
|
46
|
+
const contentLength = Number(response.headers.get('content-length') ?? 0);
|
|
47
|
+
if (contentLength > MAX_BYTES)
|
|
48
|
+
throw new Error('remote input exceeds the safety limit');
|
|
49
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
50
|
+
if (bytes.length > MAX_BYTES)
|
|
51
|
+
throw new Error('remote input exceeds the safety limit');
|
|
52
|
+
return bytes;
|
|
53
|
+
}
|
|
54
|
+
function renderHuman(outcome) {
|
|
55
|
+
return [
|
|
56
|
+
`${outcome.status}: ${outcome.artifact.name}`,
|
|
57
|
+
` SHA-256: ${outcome.artifact.sha256}`,
|
|
58
|
+
` Policy: ${outcome.policy ?? 'unavailable'}`,
|
|
59
|
+
` Identities: ${outcome.identities.join(', ') || 'none'}`,
|
|
60
|
+
` Root version: ${outcome.rootVersion ?? 'unavailable'}`,
|
|
61
|
+
...outcome.diagnostics.map(diagnostic => ` ${diagnostic.code}: ${diagnostic.message}`),
|
|
62
|
+
].join('\n');
|
|
63
|
+
}
|
|
64
|
+
async function trustCommand(ctx) {
|
|
65
|
+
const action = ctx.args[1];
|
|
66
|
+
const stateFile = resolve(ctx.cwd, valueAfter(ctx.args, '--state') ?? DEFAULT_STATE);
|
|
67
|
+
if (action === 'status') {
|
|
68
|
+
if (!existsSync(stateFile))
|
|
69
|
+
return { exitCode: 1, message: `Trust state not found: ${stateFile}` };
|
|
70
|
+
const state = parseTrustState(checkedRead(stateFile));
|
|
71
|
+
return {
|
|
72
|
+
exitCode: 0,
|
|
73
|
+
message: ctx.args.includes('--json') ? JSON.stringify(state, null, 2) : `Root v${state.rootVersion} ${state.rootSha256}\nTrusted time: ${state.trustedTime}\nChannels: ${Object.keys(state.channels).length}`,
|
|
74
|
+
rawOutput: ctx.args.includes('--json'),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (action === 'bootstrap') {
|
|
78
|
+
const rootFile = valueAfter(ctx.args, '--root');
|
|
79
|
+
const fingerprint = valueAfter(ctx.args, '--fingerprint');
|
|
80
|
+
if (!rootFile || !fingerprint)
|
|
81
|
+
return { exitCode: 1, message: 'trust bootstrap requires --root and --fingerprint' };
|
|
82
|
+
const bootstrapped = bootstrapTrustRoot(checkedRead(resolve(ctx.cwd, rootFile)), fingerprint);
|
|
83
|
+
writeTrustState(stateFile, bootstrapped.state);
|
|
84
|
+
return { exitCode: 0, message: `Bootstrapped trust root v${bootstrapped.root.signed.version} at ${stateFile}` };
|
|
85
|
+
}
|
|
86
|
+
if (action === 'update') {
|
|
87
|
+
const current = valueAfter(ctx.args, '--current');
|
|
88
|
+
const next = valueAfter(ctx.args, '--next');
|
|
89
|
+
if (!current || !next)
|
|
90
|
+
return { exitCode: 1, message: 'trust update requires --current and --next' };
|
|
91
|
+
if (!existsSync(stateFile))
|
|
92
|
+
return { exitCode: 1, message: `Trust state not found: ${stateFile}` };
|
|
93
|
+
const transition = verifyRootTransition(checkedRead(resolve(ctx.cwd, current)), checkedRead(resolve(ctx.cwd, next)), parseTrustState(checkedRead(stateFile)));
|
|
94
|
+
writeTrustState(stateFile, transition.state);
|
|
95
|
+
return { exitCode: 0, message: `Updated trust root to v${transition.state.rootVersion} at ${stateFile}` };
|
|
96
|
+
}
|
|
97
|
+
return { exitCode: 1, message: usage() };
|
|
98
|
+
}
|
|
99
|
+
export const artifactVerifyHandler = {
|
|
100
|
+
id: 'verify',
|
|
101
|
+
name: 'Artifact Verification',
|
|
102
|
+
description: 'Verify cross-asset DSSE provenance and manage trust roots',
|
|
103
|
+
category: 'utility',
|
|
104
|
+
aliases: [],
|
|
105
|
+
async execute(ctx) {
|
|
106
|
+
if (ctx.args.includes('--help') || ctx.args.includes('-h') || ctx.args.length === 0)
|
|
107
|
+
return { exitCode: 0, message: usage() };
|
|
108
|
+
try {
|
|
109
|
+
if (ctx.args[0] === 'trust')
|
|
110
|
+
return await trustCommand(ctx);
|
|
111
|
+
const artifactLocation = ctx.args[0];
|
|
112
|
+
const rootFile = valueAfter(ctx.args, '--policy');
|
|
113
|
+
if (!rootFile)
|
|
114
|
+
return { exitCode: 1, message: `--policy <root.json> is required\n\n${usage()}` };
|
|
115
|
+
const offline = ctx.args.includes('--offline');
|
|
116
|
+
const attestationLocation = valueAfter(ctx.args, '--attestation') ?? `${artifactLocation}.aiwg-attestation.json`;
|
|
117
|
+
const [artifactBytes, attestationBytes] = await Promise.all([
|
|
118
|
+
readLocation(artifactLocation, ctx.cwd, offline, ctx.signal),
|
|
119
|
+
readLocation(attestationLocation, ctx.cwd, offline, ctx.signal),
|
|
120
|
+
]);
|
|
121
|
+
const rootBytes = checkedRead(resolve(ctx.cwd, rootFile));
|
|
122
|
+
const stateFile = resolve(ctx.cwd, valueAfter(ctx.args, '--state') ?? DEFAULT_STATE);
|
|
123
|
+
let state = existsSync(stateFile) ? parseTrustState(checkedRead(stateFile)) : undefined;
|
|
124
|
+
if (!state && !offline) {
|
|
125
|
+
const fingerprint = valueAfter(ctx.args, '--root-fingerprint');
|
|
126
|
+
if (fingerprint)
|
|
127
|
+
state = bootstrapTrustRoot(rootBytes, fingerprint).state;
|
|
128
|
+
}
|
|
129
|
+
const materials = new Map();
|
|
130
|
+
for (let index = 0; index < ctx.args.length; index += 1) {
|
|
131
|
+
if (ctx.args[index] !== '--material')
|
|
132
|
+
continue;
|
|
133
|
+
const spec = ctx.args[index + 1] ?? '';
|
|
134
|
+
const separator = spec.lastIndexOf('=');
|
|
135
|
+
if (separator <= 0)
|
|
136
|
+
throw new Error('--material must use <uri>=<file>');
|
|
137
|
+
materials.set(spec.slice(0, separator), checkedRead(resolve(ctx.cwd, spec.slice(separator + 1))));
|
|
138
|
+
}
|
|
139
|
+
const outcome = await verifyArtifact({
|
|
140
|
+
artifactBytes,
|
|
141
|
+
artifactName: /^https:\/\//.test(artifactLocation) ? new URL(artifactLocation).pathname.split('/').pop() ?? artifactLocation : path.basename(artifactLocation),
|
|
142
|
+
attestation: JSON.parse(attestationBytes.toString('utf8')),
|
|
143
|
+
rootBytes,
|
|
144
|
+
state,
|
|
145
|
+
materials,
|
|
146
|
+
...(valueAfter(ctx.args, '--asset-type') && valueAfter(ctx.args, '--namespace') && valueAfter(ctx.args, '--channel')
|
|
147
|
+
? { expectedScope: {
|
|
148
|
+
assetType: valueAfter(ctx.args, '--asset-type'),
|
|
149
|
+
namespace: valueAfter(ctx.args, '--namespace'),
|
|
150
|
+
channel: valueAfter(ctx.args, '--channel'),
|
|
151
|
+
} }
|
|
152
|
+
: {}),
|
|
153
|
+
offline,
|
|
154
|
+
});
|
|
155
|
+
if (outcome.status === 'verified' && outcome.nextState && !ctx.args.includes('--no-write-state'))
|
|
156
|
+
writeTrustState(stateFile, outcome.nextState);
|
|
157
|
+
const json = ctx.args.includes('--json');
|
|
158
|
+
const processExit = outcome.status === 'policy-exempt' && ctx.args.includes('--allow-policy-exempt') ? 0 : outcome.exitCode;
|
|
159
|
+
return { exitCode: processExit, message: json ? JSON.stringify(outcome, null, 2) : renderHuman(outcome), rawOutput: json };
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
return { exitCode: 27, message: ctx.args.includes('--json') ? JSON.stringify({
|
|
164
|
+
schemaVersion: 'aiwg.verify.result.v1', status: 'malformed', exitCode: 27,
|
|
165
|
+
artifact: { name: ctx.args[0] ? path.basename(ctx.args[0]) : 'unknown', sha256: '0'.repeat(64) },
|
|
166
|
+
identities: [], diagnostics: [{ code: 'CLI_INPUT_ERROR', message }],
|
|
167
|
+
}, null, 2) : `malformed: ${message}`, rawOutput: ctx.args.includes('--json') };
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
//# sourceMappingURL=artifact-verify.js.map
|
|
@@ -61,12 +61,13 @@ import { versionsHandler } from './resource-versions.js';
|
|
|
61
61
|
import { jobHandler } from './job.js';
|
|
62
62
|
import { costReportHandler } from './cost-report.js';
|
|
63
63
|
import { evidenceHandler } from './evidence.js';
|
|
64
|
+
import { artifactVerifyHandler } from './artifact-verify.js';
|
|
64
65
|
// Re-export individual handlers
|
|
65
66
|
export {
|
|
66
67
|
// Maintenance
|
|
67
68
|
helpHandler, versionHandler, authHandler, doctorHandler, contextFirewallHandler, updateHandler, refreshHandler, regenerateHandler, workspaceContextHandler,
|
|
68
69
|
// Framework management
|
|
69
|
-
useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler,
|
|
70
|
+
useHandler, listHandler, removeHandler, promoteHandler, installHandler, packagesHandler, marketplaceHandler, initHandler, setupHandler, setupGenerateHandler, setupRunHandler, setupValidateHandler, issueHandler, issueAuditHandler, runHandler, jobHandler, costReportHandler, evidenceHandler, artifactVerifyHandler,
|
|
70
71
|
// Project
|
|
71
72
|
newBundleHandler, quickrefHandler, newProjectHandler, sessionHandler, sessionsHandler,
|
|
72
73
|
// Workspace
|
|
@@ -148,6 +149,7 @@ export const allHandlers = [
|
|
|
148
149
|
jobHandler,
|
|
149
150
|
costReportHandler,
|
|
150
151
|
evidenceHandler,
|
|
152
|
+
artifactVerifyHandler,
|
|
151
153
|
// Workspace management
|
|
152
154
|
...workspaceHandlers,
|
|
153
155
|
// Subcommand handlers (MCP, catalog, index, skills)
|
|
@@ -4,6 +4,8 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { parseDocument, stringify } from 'yaml';
|
|
6
6
|
import { findPackageRoot } from '../find-package-root.js';
|
|
7
|
+
import { sha256 } from '../../security/artifact-trust.js';
|
|
8
|
+
import { artifactVerifyHandler } from './artifact-verify.js';
|
|
7
9
|
const SETUP_SCHEMA_REL = 'agentic/code/addons/agentic-installer/schemas/v1/setup-manifest.schema.json';
|
|
8
10
|
const GENERATE_HELP = `aiwg setup-generate - generate a starter setup.aiwg.io/v1 SetupManifest
|
|
9
11
|
|
|
@@ -40,7 +42,8 @@ Usage:
|
|
|
40
42
|
aiwg setup-run [manifest-path] [--manifest PATH] [--dry-run] [--platform OS]
|
|
41
43
|
[--distro NAME] [--params-file PATH] [--param KEY=VALUE]
|
|
42
44
|
[--step STEP_ID] [--skip A,B] [--type user|developer|ci]
|
|
43
|
-
[--yes|--confirm]
|
|
45
|
+
[--yes|--confirm] [--attestation PATH] [--policy ROOT]
|
|
46
|
+
[--state PATH] [--offline]
|
|
44
47
|
|
|
45
48
|
Options:
|
|
46
49
|
--manifest PATH Manifest path. Defaults to ./setup.manifest.yaml.
|
|
@@ -53,11 +56,17 @@ Options:
|
|
|
53
56
|
--skip A,B Comma-separated step IDs to skip.
|
|
54
57
|
--type TYPE Select default manifest by install type when no path is given.
|
|
55
58
|
--yes, --confirm Explicitly authorize mutating step execution and recovery.
|
|
59
|
+
--attestation PATH Adjacent AIWG attestation for provider-orchestrated handoff.
|
|
60
|
+
--policy ROOT Explicit cross-asset trust root; required for agent handoff.
|
|
61
|
+
--state PATH Persisted trust/freshness state used by verification.
|
|
62
|
+
--offline Forbid network retrieval and require portable evidence.
|
|
56
63
|
--help, -h Show this help.
|
|
57
64
|
|
|
58
65
|
Safety:
|
|
59
66
|
setup-run always runs setup-validate before platform detection or execution.
|
|
60
67
|
Mutating execution refuses to run without explicit confirmation.
|
|
68
|
+
Provider-orchestrated manifests are never handed to an agent unless their
|
|
69
|
+
exact bytes have status verified under the explicit trust policy.
|
|
61
70
|
`;
|
|
62
71
|
function flagValue(args, name) {
|
|
63
72
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -85,6 +94,12 @@ function positionalManifest(args) {
|
|
|
85
94
|
'--type',
|
|
86
95
|
'--output',
|
|
87
96
|
'--name',
|
|
97
|
+
'--attestation',
|
|
98
|
+
'--policy',
|
|
99
|
+
'--state',
|
|
100
|
+
'--asset-type',
|
|
101
|
+
'--namespace',
|
|
102
|
+
'--channel',
|
|
88
103
|
]);
|
|
89
104
|
for (let i = 0; i < args.length; i += 1) {
|
|
90
105
|
const arg = args[i];
|
|
@@ -689,9 +704,17 @@ export function runSetupManifest(options) {
|
|
|
689
704
|
}
|
|
690
705
|
const manifest = validation.manifest;
|
|
691
706
|
if (manifest.metadata.execution_mode === 'provider-orchestrated') {
|
|
707
|
+
const expectedDigest = sha256(readFileSync(validation.manifestPath));
|
|
708
|
+
if (options.artifactVerification?.status !== 'verified'
|
|
709
|
+
|| options.artifactVerification.artifact.sha256 !== expectedDigest) {
|
|
710
|
+
return {
|
|
711
|
+
exitCode: 29,
|
|
712
|
+
message: 'setup-run: provider-orchestrated handoff blocked; verify these exact manifest bytes with an explicit trust root and adjacent attestation first',
|
|
713
|
+
};
|
|
714
|
+
}
|
|
692
715
|
return {
|
|
693
716
|
exitCode: 2,
|
|
694
|
-
message:
|
|
717
|
+
message: `setup-run: verified provider-orchestrated manifest (${options.artifactVerification.artifact.sha256}); give these exact contents to a supported AI provider instead of executing them as a deterministic CLI manifest`,
|
|
695
718
|
};
|
|
696
719
|
}
|
|
697
720
|
const target = detectPlatform(options);
|
|
@@ -801,7 +824,33 @@ export const setupRunHandler = {
|
|
|
801
824
|
process.stdout.write(RUN_HELP);
|
|
802
825
|
return { exitCode: 0 };
|
|
803
826
|
}
|
|
804
|
-
|
|
827
|
+
const options = parseRunOptions(ctx);
|
|
828
|
+
const manifestPath = options.manifestPath ?? 'setup.manifest.yaml';
|
|
829
|
+
let artifactVerification;
|
|
830
|
+
if (flagValue(ctx.args, '--policy')) {
|
|
831
|
+
const verificationArgs = [
|
|
832
|
+
manifestPath,
|
|
833
|
+
'--attestation', flagValue(ctx.args, '--attestation') ?? `${manifestPath}.aiwg-attestation.json`,
|
|
834
|
+
'--policy', flagValue(ctx.args, '--policy'),
|
|
835
|
+
'--asset-type', flagValue(ctx.args, '--asset-type') ?? 'setup-manifest',
|
|
836
|
+
'--namespace', flagValue(ctx.args, '--namespace') ?? 'aiwg',
|
|
837
|
+
'--channel', flagValue(ctx.args, '--channel') ?? 'stable',
|
|
838
|
+
'--json',
|
|
839
|
+
...(flagValue(ctx.args, '--state') ? ['--state', flagValue(ctx.args, '--state')] : []),
|
|
840
|
+
...(hasFlag(ctx.args, '--offline') ? ['--offline'] : []),
|
|
841
|
+
];
|
|
842
|
+
const verification = await artifactVerifyHandler.execute({ ...ctx, args: verificationArgs, rawArgs: verificationArgs });
|
|
843
|
+
try {
|
|
844
|
+
artifactVerification = JSON.parse(verification.message ?? '');
|
|
845
|
+
}
|
|
846
|
+
catch {
|
|
847
|
+
return { exitCode: verification.exitCode || 27, message: `setup-run: artifact verification failed: ${verification.message ?? 'invalid verifier output'}` };
|
|
848
|
+
}
|
|
849
|
+
if (artifactVerification.status !== 'verified') {
|
|
850
|
+
return { exitCode: artifactVerification.exitCode, message: `setup-run: provider handoff blocked by artifact verification status '${artifactVerification.status}'` };
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return runSetupManifest({ ...options, artifactVerification });
|
|
805
854
|
},
|
|
806
855
|
};
|
|
807
856
|
//# sourceMappingURL=setup-manifest.js.map
|
|
@@ -153,6 +153,13 @@ function secondaryRemotes(remotes, primary, issueTracker, ci) {
|
|
|
153
153
|
push_on_release: false,
|
|
154
154
|
}));
|
|
155
155
|
}
|
|
156
|
+
function normalizeForcePushPolicy(value, warnings) {
|
|
157
|
+
if (value === 'main-only-blocked') {
|
|
158
|
+
warnings.push('delivery.force_push_policy=main-only-blocked is a legacy alias; setup normalized it to own-branch-only.');
|
|
159
|
+
return 'own-branch-only';
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
156
163
|
function cloneConfig(config) {
|
|
157
164
|
return JSON.parse(JSON.stringify(config));
|
|
158
165
|
}
|
|
@@ -191,6 +198,9 @@ function validateSetupConfig(config, remotes, issueProvider) {
|
|
|
191
198
|
else {
|
|
192
199
|
checkRemote('remotes.issue_tracker', config.remotes?.issue_tracker);
|
|
193
200
|
}
|
|
201
|
+
if (config.remotes?.issue_provider && !ISSUE_PROVIDERS.includes(config.remotes.issue_provider)) {
|
|
202
|
+
errors.push('remotes.issue_provider is invalid');
|
|
203
|
+
}
|
|
194
204
|
checkRemote('remotes.ci', config.remotes?.ci);
|
|
195
205
|
if (!DELIVERY_MODES.includes(config.delivery?.mode))
|
|
196
206
|
errors.push('delivery.mode is invalid');
|
|
@@ -222,6 +232,7 @@ export async function buildSetupProjectPlan(options) {
|
|
|
222
232
|
const primaryRemote = remotes.find(r => r.name === primary);
|
|
223
233
|
const hasLocalIssues = existsSync(projectAiwgPath(options.projectDir, 'issues', 'config.json'));
|
|
224
234
|
const issueProvider = options.issueProvider ?? chooseIssueProvider(primaryRemote, hasLocalIssues);
|
|
235
|
+
const warnings = [];
|
|
225
236
|
const issueTracker = issueProvider === 'local'
|
|
226
237
|
? 'local'
|
|
227
238
|
: options.issueTracker ?? base.remotes?.issue_tracker ?? primary;
|
|
@@ -229,6 +240,7 @@ export async function buildSetupProjectPlan(options) {
|
|
|
229
240
|
const remotesConfig = {
|
|
230
241
|
primary,
|
|
231
242
|
issue_tracker: issueTracker,
|
|
243
|
+
issue_provider: issueProvider,
|
|
232
244
|
ci,
|
|
233
245
|
secondary: base.remotes?.secondary ?? secondaryRemotes(remotes, primary, issueTracker, ci),
|
|
234
246
|
};
|
|
@@ -249,9 +261,11 @@ export async function buildSetupProjectPlan(options) {
|
|
|
249
261
|
require_ci_green: options.requireCiGreen ?? existingDelivery.require_ci_green ?? true,
|
|
250
262
|
auto_close_issues: options.autoCloseIssues ?? existingDelivery.auto_close_issues ?? true,
|
|
251
263
|
issue_comment_on_cycle: options.issueCommentOnCycle ?? existingDelivery.issue_comment_on_cycle ?? true,
|
|
252
|
-
force_push_policy: options.forcePushPolicy ?? existingDelivery.force_push_policy ?? 'never',
|
|
253
264
|
require_signed_commits: options.requireSignedCommits ?? existingDelivery.require_signed_commits ?? false,
|
|
254
265
|
};
|
|
266
|
+
delivery.force_push_policy = (options.forcePushPolicy
|
|
267
|
+
?? normalizeForcePushPolicy(existingDelivery.force_push_policy, warnings)
|
|
268
|
+
?? 'never');
|
|
255
269
|
const committerName = options.committerName ?? existingDelivery.committer?.name ?? gitConfig(options.projectDir, 'user.name');
|
|
256
270
|
const committerEmail = options.committerEmail ?? existingDelivery.committer?.email ?? gitConfig(options.projectDir, 'user.email');
|
|
257
271
|
if (committerName || committerEmail) {
|
|
@@ -273,7 +287,6 @@ export async function buildSetupProjectPlan(options) {
|
|
|
273
287
|
}
|
|
274
288
|
base.remotes = remotesConfig;
|
|
275
289
|
base.delivery = delivery;
|
|
276
|
-
const warnings = [];
|
|
277
290
|
if (primaryRemote?.provider === 'unknown' && issueProvider !== 'local') {
|
|
278
291
|
warnings.push(`Remote '${primary}' is self-hosted or unknown; provider '${issueProvider}' was selected explicitly/defaulted.`);
|
|
279
292
|
}
|
|
@@ -52,10 +52,54 @@ import { generate as generateContextFiles, discoverDeployedArtifacts, } from '..
|
|
|
52
52
|
import { verifyModelWrapperDeployment } from '../../models/wrapper-deployment.js';
|
|
53
53
|
import { loadGraphIndexFile } from '../../artifacts/index-reader.js';
|
|
54
54
|
import { aggregateUseDeploymentResult, buildDryRunUseResult, renderUseDeploymentResult, verifyProviderDeployment, } from '../services/deployment-verification.js';
|
|
55
|
+
import { finalizeProviderTransformationReceipt, sourceVerificationsFromSignedWebRelease, } from '../../providers/transformation-receipt-integration.js';
|
|
56
|
+
import { loadResourceTrustRootFile, resolveWebRelease, } from '../../resources/web-release.js';
|
|
57
|
+
import { createResourceCredentialProvider } from '../../auth/resource-credentials.js';
|
|
55
58
|
/**
|
|
56
59
|
* Valid framework identifiers
|
|
57
60
|
*/
|
|
58
61
|
const VALID_FRAMEWORKS = ['sdlc', 'marketing', 'media-curator', 'research', 'forensics', 'dfir', 'security-engineering', 'ops', 'validation', 'knowledge-base', 'writing', 'general', 'all'];
|
|
62
|
+
function providerReceiptWebReleaseOptions() {
|
|
63
|
+
const baseUrl = process.env.AIWG_RESOURCE_BASE_URL;
|
|
64
|
+
const cacheRoot = process.env.AIWG_RESOURCE_CACHE_ROOT;
|
|
65
|
+
const trustRootFile = process.env.AIWG_RESOURCE_TRUST_ROOT_FILE;
|
|
66
|
+
const publicKeyPem = trustRootFile === undefined
|
|
67
|
+
? undefined
|
|
68
|
+
: loadResourceTrustRootFile(path.resolve(trustRootFile));
|
|
69
|
+
return {
|
|
70
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
71
|
+
...(cacheRoot === undefined ? {} : { cacheRoot }),
|
|
72
|
+
...(publicKeyPem === undefined ? {} : { publicKeyPem }),
|
|
73
|
+
...(process.env.AIWG_RESOURCE_ALLOW_INSECURE_LOOPBACK_HTTP === '1'
|
|
74
|
+
? { allowInsecureLoopbackHttp: true }
|
|
75
|
+
: {}),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
async function signedProviderSourceVerifications(options) {
|
|
79
|
+
const versionInfo = await getVersionInfo();
|
|
80
|
+
if (versionInfo.devMode)
|
|
81
|
+
return undefined;
|
|
82
|
+
const releaseOptions = providerReceiptWebReleaseOptions();
|
|
83
|
+
let release;
|
|
84
|
+
try {
|
|
85
|
+
release = await resolveWebRelease({ ...releaseOptions, selector: versionInfo.version, offline: true });
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
const credentialProvider = createResourceCredentialProvider(process.env);
|
|
89
|
+
const token = await credentialProvider();
|
|
90
|
+
// Protected production resources require the authenticated release
|
|
91
|
+
// credential. A configured alternate endpoint may intentionally be public.
|
|
92
|
+
if (!token && releaseOptions.baseUrl === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
release = await resolveWebRelease({
|
|
95
|
+
...releaseOptions,
|
|
96
|
+
selector: versionInfo.version,
|
|
97
|
+
credentialProvider: async () => token,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const verifications = await sourceVerificationsFromSignedWebRelease(options, release);
|
|
101
|
+
return Object.keys(verifications).length > 0 ? verifications : undefined;
|
|
102
|
+
}
|
|
59
103
|
/**
|
|
60
104
|
* Framework name to deploy mode mapping.
|
|
61
105
|
* Mode is passed as `--mode <value>` to deploy-agents.mjs, which resolves
|
|
@@ -630,7 +674,7 @@ const SESSION_RELOAD_NOTICE = {
|
|
|
630
674
|
rationale: 'OpenCode loads agent files on session start and does not hot-reload.',
|
|
631
675
|
},
|
|
632
676
|
hermes: {
|
|
633
|
-
action: 'In an active Hermes session, run /reload-skills to pick up new skills in
|
|
677
|
+
action: 'In an active Hermes session, run /reload-skills to pick up new skills in $HERMES_HOME/skills/ and /reload-mcp to pick up MCP server changes ($HERMES_HOME/config.yaml) — both are in-session slash commands, no chat restart needed. Restart the chat only as a fallback if the slash commands are unavailable.',
|
|
634
678
|
rationale: 'Hermes loads skills and MCP config at session start (verified in hermes_cli/commands.py:178 and hermes_cli/config.py:1228). The /reload-skills and /reload-mcp slash commands re-scan in place; /reload-mcp prompts for confirmation by default.',
|
|
635
679
|
symptom: 'Until reloaded, newly deployed kernel skills are missing from `hermes skills list` and unreachable via natural-language invocation; new MCP servers (incl. AIWG) are missing from the tool surface.',
|
|
636
680
|
},
|
|
@@ -1964,6 +2008,22 @@ export class UseHandler {
|
|
|
1964
2008
|
const effectiveScope = provider === 'openclaw' || provider === 'openhuman'
|
|
1965
2009
|
? 'user'
|
|
1966
2010
|
: requestedScope;
|
|
2011
|
+
if (coreResult.exitCode === 0 && !dryRun) {
|
|
2012
|
+
try {
|
|
2013
|
+
const receiptOptions = {
|
|
2014
|
+
projectRoot: projectDir,
|
|
2015
|
+
frameworkRoot,
|
|
2016
|
+
provider,
|
|
2017
|
+
scope: effectiveScope,
|
|
2018
|
+
requestedBundles: [requestedBundle],
|
|
2019
|
+
};
|
|
2020
|
+
const sourceVerifications = await signedProviderSourceVerifications(receiptOptions);
|
|
2021
|
+
await finalizeProviderTransformationReceipt({ ...receiptOptions, sourceVerifications });
|
|
2022
|
+
}
|
|
2023
|
+
catch (error) {
|
|
2024
|
+
originalConsole.warn(`Provider receipt finalization failed for ${provider}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1967
2027
|
providerResults.push(await verifyProviderDeployment({
|
|
1968
2028
|
projectRoot: projectDir,
|
|
1969
2029
|
frameworkRoot,
|
|
@@ -1974,6 +2034,10 @@ export class UseHandler {
|
|
|
1974
2034
|
invocationStartedAt: dryRun ? undefined : startedAt,
|
|
1975
2035
|
deploymentExitCode: coreResult.exitCode,
|
|
1976
2036
|
deploymentMessage: coreResult.message,
|
|
2037
|
+
// A normal first deployment has no authenticated verifier handoff
|
|
2038
|
+
// yet, so receipt absence belongs in doctor/status rather than
|
|
2039
|
+
// degrading an otherwise successful `aiwg use` result.
|
|
2040
|
+
reportMissingReceipt: false,
|
|
1977
2041
|
}));
|
|
1978
2042
|
}
|
|
1979
2043
|
result = aggregateUseDeploymentResult({
|
|
@@ -3185,11 +3249,12 @@ export class UseHandler {
|
|
|
3185
3249
|
// Collect deployment counts for registry persistence and the final
|
|
3186
3250
|
// orchestrated report. Presentation happens once, after verification, so
|
|
3187
3251
|
// users do not see a second competing summary.
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
}
|
|
3252
|
+
//
|
|
3253
|
+
// Counts are always populated from the on-disk artifacts so that the
|
|
3254
|
+
// registry record written below (#621) reflects the real deploy even on
|
|
3255
|
+
// a verbose run — the prior `if (quiet)` guard left the record
|
|
3256
|
+
// `{agents: 0, commands: 0, skills: 0, rules: 0}` on `-v` runs.
|
|
3257
|
+
const counts = await countDeployedArtifacts(target, paths, provider);
|
|
3193
3258
|
// Deploy CI workflow files when --ci-hooks-enabled is set (#661)
|
|
3194
3259
|
if (ciHooksEnabled) {
|
|
3195
3260
|
await deployCiHooks({ frameworkRoot, framework, target, dryRun });
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { homedir } from 'node:os';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
+
import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
15
|
+
export const hermesHome = resolveHermesHome;
|
|
14
16
|
/**
|
|
15
17
|
* User-scope deploy paths per provider per ADR-4 §2. Each path is absolute
|
|
16
18
|
* (rooted in os.homedir()) so the orchestrator's existing path-join logic
|
|
@@ -159,7 +161,10 @@ export const USER_SCOPE_PATHS = {
|
|
|
159
161
|
},
|
|
160
162
|
hermes: {
|
|
161
163
|
agents: '',
|
|
162
|
-
|
|
164
|
+
// #2119: honor HERMES_HOME so `--scope user` deploys land under the same
|
|
165
|
+
// root the running Hermes session scans, matching the hermes provider's
|
|
166
|
+
// paths.skills resolution.
|
|
167
|
+
skills: resolveHermesHomePath('skills'),
|
|
163
168
|
commands: '',
|
|
164
169
|
rules: '',
|
|
165
170
|
behaviors: '',
|