@aiwg/cli 2026.8.28 → 2026.9.1
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 +41 -0
- package/bin/aiwg.mjs +15 -3
- package/dist/src/api/index.d.ts +3 -0
- package/dist/src/api/index.js +3 -0
- package/dist/src/channel/manager.mjs +2 -2
- package/dist/src/cli/handlers/dataset.js +186 -0
- package/dist/src/cli/handlers/help.js +2 -1
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/init.js +1 -0
- package/dist/src/cli/handlers/output-mode.js +18 -1
- package/dist/src/cli/handlers/run.js +11 -3
- package/dist/src/cli/handlers/schema.js +221 -0
- package/dist/src/cli/handlers/steward.js +11 -1
- package/dist/src/cli/handlers/use.js +46 -9
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/scope-resolver.js +22 -0
- package/dist/src/dataset/adapter-sdk.d.ts +41 -0
- package/dist/src/dataset/adapter-sdk.js +147 -0
- package/dist/src/dataset/adapter-types.d.ts +179 -0
- package/dist/src/dataset/adapter-types.js +2 -0
- package/dist/src/dataset/adapters.d.ts +104 -0
- package/dist/src/dataset/adapters.js +518 -0
- package/dist/src/dataset/conformance-types.d.ts +84 -0
- package/dist/src/dataset/conformance-types.js +3 -0
- package/dist/src/dataset/conformance.d.ts +13 -0
- package/dist/src/dataset/conformance.js +90 -0
- package/dist/src/dataset/contracts.d.ts +17 -0
- package/dist/src/dataset/contracts.js +236 -0
- package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
- package/dist/src/dataset/file-orchestration-repository.js +68 -0
- package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
- package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
- package/dist/src/dataset/index.d.ts +21 -0
- package/dist/src/dataset/index.js +21 -0
- package/dist/src/dataset/ledger-types.d.ts +141 -0
- package/dist/src/dataset/ledger-types.js +2 -0
- package/dist/src/dataset/ledger.d.ts +27 -0
- package/dist/src/dataset/ledger.js +100 -0
- package/dist/src/dataset/local-execution-backend.d.ts +10 -0
- package/dist/src/dataset/local-execution-backend.js +32 -0
- package/dist/src/dataset/orchestration-repository.d.ts +29 -0
- package/dist/src/dataset/orchestration-repository.js +34 -0
- package/dist/src/dataset/orchestration-service.d.ts +56 -0
- package/dist/src/dataset/orchestration-service.js +466 -0
- package/dist/src/dataset/orchestration-types.d.ts +83 -0
- package/dist/src/dataset/orchestration-types.js +2 -0
- package/dist/src/dataset/presentation.d.ts +3 -0
- package/dist/src/dataset/presentation.js +8 -0
- package/dist/src/dataset/projections.d.ts +42 -0
- package/dist/src/dataset/projections.js +192 -0
- package/dist/src/dataset/schema-governance.d.ts +71 -0
- package/dist/src/dataset/schema-governance.js +135 -0
- package/dist/src/dataset/standards-types.d.ts +66 -0
- package/dist/src/dataset/standards-types.js +10 -0
- package/dist/src/dataset/standards.d.ts +13 -0
- package/dist/src/dataset/standards.js +291 -0
- package/dist/src/dataset/types.d.ts +258 -0
- package/dist/src/dataset/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +27 -1
- package/dist/src/installation/manager.mjs +5 -1
- package/dist/src/output-modes/index.js +4 -0
- package/dist/src/output-modes/registry.js +68 -24
- package/dist/src/output-modes/runtime.js +10 -8
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +41 -0
- package/dist/src/providers/provider-definitions.js +72 -0
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/schema/catalog.js +234 -0
- package/dist/src/schema/compatibility.js +42 -0
- package/dist/src/schema/diagnostics.js +36 -0
- package/dist/src/schema/index.js +8 -0
- package/dist/src/schema/policy.js +58 -0
- package/dist/src/schema/resolver.js +76 -0
- package/dist/src/schema/types.js +2 -0
- package/dist/src/schema/validator.js +82 -0
- package/dist/src/storage/backends/fortemi.js +6 -0
- package/dist/src/storage/config.js +18 -4
- package/dist/src/storage/fortemi-qualification.js +106 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/types.js +1 -1
- package/package.json +4 -1
- package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
- package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
- package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
- package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
- package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
- package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
- package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
- package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
- package/schemas/dataset/run-ledger.v1.schema.json +39 -0
- package/schemas/dataset/source-adapter.v1.schema.json +112 -0
- package/tools/agents/deploy-agents.mjs +9 -3
- package/tools/agents/providers/pi.mjs +164 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const SCHEMA_DIAGNOSTIC_CODES = {
|
|
2
|
+
invalidCatalog: 'SCHEMA_CATALOG_INVALID',
|
|
3
|
+
invalidManifest: 'SCHEMA_MANIFEST_INVALID',
|
|
4
|
+
duplicateId: 'SCHEMA_DUPLICATE_ID',
|
|
5
|
+
duplicateNameVersion: 'SCHEMA_DUPLICATE_NAME_VERSION',
|
|
6
|
+
duplicateAuthority: 'SCHEMA_DUPLICATE_AUTHORITY',
|
|
7
|
+
missingPath: 'SCHEMA_PATH_MISSING',
|
|
8
|
+
pathOutsideRoot: 'SCHEMA_PATH_OUTSIDE_ROOT',
|
|
9
|
+
digestMismatch: 'SCHEMA_DIGEST_MISMATCH',
|
|
10
|
+
unresolvedDependency: 'SCHEMA_DEPENDENCY_UNRESOLVED',
|
|
11
|
+
undeclaredCanonical: 'SCHEMA_CANONICAL_UNREGISTERED',
|
|
12
|
+
undeclaredMirror: 'SCHEMA_MIRROR_UNDECLARED',
|
|
13
|
+
expiredException: 'SCHEMA_POLICY_EXCEPTION_EXPIRED',
|
|
14
|
+
};
|
|
15
|
+
export function createDiagnostic(code, message, options = {}) {
|
|
16
|
+
return {
|
|
17
|
+
code,
|
|
18
|
+
severity: options.severity ?? 'error',
|
|
19
|
+
message,
|
|
20
|
+
...(options.artifactId === undefined ? {} : { artifactId: options.artifactId }),
|
|
21
|
+
...(options.path === undefined ? {} : { path: options.path }),
|
|
22
|
+
...(options.details === undefined ? {} : { details: options.details }),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function hasSchemaErrors(diagnostics) {
|
|
26
|
+
return diagnostics.some((diagnostic) => diagnostic.severity === 'error');
|
|
27
|
+
}
|
|
28
|
+
export class SchemaCatalogError extends Error {
|
|
29
|
+
diagnostics;
|
|
30
|
+
constructor(message, diagnostics) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'SchemaCatalogError';
|
|
33
|
+
this.diagnostics = diagnostics;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=diagnostics.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './diagnostics.js';
|
|
3
|
+
export * from './policy.js';
|
|
4
|
+
export * from './catalog.js';
|
|
5
|
+
export * from './resolver.js';
|
|
6
|
+
export * from './validator.js';
|
|
7
|
+
export * from './compatibility.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createDiagnostic, SCHEMA_DIAGNOSTIC_CODES } from './diagnostics.js';
|
|
2
|
+
export const DEFAULT_SCHEMA_POLICY = {
|
|
3
|
+
strict: true,
|
|
4
|
+
remoteReferences: 'deny',
|
|
5
|
+
compatibility: 'unknown',
|
|
6
|
+
requireFixtures: false,
|
|
7
|
+
requireDigest: false,
|
|
8
|
+
exceptions: [],
|
|
9
|
+
};
|
|
10
|
+
export function resolveEffectivePolicy(repositoryPolicy, domain, artifact) {
|
|
11
|
+
const layers = [repositoryPolicy, domain.policy, artifact.policy];
|
|
12
|
+
const merged = {};
|
|
13
|
+
for (const layer of layers) {
|
|
14
|
+
if (layer === undefined)
|
|
15
|
+
continue;
|
|
16
|
+
if (layer.strict !== undefined)
|
|
17
|
+
merged.strict = layer.strict;
|
|
18
|
+
if (layer.remoteReferences !== undefined)
|
|
19
|
+
merged.remoteReferences = layer.remoteReferences;
|
|
20
|
+
if (layer.compatibility !== undefined)
|
|
21
|
+
merged.compatibility = layer.compatibility;
|
|
22
|
+
if (layer.requireFixtures !== undefined)
|
|
23
|
+
merged.requireFixtures = layer.requireFixtures;
|
|
24
|
+
if (layer.requireDigest !== undefined)
|
|
25
|
+
merged.requireDigest = layer.requireDigest;
|
|
26
|
+
if (layer.exceptions !== undefined)
|
|
27
|
+
merged.exceptions = [...(merged.exceptions ?? []), ...layer.exceptions];
|
|
28
|
+
}
|
|
29
|
+
if (artifact.compatibility?.mode !== undefined && artifact.policy?.compatibility === undefined) {
|
|
30
|
+
merged.compatibility = artifact.compatibility.mode;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
strict: merged.strict ?? DEFAULT_SCHEMA_POLICY.strict,
|
|
34
|
+
remoteReferences: merged.remoteReferences ?? DEFAULT_SCHEMA_POLICY.remoteReferences,
|
|
35
|
+
compatibility: merged.compatibility ?? DEFAULT_SCHEMA_POLICY.compatibility,
|
|
36
|
+
requireFixtures: merged.requireFixtures ?? DEFAULT_SCHEMA_POLICY.requireFixtures,
|
|
37
|
+
requireDigest: merged.requireDigest ?? DEFAULT_SCHEMA_POLICY.requireDigest,
|
|
38
|
+
exceptions: [...(merged.exceptions ?? [])].sort((left, right) => `${left.rule}\0${left.expires}`.localeCompare(`${right.rule}\0${right.expires}`)),
|
|
39
|
+
provenance: [
|
|
40
|
+
...(repositoryPolicy === undefined ? [] : [{ source: 'repository', value: repositoryPolicy }]),
|
|
41
|
+
...(domain.policy === undefined
|
|
42
|
+
? []
|
|
43
|
+
: [{ source: 'domain', domain: domain.domain, value: domain.policy }]),
|
|
44
|
+
...(artifact.policy === undefined ? [] : [{ source: 'artifact', value: artifact.policy }]),
|
|
45
|
+
],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function diagnosePolicy(policy, artifactId, now = new Date()) {
|
|
49
|
+
const diagnostics = [];
|
|
50
|
+
for (const exception of policy.exceptions) {
|
|
51
|
+
const expires = new Date(exception.expires);
|
|
52
|
+
if (Number.isNaN(expires.getTime()) || expires.getTime() <= now.getTime()) {
|
|
53
|
+
diagnostics.push(createDiagnostic(SCHEMA_DIAGNOSTIC_CODES.expiredException, `Policy exception ${exception.rule} expired on ${exception.expires}`, { artifactId, details: { rule: exception.rule, owner: exception.owner, expires: exception.expires } }));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return diagnostics;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { relative, resolve, sep } from 'node:path';
|
|
2
|
+
export class SchemaResolver {
|
|
3
|
+
catalog;
|
|
4
|
+
rootDir;
|
|
5
|
+
index = new Map();
|
|
6
|
+
constructor(catalog, options = {}) {
|
|
7
|
+
this.catalog = catalog;
|
|
8
|
+
this.rootDir = options.rootDir === undefined ? undefined : resolve(options.rootDir);
|
|
9
|
+
for (const entry of catalog.entries) {
|
|
10
|
+
this.index.set(entry.artifact.id, entry);
|
|
11
|
+
this.index.set(`${entry.artifact.logicalName}@${entry.artifact.version}`, entry);
|
|
12
|
+
// The unqualified logical name resolves to the last deterministically
|
|
13
|
+
// ordered revision. Callers requiring reproducibility should use id or
|
|
14
|
+
// name@version.
|
|
15
|
+
this.index.set(entry.artifact.logicalName, entry);
|
|
16
|
+
for (const alias of entry.artifact.aliases ?? [])
|
|
17
|
+
this.index.set(alias, entry);
|
|
18
|
+
if (entry.artifact.authority.path) {
|
|
19
|
+
const normalized = entry.artifact.authority.path.split('\\').join('/');
|
|
20
|
+
this.index.set(normalized, entry);
|
|
21
|
+
if (this.rootDir)
|
|
22
|
+
this.index.set(relative(this.rootDir, resolve(this.rootDir, normalized)).split(sep).join('/'), entry);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
list(options = {}) {
|
|
27
|
+
return this.catalog.entries.filter((entry) => (options.domain === undefined || entry.domain === options.domain) &&
|
|
28
|
+
(options.lifecycle === undefined || entry.artifact.lifecycle === options.lifecycle) &&
|
|
29
|
+
(options.format === undefined || entry.artifact.format === options.format));
|
|
30
|
+
}
|
|
31
|
+
resolve(query) {
|
|
32
|
+
return this.index.get(query) ?? this.index.get(query.split('\\').join('/'));
|
|
33
|
+
}
|
|
34
|
+
require(query) {
|
|
35
|
+
const entry = this.resolve(query);
|
|
36
|
+
if (!entry)
|
|
37
|
+
throw new Error(`Unknown schema: ${query}`);
|
|
38
|
+
return entry;
|
|
39
|
+
}
|
|
40
|
+
policy(query) {
|
|
41
|
+
return this.require(query).effectivePolicy;
|
|
42
|
+
}
|
|
43
|
+
graph(query, options = {}) {
|
|
44
|
+
const direction = options.direction ?? 'both';
|
|
45
|
+
const allEdges = [];
|
|
46
|
+
for (const entry of this.catalog.entries) {
|
|
47
|
+
for (const dependency of entry.artifact.dependencies ?? [])
|
|
48
|
+
allEdges.push({ from: entry.artifact.id, to: dependency.id, kind: 'depends-on' });
|
|
49
|
+
for (const superseded of entry.artifact.supersedes ?? [])
|
|
50
|
+
allEdges.push({ from: entry.artifact.id, to: superseded, kind: 'supersedes' });
|
|
51
|
+
}
|
|
52
|
+
const selected = query === undefined ? new Set(this.catalog.entries.map((entry) => entry.artifact.id)) : this.collectConnected(this.require(query).artifact.id, allEdges, direction);
|
|
53
|
+
return {
|
|
54
|
+
nodes: this.catalog.entries.filter((entry) => selected.has(entry.artifact.id)).map((entry) => ({ id: entry.artifact.id, logicalName: entry.artifact.logicalName, version: entry.artifact.version, domain: entry.domain })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
55
|
+
edges: allEdges.filter((edge) => selected.has(edge.from) && selected.has(edge.to)).sort((a, b) => `${a.from}\0${a.to}\0${a.kind}`.localeCompare(`${b.from}\0${b.to}\0${b.kind}`)),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
collectConnected(seed, edges, direction) {
|
|
59
|
+
const result = new Set([seed]);
|
|
60
|
+
const queue = [seed];
|
|
61
|
+
while (queue.length) {
|
|
62
|
+
const current = queue.shift();
|
|
63
|
+
if (!current)
|
|
64
|
+
continue;
|
|
65
|
+
for (const edge of edges) {
|
|
66
|
+
const next = edge.from === current && direction !== 'dependents' ? edge.to : edge.to === current && direction !== 'dependencies' ? edge.from : undefined;
|
|
67
|
+
if (next && !result.has(next)) {
|
|
68
|
+
result.add(next);
|
|
69
|
+
queue.push(next);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=resolver.js.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import Ajv from 'ajv';
|
|
4
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
5
|
+
import addFormats from 'ajv-formats';
|
|
6
|
+
function depth(value, seen = new Set()) {
|
|
7
|
+
if (typeof value !== 'object' || value === null)
|
|
8
|
+
return 0;
|
|
9
|
+
if (seen.has(value))
|
|
10
|
+
return 0;
|
|
11
|
+
seen.add(value);
|
|
12
|
+
const children = Array.isArray(value) ? value : Object.values(value);
|
|
13
|
+
return children.length === 0 ? 1 : 1 + Math.max(...children.map(item => depth(item, seen)));
|
|
14
|
+
}
|
|
15
|
+
function errorDiagnostic(artifactId, error) {
|
|
16
|
+
return {
|
|
17
|
+
code: 'SCHEMA_INSTANCE_INVALID',
|
|
18
|
+
severity: 'error',
|
|
19
|
+
message: error.message ?? 'validation failed',
|
|
20
|
+
artifactId,
|
|
21
|
+
path: error.instancePath,
|
|
22
|
+
details: { schemaPath: error.schemaPath, keyword: error.keyword, params: error.params },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** Offline, catalog-backed validator with bounded inputs and compiled-schema caching. */
|
|
26
|
+
export class SchemaValidator {
|
|
27
|
+
resolver;
|
|
28
|
+
options;
|
|
29
|
+
cache = new Map();
|
|
30
|
+
constructor(resolver, options) {
|
|
31
|
+
this.resolver = resolver;
|
|
32
|
+
this.options = options;
|
|
33
|
+
}
|
|
34
|
+
compile(query) {
|
|
35
|
+
const entry = this.resolver.require(query);
|
|
36
|
+
if (entry.artifact.format !== 'json-schema') {
|
|
37
|
+
throw new Error(`Validation requires a JSON Schema artifact; got ${entry.artifact.format}`);
|
|
38
|
+
}
|
|
39
|
+
const key = `${entry.artifact.id}\0${entry.digest ?? entry.artifact.version}`;
|
|
40
|
+
const cached = this.cache.get(key);
|
|
41
|
+
if (cached)
|
|
42
|
+
return cached;
|
|
43
|
+
const draft2020 = entry.artifact.dialect?.includes('2020-12') ?? false;
|
|
44
|
+
const ajv = draft2020
|
|
45
|
+
? new Ajv2020({ strict: entry.effectivePolicy.strict, allErrors: true, loadSchema: undefined })
|
|
46
|
+
: new Ajv({ strict: entry.effectivePolicy.strict, allErrors: true, loadSchema: undefined });
|
|
47
|
+
addFormats(ajv);
|
|
48
|
+
for (const candidate of this.resolver.list())
|
|
49
|
+
this.addCompatibleSchema(ajv, candidate, draft2020);
|
|
50
|
+
const compiled = ajv.getSchema(entry.artifact.id);
|
|
51
|
+
if (!compiled)
|
|
52
|
+
throw new Error(`Could not compile schema ${entry.artifact.id}`);
|
|
53
|
+
this.cache.set(key, compiled);
|
|
54
|
+
return compiled;
|
|
55
|
+
}
|
|
56
|
+
validate(query, instance) {
|
|
57
|
+
const entry = this.resolver.require(query);
|
|
58
|
+
const diagnostics = [];
|
|
59
|
+
const serialized = JSON.stringify(instance);
|
|
60
|
+
const maxBytes = this.options.limits?.maxBytes ?? 10 * 1024 * 1024;
|
|
61
|
+
const maxDepth = this.options.limits?.maxDepth ?? 128;
|
|
62
|
+
if (Buffer.byteLength(serialized) > maxBytes)
|
|
63
|
+
diagnostics.push({ code: 'SCHEMA_RESOURCE_LIMIT', severity: 'error', message: `Instance exceeds ${maxBytes} byte limit`, artifactId: entry.artifact.id });
|
|
64
|
+
if (depth(instance) > maxDepth)
|
|
65
|
+
diagnostics.push({ code: 'SCHEMA_RESOURCE_LIMIT', severity: 'error', message: `Instance exceeds depth limit ${maxDepth}`, artifactId: entry.artifact.id });
|
|
66
|
+
if (diagnostics.length)
|
|
67
|
+
return { valid: false, artifactId: entry.artifact.id, diagnostics };
|
|
68
|
+
const check = this.compile(query);
|
|
69
|
+
const valid = check(instance);
|
|
70
|
+
return { valid, artifactId: entry.artifact.id, diagnostics: (check.errors ?? []).map(error => errorDiagnostic(entry.artifact.id, error)) };
|
|
71
|
+
}
|
|
72
|
+
addCompatibleSchema(ajv, entry, draft2020) {
|
|
73
|
+
if (entry.artifact.format !== 'json-schema' || !entry.artifact.authority.path)
|
|
74
|
+
return;
|
|
75
|
+
if ((entry.artifact.dialect?.includes('2020-12') ?? false) !== draft2020)
|
|
76
|
+
return;
|
|
77
|
+
const path = resolve(this.options.rootDir, entry.artifact.authority.path);
|
|
78
|
+
const schema = JSON.parse(readFileSync(path, 'utf8'));
|
|
79
|
+
ajv.addSchema(schema, entry.artifact.id);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -301,6 +301,12 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
|
|
|
301
301
|
async callTool(name, args) {
|
|
302
302
|
return unwrapMcpToolResult(await client.callTool({ name, arguments: args }));
|
|
303
303
|
},
|
|
304
|
+
async listTools() {
|
|
305
|
+
return client.listTools();
|
|
306
|
+
},
|
|
307
|
+
serverVersion() {
|
|
308
|
+
return client.getServerVersion();
|
|
309
|
+
},
|
|
304
310
|
async close() {
|
|
305
311
|
await client.close();
|
|
306
312
|
},
|
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
* v1 schema, and returns a typed `StorageConfig`. Absence of the file is
|
|
6
6
|
* a no-op: every subsystem defaults to `fs` rooted under `.aiwg/`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* canonical for editor tooling and external consumers.
|
|
8
|
+
* Runtime validation is kept in parity with the canonical JSON Schema at
|
|
9
|
+
* `schemas/storage/storage.config.v1.schema.json`; the lightweight path here
|
|
10
|
+
* avoids loading the full catalog during storage bootstrap.
|
|
12
11
|
*
|
|
13
12
|
* @design @.aiwg/architecture/storage-design.md
|
|
14
13
|
* @issue #934
|
|
@@ -98,6 +97,7 @@ export function validateStorageConfig(parsed, source = '<input>') {
|
|
|
98
97
|
`(expected "1"). Update the AIWG CLI to read newer config versions.`);
|
|
99
98
|
}
|
|
100
99
|
walkRejectingCredentials(obj, source, 'storage');
|
|
100
|
+
rejectUnknownKeys(obj, ['version', 'roots', 'backends', 'fallback'], source, 'storage');
|
|
101
101
|
const roots = validateRoots(obj['roots'], source);
|
|
102
102
|
const backends = validateBackends(obj['backends'], source);
|
|
103
103
|
let fallback;
|
|
@@ -164,19 +164,24 @@ function validateBackendConfig(raw, source) {
|
|
|
164
164
|
// — the adapter is responsible for any further validation it needs.
|
|
165
165
|
switch (type) {
|
|
166
166
|
case 'fs':
|
|
167
|
+
rejectUnknownKeys(obj, ['type'], source, 'backend');
|
|
167
168
|
return { type: 'fs' };
|
|
168
169
|
case 'obsidian':
|
|
170
|
+
rejectUnknownKeys(obj, ['type', 'vault', 'folder', 'useCli'], source, 'backend');
|
|
169
171
|
requireString(obj, 'vault', source);
|
|
170
172
|
return obj;
|
|
171
173
|
case 'logseq':
|
|
174
|
+
rejectUnknownKeys(obj, ['type', 'graph', 'apiUrl', 'useApi'], source, 'backend');
|
|
172
175
|
requireString(obj, 'graph', source);
|
|
173
176
|
return obj;
|
|
174
177
|
case 'notion': {
|
|
178
|
+
rejectUnknownKeys(obj, ['type', 'parent', 'externalIdProperty'], source, 'backend');
|
|
175
179
|
const parent = obj['parent'];
|
|
176
180
|
if (typeof parent !== 'object' || parent === null) {
|
|
177
181
|
throw new Error(`${source}.parent must be an object with pageId or databaseId`);
|
|
178
182
|
}
|
|
179
183
|
const p = parent;
|
|
184
|
+
rejectUnknownKeys(p, ['pageId', 'databaseId'], source, 'parent');
|
|
180
185
|
const hasPage = typeof p['pageId'] === 'string' && p['pageId'].length > 0;
|
|
181
186
|
const hasDb = typeof p['databaseId'] === 'string' && p['databaseId'].length > 0;
|
|
182
187
|
if (hasPage === hasDb) {
|
|
@@ -185,21 +190,30 @@ function validateBackendConfig(raw, source) {
|
|
|
185
190
|
return obj;
|
|
186
191
|
}
|
|
187
192
|
case 'anythingllm':
|
|
193
|
+
rejectUnknownKeys(obj, ['type', 'baseUrl', 'workspace', 'folder'], source, 'backend');
|
|
188
194
|
requireString(obj, 'baseUrl', source);
|
|
189
195
|
requireString(obj, 'workspace', source);
|
|
190
196
|
return obj;
|
|
191
197
|
case 'fortemi':
|
|
198
|
+
rejectUnknownKeys(obj, ['type', 'mcpServer', 'scheme'], source, 'backend');
|
|
192
199
|
return obj;
|
|
193
200
|
case 's3':
|
|
201
|
+
rejectUnknownKeys(obj, ['type', 'bucket', 'prefix', 'region', 'endpoint'], source, 'backend');
|
|
194
202
|
requireString(obj, 'bucket', source);
|
|
195
203
|
return obj;
|
|
196
204
|
case 'webdav':
|
|
205
|
+
rejectUnknownKeys(obj, ['type', 'url', 'basePath', 'authMode'], source, 'backend');
|
|
197
206
|
requireString(obj, 'url', source);
|
|
198
207
|
return obj;
|
|
199
208
|
default:
|
|
200
209
|
throw new Error(`${source}: unhandled backend type ${type}`);
|
|
201
210
|
}
|
|
202
211
|
}
|
|
212
|
+
function rejectUnknownKeys(obj, allowed, source, label) {
|
|
213
|
+
const unknown = Object.keys(obj).find(key => !allowed.includes(key));
|
|
214
|
+
if (unknown)
|
|
215
|
+
throw new Error(`${source}: ${label}.${unknown} is not a supported property`);
|
|
216
|
+
}
|
|
203
217
|
function requireString(obj, key, source) {
|
|
204
218
|
const v = obj[key];
|
|
205
219
|
if (typeof v !== 'string' || v.length === 0) {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { FortemiAdapter } from "./backends/fortemi.js";
|
|
3
|
+
export const FORTEMI_QUALIFICATION_VERSION = "aiwg.fortemi-live-qualification/v1";
|
|
4
|
+
const EXPECTED = {
|
|
5
|
+
read: { tool: "get_note", required: ["note_id"], properties: ["note_id"] },
|
|
6
|
+
write: {
|
|
7
|
+
tool: "capture_knowledge",
|
|
8
|
+
required: ["note_id", "content"],
|
|
9
|
+
properties: ["note_id", "content"],
|
|
10
|
+
},
|
|
11
|
+
update: {
|
|
12
|
+
tool: "update_note",
|
|
13
|
+
required: ["note_id"],
|
|
14
|
+
properties: ["note_id", "content", "archived"],
|
|
15
|
+
},
|
|
16
|
+
list: { tool: "list_notes", required: [], properties: ["id_prefix"] },
|
|
17
|
+
query: {
|
|
18
|
+
tool: "search",
|
|
19
|
+
required: ["query"],
|
|
20
|
+
properties: ["query", "id_prefix"],
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
function bounded(work, timeoutMs, label) {
|
|
24
|
+
let timer;
|
|
25
|
+
return Promise.race([
|
|
26
|
+
work,
|
|
27
|
+
new Promise((_, reject) => {
|
|
28
|
+
timer = setTimeout(() => reject(new Error(`FORTEMI_LIVE_TIMEOUT: ${label} exceeded ${timeoutMs}ms`)), timeoutMs);
|
|
29
|
+
}),
|
|
30
|
+
]).finally(() => clearTimeout(timer));
|
|
31
|
+
}
|
|
32
|
+
/** Schema-first live qualification. No tool operation runs when preflight detects drift. */
|
|
33
|
+
export async function qualifyLiveFortemi(client, options = {}) {
|
|
34
|
+
const timeoutMs = Math.max(250, Math.min(options.timeoutMs ?? 5_000, 30_000));
|
|
35
|
+
const namespace = `aiwg-qualification-${randomUUID()}`;
|
|
36
|
+
const report = {
|
|
37
|
+
schema: FORTEMI_QUALIFICATION_VERSION,
|
|
38
|
+
compatible: false,
|
|
39
|
+
mutationAttempted: false,
|
|
40
|
+
server: {
|
|
41
|
+
...(client.serverVersion?.() ?? {}),
|
|
42
|
+
...(options.contractRevision
|
|
43
|
+
? { contractRevision: options.contractRevision }
|
|
44
|
+
: {}),
|
|
45
|
+
},
|
|
46
|
+
namespace,
|
|
47
|
+
operations: [],
|
|
48
|
+
};
|
|
49
|
+
try {
|
|
50
|
+
if (!client.listTools)
|
|
51
|
+
throw new Error("FORTEMI_TOOL_DISCOVERY_UNAVAILABLE");
|
|
52
|
+
const discovered = await bounded(client.listTools(), timeoutMs, "tools/list");
|
|
53
|
+
const tools = new Map((discovered.tools ?? []).map((tool) => [tool.name, tool]));
|
|
54
|
+
for (const [operation, expected] of Object.entries(EXPECTED)) {
|
|
55
|
+
const tool = tools.get(expected.tool);
|
|
56
|
+
const schema = tool?.inputSchema;
|
|
57
|
+
const properties = schema?.properties && typeof schema.properties === "object"
|
|
58
|
+
? schema.properties
|
|
59
|
+
: {};
|
|
60
|
+
const required = Array.isArray(schema?.required)
|
|
61
|
+
? schema.required.filter((value) => typeof value === "string")
|
|
62
|
+
: [];
|
|
63
|
+
const missing = expected.properties.filter((name) => !(name in properties));
|
|
64
|
+
const missingRequired = expected.required.filter((name) => !required.includes(name));
|
|
65
|
+
const compatible = Boolean(tool && schema) &&
|
|
66
|
+
missing.length === 0 &&
|
|
67
|
+
missingRequired.length === 0;
|
|
68
|
+
report.operations.push({
|
|
69
|
+
operation,
|
|
70
|
+
tool: expected.tool,
|
|
71
|
+
compatible,
|
|
72
|
+
code: compatible
|
|
73
|
+
? "FORTEMI_TOOL_SCHEMA_COMPATIBLE"
|
|
74
|
+
: tool
|
|
75
|
+
? "FORTEMI_TOOL_SCHEMA_DRIFT"
|
|
76
|
+
: "FORTEMI_TOOL_MISSING",
|
|
77
|
+
detail: compatible
|
|
78
|
+
? "expected adapter arguments are accepted"
|
|
79
|
+
: `missing properties: ${missing.join(", ") || "none"}; not required: ${missingRequired.join(", ") || "none"}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
report.compatible = report.operations.every((item) => item.compatible);
|
|
83
|
+
if (!report.compatible)
|
|
84
|
+
return report;
|
|
85
|
+
const adapter = new FortemiAdapter({
|
|
86
|
+
subsystem: namespace,
|
|
87
|
+
config: { type: "fortemi", mcpServer: "live-qualification" },
|
|
88
|
+
clientFactory: async () => client,
|
|
89
|
+
});
|
|
90
|
+
await bounded(adapter.init(), timeoutMs, "adapter init");
|
|
91
|
+
await bounded(adapter.read(randomUUID()), timeoutMs, "adapter read");
|
|
92
|
+
await bounded(adapter.list(""), timeoutMs, "adapter list");
|
|
93
|
+
await bounded(adapter.query(`aiwg qualification ${namespace}`), timeoutMs, "adapter query");
|
|
94
|
+
if (options.allowMutation) {
|
|
95
|
+
report.mutationAttempted = true;
|
|
96
|
+
await bounded(adapter.write(randomUUID(), `AIWG live qualification ${namespace}`, {
|
|
97
|
+
contentType: "text/plain",
|
|
98
|
+
}), timeoutMs, "adapter write");
|
|
99
|
+
}
|
|
100
|
+
return report;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
await client.close?.();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=fortemi-qualification.js.map
|
|
@@ -21,6 +21,7 @@ export { FilesystemAdapter } from './backends/fs.js';
|
|
|
21
21
|
export { ObsidianAdapter } from './backends/obsidian.js';
|
|
22
22
|
export { LogseqAdapter } from './backends/logseq.js';
|
|
23
23
|
export { FortemiAdapter } from './backends/fortemi.js';
|
|
24
|
+
export { qualifyLiveFortemi, FORTEMI_QUALIFICATION_VERSION, } from './fortemi-qualification.js';
|
|
24
25
|
export { STORAGE_BACKEND_CONTRACT, STORAGE_BACKEND_MATRIX, StorageCapabilityError, negotiateStorageCapabilities, } from './backend-contract.js';
|
|
25
26
|
export { STORAGE_MIGRATION_PROTOCOL, MigrationProtocolError, StorageMigrationCoordinator, approvalDigest, digestRecordChunks, digestRecords, validateManifest, } from './migration-protocol.js';
|
|
26
27
|
export { PostgresStorageBackend, PostgresBackendError, } from './backends/postgres.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwg/cli",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.9.1",
|
|
4
4
|
"description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,12 +28,14 @@
|
|
|
28
28
|
"bin/",
|
|
29
29
|
"agentic/code/providers/",
|
|
30
30
|
"schemas/security/",
|
|
31
|
+
"schemas/dataset/",
|
|
31
32
|
"dist/src/",
|
|
32
33
|
"!dist/**/*.d.ts",
|
|
33
34
|
"!dist/**/*.map",
|
|
34
35
|
"dist/src/api/index.d.ts",
|
|
35
36
|
"dist/src/resources/index.d.ts",
|
|
36
37
|
"dist/src/resources/web-release.d.ts",
|
|
38
|
+
"dist/src/dataset/**/*.d.ts",
|
|
37
39
|
"tools/_resolve-impl.mjs",
|
|
38
40
|
"tools/agents/deploy-agents.mjs",
|
|
39
41
|
"tools/agents/providers/",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"@fortemi/core": "2026.7.15",
|
|
69
71
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
70
72
|
"ajv": "^8.20.0",
|
|
73
|
+
"ajv-formats": "^3.0.1",
|
|
71
74
|
"chalk": "^4.1.2",
|
|
72
75
|
"chokidar": "^4.0.3",
|
|
73
76
|
"commander": "^12.1.0",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aiwg.io/schemas/dataset/conformance-manifest.v1.schema.json",
|
|
4
|
+
"title": "AIWG Dataset Intelligence Conformance Manifest v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["contract", "schemaVersion", "corpusVersion", "cells"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"contract": { "const": "aiwg.dataset.conformance/v1" },
|
|
10
|
+
"schemaVersion": { "$ref": "#/$defs/version" },
|
|
11
|
+
"corpusVersion": { "$ref": "#/$defs/version" },
|
|
12
|
+
"cells": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cell" } }
|
|
13
|
+
},
|
|
14
|
+
"$defs": {
|
|
15
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" },
|
|
16
|
+
"digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
17
|
+
"cell": {
|
|
18
|
+
"type": "object", "additionalProperties": false,
|
|
19
|
+
"required": ["id", "area", "requiredCapabilities", "sourceClass", "runtimeClass", "fixture", "expected", "maturity", "evidence", "resourceEnvelope"],
|
|
20
|
+
"properties": {
|
|
21
|
+
"id": { "type": "string", "pattern": "^[a-z0-9][a-z0-9.-]+$" },
|
|
22
|
+
"area": { "enum": ["adapter", "capability", "replay", "checkpoint", "provenance", "security", "offline", "parity", "standards", "migration"] },
|
|
23
|
+
"requiredCapabilities": { "type": "array", "items": { "type": "string", "minLength": 1 }, "uniqueItems": true },
|
|
24
|
+
"sourceClass": { "enum": ["synthetic", "file", "directory", "jsonl", "csv", "http", "cache", "fortemi"] },
|
|
25
|
+
"runtimeClass": { "enum": ["local", "fortemi-core", "fortemi-server"] },
|
|
26
|
+
"fixture": { "type": "object", "additionalProperties": false, "required": ["path", "digest", "revision"], "properties": { "path": { "type": "string", "pattern": "^test/fixtures/dataset-intelligence/" }, "digest": { "$ref": "#/$defs/digest" }, "revision": { "$ref": "#/$defs/version" } } },
|
|
27
|
+
"expected": { "type": "object", "additionalProperties": false, "required": ["result"], "properties": { "result": { "enum": ["pass", "reject", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 } } },
|
|
28
|
+
"maturity": { "enum": ["experimental", "qualified", "stable"] },
|
|
29
|
+
"evidence": { "type": "array", "minItems": 1, "items": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "uniqueItems": true },
|
|
30
|
+
"resourceEnvelope": { "type": "object", "additionalProperties": false, "required": ["maxBytes", "maxRecords", "maxDurationMs"], "properties": { "maxBytes": { "type": "integer", "minimum": 1 }, "maxRecords": { "type": "integer", "minimum": 1 }, "maxDurationMs": { "type": "integer", "minimum": 1 } } },
|
|
31
|
+
"liveAuthorizationRequired": { "type": "boolean" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://aiwg.io/schemas/dataset/conformance-receipt.v1.schema.json",
|
|
4
|
+
"title": "AIWG Dataset Intelligence Conformance Receipt v1",
|
|
5
|
+
"type": "object", "additionalProperties": false,
|
|
6
|
+
"required": ["contract", "schemaVersion", "corpusVersion", "manifestDigest", "resultDigest", "bindings", "startedAt", "endedAt", "results", "summary"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"contract": { "const": "aiwg.dataset.conformance/v1" }, "schemaVersion": { "$ref": "#/$defs/version" }, "corpusVersion": { "$ref": "#/$defs/version" },
|
|
9
|
+
"manifestDigest": { "$ref": "#/$defs/digest" }, "resultDigest": { "$ref": "#/$defs/digest" },
|
|
10
|
+
"startedAt": { "$ref": "#/$defs/time" }, "endedAt": { "$ref": "#/$defs/time" },
|
|
11
|
+
"bindings": { "$ref": "#/$defs/bindings" },
|
|
12
|
+
"results": { "type": "array", "items": { "$ref": "#/$defs/result" } },
|
|
13
|
+
"summary": { "type": "object", "additionalProperties": false, "required": ["passed", "failed", "pending", "stableEligible"], "properties": { "passed": { "type": "integer", "minimum": 0 }, "failed": { "type": "integer", "minimum": 0 }, "pending": { "type": "integer", "minimum": 0 }, "stableEligible": { "type": "boolean" } } }
|
|
14
|
+
},
|
|
15
|
+
"$defs": {
|
|
16
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$" }, "digest": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" }, "time": { "type": "string", "format": "date-time", "pattern": "Z$" },
|
|
17
|
+
"digestMap": { "type": "object", "minProperties": 1, "additionalProperties": { "$ref": "#/$defs/digest" } },
|
|
18
|
+
"bindings": { "type": "object", "additionalProperties": false, "required": ["aiwgCommit", "packageDigests", "schemaDigests", "fixtureDigest", "configurationDigest"], "properties": { "aiwgCommit": { "type": "string", "pattern": "^(?:[0-9a-f]{40}|sha256:[0-9a-f]{64})$" }, "fortemiCommit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, "packageDigests": { "$ref": "#/$defs/digestMap" }, "schemaDigests": { "$ref": "#/$defs/digestMap" }, "fixtureDigest": { "$ref": "#/$defs/digest" }, "descriptorDigest": { "$ref": "#/$defs/digest" }, "configurationDigest": { "$ref": "#/$defs/digest" } } },
|
|
19
|
+
"evidence": { "type": "object", "additionalProperties": false, "required": ["kind", "reference", "digest"], "properties": { "kind": { "enum": ["fixture", "real-source", "cross-repo", "live-qualification"] }, "reference": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } },
|
|
20
|
+
"result": { "type": "object", "additionalProperties": false, "required": ["cellId", "status", "evidence", "observed"], "properties": { "cellId": { "type": "string", "minLength": 1 }, "status": { "enum": ["passed", "failed", "pending"] }, "diagnostic": { "type": "string", "minLength": 1 }, "evidence": { "type": "array", "items": { "$ref": "#/$defs/evidence" } }, "observed": { "type": "object", "additionalProperties": false, "required": ["networkAttempts"], "properties": { "records": { "type": "integer", "minimum": 0 }, "bytes": { "type": "integer", "minimum": 0 }, "durationMs": { "type": "integer", "minimum": 0 }, "networkAttempts": { "type": "integer", "minimum": 0 } } } } }
|
|
21
|
+
}
|
|
22
|
+
}
|