@aiwg/cli 2026.9.0 → 2026.9.2

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 (107) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +41 -0
  2. package/agentic/code/providers/model-capabilities.v1.json +11 -0
  3. package/agentic/code/providers/model-catalog.v1.json +8 -0
  4. package/agentic/code/providers/pi/aiwg-bridge.ts +26 -0
  5. package/bin/aiwg.mjs +15 -3
  6. package/dist/src/api/index.d.ts +3 -0
  7. package/dist/src/api/index.js +3 -0
  8. package/dist/src/auth/credential-store.js +6 -0
  9. package/dist/src/channel/manager.mjs +2 -2
  10. package/dist/src/cli/handlers/dataset.js +186 -0
  11. package/dist/src/cli/handlers/help.js +2 -1
  12. package/dist/src/cli/handlers/index.js +5 -1
  13. package/dist/src/cli/handlers/init.js +1 -0
  14. package/dist/src/cli/handlers/output-mode.js +18 -1
  15. package/dist/src/cli/handlers/run.js +11 -3
  16. package/dist/src/cli/handlers/schema.js +221 -0
  17. package/dist/src/cli/handlers/sessions.js +30 -12
  18. package/dist/src/cli/handlers/steward.js +11 -1
  19. package/dist/src/cli/handlers/use.js +6 -2
  20. package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
  21. package/dist/src/cli/router.js +1 -1
  22. package/dist/src/cli/scope-resolver.js +22 -0
  23. package/dist/src/dataset/adapter-sdk.d.ts +41 -0
  24. package/dist/src/dataset/adapter-sdk.js +147 -0
  25. package/dist/src/dataset/adapter-types.d.ts +179 -0
  26. package/dist/src/dataset/adapter-types.js +2 -0
  27. package/dist/src/dataset/adapters.d.ts +104 -0
  28. package/dist/src/dataset/adapters.js +518 -0
  29. package/dist/src/dataset/conformance-types.d.ts +84 -0
  30. package/dist/src/dataset/conformance-types.js +3 -0
  31. package/dist/src/dataset/conformance.d.ts +13 -0
  32. package/dist/src/dataset/conformance.js +90 -0
  33. package/dist/src/dataset/contracts.d.ts +17 -0
  34. package/dist/src/dataset/contracts.js +236 -0
  35. package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
  36. package/dist/src/dataset/file-orchestration-repository.js +68 -0
  37. package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
  38. package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
  39. package/dist/src/dataset/index.d.ts +21 -0
  40. package/dist/src/dataset/index.js +21 -0
  41. package/dist/src/dataset/ledger-types.d.ts +141 -0
  42. package/dist/src/dataset/ledger-types.js +2 -0
  43. package/dist/src/dataset/ledger.d.ts +27 -0
  44. package/dist/src/dataset/ledger.js +100 -0
  45. package/dist/src/dataset/local-execution-backend.d.ts +10 -0
  46. package/dist/src/dataset/local-execution-backend.js +32 -0
  47. package/dist/src/dataset/orchestration-repository.d.ts +29 -0
  48. package/dist/src/dataset/orchestration-repository.js +34 -0
  49. package/dist/src/dataset/orchestration-service.d.ts +56 -0
  50. package/dist/src/dataset/orchestration-service.js +466 -0
  51. package/dist/src/dataset/orchestration-types.d.ts +83 -0
  52. package/dist/src/dataset/orchestration-types.js +2 -0
  53. package/dist/src/dataset/presentation.d.ts +3 -0
  54. package/dist/src/dataset/presentation.js +8 -0
  55. package/dist/src/dataset/projections.d.ts +42 -0
  56. package/dist/src/dataset/projections.js +192 -0
  57. package/dist/src/dataset/schema-governance.d.ts +71 -0
  58. package/dist/src/dataset/schema-governance.js +135 -0
  59. package/dist/src/dataset/standards-types.d.ts +66 -0
  60. package/dist/src/dataset/standards-types.js +10 -0
  61. package/dist/src/dataset/standards.d.ts +13 -0
  62. package/dist/src/dataset/standards.js +291 -0
  63. package/dist/src/dataset/types.d.ts +258 -0
  64. package/dist/src/dataset/types.js +2 -0
  65. package/dist/src/extensions/commands/definitions.js +27 -1
  66. package/dist/src/installation/manager.mjs +5 -1
  67. package/dist/src/models/model-capabilities.v1.json +11 -0
  68. package/dist/src/models/model-catalog.v1.json +8 -0
  69. package/dist/src/models/model-discovery.js +32 -0
  70. package/dist/src/models/provider-policy.js +3 -2
  71. package/dist/src/output-modes/index.js +4 -0
  72. package/dist/src/output-modes/registry.js +68 -24
  73. package/dist/src/output-modes/runtime.js +10 -8
  74. package/dist/src/plugin/skill-command-translator.js +1 -0
  75. package/dist/src/providers/capability-matrix.yaml +41 -0
  76. package/dist/src/providers/provider-definitions.js +72 -0
  77. package/dist/src/providers/provider-inventory.js +1 -0
  78. package/dist/src/schema/catalog.js +234 -0
  79. package/dist/src/schema/compatibility.js +42 -0
  80. package/dist/src/schema/diagnostics.js +36 -0
  81. package/dist/src/schema/index.js +8 -0
  82. package/dist/src/schema/policy.js +58 -0
  83. package/dist/src/schema/resolver.js +76 -0
  84. package/dist/src/schema/types.js +2 -0
  85. package/dist/src/schema/validator.js +82 -0
  86. package/dist/src/sessions/adapters/pi.js +141 -0
  87. package/dist/src/sessions/contracts.js +1 -1
  88. package/dist/src/sessions/index.js +1 -0
  89. package/dist/src/sessions/workspace-discovery.js +10 -0
  90. package/dist/src/storage/backends/fortemi.js +6 -0
  91. package/dist/src/storage/config.js +18 -4
  92. package/dist/src/storage/fortemi-qualification.js +106 -0
  93. package/dist/src/storage/index.js +1 -0
  94. package/dist/src/storage/types.js +1 -1
  95. package/package.json +3 -1
  96. package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
  97. package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
  98. package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
  99. package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
  100. package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
  101. package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
  102. package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
  103. package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
  104. package/schemas/dataset/run-ledger.v1.schema.json +39 -0
  105. package/schemas/dataset/source-adapter.v1.schema.json +112 -0
  106. package/tools/agents/deploy-agents.mjs +9 -3
  107. package/tools/agents/providers/pi.mjs +176 -0
