@aiwg/cli 2026.9.24 → 2026.10.0
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 +2 -1
- package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
- package/dist/src/artifacts/discovery-shadow.js +195 -0
- package/dist/src/artifacts/index-builder.js +47 -9
- package/dist/src/artifacts/query-engine.js +94 -1
- package/dist/src/artifacts/types.js +1 -0
- package/dist/src/cli/handlers/decision.js +88 -0
- package/dist/src/cli/handlers/gates.js +154 -0
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/sessions.js +10 -7
- package/dist/src/cli/handlers/utilities.js +20 -0
- package/dist/src/config/aiwg-config.js +15 -2
- package/dist/src/decision/acceptance.js +25 -0
- package/dist/src/decision/adapters/jev.js +24 -2
- package/dist/src/decision/calibration/registry.js +9 -0
- package/dist/src/decision/compose.js +74 -15
- package/dist/src/decision/context-live-promotion.js +344 -0
- package/dist/src/decision/context-live-qualification.js +591 -0
- package/dist/src/decision/context-pruning.js +847 -0
- package/dist/src/decision/driver.js +430 -0
- package/dist/src/decision/egress-live-qualification.js +874 -0
- package/dist/src/decision/ensemble/contract.js +64 -1
- package/dist/src/decision/ensemble/index.js +1 -0
- package/dist/src/decision/ensemble/report.js +8 -42
- package/dist/src/decision/ensemble/runtime.js +571 -0
- package/dist/src/decision/ensemble-study/artifacts.js +85 -0
- package/dist/src/decision/ensemble-study/calibration.js +443 -0
- package/dist/src/decision/ensemble-study/corpus.js +229 -0
- package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
- package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
- package/dist/src/decision/ensemble-study/multifact.js +241 -0
- package/dist/src/decision/ensemble-study/protocol.js +77 -0
- package/dist/src/decision/ensemble-study/score.js +462 -0
- package/dist/src/decision/ensemble-study/staged.js +87 -0
- package/dist/src/decision/ensemble-study/statistics.js +84 -0
- package/dist/src/decision/evaluate.js +227 -14
- package/dist/src/decision/export.js +4 -0
- package/dist/src/decision/feature-export.js +1074 -0
- package/dist/src/decision/graph-index.js +16 -0
- package/dist/src/decision/graph-live-qualification.js +1041 -0
- package/dist/src/decision/graph-live-workload.js +245 -0
- package/dist/src/decision/graph-skill-bridge.js +21 -3
- package/dist/src/decision/heldout/calibration.js +140 -0
- package/dist/src/decision/heldout/collector.js +430 -0
- package/dist/src/decision/heldout/contract.js +309 -0
- package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
- package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
- package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
- package/dist/src/decision/heldout/d29-pools.js +288 -0
- package/dist/src/decision/heldout/d29-v6.js +169 -0
- package/dist/src/decision/heldout/d29-v7.js +202 -0
- package/dist/src/decision/heldout/d29-v8.js +586 -0
- package/dist/src/decision/heldout/generator-registry.js +67 -0
- package/dist/src/decision/heldout/generators.js +696 -0
- package/dist/src/decision/heldout/index.js +8 -0
- package/dist/src/decision/heldout/journal.js +346 -0
- package/dist/src/decision/heldout/study-generators.js +64 -0
- package/dist/src/decision/heldout/types.js +2 -0
- package/dist/src/decision/host-policy.js +61 -0
- package/dist/src/decision/index.js +11 -0
- package/dist/src/decision/interop-profile.v1.json +112 -0
- package/dist/src/decision/interop.js +653 -0
- package/dist/src/decision/issue-triage/index.js +3 -0
- package/dist/src/decision/issue-triage/runtime.js +1069 -0
- package/dist/src/decision/issue-triage/types.js +2 -0
- package/dist/src/decision/patterns/catalog.js +28 -8
- package/dist/src/decision/patterns/playground.js +28 -11
- package/dist/src/decision/patterns/specs.js +19 -10
- package/dist/src/decision/preprocessed-evidence.js +724 -0
- package/dist/src/decision/qualification/capture.js +8 -2
- package/dist/src/decision/qualification/gate-evidence.js +19 -4
- package/dist/src/decision/qualification/quality.js +57 -49
- package/dist/src/decision/qualification/release.js +76 -3
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/routing/contract.js +175 -0
- package/dist/src/decision/routing/index.js +5 -0
- package/dist/src/decision/routing/report.js +216 -0
- package/dist/src/decision/routing/runtime.js +685 -0
- package/dist/src/decision/routing/types.js +2 -0
- package/dist/src/decision/sdlc-screening.js +1157 -0
- package/dist/src/decision/sensitivity/comparative.js +154 -0
- package/dist/src/decision/sensitivity/contract.js +218 -0
- package/dist/src/decision/sensitivity/index.js +5 -0
- package/dist/src/decision/sensitivity/runtime.js +912 -0
- package/dist/src/decision/sensitivity/types.js +2 -0
- package/dist/src/decision/validate.js +26 -0
- package/dist/src/extensions/commands/definitions.js +38 -0
- package/dist/src/extensions/manifest.js +44 -0
- package/dist/src/gates/discovery.js +315 -0
- package/dist/src/gates/driver.js +388 -0
- package/dist/src/gates/evaluate.js +719 -0
- package/dist/src/gates/floors.js +255 -0
- package/dist/src/gates/index.js +17 -0
- package/dist/src/gates/providers/evidence.js +38 -0
- package/dist/src/gates/providers/index.js +21 -0
- package/dist/src/gates/providers/isolated-child-source.js +446 -0
- package/dist/src/gates/providers/loader.js +1069 -0
- package/dist/src/gates/providers/paired.js +52 -0
- package/dist/src/gates/providers/proportion.js +60 -0
- package/dist/src/gates/providers/registry.js +40 -0
- package/dist/src/gates/providers/scalar.js +41 -0
- package/dist/src/gates/providers/screening.js +53 -0
- package/dist/src/gates/providers/types.js +25 -0
- package/dist/src/gates/registry.js +1003 -0
- package/dist/src/gates/report.js +102 -0
- package/dist/src/gates/schema.js +56 -0
- package/dist/src/gates/stats/binomial.js +26 -0
- package/dist/src/gates/stats/bootstrap.js +62 -0
- package/dist/src/gates/stats/clopper-pearson.js +95 -0
- package/dist/src/gates/stats/digest.js +26 -0
- package/dist/src/gates/stats/error.js +5 -0
- package/dist/src/gates/stats/index.js +9 -0
- package/dist/src/gates/stats/normal.js +35 -0
- package/dist/src/gates/stats/paired.js +120 -0
- package/dist/src/gates/stats/splits.js +30 -0
- package/dist/src/gates/types.js +8 -0
- package/dist/src/governance/redaction.js +14 -4
- package/dist/src/mcp/muse-native-config.mjs +123 -0
- package/dist/src/mcp/tools/decision.mjs +188 -0
- package/dist/src/mcp/tools/subsystems.mjs +2 -0
- package/dist/src/providers/capability-matrix.yaml +2 -1
- package/dist/src/providers/provider-definitions.js +2 -1
- package/dist/src/sessions/adapters/muse.js +336 -39
- package/dist/src/sessions/batch-import.js +7 -0
- package/dist/src/sessions/workspace-discovery.js +13 -2
- package/package.json +1 -1
- package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
- package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
- package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
- package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
- package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
- package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
- package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
- package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
- package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
- package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
- package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
- package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
- package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
- package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
- package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
- package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
- package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
- package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
- package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
- package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
- package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
- package/schemas/decision/D29Study.v1.schema.json +1903 -0
- package/schemas/decision/D29Study.v2.schema.json +1344 -0
- package/schemas/decision/D29Study.v3.schema.json +609 -0
- package/schemas/decision/D29Study.v4.schema.json +1133 -0
- package/schemas/decision/D29Study.v6.schema.json +1205 -0
- package/schemas/decision/D29Study.v7.schema.json +1471 -0
- package/schemas/decision/D29Study.v8.schema.json +671 -0
- package/schemas/decision/D29Study.v9.schema.json +778 -0
- package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
- package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
- package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
- package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
- package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
- package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
- package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
- package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
- package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
- package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
- package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
- package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
- package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
- package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
- package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
- package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
- package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
- package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
- package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
- package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
- package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
- package/schemas/decision/DecisionRuleset.schema.json +8 -2
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
- package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
- package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
- package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
- package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
- package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
- package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
- package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
- package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
- package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
- package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
- package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
- package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
- package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
- package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
- package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
- package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
- package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
- package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
- package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
- package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
- package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
- package/tools/agents/deploy-agents.mjs +1 -1
- package/tools/agents/providers/base.mjs +26 -7
- package/tools/agents/providers/factory.mjs +10 -2
- package/tools/agents/providers/muse-hooks.mjs +1 -29
- package/tools/agents/providers/muse.mjs +6 -3
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { lstatSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { findPackageRoot } from '../cli/find-package-root.js';
|
|
7
|
+
import { readAiwgConfig } from '../config/aiwg-config.js';
|
|
8
|
+
import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
|
|
9
|
+
import { artifactDigest } from '../decision/validate.js';
|
|
10
|
+
import { discoverShippedGatePacks, loadGatePackFile, originForPackDir, registerBundleGatePacks } from './discovery.js';
|
|
11
|
+
import { evaluateGates } from './evaluate.js';
|
|
12
|
+
import { resolveProjectFloors, validateGatesConfig } from './floors.js';
|
|
13
|
+
import { createCoreProviderRegistry } from './providers/index.js';
|
|
14
|
+
import { GateRegistry, splitPackId, validateResolvedPack } from './registry.js';
|
|
15
|
+
import { validateGateDocument } from './schema.js';
|
|
16
|
+
function baseDir(options = {}) {
|
|
17
|
+
return path.resolve(options.cwd ?? process.cwd());
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The installed package root: the only tree allowed to contribute `aiwg:`
|
|
21
|
+
* packs. Resolved from the installed package location, never from the cwd,
|
|
22
|
+
* so an arbitrary checkout cannot impersonate the shipped namespace.
|
|
23
|
+
*/
|
|
24
|
+
export function installedAiwgRoot() {
|
|
25
|
+
const root = findPackageRoot(dirname(fileURLToPath(import.meta.url)));
|
|
26
|
+
if (root === null)
|
|
27
|
+
throw new Error('installed aiwg package root is unavailable');
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
function rootDir(options = {}) {
|
|
31
|
+
return path.resolve(options.aiwgRoot ?? installedAiwgRoot());
|
|
32
|
+
}
|
|
33
|
+
function resolvePath(candidate, base) {
|
|
34
|
+
if (!candidate)
|
|
35
|
+
throw new Error('Path is required');
|
|
36
|
+
return path.resolve(base, candidate);
|
|
37
|
+
}
|
|
38
|
+
async function readDocument(filePath) {
|
|
39
|
+
const content = await readFile(filePath, 'utf8');
|
|
40
|
+
return filePath.toLowerCase().endsWith('.json') ? parseDecisionJson(content) : parseDecisionYaml(content);
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build an offline registry from shipped packs plus explicit pack dirs/files.
|
|
47
|
+
* Trust rules: `aiwg:` is reserved for the installed tree (an `--aiwg-root`
|
|
48
|
+
* override is marked `untrusted-override` and can never feed evaluation);
|
|
49
|
+
* single files loaded via `packDirs` are always `project:` packs regardless
|
|
50
|
+
* of the id they declare; `--pack-dir` bundle directories are always
|
|
51
|
+
* project-scoped too (`originForPackDir` refuses manifests claiming a
|
|
52
|
+
* shipped or installed bundle id, and symlinked bundle dirs refuse), so
|
|
53
|
+
* `addon:`/`framework:`/`extension:` packs never enter through an unverified
|
|
54
|
+
* path (never path sniffing). Read-only inspection (`list`/`show`/`validate`)
|
|
55
|
+
* works on any root; `evaluateGatesFromFiles` refuses untrusted roots.
|
|
56
|
+
*/
|
|
57
|
+
export function buildGatesRegistry(options = {}) {
|
|
58
|
+
const registry = new GateRegistry(createCoreProviderRegistry());
|
|
59
|
+
const loaded = [];
|
|
60
|
+
const installed = installedAiwgRoot();
|
|
61
|
+
const root = rootDir(options);
|
|
62
|
+
const shippedTrust = options.aiwgRoot !== undefined
|
|
63
|
+
&& path.resolve(options.aiwgRoot) !== path.resolve(installed)
|
|
64
|
+
? 'untrusted-override' : 'shipped';
|
|
65
|
+
try {
|
|
66
|
+
loaded.push(...discoverShippedGatePacks(root, registry));
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw new Error(`shipped gate packs rejected: ${error instanceof Error ? error.message : String(error)}`);
|
|
70
|
+
}
|
|
71
|
+
for (const dir of options.packDirs ?? []) {
|
|
72
|
+
const resolved = resolvePath(dir, baseDir(options));
|
|
73
|
+
// A symlinked --pack-dir bundle directory refuses: it could point at the
|
|
74
|
+
// installed tree (or anywhere else) while claiming to be project-local.
|
|
75
|
+
try {
|
|
76
|
+
const status = lstatSync(resolved);
|
|
77
|
+
if (status.isSymbolicLink())
|
|
78
|
+
throw new Error(`pack-dir bundle must not be a symlink: ${resolved}`);
|
|
79
|
+
if (!status.isDirectory() && !status.isFile()) {
|
|
80
|
+
throw new Error(`pack-dir path is not a file or directory: ${resolved}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof Error && /symlink|not a file or directory/.test(error.message))
|
|
85
|
+
throw error;
|
|
86
|
+
throw new Error(`pack-dir path is unavailable: ${resolved}`);
|
|
87
|
+
}
|
|
88
|
+
const lower = resolved.toLowerCase();
|
|
89
|
+
if (lower.endsWith('.gatepack.yaml') || lower.endsWith('.gatepack.yml') || lower.endsWith('.gatepack.json')
|
|
90
|
+
|| lower.endsWith('.json') || lower.endsWith('.yaml') || lower.endsWith('.yml')) {
|
|
91
|
+
const pack = loadGatePackFile(resolved);
|
|
92
|
+
const { namespace } = splitPackId(pack.metadata.id);
|
|
93
|
+
if (namespace !== 'project') {
|
|
94
|
+
throw new Error(`gate pack rejected: ${resolved}: pack ${pack.metadata.id} from --pack-dir`
|
|
95
|
+
+ ` must use the project: namespace (aiwg: is reserved for shipped packs;`
|
|
96
|
+
+ ` addon:/framework:/extension: packs load only from manifest-declared bundles)`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
registry.registerPack(pack, { namespace: 'project' });
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw new Error(`gate pack rejected: ${resolved}: ${error instanceof Error ? error.message : String(error)}`);
|
|
103
|
+
}
|
|
104
|
+
loaded.push(pack.metadata.id);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
loaded.push(...registerBundleGatePacks(registry, resolved, originForPackDir(resolved, { cwd: baseDir(options), aiwgRoot: root })));
|
|
108
|
+
}
|
|
109
|
+
return { registry, loaded, shippedTrust };
|
|
110
|
+
}
|
|
111
|
+
/** Offline validation for one pack/binding/report file. Never throws for invalid content. */
|
|
112
|
+
export async function validateGateFile(kind, filePath, options = {}) {
|
|
113
|
+
const resolved = resolvePath(filePath, baseDir(options));
|
|
114
|
+
let document;
|
|
115
|
+
try {
|
|
116
|
+
document = await readDocument(resolved);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
schema: 'aiwg-gates-validation/v1', kind: kind === 'auto' ? 'pack' : kind, source: 'path',
|
|
121
|
+
valid: false, summary: { id: null, version: null, apiVersion: null },
|
|
122
|
+
errors: [error instanceof Error ? error.message : 'unreadable-file'],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const detected = kind !== 'auto'
|
|
126
|
+
? kind
|
|
127
|
+
: isRecord(document) && document.kind === 'GateBinding' ? 'binding'
|
|
128
|
+
: isRecord(document) && document.kind === 'GateReport' ? 'report' : 'pack';
|
|
129
|
+
const summary = {
|
|
130
|
+
id: isRecord(document) && isRecord(document.metadata) && typeof document.metadata.id === 'string' ? document.metadata.id : null,
|
|
131
|
+
version: isRecord(document) && isRecord(document.metadata) && typeof document.metadata.version === 'string' ? document.metadata.version : null,
|
|
132
|
+
apiVersion: isRecord(document) && typeof document.apiVersion === 'string' ? document.apiVersion : null,
|
|
133
|
+
};
|
|
134
|
+
const errors = [];
|
|
135
|
+
try {
|
|
136
|
+
if (detected === 'pack') {
|
|
137
|
+
const pack = validateGateDocument(document);
|
|
138
|
+
if (pack.kind !== 'GatePack')
|
|
139
|
+
throw new Error(`expected kind GatePack, got '${String(document.kind)}'`);
|
|
140
|
+
const { registry } = buildGatesRegistry(options);
|
|
141
|
+
const providers = registry.providers
|
|
142
|
+
?? createCoreProviderRegistry();
|
|
143
|
+
validateResolvedPack(pack, providers);
|
|
144
|
+
}
|
|
145
|
+
else if (detected === 'binding') {
|
|
146
|
+
const { registry } = buildGatesRegistry(options);
|
|
147
|
+
// Binding validation resolves with the config floors, exactly like
|
|
148
|
+
// evaluation (R3): a binding that loosens project policy is invalid.
|
|
149
|
+
// Floors load failures are validation errors, never throws.
|
|
150
|
+
let floors;
|
|
151
|
+
try {
|
|
152
|
+
floors = await loadProjectFloorsForEvaluate(baseDir(options));
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
throw new Error(`invalid gates section in aiwg.config: ${error instanceof Error ? error.message : String(error)}`);
|
|
156
|
+
}
|
|
157
|
+
registry.resolveBinding(document, floors);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
// Full report validation needs the trusted evaluation context
|
|
161
|
+
// (`validateGateReport` re-derives the report byte-identically).
|
|
162
|
+
// `validate` checks the closed schema, the kind and the self-digest,
|
|
163
|
+
// so rewritten evidence with a stale digest still fails here.
|
|
164
|
+
const report = validateGateDocument(document);
|
|
165
|
+
if (!isRecord(document) || document.kind !== 'GateReport')
|
|
166
|
+
throw new Error('expected kind GateReport');
|
|
167
|
+
const { digest, ...fields } = report;
|
|
168
|
+
if (typeof digest !== 'string' || artifactDigest(fields) !== digest) {
|
|
169
|
+
throw new Error('report digest does not match its content');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
errors.push(error instanceof Error ? error.message : 'validation-failed');
|
|
175
|
+
}
|
|
176
|
+
return { schema: 'aiwg-gates-validation/v1', kind: detected, source: 'path', valid: errors.length === 0, summary, errors };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Load a sealed holdout record from disk. The record must already carry its
|
|
180
|
+
* digest; the seal is re-derived and a missing digest is refused (never
|
|
181
|
+
* auto-sealed: sealing is a trust assertion the file alone cannot make).
|
|
182
|
+
*/
|
|
183
|
+
export async function loadHoldoutFile(filePath, cwd) {
|
|
184
|
+
const resolved = resolvePath(filePath, cwd);
|
|
185
|
+
const document = await readDocument(resolved);
|
|
186
|
+
if (!isRecord(document))
|
|
187
|
+
throw new Error(`holdout file must be an object: ${resolved}`);
|
|
188
|
+
const { frozenDigest, firstAccessedAt, digest } = document;
|
|
189
|
+
if (typeof frozenDigest !== 'string' || !(firstAccessedAt === null || typeof firstAccessedAt === 'string')) {
|
|
190
|
+
throw new Error(`holdout file must carry {frozenDigest, firstAccessedAt}: ${resolved}`);
|
|
191
|
+
}
|
|
192
|
+
if (typeof digest !== 'string') {
|
|
193
|
+
throw new Error(`holdout file must already carry its sealed digest (refusing to auto-seal): ${resolved}`);
|
|
194
|
+
}
|
|
195
|
+
const expected = artifactDigest({ frozenDigest, firstAccessedAt });
|
|
196
|
+
if (expected !== digest)
|
|
197
|
+
throw new Error(`holdout record seal does not match its content: ${resolved}`);
|
|
198
|
+
return { frozenDigest: frozenDigest, firstAccessedAt: firstAccessedAt, digest: digest };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Load a sealed upstream record from disk. The file must be a sealed
|
|
202
|
+
* `{metadata, digest}` record whose digest is re-derived; raw upstream
|
|
203
|
+
* metadata is refused (never auto-sealed).
|
|
204
|
+
*/
|
|
205
|
+
export async function loadUpstreamFile(filePath, cwd) {
|
|
206
|
+
const resolved = resolvePath(filePath, cwd);
|
|
207
|
+
const document = await readDocument(resolved);
|
|
208
|
+
if (!isRecord(document))
|
|
209
|
+
throw new Error(`upstream file must be an object: ${resolved}`);
|
|
210
|
+
if (!isRecord(document.metadata) || typeof document.digest !== 'string') {
|
|
211
|
+
throw new Error(`upstream file must be a sealed record {metadata, digest} (refusing to auto-seal): ${resolved}`);
|
|
212
|
+
}
|
|
213
|
+
const expected = artifactDigest(document.metadata);
|
|
214
|
+
if (expected !== document.digest)
|
|
215
|
+
throw new Error(`upstream integrity digest does not match its report: ${resolved}`);
|
|
216
|
+
return { metadata: document.metadata, digest: document.digest };
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Load the trusted project floors for one CLI evaluation from
|
|
220
|
+
* `<cwd>/.aiwg/aiwg.config`. A missing config resolves to the operator
|
|
221
|
+
* defaults; an unreadable file or an invalid `gates` section refuses
|
|
222
|
+
* evaluation (fail closed). `readAiwgConfig` itself only warns on gates
|
|
223
|
+
* errors (so unrelated commands keep working); this path re-validates
|
|
224
|
+
* strictly because it feeds the evaluator.
|
|
225
|
+
*/
|
|
226
|
+
export async function loadProjectFloorsForEvaluate(cwd) {
|
|
227
|
+
const config = await readAiwgConfig(cwd);
|
|
228
|
+
const errors = validateGatesConfig(config?.gates);
|
|
229
|
+
if (errors.length > 0) {
|
|
230
|
+
throw new Error(`refusing evaluation: invalid gates section in aiwg.config:\n${errors.join('\n')}`);
|
|
231
|
+
}
|
|
232
|
+
return resolveProjectFloors(config?.gates);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Offline evaluation from files. Packs resolve through the offline registry;
|
|
236
|
+
* digests, pins and seals are re-derived inside the evaluator. The report is
|
|
237
|
+
* labeled `attestation: 'offline-cli'` so CLI-produced reports are
|
|
238
|
+
* distinguishable from reports built over a verified trust ceremony.
|
|
239
|
+
* Returns the report object (the CLI prints it as JSON).
|
|
240
|
+
*/
|
|
241
|
+
export async function evaluateGatesFromFiles(files, options = {}) {
|
|
242
|
+
const cwd = baseDir(options);
|
|
243
|
+
const { registry, shippedTrust } = buildGatesRegistry(options);
|
|
244
|
+
if (shippedTrust !== 'shipped') {
|
|
245
|
+
throw new Error('refusing evaluation: packs from an --aiwg-root override are untrusted'
|
|
246
|
+
+ ' and cannot be used by evaluate (list/show/validate remain available for inspection)');
|
|
247
|
+
}
|
|
248
|
+
const floors = await loadProjectFloorsForEvaluate(cwd);
|
|
249
|
+
const binding = await readDocument(resolvePath(files.bindingPath, cwd));
|
|
250
|
+
const metrics = await readDocument(resolvePath(files.metricsPath, cwd));
|
|
251
|
+
const holdout = await loadHoldoutFile(files.holdoutPath, cwd);
|
|
252
|
+
const upstream = files.upstreamPath ? await loadUpstreamFile(files.upstreamPath, cwd) : null;
|
|
253
|
+
return evaluateGates({
|
|
254
|
+
binding, registry, trustedBindingDigest: files.trustedBindingDigest,
|
|
255
|
+
holdout, metrics, upstream, now: files.now, floors, attestation: 'offline-cli',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/** List registered packs, optionally filtered by namespace. */
|
|
259
|
+
export function listGatePacks(options = {}) {
|
|
260
|
+
const { registry } = buildGatesRegistry(options);
|
|
261
|
+
const entries = [...registry.packs.entries()];
|
|
262
|
+
const summaries = entries.map(([id, entry]) => {
|
|
263
|
+
let resolvedDigest = entry.digest;
|
|
264
|
+
try {
|
|
265
|
+
resolvedDigest = String(artifactDigest(registry.resolvePack(id).resolved));
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
resolvedDigest = entry.digest;
|
|
269
|
+
}
|
|
270
|
+
return {
|
|
271
|
+
id, version: entry.authored.metadata.version, description: entry.authored.metadata.description,
|
|
272
|
+
digest: entry.digest, resolvedDigest, namespace: entry.namespace, gates: entry.authored.spec.gates.length,
|
|
273
|
+
};
|
|
274
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
275
|
+
if (options.namespace)
|
|
276
|
+
return summaries.filter(entry => entry.namespace === options.namespace);
|
|
277
|
+
return summaries;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Packs referenced by one rule's `enforcedBy` frontmatter (#2830 stub; full
|
|
281
|
+
* coverage is #2839). Scans bundled and project-local `rules/*.md` for an
|
|
282
|
+
* `enforcedBy: [aiwg:<bundle>/<pack>#<gate>]` list naming the rule. Returns
|
|
283
|
+
* the referenced pack ids (without gate fragments). Empty when no rule
|
|
284
|
+
* declares `enforcedBy` — rules remain guidance until they name a check.
|
|
285
|
+
*/
|
|
286
|
+
export async function packsReferencedByRule(ruleId, options = {}) {
|
|
287
|
+
const { readdir, readFile: readRuleFile, stat } = await import('node:fs/promises');
|
|
288
|
+
const roots = [];
|
|
289
|
+
const root = rootDir(options);
|
|
290
|
+
for (const sub of ['frameworks', 'addons', 'extensions']) {
|
|
291
|
+
roots.push(path.join(root, 'agentic/code', sub));
|
|
292
|
+
}
|
|
293
|
+
roots.push(path.join(baseDir(options), '.aiwg'));
|
|
294
|
+
const ruleFiles = [];
|
|
295
|
+
const walk = async (dir, depth) => {
|
|
296
|
+
if (depth > 5 || ruleFiles.length > 500)
|
|
297
|
+
return;
|
|
298
|
+
let entries;
|
|
299
|
+
try {
|
|
300
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
301
|
+
}
|
|
302
|
+
catch {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
306
|
+
if (ruleFiles.length > 500)
|
|
307
|
+
return;
|
|
308
|
+
const full = path.join(dir, entry.name);
|
|
309
|
+
if (entry.isDirectory()) {
|
|
310
|
+
if (entry.name === 'node_modules' || entry.name.startsWith('.git'))
|
|
311
|
+
continue;
|
|
312
|
+
await walk(full, depth + 1);
|
|
313
|
+
}
|
|
314
|
+
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
315
|
+
if (!full.replace(/\\/g, '/').includes('/rules/'))
|
|
316
|
+
continue;
|
|
317
|
+
ruleFiles.push(full);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
for (const dir of roots)
|
|
322
|
+
await walk(dir, 0);
|
|
323
|
+
const referenced = new Set();
|
|
324
|
+
for (const file of ruleFiles) {
|
|
325
|
+
let content;
|
|
326
|
+
try {
|
|
327
|
+
const st = await stat(file);
|
|
328
|
+
if (st.size > 65_536)
|
|
329
|
+
continue;
|
|
330
|
+
content = await readRuleFile(file, 'utf8');
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
336
|
+
if (!match)
|
|
337
|
+
continue;
|
|
338
|
+
const frontmatter = match[1];
|
|
339
|
+
if (!frontmatter.includes('enforcedBy'))
|
|
340
|
+
continue;
|
|
341
|
+
const ruleName = path.basename(file, '.md');
|
|
342
|
+
const dirName = path.basename(path.dirname(file));
|
|
343
|
+
const names = new Set([ruleName, dirName === 'rules' ? null : `${dirName}/${ruleName}`, file]);
|
|
344
|
+
if (!names.has(ruleId) && ![...names].filter(Boolean).some(name => ruleId.endsWith(String(name))))
|
|
345
|
+
continue;
|
|
346
|
+
for (const line of frontmatter.split('\n')) {
|
|
347
|
+
const trimmed = line.trim().replace(/^-\s*/, '');
|
|
348
|
+
const id = trimmed.match(/(aiwg|framework|addon|extension|project):[^\s,'"\]]+/)?.[1]
|
|
349
|
+
? trimmed.match(/((?:aiwg|framework|addon|extension|project):[^\s,'"\]]+)/)?.[1]
|
|
350
|
+
: null;
|
|
351
|
+
if (id)
|
|
352
|
+
referenced.add(id.split('#')[0]);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return [...referenced].sort();
|
|
356
|
+
}
|
|
357
|
+
/** Show one pack by full id, rest-path or short pack name. */
|
|
358
|
+
export function showGatePack(id, options = {}) {
|
|
359
|
+
const { registry } = buildGatesRegistry(options);
|
|
360
|
+
let resolved;
|
|
361
|
+
try {
|
|
362
|
+
resolved = registry.resolvePack(id);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
// Rest-path lookup needs the full `<bundle>/<name>` rest; a short pack
|
|
366
|
+
// name (`integrity-ceiling`) resolves when exactly one pack owns it.
|
|
367
|
+
const needle = id.includes(':') ? id.slice(id.indexOf(':') + 1) : id;
|
|
368
|
+
if (needle.includes('/') || /\s/.test(needle))
|
|
369
|
+
throw error;
|
|
370
|
+
const entries = [...registry.packs.keys()].filter(key => {
|
|
371
|
+
const rest = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key;
|
|
372
|
+
return rest === needle || rest.endsWith(`/${needle}`);
|
|
373
|
+
}).sort();
|
|
374
|
+
if (entries.length !== 1)
|
|
375
|
+
throw error;
|
|
376
|
+
resolved = registry.resolvePack(entries[0]);
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
schema: 'aiwg-gates-show/v1',
|
|
380
|
+
id: resolved.authored.metadata.id,
|
|
381
|
+
version: resolved.authored.metadata.version,
|
|
382
|
+
description: resolved.authored.metadata.description,
|
|
383
|
+
digest: resolved.digest,
|
|
384
|
+
resolvedDigest: resolved.resolvedDigest,
|
|
385
|
+
pack: resolved.resolved,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
//# sourceMappingURL=driver.js.map
|