@aiwg/cli 2026.9.2 → 2026.9.3

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 (67) hide show
  1. package/agentic/code/providers/antigravity/provider-contract.v1.json +121 -0
  2. package/agentic/code/providers/capability-matrix.yaml +87 -1
  3. package/agentic/code/providers/model-capabilities.v1.json +31 -0
  4. package/agentic/code/providers/model-catalog.v1.json +29 -0
  5. package/agentic/code/providers/omp/README.md +58 -0
  6. package/agentic/code/providers/omp/aiwg-bridge.ts +52 -0
  7. package/dist/src/agents/agent-deployer.js +18 -0
  8. package/dist/src/agents/agent-packager.js +25 -0
  9. package/dist/src/artifacts/backends/sqlite-backend.js +18 -10
  10. package/dist/src/artifacts/query-engine.js +30 -0
  11. package/dist/src/cli/agent-spawn.js +13 -2
  12. package/dist/src/cli/handlers/help.js +3 -1
  13. package/dist/src/cli/handlers/init.js +2 -0
  14. package/dist/src/cli/handlers/models.js +1 -1
  15. package/dist/src/cli/handlers/runtime-info.js +8 -1
  16. package/dist/src/cli/handlers/session.js +5 -4
  17. package/dist/src/cli/handlers/sessions.js +26 -9
  18. package/dist/src/cli/handlers/setup.js +9 -2
  19. package/dist/src/cli/handlers/steward.js +13 -2
  20. package/dist/src/cli/handlers/subcommands.js +11 -0
  21. package/dist/src/cli/handlers/team.js +68 -7
  22. package/dist/src/cli/handlers/use.js +121 -9
  23. package/dist/src/cli/scope-resolver.js +7 -0
  24. package/dist/src/config/aiwg-config.js +1 -0
  25. package/dist/src/dataset/fortemi-live-qualification.d.ts +53 -0
  26. package/dist/src/dataset/fortemi-live-qualification.js +297 -0
  27. package/dist/src/dataset/index.d.ts +1 -0
  28. package/dist/src/dataset/index.js +1 -0
  29. package/dist/src/mcp/cli.mjs +30 -1
  30. package/dist/src/mcp/omp-config.mjs +128 -0
  31. package/dist/src/mcp/registry.js +45 -5
  32. package/dist/src/mcp/registry.mjs +29 -6
  33. package/dist/src/models/model-capabilities.v1.json +31 -0
  34. package/dist/src/models/model-catalog.v1.json +29 -0
  35. package/dist/src/models/model-discovery.js +46 -5
  36. package/dist/src/models/provider-policy.js +5 -3
  37. package/dist/src/plugin/skill-command-translator.js +2 -0
  38. package/dist/src/providers/capability-matrix.yaml +87 -1
  39. package/dist/src/providers/omp-agent.mjs +40 -0
  40. package/dist/src/providers/omp-diagnostics.mjs +15 -0
  41. package/dist/src/providers/omp-paths.mjs +38 -0
  42. package/dist/src/providers/provider-definitions.js +83 -0
  43. package/dist/src/providers/provider-definitions.mjs +25 -1
  44. package/dist/src/providers/provider-inventory.js +2 -0
  45. package/dist/src/sessions/adapters/omp.js +203 -0
  46. package/dist/src/sessions/batch-import.js +7 -0
  47. package/dist/src/sessions/contracts.js +1 -1
  48. package/dist/src/sessions/importer.js +4 -3
  49. package/dist/src/sessions/index.js +1 -0
  50. package/dist/src/sessions/readers.js +4 -3
  51. package/dist/src/sessions/workspace-discovery.js +12 -2
  52. package/dist/src/skills/deployer.js +21 -1
  53. package/dist/src/smiths/agentsmith/generator.js +1 -0
  54. package/dist/src/smiths/context-pipeline/parallelism-section.js +2 -0
  55. package/dist/src/smiths/context-pipeline/provider-policy.js +2 -2
  56. package/dist/src/smiths/context-pipeline/workspace-context.js +2 -2
  57. package/dist/src/storage/backends/fortemi.js +142 -17
  58. package/dist/src/storage/fortemi-qualification-receipt.js +206 -0
  59. package/dist/src/storage/fortemi-qualification.js +67 -6
  60. package/dist/src/storage/index.js +1 -0
  61. package/package.json +2 -1
  62. package/schemas/dataset/fortemi-live-qualification-receipt.v1.schema.json +132 -0
  63. package/tools/agents/deploy-agents.mjs +6 -3
  64. package/tools/agents/providers/antigravity.mjs +147 -0
  65. package/tools/agents/providers/omp.d.mts +4 -0
  66. package/tools/agents/providers/omp.mjs +256 -0
  67. package/tools/providers/antigravity-transport.mjs +124 -0