@@ -0,0 +1,141 @@
1
+ import { opendir } from 'node:fs/promises';
2
+ import { basename, resolve } from 'node:path';
3
+ import { z } from 'zod';
4
+ import { SessionContractError, } from '../contracts.js';
5
+ import { readBoundedJsonLines, streamBoundedJsonLines } from '../readers.js';
6
+ export const PI_ADAPTER_VERSION = '1.0.0';
7
+ export const PI_SOURCE_SCHEMA_VERSION = '3.0.0';
8
+ const Header = z.object({ type: z.literal('session'), version: z.number().int(), id: z.string().min(1),
9
+ timestamp: z.string().datetime({ offset: true }), cwd: z.string(), parentSession: z.string().optional() }).passthrough();
10
+ const Entry = z.object({ type: z.string().min(1), id: z.string().min(1),
11
+ parentId: z.string().nullable(), timestamp: z.string().datetime({ offset: true }) }).passthrough();
12
+ export class PiSessionAdapter {
13
+ limits;
14
+ maxFiles;
15
+ provider = 'pi';
16
+ adapterVersion = PI_ADAPTER_VERSION;
17
+ disposition = 'implemented';
18
+ supportedOperations = ['discover', 'inspect', 'stream'];
19
+ acquisitionModes = ['jsonl'];
20
+ constructor(limits, maxFiles = 10_000) {
21
+ this.limits = limits;
22
+ this.maxFiles = maxFiles;
23
+ }
24
+ async *discover(scope) {
25
+ if (!scope.allowedRoots.length)
26
+ throw new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Pi discovery requires an authorized sessions root');
27
+ let count = 0;
28
+ for (const root of [...scope.allowedRoots].sort()) {
29
+ for await (const locator of jsonlFiles(resolve(root))) {
30
+ if (++count > this.maxFiles)
31
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'Pi discovery file limit exceeded');
32
+ yield { provider: 'pi', locator, locatorClass: 'pi-session-v3-jsonl' };
33
+ }
34
+ }
35
+ }
36
+ async inspect(source) {
37
+ let input;
38
+ try {
39
+ input = await readBoundedJsonLines({ selectedPath: source.locator,
40
+ allowedRoots: source.authorizedScope.allowedRoots }, { consistency: 'provisional', limits: this.limits });
41
+ }
42
+ catch (error) {
43
+ throw normalizeAuthorizationError(error);
44
+ }
45
+ const header = parseHeader(input.records[0]?.value);
46
+ if (input.incompleteTail)
47
+ throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
48
+ return { sourceSchemaVersion: `${header.version}.0.0`, consistency: 'complete', operationalState: 'available' };
49
+ }
50
+ async *stream(source, cursor) {
51
+ let input;
52
+ try {
53
+ input = await streamBoundedJsonLines({ selectedPath: source.locator,
54
+ allowedRoots: source.authorizedScope.allowedRoots }, { cursor: cursor?.value, consistency: 'provisional', limits: this.limits });
55
+ }
56
+ catch (error) {
57
+ throw normalizeAuthorizationError(error);
58
+ }
59
+ let sessionId = '';
60
+ let index = 0;
61
+ const ids = new Set();
62
+ for await (const line of input) {
63
+ if (index++ === 0 && !cursor) {
64
+ sessionId = parseHeader(line.value).id;
65
+ continue;
66
+ }
67
+ if (!sessionId)
68
+ sessionId = basename(source.locator, '.jsonl');
69
+ const parsed = Entry.safeParse(line.value);
70
+ if (!parsed.success)
71
+ throw new SessionContractError('MALFORMED_SOURCE', 'Pi session entry is malformed');
72
+ const entry = parsed.data;
73
+ if (ids.has(entry.id))
74
+ throw new SessionContractError('DUPLICATE_NATIVE_ID', 'Pi session contains a duplicate entry id');
75
+ ids.add(entry.id);
76
+ const message = object(entry.message);
77
+ const role = string(message.role) ?? roleFor(entry.type);
78
+ const sensitive = role === 'toolResult' || entry.type === 'custom';
79
+ const text = sensitive ? '[redacted provider content]' : entryText(entry, message);
80
+ yield { nativeSessionId: sessionId, nativeEventId: entry.id, sequence: line.sequence,
81
+ kind: kindFor(entry.type, role), role, toolName: sensitive ? string(message.toolName) : undefined,
82
+ toolCallId: sensitive ? string(message.toolCallId) : undefined,
83
+ occurredAt: entry.timestamp, text, sourceCursor: String(line.byteOffset + line.byteLength),
84
+ sourceBytes: line.byteLength, rawReference: { locatorClass: 'pi-session-v3-jsonl', offset: line.byteOffset },
85
+ activityBoundary: entry.type === 'compaction' ? 'continuation' : undefined,
86
+ activityBoundaryBasis: entry.type === 'compaction' ? 'pi-session:compaction' : undefined,
87
+ activityBoundaryConfidence: entry.type === 'compaction' ? 'high' : undefined,
88
+ extensions: { provenance: { acquisition: 'pi-session-v3', parentId: entry.parentId },
89
+ opaque: !KNOWN.has(entry.type), redacted: sensitive,
90
+ nativeType: entry.type, parentId: entry.parentId } };
91
+ }
92
+ if (input.incompleteTail)
93
+ throw new SessionContractError('TRUNCATED_SOURCE', 'Pi session has a truncated JSONL tail');
94
+ }
95
+ }
96
+ const KNOWN = new Set(['message', 'thinking_level_change', 'model_change', 'compaction',
97
+ 'branch_summary', 'custom', 'label', 'session_info', 'custom_message']);
98
+ function parseHeader(value) {
99
+ const parsed = Header.safeParse(value);
100
+ if (!parsed.success)
101
+ throw new SessionContractError('MALFORMED_SOURCE', 'Pi session header is malformed');
102
+ if (parsed.data.version !== 3)
103
+ throw new SessionContractError('UNKNOWN_SCHEMA_MAJOR', `unsupported Pi session version: ${parsed.data.version}`);
104
+ return parsed.data;
105
+ }
106
+ function object(value) { return value && typeof value === 'object' ? value : {}; }
107
+ function string(value) { return typeof value === 'string' && value ? value : undefined; }
108
+ function roleFor(type) { return type === 'compaction' || type === 'branch_summary' ? 'system' : undefined; }
109
+ function kindFor(type, role) { return type === 'message' ? `message.${role ?? 'unknown'}` : `pi.${type}`; }
110
+ function entryText(entry, message) {
111
+ const value = entry.type === 'message' ? message.content : entry.summary ?? entry.name ?? entry.label ?? '';
112
+ if (typeof value === 'string')
113
+ return value;
114
+ if (Array.isArray(value))
115
+ return value.flatMap(part => typeof part === 'string' ? part : string(object(part).text) ?? []).join('\n');
116
+ return '';
117
+ }
118
+ async function* jsonlFiles(root) {
119
+ let directory;
120
+ try {
121
+ directory = await opendir(root);
122
+ }
123
+ catch {
124
+ return;
125
+ }
126
+ for await (const entry of directory) {
127
+ const path = resolve(root, entry.name);
128
+ if (entry.isDirectory())
129
+ yield* jsonlFiles(path);
130
+ else if (entry.isFile() && entry.name.endsWith('.jsonl'))
131
+ yield path;
132
+ }
133
+ }
134
+ function normalizeAuthorizationError(error) {
135
+ if (error instanceof SessionContractError && (error.code === 'SOURCE_OUTSIDE_ALLOWED_ROOT'
136
+ || error.code === 'SOURCE_SYMLINK' || error.code === 'SOURCE_NOT_REGULAR_FILE')) {
137
+ return new SessionContractError('SOURCE_NOT_AUTHORIZED', 'Pi source is not an authorized regular file');
138
+ }
139
+ return error;
140
+ }
141
+ //# sourceMappingURL=pi.js.map
@@ -3,7 +3,7 @@ import { z } from 'zod';
3
3
  export const SESSION_CONTRACT_VERSION = '1.0.0';
