@aiwg/cli 2026.8.10 → 2026.8.12
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/agents/packaged-agent-inventory.js +37 -1
- package/dist/src/api/index.d.ts +6 -0
- package/dist/src/api/index.js +6 -0
- package/dist/src/artifacts/fortemi-core-sync.js +23 -8
- package/dist/src/cli/handlers/artifact-verify.js +171 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/refresh.js +43 -9
- 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 +80 -0
- 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/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/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/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/providers/base.mjs +14 -2
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
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
const MANAGED_MARKER_RE = /(?:^|\n)(?:#|<!--)\s*aiwg:managed\s+v?([^\s]+)\s+([^\s>]+)(?:\s*-->)?/;
|
|
4
5
|
/** Normalize provider-specific deployed filenames to the source agent id. */
|
|
@@ -12,6 +13,31 @@ export function parseManagedArtifactMarker(content) {
|
|
|
12
13
|
const match = MANAGED_MARKER_RE.exec(content);
|
|
13
14
|
return match ? { version: match[1], source: match[2] } : null;
|
|
14
15
|
}
|
|
16
|
+
/** Extract the developer-instruction body from a canonical Markdown agent. */
|
|
17
|
+
export function extractAgentInstructionBody(content) {
|
|
18
|
+
if (!content.startsWith('---'))
|
|
19
|
+
return content.trim();
|
|
20
|
+
const withoutFrontmatter = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '');
|
|
21
|
+
return withoutFrontmatter.trim();
|
|
22
|
+
}
|
|
23
|
+
function instructionHash(content) {
|
|
24
|
+
return createHash('sha256').update(content).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
function extractDeployedInstructions(filename, content) {
|
|
27
|
+
if (!filename.toLowerCase().endsWith('.toml')) {
|
|
28
|
+
return extractAgentInstructionBody(content);
|
|
29
|
+
}
|
|
30
|
+
const match = content.match(/^developer_instructions\s*=\s*(.+)$/m);
|
|
31
|
+
if (!match)
|
|
32
|
+
return null;
|
|
33
|
+
try {
|
|
34
|
+
const value = JSON.parse(match[1]);
|
|
35
|
+
return typeof value === 'string' ? value.trim() : null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
15
41
|
async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdownFiles) {
|
|
16
42
|
let entries;
|
|
17
43
|
try {
|
|
@@ -31,8 +57,12 @@ async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdow
|
|
|
31
57
|
if (!allMarkdownFiles && path.basename(rootDir) !== 'agents')
|
|
32
58
|
return;
|
|
33
59
|
let stat;
|
|
60
|
+
let content;
|
|
34
61
|
try {
|
|
35
|
-
stat = await
|
|
62
|
+
[stat, content] = await Promise.all([
|
|
63
|
+
fs.stat(absolute),
|
|
64
|
+
fs.readFile(absolute, 'utf8'),
|
|
65
|
+
]);
|
|
36
66
|
}
|
|
37
67
|
catch {
|
|
38
68
|
return;
|
|
@@ -42,6 +72,7 @@ async function collectAgentSources(frameworkRoot, rootDir, inventory, allMarkdow
|
|
|
42
72
|
sources.push({
|
|
43
73
|
path: path.relative(frameworkRoot, absolute),
|
|
44
74
|
size: stat.size,
|
|
75
|
+
instructionHash: instructionHash(extractAgentInstructionBody(content)),
|
|
45
76
|
});
|
|
46
77
|
inventory.set(name, sources);
|
|
47
78
|
}));
|
|
@@ -67,6 +98,11 @@ export function diagnoseOversizedAgent(filename, content, inventory, ceilingByte
|
|
|
67
98
|
const packaged = inventory.get(normalizeAgentArtifactName(filename)) ?? [];
|
|
68
99
|
if (packaged.some((source) => source.size > ceilingBytes))
|
|
69
100
|
return 'current-package';
|
|
101
|
+
const deployedInstructions = extractDeployedInstructions(filename, content);
|
|
102
|
+
if (deployedInstructions !== null
|
|
103
|
+
&& packaged.some((source) => source.instructionHash === instructionHash(deployedInstructions))) {
|
|
104
|
+
return 'current-package';
|
|
105
|
+
}
|
|
70
106
|
const marker = parseManagedArtifactMarker(content);
|
|
71
107
|
if (marker?.source === 'bundled')
|
|
72
108
|
return 'stale-deployment';
|
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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { GRAPH_CONFIGS, getProjectIndexRoot, loadGlobalGraphConfigs } from "./types.js";
|
|
5
6
|
import { buildAiwgFortemiIndexExport, } from "./browser-export.js";
|
|
6
7
|
import { loadGraphIndexFile } from "./index-reader.js";
|
|
@@ -25,7 +26,7 @@ function findPackageRoot(startDir) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
function prebuiltDir(graph) {
|
|
28
|
-
const moduleDir = path.dirname(
|
|
29
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
29
30
|
const packageRoot = findPackageRoot(moduleDir);
|
|
30
31
|
if (!packageRoot)
|
|
31
32
|
return null;
|
|
@@ -215,17 +216,31 @@ export function getFortemiCorePrebuiltStatus(graph = "framework") {
|
|
|
215
216
|
reason: manifestReadReason,
|
|
216
217
|
};
|
|
217
218
|
}
|
|
219
|
+
const prebuiltManifest = manifest;
|
|
218
220
|
const exportExists = fs.existsSync(exportPath);
|
|
219
221
|
let reason = null;
|
|
220
222
|
if (exportExists) {
|
|
221
223
|
try {
|
|
222
224
|
const exportText = fs.readFileSync(exportPath, "utf-8");
|
|
223
225
|
const exported = JSON.parse(exportText);
|
|
224
|
-
if (
|
|
226
|
+
if (prebuiltManifest.schema_version !== "aiwg.fortemi.prebuilt.v1" ||
|
|
227
|
+
prebuiltManifest.backend !== "fortemi-core" ||
|
|
228
|
+
prebuiltManifest.graph !== graph ||
|
|
229
|
+
prebuiltManifest.export_path !== "aiwg-fortemi-index-v2.json" ||
|
|
230
|
+
prebuiltManifest.export_schema_version !== "aiwg.fortemi.index.export.v2") {
|
|
231
|
+
reason = "prebuilt manifest is incompatible with the requested graph";
|
|
232
|
+
}
|
|
233
|
+
else if (sha256(exportText) !== prebuiltManifest.export_checksum) {
|
|
225
234
|
reason = "prebuilt export checksum does not match manifest";
|
|
226
235
|
}
|
|
227
|
-
else if (exported.schema_version !==
|
|
228
|
-
reason = `prebuilt export schema '${exported.schema_version}' does not match manifest '${
|
|
236
|
+
else if (exported.schema_version !== prebuiltManifest.export_schema_version) {
|
|
237
|
+
reason = `prebuilt export schema '${exported.schema_version}' does not match manifest '${prebuiltManifest.export_schema_version}'`;
|
|
238
|
+
}
|
|
239
|
+
else if (exported.source?.graph !== graph) {
|
|
240
|
+
reason = `prebuilt export graph '${exported.source?.graph ?? "unknown"}' does not match requested graph '${graph}'`;
|
|
241
|
+
}
|
|
242
|
+
else if (!Array.isArray(exported.items) || exported.items.length !== prebuiltManifest.item_count) {
|
|
243
|
+
reason = "prebuilt export item count does not match manifest";
|
|
229
244
|
}
|
|
230
245
|
}
|
|
231
246
|
catch (err) {
|
|
@@ -241,10 +256,10 @@ export function getFortemiCorePrebuiltStatus(graph = "framework") {
|
|
|
241
256
|
exportPath,
|
|
242
257
|
built: exportExists,
|
|
243
258
|
stale: !exportExists || reason !== null,
|
|
244
|
-
itemCount:
|
|
245
|
-
exportChecksum:
|
|
246
|
-
generatedAt:
|
|
247
|
-
sourceIndexBuiltAt:
|
|
259
|
+
itemCount: prebuiltManifest.item_count,
|
|
260
|
+
exportChecksum: prebuiltManifest.export_checksum,
|
|
261
|
+
generatedAt: prebuiltManifest.generated_at,
|
|
262
|
+
sourceIndexBuiltAt: prebuiltManifest.source_index_built_at,
|
|
248
263
|
reason: !exportExists ? "prebuilt manifest exists but export file is missing" : reason,
|
|
249
264
|
};
|
|
250
265
|
}
|
|
@@ -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)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
import { promises as fs } from 'fs';
|
|
16
16
|
import path from 'path';
|
|
17
17
|
import { createScriptRunner } from './script-runner.js';
|
|
18
|
+
import { createUseHandler } from './use.js';
|
|
18
19
|
import { getFrameworkRoot } from '../../channel/manager.mjs';
|
|
19
20
|
import { refreshAllPackages } from '../../packages/registry.js';
|
|
20
21
|
import { resolveActiveProvider } from '../provider-resolution.js';
|
|
@@ -97,7 +98,14 @@ export async function pruneStaleManagedAgentFiles(options) {
|
|
|
97
98
|
continue;
|
|
98
99
|
const artifactName = normalizeAgentArtifactName(entry.name);
|
|
99
100
|
const missingFromCurrentPackage = !desired.has(artifactName);
|
|
100
|
-
|
|
101
|
+
// Addons have independent manifest versions. Comparing their managed
|
|
102
|
+
// marker to the top-level package version makes a successful refresh
|
|
103
|
+
// delete freshly restored addon agents. Version-based cleanup remains
|
|
104
|
+
// valid for other provider trees that were not refreshed, while the
|
|
105
|
+
// active provider removes only artifacts absent from current sources.
|
|
106
|
+
const fromOlderPackage = provider !== options.provider
|
|
107
|
+
&& currentVersion !== null
|
|
108
|
+
&& isOlderManagedVersion(marker.version, currentVersion);
|
|
101
109
|
if (!missingFromCurrentPackage && !fromOlderPackage)
|
|
102
110
|
continue;
|
|
103
111
|
const relFile = path.relative(options.projectRoot, file);
|
|
@@ -163,6 +171,7 @@ export const refreshHandler = {
|
|
|
163
171
|
const modelDeployArgs = collectModelDeployArgs(ctx.args);
|
|
164
172
|
const frameworkRoot = await getFrameworkRoot();
|
|
165
173
|
const runner = createScriptRunner(frameworkRoot);
|
|
174
|
+
const activeUseHandler = createUseHandler();
|
|
166
175
|
if (!quiet) {
|
|
167
176
|
ui.blank();
|
|
168
177
|
// Deprecation notice when invoked as 'sync'
|
|
@@ -195,6 +204,7 @@ export const refreshHandler = {
|
|
|
195
204
|
// Step 2.5: Refresh remote packages (always, unless --packages-only skips npm)
|
|
196
205
|
if (!quiet)
|
|
197
206
|
ui.info(dryRun ? 'Would refresh remote packages...' : 'Refreshing remote packages...');
|
|
207
|
+
const deploymentFailures = [];
|
|
198
208
|
if (!dryRun) {
|
|
199
209
|
try {
|
|
200
210
|
const refreshed = await refreshAllPackages();
|
|
@@ -259,13 +269,30 @@ export const refreshHandler = {
|
|
|
259
269
|
ui.dim(' No installed frameworks or addons to re-deploy');
|
|
260
270
|
}
|
|
261
271
|
for (const fw of frameworks) {
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
// Invoke the active installation's handler directly. The historical
|
|
273
|
+
// deploy.mjs bridge shells out to the first `aiwg` on PATH, which can
|
|
274
|
+
// be a different version/root and therefore cannot safely refresh
|
|
275
|
+
// addons installed by this package (#143/#2102).
|
|
276
|
+
const useResult = await activeUseHandler.execute({
|
|
277
|
+
...ctx,
|
|
278
|
+
cwd: ctx.cwd,
|
|
279
|
+
frameworkRoot,
|
|
280
|
+
args: [
|
|
281
|
+
fw,
|
|
282
|
+
'--provider', detectedProvider,
|
|
283
|
+
'--target', ctx.cwd,
|
|
284
|
+
'--yes',
|
|
285
|
+
'--json',
|
|
286
|
+
...modelDeployArgs,
|
|
287
|
+
],
|
|
288
|
+
rawArgs: ['use', fw],
|
|
289
|
+
});
|
|
264
290
|
if (useResult.exitCode === 0) {
|
|
265
291
|
if (!quiet)
|
|
266
292
|
ui.success(`Deployed: ${fw}`);
|
|
267
293
|
}
|
|
268
294
|
else {
|
|
295
|
+
deploymentFailures.push(fw);
|
|
269
296
|
if (!quiet)
|
|
270
297
|
ui.warn(`Deploy issue: ${fw} (exit ${useResult.exitCode})`);
|
|
271
298
|
}
|
|
@@ -281,11 +308,10 @@ export const refreshHandler = {
|
|
|
281
308
|
}
|
|
282
309
|
}
|
|
283
310
|
// Step 4.25: Report planned project-local deploys (#1035).
|
|
284
|
-
// The
|
|
285
|
-
// this block surfaces
|
|
286
|
-
// covered during a real refresh.
|
|
311
|
+
// The active use handler performs the actual project-local deploy during
|
|
312
|
+
// framework refresh; this block surfaces dry-run and completion details.
|
|
287
313
|
try {
|
|
288
|
-
const plDiscovery = await discoverProjectLocalBundles(
|
|
314
|
+
const plDiscovery = await discoverProjectLocalBundles(ctx.cwd);
|
|
289
315
|
const plCount = plDiscovery.bundles.length;
|
|
290
316
|
if (plCount > 0) {
|
|
291
317
|
if (dryRun) {
|
|
@@ -312,11 +338,12 @@ export const refreshHandler = {
|
|
|
312
338
|
if (!quiet)
|
|
313
339
|
ui.info('Checking for stale deployments...');
|
|
314
340
|
let staleAgentRemovals = [];
|
|
315
|
-
if (!dryRun) {
|
|
341
|
+
if (!dryRun && deploymentFailures.length === 0) {
|
|
316
342
|
try {
|
|
317
343
|
staleAgentRemovals = await pruneStaleManagedAgentFiles({
|
|
318
344
|
projectRoot: ctx.cwd,
|
|
319
345
|
frameworkRoot,
|
|
346
|
+
provider: detectedProvider,
|
|
320
347
|
});
|
|
321
348
|
if (staleAgentRemovals.length > 0 && !quiet) {
|
|
322
349
|
const total = staleAgentRemovals.reduce((sum, item) => sum + item.paths.length, 0);
|
|
@@ -435,10 +462,17 @@ export const refreshHandler = {
|
|
|
435
462
|
skipUpdate,
|
|
436
463
|
channel: channel || undefined,
|
|
437
464
|
staleAgentRemovals,
|
|
465
|
+
deploymentFailures,
|
|
438
466
|
});
|
|
439
467
|
console.log(output);
|
|
440
468
|
}
|
|
441
|
-
|
|
469
|
+
if (deploymentFailures.length > 0) {
|
|
470
|
+
return {
|
|
471
|
+
exitCode: 1,
|
|
472
|
+
message: `Failed to re-deploy installed bundle(s): ${deploymentFailures.join(', ')}`,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
return { exitCode: 0 };
|
|
442
476
|
},
|
|
443
477
|
};
|
|
444
478
|
//# sourceMappingURL=refresh.js.map
|
|
@@ -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
|
}
|