@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.
Files changed (94) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +41 -0
  2. package/bin/aiwg.mjs +15 -3
  3. package/dist/src/api/index.d.ts +3 -0
  4. package/dist/src/api/index.js +3 -0
  5. package/dist/src/channel/manager.mjs +2 -2
  6. package/dist/src/cli/handlers/dataset.js +186 -0
  7. package/dist/src/cli/handlers/help.js +2 -1
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/init.js +1 -0
  10. package/dist/src/cli/handlers/output-mode.js +18 -1
  11. package/dist/src/cli/handlers/run.js +11 -3
  12. package/dist/src/cli/handlers/schema.js +221 -0
  13. package/dist/src/cli/handlers/steward.js +11 -1
  14. package/dist/src/cli/handlers/use.js +46 -9
  15. package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
  16. package/dist/src/cli/router.js +1 -1
  17. package/dist/src/cli/scope-resolver.js +22 -0
  18. package/dist/src/dataset/adapter-sdk.d.ts +41 -0
  19. package/dist/src/dataset/adapter-sdk.js +147 -0
  20. package/dist/src/dataset/adapter-types.d.ts +179 -0
  21. package/dist/src/dataset/adapter-types.js +2 -0
  22. package/dist/src/dataset/adapters.d.ts +104 -0
  23. package/dist/src/dataset/adapters.js +518 -0
  24. package/dist/src/dataset/conformance-types.d.ts +84 -0
  25. package/dist/src/dataset/conformance-types.js +3 -0
  26. package/dist/src/dataset/conformance.d.ts +13 -0
  27. package/dist/src/dataset/conformance.js +90 -0
  28. package/dist/src/dataset/contracts.d.ts +17 -0
  29. package/dist/src/dataset/contracts.js +236 -0
  30. package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
  31. package/dist/src/dataset/file-orchestration-repository.js +68 -0
  32. package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
  33. package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
  34. package/dist/src/dataset/index.d.ts +21 -0
  35. package/dist/src/dataset/index.js +21 -0
  36. package/dist/src/dataset/ledger-types.d.ts +141 -0
  37. package/dist/src/dataset/ledger-types.js +2 -0
  38. package/dist/src/dataset/ledger.d.ts +27 -0
  39. package/dist/src/dataset/ledger.js +100 -0
  40. package/dist/src/dataset/local-execution-backend.d.ts +10 -0
  41. package/dist/src/dataset/local-execution-backend.js +32 -0
  42. package/dist/src/dataset/orchestration-repository.d.ts +29 -0
  43. package/dist/src/dataset/orchestration-repository.js +34 -0
  44. package/dist/src/dataset/orchestration-service.d.ts +56 -0
  45. package/dist/src/dataset/orchestration-service.js +466 -0
  46. package/dist/src/dataset/orchestration-types.d.ts +83 -0
  47. package/dist/src/dataset/orchestration-types.js +2 -0
  48. package/dist/src/dataset/presentation.d.ts +3 -0
  49. package/dist/src/dataset/presentation.js +8 -0
  50. package/dist/src/dataset/projections.d.ts +42 -0
  51. package/dist/src/dataset/projections.js +192 -0
  52. package/dist/src/dataset/schema-governance.d.ts +71 -0
  53. package/dist/src/dataset/schema-governance.js +135 -0
  54. package/dist/src/dataset/standards-types.d.ts +66 -0
  55. package/dist/src/dataset/standards-types.js +10 -0
  56. package/dist/src/dataset/standards.d.ts +13 -0
  57. package/dist/src/dataset/standards.js +291 -0
  58. package/dist/src/dataset/types.d.ts +258 -0
  59. package/dist/src/dataset/types.js +2 -0
  60. package/dist/src/extensions/commands/definitions.js +27 -1
  61. package/dist/src/installation/manager.mjs +5 -1
  62. package/dist/src/output-modes/index.js +4 -0
  63. package/dist/src/output-modes/registry.js +68 -24
  64. package/dist/src/output-modes/runtime.js +10 -8
  65. package/dist/src/plugin/skill-command-translator.js +1 -0
  66. package/dist/src/providers/capability-matrix.yaml +41 -0
  67. package/dist/src/providers/provider-definitions.js +72 -0
  68. package/dist/src/providers/provider-inventory.js +1 -0
  69. package/dist/src/schema/catalog.js +234 -0
  70. package/dist/src/schema/compatibility.js +42 -0
  71. package/dist/src/schema/diagnostics.js +36 -0
  72. package/dist/src/schema/index.js +8 -0
  73. package/dist/src/schema/policy.js +58 -0
  74. package/dist/src/schema/resolver.js +76 -0
  75. package/dist/src/schema/types.js +2 -0
  76. package/dist/src/schema/validator.js +82 -0
  77. package/dist/src/storage/backends/fortemi.js +6 -0
  78. package/dist/src/storage/config.js +18 -4
  79. package/dist/src/storage/fortemi-qualification.js +106 -0
  80. package/dist/src/storage/index.js +1 -0
  81. package/dist/src/storage/types.js +1 -1
  82. package/package.json +4 -1
  83. package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
  84. package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
  85. package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
  86. package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
  87. package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
  88. package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
  89. package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
  90. package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
  91. package/schemas/dataset/run-ledger.v1.schema.json +39 -0
  92. package/schemas/dataset/source-adapter.v1.schema.json +112 -0
  93. package/tools/agents/deploy-agents.mjs +9 -3
  94. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.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
- * Validation is hand-rolled rather than schema-validator-driven to avoid
9
- * adding an `ajv` dependency for a single file. The published JSON
10
- * Schema (`.aiwg/architecture/schemas/storage.config.v1.json`) remains
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';
@@ -6,7 +6,7 @@
6
6
  * conforming to `StorageAdapter`.
7
7
  *
8
8
  * @design @.aiwg/architecture/storage-design.md
9
- * @schema @.aiwg/architecture/schemas/storage.config.v1.json
9
+ * @schema @schemas/storage/storage.config.v1.schema.json
10
10
  * @issue #934
11
11
  * @issue #953
12
12
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.8.28",
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
+ }