@@ -36,6 +36,45 @@
36
36
  * @issue #961
37
37
  * @issue #972
38
38
  */
39
+ import { createHash } from 'node:crypto';
40
+ const FORTEMI_QUERY_LIMIT = 50;
41
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42
+ function schemaProperties(tool) {
43
+ const properties = tool?.inputSchema?.properties;
44
+ return properties && typeof properties === 'object'
45
+ ? properties
46
+ : {};
47
+ }
48
+ /** Select an argument contract from discovered capabilities, never a version string. */
49
+ export function resolveFortemiToolProfile(tools) {
50
+ const byName = new Map(tools.map((tool) => [tool.name, tool]));
51
+ const legacy = schemaProperties(byName.get('get_note'));
52
+ if ('note_id' in legacy)
53
+ return 'legacy-note-id';
54
+ const currentGet = schemaProperties(byName.get('get_note'));
55
+ const currentUpsert = schemaProperties(byName.get('upsert_external_notes'));
56
+ const currentItems = currentUpsert.items;
57
+ if ('id' in currentGet &&
58
+ 'source_namespace' in currentUpsert &&
59
+ 'items' in currentUpsert &&
60
+ currentItems?.items?.properties &&
61
+ 'external_id' in currentItems.items.properties &&
62
+ 'content' in currentItems.items.properties &&
63
+ 'caller_stable_id' in currentItems.items.properties)
64
+ return 'source-addressed-v1';
65
+ throw new Error('storage(fortemi): unsupported live MCP tool contract');
66
+ }
67
+ /** Stable UUID used as the opaque Fortemi handle for a subsystem/path identity. */
68
+ export function fortemiStableNoteId(subsystem, path) {
69
+ const bytes = createHash('sha256')
70
+ .update(`aiwg-storage\0${subsystem}\0${path}`)
71
+ .digest()
72
+ .subarray(0, 16);
73
+ bytes[6] = (bytes[6] & 0x0f) | 0x50;
74
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
75
+ const hex = bytes.toString('hex');
76
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
77
+ }
39
78
  const DEFAULT_MCP_SERVER = 'fortemi';
40
79
  const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
41
80
  export function resolveMcpRequestHeaders(server, environment = process.env) {
@@ -48,7 +87,8 @@ export function resolveMcpRequestHeaders(server, environment = process.env) {
48
87
  if (!value) {
49
88
  throw new Error(`storage(fortemi): required credential environment variable "${envName}" is not set`);
50
89
  }
51
- headers[header] = header.toLowerCase() === 'authorization' ? `Bearer ${value}` : value;
90
+ headers[header] =
91
+ header.toLowerCase() === 'authorization' ? `Bearer ${value}` : value;
52
92
  }
53
93
  return headers;
54
94
  }
@@ -75,6 +115,11 @@ export function unwrapMcpToolResult(result) {
75
115
  ?.filter((item) => item.type === 'text' && typeof item.text === 'string')
76
116
  .map((item) => item.text)
77
117
  .join('; ');
118
+ if (detail &&
119
+ /(?:API error 404|status(?: code)? 404)/i.test(detail) &&
120
+ /(?:Note not found|problems\/not-found)/i.test(detail)) {
121
+ return { not_found: true };
122
+ }
78
123
  throw new Error(`storage(fortemi): MCP tool failed${detail ? `: ${detail}` : ''}`);
79
124
  }
80
125
  if (envelope.structuredContent !== undefined)