4
4
  export const SESSION_PROVIDER_IDS = [
5
5
  'claude', 'codex', 'copilot', 'cursor', 'factory', 'hermes',
6
- 'opencode', 'openclaw', 'openhuman', 'warp', 'devin-desktop', 'generic',
6
+ 'opencode', 'openclaw', 'openhuman', 'pi', 'warp', 'devin-desktop', 'generic',
7
7
  ];
8
8
  export const SessionProviderIdSchema = z.enum(SESSION_PROVIDER_IDS);
9
9
  export const SESSION_PROVIDER_ALIASES = Object.freeze({
@@ -28,6 +28,7 @@ export * from './adapters/hermes.js';
28
28
  export * from './adapters/opencode.js';
29
29
  export * from './adapters/openclaw.js';
30
30
  export * from './adapters/openhuman.js';
31
+ export * from './adapters/pi.js';
31
32
  export * from './adapters/warp.js';
32
33
  export * from './adapters/windsurf.js';
33
34
  //# sourceMappingURL=index.js.map
@@ -7,6 +7,7 @@ import { ClaudeSessionAdapter } from './adapters/claude.js';
7
7
  import { CodexSessionAdapter } from './adapters/codex.js';
8
8
  import { CursorSessionAdapter } from './adapters/cursor.js';
9
9
  import { FactorySessionAdapter } from './adapters/factory.js';
10
+ import { PiSessionAdapter } from './adapters/pi.js';
10
11
  import { SESSION_PROVIDER_IDS, sha256, } from './contracts.js';
11
12
  import { redactSourceLocator } from './discovery.js';
12
13
  import { fingerprintSourceFile } from './readers.js';
@@ -49,6 +50,15 @@ export async function discoverWorkspaceHistories(options) {
49
50
  join(providerHome, '.factory', 'sessions', keyWithLeadingDash),
50
51
  ]),
51
52
  },
53
+ {
54
+ provider: 'pi',
55
+ adapter: new PiSessionAdapter(),
56
+ roots: options.providerHome
57
+ ? [process.env.PI_CODING_AGENT_SESSION_DIR
58
+ ? resolve(process.env.PI_CODING_AGENT_SESSION_DIR)
59
+ : join(resolve(options.providerHome), '.pi', 'agent', 'sessions')]
60
+ : [],
61
+ },
52
62
  ];
53
63
  const reports = new Map();
54
64
  const candidates = [];
@@ -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.9.0",
3
+ "version": "2026.9.2",
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/",
@@ -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
+ }