@@ -95,6 +140,7 @@ export class FortemiAdapter {
95
140
  scheme;
96
141
  clientFactory;
97
142
  client = null;
143
+ profile = null;
98
144
  constructor(opts) {
99
145
  this.subsystem = opts.subsystem;
100
146
  this.mcpServer = opts.config.mcpServer ?? DEFAULT_MCP_SERVER;
@@ -105,12 +151,21 @@ export class FortemiAdapter {
105
151
  if (this.client)
106
152
  return;
107
153
  this.client = await this.clientFactory(this.mcpServer);
154
+ if (this.client.listTools) {
155
+ const discovered = await this.client.listTools();
156
+ this.profile = resolveFortemiToolProfile(discovered.tools ?? []);
157
+ }
158
+ else {
159
+ // Preserve injected/older clients which predate tool discovery.
160
+ this.profile = 'legacy-note-id';
161
+ }
108
162
  }
109
163
  async close() {
110
164
  if (this.client?.close) {
111
165
  await this.client.close();
112
166
  }
113
167
  this.client = null;
168
+ this.profile = null;
114
169
  }
115
170
  async getClient() {
116
171
  if (!this.client)
@@ -132,17 +187,44 @@ export class FortemiAdapter {
132
187
  async read(path) {
133
188
  const id = this.noteId(path);
134
189
  const client = await this.getClient();
135
- const result = (await client.callTool('get_note', { note_id: id }));
190
+ const result = (await client.callTool('get_note', this.profile === 'source-addressed-v1'
191
+ ? { id: fortemiStableNoteId(this.subsystem, path) }
192
+ : { note_id: id }));
136
193
  if (!result || result.not_found)
137
194
  return null;
138
- const note = result.note;
195
+ const note = result.note ?? result;
139
196
  if (!note)
140
197
  return null;
141
- return note.revised_content ?? note.content ?? null;
198
+ return (result.revised?.content ??
199
+ result.original?.content ??
200
+ note.revised_content ??
201
+ note.content ??
202
+ null);
142
203
  }
143
204
  async write(path, content, meta) {
144
205
  const id = this.noteId(path);
145
206
  const client = await this.getClient();
207
+ if (this.profile === 'source-addressed-v1') {
208
+ const digest = createHash('sha256').update(content).digest('hex');
209
+ await client.callTool('upsert_external_notes', {
210
+ source_namespace: `aiwg.storage.${this.subsystem}`,
211
+ source_schema_version: 'aiwg.storage-entry/v1',
212
+ import_run_id: `sha256:${digest}`,
213
+ batch_id: `sha256:${digest}`,
214
+ policy: 'replace',
215
+ items: [
216
+ {
217
+ external_id: path,
218
+ content,
219
+ content_digest: `sha256:${digest}`,
220
+ caller_stable_id: fortemiStableNoteId(this.subsystem, path),
221
+ metadata: { ...this.buildMetadata(meta), aiwg_storage_path: path },
222
+ policy: 'replace',
223
+ },
224
+ ],
225
+ });
226
+ return;
227
+ }
146
228
  // Try update first; if not found, capture as new. Two calls in the
147
229
  // worst case but idempotent — Fortemi's update_note increments the
148
230
  // version rather than overwriting, which matches the Phase-4 design.
@@ -171,16 +253,29 @@ export class FortemiAdapter {
171
253
  const subsystemPrefix = `${this.subsystem}:`;
172
254
  const fullPrefix = prefix.length === 0 ? subsystemPrefix : `${subsystemPrefix}${prefix}`;
173
255
  const result = (await client.callTool('list_notes', {
174
- id_prefix: fullPrefix,
175
- scheme: this.scheme,
256
+ ...(this.profile === 'source-addressed-v1'
257
+ ? { limit: 500, offset: 0 }
258
+ : { id_prefix: fullPrefix, scheme: this.scheme }),
176
259
  }));
177
260
  const notes = result?.notes ?? [];
178
261
  return notes
179
- .filter((n) => typeof n.note_id === 'string' && n.note_id.startsWith(subsystemPrefix))
262
+ .map((n) => {
263
+ const current = n;
264
+ const path = current.metadata?.aiwg_storage_path;
265
+ return typeof path === 'string' &&
266
+ current.metadata?.subsystem === this.subsystem
267
+ ? {
268
+ ...n,
269
+ note_id: `${subsystemPrefix}${path}`,
270
+ external_id: current.id,
271
+ }
272
+ : n;
273
+ })
274
+ .filter((n) => typeof n.note_id === 'string' && n.note_id.startsWith(fullPrefix))
180
275
  .map((n) => {
181
276
  const entry = {
182
277
  path: n.note_id.slice(subsystemPrefix.length),
183
- externalId: n.note_id,
278
+ externalId: n.external_id ?? n.note_id,
184
279
  };
185
280
  if (typeof n.size === 'number')
186
281
  entry.size = n.size;
@@ -200,11 +295,16 @@ export class FortemiAdapter {
200
295
  // the note from list/read by archiving it).
201
296
  const id = this.noteId(path);
202
297
  const client = await this.getClient();
203
- const existing = (await client.callTool('get_note', { note_id: id }));
204
- if (!existing || existing.not_found || !existing.note)
298
+ const identityArgs = this.profile === 'source-addressed-v1'
299
+ ? { id: fortemiStableNoteId(this.subsystem, path) }
300
+ : { note_id: id };
301
+ const existing = (await client.callTool('get_note', identityArgs));
302
+ if (!existing ||
303
+ existing.not_found ||
304
+ (this.profile !== 'source-addressed-v1' && !existing.note))
205
305
  return;
206
306
  await client.callTool('update_note', {
207
- note_id: id,
307
+ ...identityArgs,
208
308
  archived: true,
209
309
  });
210
310
  }
@@ -212,16 +312,41 @@ export class FortemiAdapter {
212
312
  const client = await this.getClient();
213
313
  const subsystemPrefix = `${this.subsystem}:`;
214
314
  const result = (await client.callTool('search', {
215
- query: q,
216
- id_prefix: subsystemPrefix,
217
- scheme: this.scheme,
315
+ ...(this.profile === 'source-addressed-v1'
316
+ ? { action: 'text', query: q, limit: FORTEMI_QUERY_LIMIT }
317
+ : { query: q, id_prefix: subsystemPrefix, scheme: this.scheme }),
218
318
  }));
219
319
  const results = result?.results ?? [];
220
- return results
221
- .filter((r) => typeof r.note_id === 'string' && r.note_id.startsWith(subsystemPrefix))
320
+ const hydrated = [];
321
+ for (const resultItem of results.slice(0, FORTEMI_QUERY_LIMIT)) {
322
+ let item = resultItem;
323
+ if (this.profile === 'source-addressed-v1' &&
324
+ typeof item.id === 'string' &&
325
+ UUID.test(item.id) &&
326
+ (!item.metadata || typeof item.metadata.aiwg_storage_path !== 'string')) {
327
+ const detail = (await client.callTool('get_note', { id: item.id }));
328
+ if (!detail || detail.not_found)
329
+ continue;
330
+ const note = detail.note;
331
+ if (!note || note.id !== item.id)
332
+ continue;
333
+ item = { ...item, metadata: note.metadata };
334
+ }
335
+ hydrated.push(item);
336
+ }
337
+ return hydrated
338
+ .map((r) => {
339
+ const path = r.metadata?.aiwg_storage_path;
340
+ return typeof path === 'string' &&
341
+ r.metadata?.subsystem === this.subsystem
342
+ ? { ...r, note_id: `${subsystemPrefix}${path}` }
343
+ : r;
344
+ })
345
+ .filter((r) => typeof r.note_id === 'string' &&
346
+ r.note_id.startsWith(subsystemPrefix))
222
347
  .map((r) => ({
223
348
  path: r.note_id.slice(subsystemPrefix.length),
224
- externalId: r.note_id,
349
+ externalId: r.id ?? r.note_id,
225
350
  }));
226
351
  }
227
352
  buildMetadata(meta) {
@@ -0,0 +1,206 @@
1
+ import { createHash } from "node:crypto";
2
+ import { link, mkdir, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { execFileSync } from "node:child_process";
5
+ export const FORTEMI_QUALIFICATION_RECEIPT = "aiwg.fortemi-live-qualification-receipt/v1";
6
+ const DIGEST = /^sha256:[0-9a-f]{64}$/;
7
+ const COMMIT = /^[0-9a-f]{40}$/;
8
+ const REF = /^(?:refs\/(?:heads|tags)\/[A-Za-z0-9._/-]+|[0-9a-f]{40})$/;
9
+ const SHORT_REF = /^[A-Za-z0-9._/-]+$/;
10
+ const SAFE = /^[A-Za-z0-9._:/-]+$/;
11
+ const NAMESPACE = /^aiwg-qualification-[0-9a-f-]{36}$/;
12
+ const REQUIRED_OPERATIONS = new Set([
13
+ "read",
14
+ "write",
15
+ "update",
16
+ "list",
17
+ "query",
18
+ ]);
19
+ function hasCompleteOperationInventory(operations) {
20
+ const names = operations.map((operation) => operation.operation);
21
+ return (names.length === REQUIRED_OPERATIONS.size &&
22
+ new Set(names).size === REQUIRED_OPERATIONS.size &&
23
+ names.every((name) => REQUIRED_OPERATIONS.has(name)));
24
+ }
25
+ export function resolveFortemiQualificationSource(env = process.env, cwd = process.cwd()) {
26
+ const git = (...args) => execFileSync("git", args, {
27
+ cwd,
28
+ encoding: "utf8",
29
+ stdio: ["ignore", "pipe", "ignore"],
30
+ }).trim();
31
+ const aiwgCommit = env.AIWG_STORAGE_QUALIFICATION_COMMIT || git("rev-parse", "HEAD");
32
+ const configuredRef = env.AIWG_STORAGE_QUALIFICATION_BRANCH;
33
+ const aiwgRef = configuredRef
34
+ ? REF.test(configuredRef)
35
+ ? configuredRef
36
+ : SHORT_REF.test(configuredRef) && !configuredRef.includes("..")
37
+ ? `refs/heads/${configuredRef}`
38
+ : configuredRef
39
+ : (() => {
40
+ try {
41
+ return git("symbolic-ref", "-q", "HEAD");
42
+ }
43
+ catch {
44
+ return aiwgCommit;
45
+ }
46
+ })();
47
+ if (!COMMIT.test(aiwgCommit))
48
+ throw new Error("FORTEMI_RECEIPT_INVALID_COMMIT");
49
+ if (!REF.test(aiwgRef) || aiwgRef.includes(".."))
50
+ throw new Error("FORTEMI_RECEIPT_INVALID_REF");
51
+ return { aiwgCommit, aiwgRef };
52
+ }
53
+ function canonical(value) {
54
+ if (Array.isArray(value))
55
+ return `[${value.map(canonical).join(",")}]`;
56
+ if (value && typeof value === "object")
57
+ return `{${Object.entries(value)
58
+ .sort(([a], [b]) => a.localeCompare(b))
59
+ .map(([key, item]) => `${JSON.stringify(key)}:${canonical(item)}`)
60
+ .join(",")}}`;
61
+ return JSON.stringify(value);
62
+ }
63
+ export function fortemiReceiptDigest(value) {
64
+ return `sha256:${createHash("sha256").update(canonical(value)).digest("hex")}`;
65
+ }
66
+ export function endpointFingerprint(rawUrl) {
67
+ const url = new URL(rawUrl);
68
+ url.username = "";
69
+ url.password = "";
70
+ url.hash = "";
71
+ return fortemiReceiptDigest(url.toString());
72
+ }
73
+ function receiptMaterial(receipt) {
74
+ const { receiptDigest: _digest, ...material } = receipt;
75
+ return material;
76
+ }
77
+ export function createFortemiQualificationReceipt(input) {
78
+ if (!COMMIT.test(input.aiwgCommit))
79
+ throw new Error("FORTEMI_RECEIPT_INVALID_COMMIT");
80
+ if (!REF.test(input.aiwgRef) || input.aiwgRef.includes(".."))
81
+ throw new Error("FORTEMI_RECEIPT_INVALID_REF");
82
+ if (!input.report.server.name || !SAFE.test(input.report.server.name))
83
+ throw new Error("FORTEMI_RECEIPT_INVALID_SERVER_NAME");
84
+ if (!input.report.server.version || !SAFE.test(input.report.server.version))
85
+ throw new Error("FORTEMI_RECEIPT_INVALID_SERVER_VERSION");
86
+ if (!SAFE.test(input.contractRevision))
87
+ throw new Error("FORTEMI_RECEIPT_INVALID_CONTRACT_REVISION");
88
+ if (!NAMESPACE.test(input.report.namespace))
89
+ throw new Error("FORTEMI_RECEIPT_INVALID_NAMESPACE");
90
+ if (input.report.mutationAttempted !== Boolean(input.mutationObjectId))
91
+ throw new Error("FORTEMI_RECEIPT_MUTATION_BINDING_MISMATCH");
92
+ if (input.mutationObjectId && !SAFE.test(input.mutationObjectId))
93
+ throw new Error("FORTEMI_RECEIPT_INVALID_OBJECT_ID");
94
+ const start = Date.parse(input.startedAt);
95
+ const end = Date.parse(input.endedAt);
96
+ if (!Number.isFinite(start) || !Number.isFinite(end) || end < start)
97
+ throw new Error("FORTEMI_RECEIPT_INVALID_TIMESTAMPS");
98
+ if (!Number.isInteger(input.timeoutMs) ||
99
+ input.timeoutMs < 250 ||
100
+ input.timeoutMs > 30_000 ||
101
+ !Number.isInteger(input.networkAttempts) ||
102
+ input.networkAttempts < 0)
103
+ throw new Error("FORTEMI_RECEIPT_INVALID_RESOURCES");
104
+ const operations = input.report.operations.map(({ operation, tool, compatible, code }) => {
105
+ if (![operation, tool, code].every((value) => SAFE.test(value)))
106
+ throw new Error("FORTEMI_RECEIPT_UNSAFE_OPERATION");
107
+ return { operation, tool, compatible, code };
108
+ });
109
+ if (!hasCompleteOperationInventory(operations))
110
+ throw new Error("FORTEMI_RECEIPT_OPERATION_INVENTORY_INVALID");
111
+ const material = {
112
+ contract: FORTEMI_QUALIFICATION_RECEIPT,
113
+ outcome: input.report.compatible ? "passed" : "failed",
114
+ bindings: {
115
+ aiwgCommit: input.aiwgCommit,
116
+ aiwgRef: input.aiwgRef,
117
+ endpointFingerprint: endpointFingerprint(input.endpointUrl),
118
+ toolSchemaDigest: fortemiReceiptDigest(input.toolSchemas),
119
+ },
120
+ observed: {
121
+ serverName: input.report.server.name,
122
+ serverVersion: input.report.server.version,
123
+ contractRevision: input.contractRevision,
124
+ },
125
+ namespace: input.report.namespace,
126
+ operations,
127
+ mutation: {
128
+ attempted: input.report.mutationAttempted,
129
+ ...(input.mutationObjectId ? { objectId: input.mutationObjectId } : {}),
130
+ },
131
+ startedAt: new Date(start).toISOString(),
132
+ endedAt: new Date(end).toISOString(),
133
+ resources: {
134
+ timeoutMs: input.timeoutMs,
135
+ durationMs: end - start,
136
+ networkAttempts: input.networkAttempts,
137
+ toolCount: operations.length,
138
+ },
139
+ };
140
+ return { ...material, receiptDigest: fortemiReceiptDigest(material) };
141
+ }
142
+ export function verifyFortemiQualificationReceipt(receipt) {
143
+ const errors = [];
144
+ if (receipt.contract !== FORTEMI_QUALIFICATION_RECEIPT)
145
+ errors.push("FORTEMI_RECEIPT_CONTRACT_MISMATCH");
146
+ if (!["passed", "failed"].includes(receipt.outcome) ||
147
+ receipt.outcome !==
148
+ (receipt.operations.every((operation) => operation.compatible)
149
+ ? "passed"
150
+ : "failed"))
151
+ errors.push("FORTEMI_RECEIPT_OUTCOME_INVALID");
152
+ if (!DIGEST.test(receipt.receiptDigest) ||
153
+ receipt.receiptDigest !== fortemiReceiptDigest(receiptMaterial(receipt)))
154
+ errors.push("FORTEMI_RECEIPT_DIGEST_MISMATCH");
155
+ if (!DIGEST.test(receipt.bindings.endpointFingerprint) ||
156
+ !DIGEST.test(receipt.bindings.toolSchemaDigest))
157
+ errors.push("FORTEMI_RECEIPT_BINDING_INVALID");
158
+ if (!COMMIT.test(receipt.bindings.aiwgCommit) ||
159
+ !REF.test(receipt.bindings.aiwgRef))
160
+ errors.push("FORTEMI_RECEIPT_SOURCE_INVALID");
161
+ if (!SAFE.test(receipt.observed.serverName) ||
162
+ !SAFE.test(receipt.observed.serverVersion) ||
163
+ !SAFE.test(receipt.observed.contractRevision) ||
164
+ !NAMESPACE.test(receipt.namespace))
165
+ errors.push("FORTEMI_RECEIPT_OBSERVATION_INVALID");
166
+ if (receipt.operations.some((item) => !SAFE.test(item.operation) ||
167
+ !SAFE.test(item.tool) ||
168
+ !SAFE.test(item.code)))
169
+ errors.push("FORTEMI_RECEIPT_OPERATION_INVALID");
170
+ if (!hasCompleteOperationInventory(receipt.operations))
171
+ errors.push("FORTEMI_RECEIPT_OPERATION_INVENTORY_INVALID");
172
+ if (receipt.mutation.attempted !== Boolean(receipt.mutation.objectId) ||
173
+ (receipt.mutation.objectId && !SAFE.test(receipt.mutation.objectId)))
174
+ errors.push("FORTEMI_RECEIPT_MUTATION_INVALID");
175
+ if (Date.parse(receipt.endedAt) < Date.parse(receipt.startedAt) ||
176
+ receipt.resources.durationMs !==
177
+ Date.parse(receipt.endedAt) - Date.parse(receipt.startedAt))
178
+ errors.push("FORTEMI_RECEIPT_TIME_INVALID");
179
+ if (!Number.isInteger(receipt.resources.timeoutMs) ||
180
+ receipt.resources.timeoutMs < 250 ||
181
+ receipt.resources.timeoutMs > 30_000 ||
182
+ !Number.isInteger(receipt.resources.networkAttempts) ||
183
+ receipt.resources.networkAttempts < 0 ||
184
+ receipt.resources.toolCount !== receipt.operations.length)
185
+ errors.push("FORTEMI_RECEIPT_RESOURCES_INVALID");
186
+ return errors;
187
+ }
188
+ export async function writeFortemiQualificationReceipt(path, receipt) {
189
+ const errors = verifyFortemiQualificationReceipt(receipt);
190
+ if (errors.length)
191
+ throw new Error(errors.join(","));
192
+ await mkdir(dirname(path), { recursive: true });
193
+ const temporary = `${path}.tmp-${process.pid}`;
194
+ await writeFile(temporary, `${JSON.stringify(receipt, null, 2)}\n`, {
195
+ encoding: "utf8",
196
+ mode: 0o600,
197
+ flag: "wx",
198
+ });
199
+ try {
200
+ await link(temporary, path);
201
+ }
202
+ finally {
203
+ await unlink(temporary).catch(() => undefined);
204
+ }
205
+ }
206
+ //# sourceMappingURL=fortemi-qualification-receipt.js.map
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { FortemiAdapter } from "./backends/fortemi.js";
2
+ import { FortemiAdapter, fortemiStableNoteId, } from "./backends/fortemi.js";
3
3
  export const FORTEMI_QUALIFICATION_VERSION = "aiwg.fortemi-live-qualification/v1";
4
- const EXPECTED = {
4
+ const LEGACY_EXPECTED = {
5
5
  read: { tool: "get_note", required: ["note_id"], properties: ["note_id"] },
6
6
  write: {
7
7
  tool: "capture_knowledge",
@@ -20,6 +20,37 @@ const EXPECTED = {
20
20
  properties: ["query", "id_prefix"],
21
21
  },
22
22
  };
23
+ const SOURCE_ADDRESSED_EXPECTED = {
24
+ read: { tool: "get_note", required: ["id"], properties: ["id"] },
25
+ write: {
26
+ tool: "upsert_external_notes",
27
+ required: [
28
+ "source_namespace",
29
+ "source_schema_version",
30
+ "import_run_id",
31
+ "items",
32
+ ],
33
+ properties: [
34
+ "source_namespace",
35
+ "source_schema_version",
36
+ "import_run_id",
37
+ "batch_id",
38
+ "policy",
39
+ "items",
40
+ ],
41
+ },
42
+ update: {
43
+ tool: "update_note",
44
+ required: ["id"],
45
+ properties: ["id", "content", "archived"],
46
+ },
47
+ list: { tool: "list_notes", required: [], properties: ["limit", "offset"] },
48
+ query: {
49
+ tool: "search",
50
+ required: ["action"],
51
+ properties: ["action", "query", "limit"],
52
+ },
53
+ };
23
54
  function bounded(work, timeoutMs, label) {
24
55
  let timer;
25
56
  return Promise.race([
@@ -50,8 +81,19 @@ export async function qualifyLiveFortemi(client, options = {}) {
50
81
  if (!client.listTools)
51
82
  throw new Error("FORTEMI_TOOL_DISCOVERY_UNAVAILABLE");
52
83
  const discovered = await bounded(client.listTools(), timeoutMs, "tools/list");
84
+ options.onToolSchemas?.(discovered.tools ?? []);
53
85
  const tools = new Map((discovered.tools ?? []).map((tool) => [tool.name, tool]));
54
- for (const [operation, expected] of Object.entries(EXPECTED)) {
86
+ const getProperties = tools.get("get_note")?.inputSchema?.properties;
87
+ const profile = tools.has("upsert_external_notes") &&
88
+ getProperties &&
89
+ typeof getProperties === "object" &&
90
+ "id" in getProperties
91
+ ? "source-addressed-v1"
92
+ : "legacy-note-id";
93
+ const expectedOperations = profile === "source-addressed-v1"
94
+ ? SOURCE_ADDRESSED_EXPECTED
95
+ : LEGACY_EXPECTED;
96
+ for (const [operation, expected] of Object.entries(expectedOperations)) {
55
97
  const tool = tools.get(expected.tool);
56
98
  const schema = tool?.inputSchema;
57
99
  const properties = schema?.properties && typeof schema.properties === "object"
@@ -62,9 +104,23 @@ export async function qualifyLiveFortemi(client, options = {}) {
62
104
  : [];
63
105
  const missing = expected.properties.filter((name) => !(name in properties));
64
106
  const missingRequired = expected.required.filter((name) => !required.includes(name));
107
+ const itemProperties = operation === "write" && profile === "source-addressed-v1"
108
+ ? (properties.items?.items?.properties ?? {})
109
+ : {};
110
+ const missingItemProperties = operation === "write" && profile === "source-addressed-v1"
111
+ ? [
112
+ "external_id",
113
+ "content",
114
+ "content_digest",
115
+ "caller_stable_id",
116
+ "metadata",
117
+ "policy",
118
+ ].filter((name) => !(name in itemProperties))
119
+ : [];
65
120
  const compatible = Boolean(tool && schema) &&
66
121
  missing.length === 0 &&
67
- missingRequired.length === 0;
122
+ missingRequired.length === 0 &&
123
+ missingItemProperties.length === 0;
68
124
  report.operations.push({
69
125
  operation,
70
126
  tool: expected.tool,
@@ -76,7 +132,7 @@ export async function qualifyLiveFortemi(client, options = {}) {
76
132
  : "FORTEMI_TOOL_MISSING",
77
133
  detail: compatible
78
134
  ? "expected adapter arguments are accepted"
79
- : `missing properties: ${missing.join(", ") || "none"}; not required: ${missingRequired.join(", ") || "none"}`,
135
+ : `missing properties: ${missing.join(", ") || "none"}; not required: ${missingRequired.join(", ") || "none"}; missing item properties: ${missingItemProperties.join(", ") || "none"}`,
80
136
  });
81
137
  }
82
138
  report.compatible = report.operations.every((item) => item.compatible);
@@ -93,7 +149,12 @@ export async function qualifyLiveFortemi(client, options = {}) {
93
149
  await bounded(adapter.query(`aiwg qualification ${namespace}`), timeoutMs, "adapter query");
94
150
  if (options.allowMutation) {
95
151
  report.mutationAttempted = true;
96
- await bounded(adapter.write(randomUUID(), `AIWG live qualification ${namespace}`, {
152
+ const mutationPath = randomUUID();
153
+ report.mutationObjectId =
154
+ profile === "source-addressed-v1"
155
+ ? fortemiStableNoteId(namespace, mutationPath)
156
+ : `${namespace}:${mutationPath}`;
157
+ await bounded(adapter.write(mutationPath, `AIWG live qualification ${namespace}`, {
97
158
  contentType: "text/plain",
98
159
  }), timeoutMs, "adapter write");
99
160
  }
@@ -22,6 +22,7 @@ export { ObsidianAdapter } from './backends/obsidian.js';
22
22
  export { LogseqAdapter } from './backends/logseq.js';
23
23
  export { FortemiAdapter } from './backends/fortemi.js';
24
24
  export { qualifyLiveFortemi, FORTEMI_QUALIFICATION_VERSION, } from './fortemi-qualification.js';
25
+ export { createFortemiQualificationReceipt, endpointFingerprint, fortemiReceiptDigest, verifyFortemiQualificationReceipt, writeFortemiQualificationReceipt, FORTEMI_QUALIFICATION_RECEIPT, } from './fortemi-qualification-receipt.js';
25
26
  export { STORAGE_BACKEND_CONTRACT, STORAGE_BACKEND_MATRIX, StorageCapabilityError, negotiateStorageCapabilities, } from './backend-contract.js';
26
27
  export { STORAGE_MIGRATION_PROTOCOL, MigrationProtocolError, StorageMigrationCoordinator, approvalDigest, digestRecordChunks, digestRecords, validateManifest, } from './migration-protocol.js';
27
28
  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.9.2",
3
+ "version": "2026.9.3",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,6 +39,7 @@
39
39
  "tools/_resolve-impl.mjs",
40
40
  "tools/agents/deploy-agents.mjs",
41
41
  "tools/agents/providers/",
42
+ "tools/providers/antigravity-transport.mjs",
42
43
  "tools/commands/deploy-prompts-codex.mjs",
43
44
  "tools/plugin/package-plugins.mjs",
44
45
  "tools/skills/deploy-skills-codex.mjs",