@aiwg/cli 2026.9.1 → 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 (71) hide show
  1. package/agentic/code/providers/antigravity/provider-contract.v1.json +121 -0
  2. package/agentic/code/providers/capability-matrix.yaml +88 -2
  3. package/agentic/code/providers/model-capabilities.v1.json +42 -0
  4. package/agentic/code/providers/model-catalog.v1.json +37 -0
  5. package/agentic/code/providers/omp/README.md +58 -0
  6. package/agentic/code/providers/omp/aiwg-bridge.ts +52 -0
  7. package/agentic/code/providers/pi/aiwg-bridge.ts +26 -0
  8. package/dist/src/agents/agent-deployer.js +18 -0
  9. package/dist/src/agents/agent-packager.js +25 -0
  10. package/dist/src/artifacts/backends/sqlite-backend.js +18 -10
  11. package/dist/src/artifacts/query-engine.js +30 -0
  12. package/dist/src/auth/credential-store.js +6 -0
  13. package/dist/src/cli/agent-spawn.js +13 -2
  14. package/dist/src/cli/handlers/help.js +3 -1
  15. package/dist/src/cli/handlers/init.js +2 -0
  16. package/dist/src/cli/handlers/models.js +1 -1
  17. package/dist/src/cli/handlers/runtime-info.js +8 -1
  18. package/dist/src/cli/handlers/session.js +5 -4
  19. package/dist/src/cli/handlers/sessions.js +54 -19
  20. package/dist/src/cli/handlers/setup.js +9 -2
  21. package/dist/src/cli/handlers/steward.js +13 -2
  22. package/dist/src/cli/handlers/subcommands.js +11 -0
  23. package/dist/src/cli/handlers/team.js +68 -7
  24. package/dist/src/cli/handlers/use.js +121 -9
  25. package/dist/src/cli/scope-resolver.js +7 -0
  26. package/dist/src/config/aiwg-config.js +1 -0
  27. package/dist/src/dataset/fortemi-live-qualification.d.ts +53 -0
  28. package/dist/src/dataset/fortemi-live-qualification.js +297 -0
  29. package/dist/src/dataset/index.d.ts +1 -0
  30. package/dist/src/dataset/index.js +1 -0
  31. package/dist/src/mcp/cli.mjs +30 -1
  32. package/dist/src/mcp/omp-config.mjs +128 -0
  33. package/dist/src/mcp/registry.js +45 -5
  34. package/dist/src/mcp/registry.mjs +29 -6
  35. package/dist/src/models/model-capabilities.v1.json +42 -0
  36. package/dist/src/models/model-catalog.v1.json +37 -0
  37. package/dist/src/models/model-discovery.js +78 -5
  38. package/dist/src/models/provider-policy.js +6 -3
  39. package/dist/src/plugin/skill-command-translator.js +2 -0
  40. package/dist/src/providers/capability-matrix.yaml +88 -2
  41. package/dist/src/providers/omp-agent.mjs +40 -0
  42. package/dist/src/providers/omp-diagnostics.mjs +15 -0
  43. package/dist/src/providers/omp-paths.mjs +38 -0
  44. package/dist/src/providers/provider-definitions.js +83 -0
  45. package/dist/src/providers/provider-definitions.mjs +25 -1
  46. package/dist/src/providers/provider-inventory.js +2 -0
  47. package/dist/src/sessions/adapters/omp.js +203 -0
  48. package/dist/src/sessions/adapters/pi.js +141 -0
  49. package/dist/src/sessions/batch-import.js +7 -0
  50. package/dist/src/sessions/contracts.js +1 -1
  51. package/dist/src/sessions/importer.js +4 -3
  52. package/dist/src/sessions/index.js +2 -0
  53. package/dist/src/sessions/readers.js +4 -3
  54. package/dist/src/sessions/workspace-discovery.js +22 -2
  55. package/dist/src/skills/deployer.js +21 -1
  56. package/dist/src/smiths/agentsmith/generator.js +1 -0
  57. package/dist/src/smiths/context-pipeline/parallelism-section.js +2 -0
  58. package/dist/src/smiths/context-pipeline/provider-policy.js +2 -2
  59. package/dist/src/smiths/context-pipeline/workspace-context.js +2 -2
  60. package/dist/src/storage/backends/fortemi.js +142 -17
  61. package/dist/src/storage/fortemi-qualification-receipt.js +206 -0
  62. package/dist/src/storage/fortemi-qualification.js +67 -6
  63. package/dist/src/storage/index.js +1 -0
  64. package/package.json +2 -1
  65. package/schemas/dataset/fortemi-live-qualification-receipt.v1.schema.json +132 -0
  66. package/tools/agents/deploy-agents.mjs +6 -3
  67. package/tools/agents/providers/antigravity.mjs +147 -0
  68. package/tools/agents/providers/omp.d.mts +4 -0
  69. package/tools/agents/providers/omp.mjs +256 -0
  70. package/tools/agents/providers/pi.mjs +13 -1
  71. package/tools/providers/antigravity-transport.mjs +124 -0
@@ -12,6 +12,8 @@
12
12
  import fs from 'fs/promises';
13
13
  import path from 'path';
14
14
  import os from 'os';
15
+ import { pathToFileURL } from 'node:url';
16
+ import { resolveOmpPaths } from '../../providers/omp-paths.mjs';
15
17
  import YAML from 'yaml';
16
18
  import { createScriptRunner } from './script-runner.js';
17
19
  import { getFrameworkRoot, getVersionInfo } from '../../channel/manager.mjs';
@@ -1041,10 +1043,10 @@ async function reconcileDeployedSkillAssets(bundlePath, target, provider, option
1041
1043
  }
1042
1044
  const paths = getProviderPaths(provider);
1043
1045
  const kernelSkillsPath = getProviderKernelSkillsPath(provider);
1044
- const deployRoots = [...new Set([
1045
- paths.skills,
1046
- kernelSkillsPath,
1047
- ].filter((value) => Boolean(value)).map(value => resolveDeployPath(target, value)))];
1046
+ const deployRoots = provider === 'omp' && options.scope === 'user'
1047
+ ? [path.join(resolveOmpPaths({ cwd: target }).agentDir, 'skills')]
1048
+ : [...new Set([paths.skills, kernelSkillsPath]
1049
+ .filter((value) => Boolean(value)).map(value => resolveDeployPath(target, value)))];
1048
1050
  for (const skillName of skillDirs) {
1049
1051
  const sourceSkillDir = path.join(skillsRoot, skillName);
1050
1052
  const sourceSkillMd = path.join(sourceSkillDir, 'SKILL.md');
@@ -1055,6 +1057,8 @@ async function reconcileDeployedSkillAssets(bundlePath, target, provider, option
1055
1057
  catch {
1056
1058
  continue;
1057
1059
  }
1060
+ if (options.skipUndeployed && !(await Promise.all(deployRoots.map(root => fileExists(path.join(root, skillName, 'SKILL.md'))))).some(Boolean))
1061
+ continue;
1058
1062
  const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '';
1059
1063
  const declaredEntrypoint = frontmatter
1060
1064
  .match(/^[ \t]+entrypoint:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1];
@@ -1097,6 +1101,11 @@ async function reconcileDeployedSkillAssets(bundlePath, target, provider, option
1097
1101
  if (!deployedSkillRoot)
1098
1102
  throw new Error(`deployed skill '${skillName}' not found while reconciling support assets`);
1099
1103
  const destination = path.join(deployedSkillRoot, skillName, ...normalized.split('/'));
1104
+ if (provider === 'omp') {
1105
+ const adapter = await import(pathToFileURL(path.join(await getFrameworkRoot(), 'tools/agents/providers/omp.mjs')).href);
1106
+ adapter.deploySkillSupportAsset(source, destination, { quiet: true });
1107
+ continue;
1108
+ }
1100
1109
  await fs.mkdir(path.dirname(destination), { recursive: true });
1101
1110
  await fs.copyFile(source, destination);
1102
1111
  const mode = (await fs.stat(source)).mode & 0o777;
@@ -1348,6 +1357,18 @@ async function deployProjectLocalBundles(opts) {
1348
1357
  }
1349
1358
  }
1350
1359
  }
1360
+ // Automatic reconciliation (also used by refresh/upgrade) must restore the
1361
+ // project search cache for existing bundles, just as a named local install
1362
+ // does. Named installs refresh once after all selected providers finish.
1363
+ if (deployed > 0 && !dryRun && !onlyBundleId) {
1364
+ try {
1365
+ await rebuildExternalBundleIndex(projectDir, 'project', verbose);
1366
+ }
1367
+ catch (error) {
1368
+ failed++;
1369
+ ui.warn(`Project-local index refresh failed: ${error instanceof Error ? error.message : String(error)}`);
1370
+ }
1371
+ }
1351
1372
  // Refresh the project kernel quickref from either legacy operator input or
1352
1373
  // managed project-local discovery whenever bundles deploy.
1353
1374
  const { hasProjectQuickref, deployProjectQuickref } = await import('../../extensions/project-quickref.js');
@@ -1968,6 +1989,65 @@ async function rebuildExternalBundleIndex(projectDir, graph, verbose) {
1968
1989
  }
1969
1990
  ui.dim(` Refreshed ${graph}-scope capability index`);
1970
1991
  }
1992
+ /** User OMP writes use native ownership receipts instead of an unconditional copy. */
1993
+ async function deployOmpUserSource(opts) {
1994
+ const adapter = await import(pathToFileURL(path.join(opts.frameworkRoot, 'tools/agents/providers/omp.mjs')).href);
1995
+ await adapter.deploy({ srcRoot: opts.source, target: opts.target, provider: 'omp', scope: 'user',
1996
+ mode: opts.mode ?? 'general', deployCommands: true, deploySkills: true, deployRules: true,
1997
+ copyStandardSkills: opts.copyAll, quiet: true, deployVersion: (await getVersionInfo()).version });
1998
+ const sourceBundles = new Set([opts.source]);
1999
+ const { resourceDirs } = resolveOmpPaths({ cwd: opts.target });
2000
+ const entries = {
2001
+ agents: [], commands: [], skills: [], rules: [], behaviors: [],
2002
+ };
2003
+ const belongsToBundle = (entry) => {
2004
+ if (entry.provider !== 'omp' || typeof entry.source !== 'string')
2005
+ return false;
2006
+ if (entry.transformation === 'omp-extension')
2007
+ return true;
2008
+ const relative = path.relative(opts.source, entry.source);
2009
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
2010
+ };
2011
+ const locations = resourceDirs;
2012
+ for (const kind of Object.keys(entries)) {
2013
+ const directory = locations[kind];
2014
+ if (kind === 'skills') {
2015
+ let children;
2016
+ try {
2017
+ children = await fs.readdir(directory);
2018
+ }
2019
+ catch {
2020
+ continue;
2021
+ }
2022
+ for (const child of children) {
2023
+ try {
2024
+ const receipt = JSON.parse(await fs.readFile(path.join(directory, child, '.aiwg-manifest.json'), 'utf8'));
2025
+ if (receipt.managed?.['SKILL.md'] && belongsToBundle(receipt.managed['SKILL.md'])) {
2026
+ entries.skills.push(child);
2027
+ const source = receipt.managed['SKILL.md'].source;
2028
+ if (typeof source === 'string')
2029
+ sourceBundles.add(path.dirname(path.dirname(path.dirname(source))));
2030
+ }
2031
+ }
2032
+ catch { /* operator resources have no OMP receipt */ }
2033
+ }
2034
+ }
2035
+ else {
2036
+ try {
2037
+ const receipt = JSON.parse(await fs.readFile(path.join(directory, '.aiwg-manifest.json'), 'utf8'));
2038
+ entries[kind] = Object.entries(receipt.managed ?? {}).filter(([, entry]) => belongsToBundle(entry)).map(([name]) => name);
2039
+ }
2040
+ catch { /* no deployed resources of this kind */ }
2041
+ }
2042
+ }
2043
+ for (const source of sourceBundles)
2044
+ await reconcileDeployedSkillAssets(source, opts.target, 'omp', { strictReferences: false, scope: 'user', skipUndeployed: !opts.copyAll });
2045
+ const { recordUserDeploy } = await import('../../config/user-registry.js');
2046
+ await recordUserDeploy({ framework: opts.bundle, provider: 'omp',
2047
+ version: (await getVersionInfo()).version, source: 'bundled',
2048
+ counts: { agents: entries.agents.length, commands: entries.commands.length,
2049
+ skills: entries.skills.length, rules: entries.rules.length }, entries });
2050
+ }
1971
2051
  async function mirrorProjectLocalBundleToUserScope(opts) {
1972
2052
  const paths = getProviderPaths(opts.provider);
1973
2053
  const resolveProjectPath = (value) => !value ? '' : path.isAbsolute(value) ? value : path.join(opts.target, value);
@@ -2701,10 +2781,25 @@ export class UseHandler {
2701
2781
  if (isAddon || isExtension) {
2702
2782
  const providerIdx = remainingArgs.findIndex(a => a === '--provider' || a === '--platform');
2703
2783
  const explicitAddonProvider = providerIdx >= 0 && remainingArgs[providerIdx + 1] ? remainingArgs[providerIdx + 1] : null;
2704
- const provider = explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude');
2784
+ const provider = resolveBuiltInProviderForUse(explicitAddonProvider ?? (config?.providers?.[0] ?? 'claude')).provider;
2705
2785
  const targetIdx = remainingArgs.findIndex(a => a === '--target');
2706
2786
  const target = targetIdx >= 0 && remainingArgs[targetIdx + 1] ? remainingArgs[targetIdx + 1] : process.cwd();
2707
2787
  const dryRunAddon = remainingArgs.includes('--dry-run');
2788
+ let addonScope;
2789
+ try {
2790
+ addonScope = detectScope(remainingArgs);
2791
+ if (addonScope === 'project' && remainingArgs.includes('--user'))
2792
+ addonScope = 'user';
2793
+ }
2794
+ catch (error) {
2795
+ return { exitCode: 1, message: `Error: ${error instanceof Error ? error.message : String(error)}` };
2796
+ }
2797
+ if (addonScope === 'user' && !USER_SCOPE_PATHS[provider]) {
2798
+ return {
2799
+ exitCode: 1,
2800
+ message: `--scope user not supported for provider '${provider}' — see docs/customization/user-scope-deployment.md for the supported list`,
2801
+ };
2802
+ }
2708
2803
  const runner = createScriptRunner(frameworkRoot);
2709
2804
  const addonBaseArgs = ['--deploy-commands', '--deploy-skills', '--deploy-rules'];
2710
2805
  // An explicitly selected upstream addon must be self-contained in the
@@ -2797,6 +2892,14 @@ export class UseHandler {
2797
2892
  };
2798
2893
  }
2799
2894
  }
2895
+ if (!dryRunAddon && provider === 'omp' && (detectScope(remainingArgs) === 'user' || remainingArgs.includes('--user'))) {
2896
+ try {
2897
+ await deployOmpUserSource({ frameworkRoot, source: addonSource, target, bundle: framework, copyAll: true });
2898
+ }
2899
+ catch (error) {
2900
+ return { exitCode: 1, message: `OMP user deployment failed: ${error instanceof Error ? error.message : String(error)}` };
2901
+ }
2902
+ }
2800
2903
  // Register only artifacts actually written by a confirmed deployment.
2801
2904
  if (!dryRunAddon) {
2802
2905
  try {
@@ -2922,7 +3025,7 @@ export class UseHandler {
2922
3025
  catch {
2923
3026
  // Profile selection is optional — don't fail deployment
2924
3027
  }
2925
- if (framework === 'aiwg-utils' && provider !== 'pi' && !remainingArgs.includes('--dry-run')) {
3028
+ if (framework === 'aiwg-utils' && !['pi', 'omp'].includes(provider) && !remainingArgs.includes('--dry-run')) {
2926
3029
  const wrapperValidation = await validateDeployedModelWrappers({
2927
3030
  provider: normalizeProviderDefinitionId(provider) ?? provider,
2928
3031
  target,
@@ -3025,7 +3128,7 @@ export class UseHandler {
3025
3128
  ? withProviderOverride(deployFilteredArgs, provider)
3026
3129
  : deployFilteredArgs;
3027
3130
  const bulkKernelOnly = framework === 'all'
3028
- && provider !== 'pi'
3131
+ && !['pi', 'omp'].includes(provider)
3029
3132
  && !remainingArgs.includes('--copy-all')
3030
3133
  && !remainingArgs.includes('--copy-standard-skills');
3031
3134
  if (bulkKernelOnly)
@@ -3234,7 +3337,7 @@ export class UseHandler {
3234
3337
  // Pi model selection/headless routing is delivered by #2151. Until that
3235
3338
  // adapter exists, do not require model-wrapper artifacts that Pi cannot
3236
3339
  // load; resource deployment remains independently valid.
3237
- if (!dryRun && !skipUtils && !bulkKernelOnly && provider !== 'pi') {
3340
+ if (!dryRun && !skipUtils && !bulkKernelOnly && !['pi', 'omp'].includes(provider)) {
3238
3341
  const wrapperValidation = await validateDeployedModelWrappers({
3239
3342
  provider,
3240
3343
  target,
@@ -3424,7 +3527,16 @@ export class UseHandler {
3424
3527
  // mirror, record the deploy in the per-user registry at
3425
3528
  // ~/.aiwg/installed.json so `aiwg list --scope user` and `aiwg remove
3426
3529
  // --scope user` can find it from any cwd.
3427
- if (scope === 'user' && provider !== 'openhuman' && !dryRun) {
3530
+ if (scope === 'user' && provider === 'omp' && !dryRun) {
3531
+ try {
3532
+ await deployOmpUserSource({ frameworkRoot, source: frameworkRoot, target, bundle: framework, mode,
3533
+ copyAll: remainingArgs.includes('--copy-all') || remainingArgs.includes('--copy-standard-skills') });
3534
+ }
3535
+ catch (error) {
3536
+ return { exitCode: 1, message: `OMP user deployment failed: ${error instanceof Error ? error.message : String(error)}` };
3537
+ }
3538
+ }
3539
+ if (scope === 'user' && provider !== 'openhuman' && provider !== 'omp' && !dryRun) {
3428
3540
  try {
3429
3541
  const paths = getProviderPaths(provider);
3430
3542
  const resolveProjectPath = (p) => !p ? '' : path.isAbsolute(p) ? p : path.join(target, p);
@@ -9,6 +9,7 @@
9
9
  * Per ADR-4 §2 path map. Per ADR-4 §1: `--scope user` and `--scope
10
10
  * project` are mutually exclusive; default is `project`.
11
11
  */
12
+ import { resolveOmpPaths } from '../providers/omp-paths.mjs';
12
13
  import { homedir } from 'node:os';
13
14
  import * as path from 'node:path';
14
15
  import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
@@ -57,6 +58,12 @@ export const USER_SCOPE_PATHS = {
57
58
  rules: '',
58
59
  behaviors: '',
59
60
  },
61
+ get omp() {
62
+ const { agentDir } = resolveOmpPaths();
63
+ return { agents: path.join(agentDir, 'agents'), skills: path.join(agentDir, 'skills'),
64
+ commands: path.join(agentDir, 'prompts'), rules: path.join(agentDir, 'rules'),
65
+ behaviors: path.join(agentDir, 'extensions') };
66
+ },
60
67
  pi: {
61
68
  // Pi's global resource root is configurable. Unlike project deployment,
62
69
  // user-scope resources belong under the effective agent directory. Keep
@@ -89,6 +89,7 @@ export function resolveDelivery(delivery) {
89
89
  * - unknown: conservative 4 default.
90
90
  */
91
91
  export const PROVIDER_PARALLELISM_DEFAULTS = {
92
+ antigravity: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
92
93
  claude: { max_parallel_subagents: 4, max_parallel_ralph_loops: 2, max_parallel_mc_missions: 4 },
93
94
  codex: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
94
95
  copilot: { max_parallel_subagents: 10, max_parallel_ralph_loops: 3, max_parallel_mc_missions: 6 },
@@ -0,0 +1,53 @@
1
+ import type { McpClientLike } from "../storage/backends/fortemi.js";
2
+ export declare const FORTEMI_DATASET_LIVE_CONTRACT: "aiwg.fortemi-dataset-live-qualification/v1";
3
+ export declare const FORTEMI_DATASET_CAPABILITIES_TOOL = "dataset_capabilities";
4
+ export declare const FORTEMI_DATASET_EXECUTE_TOOL = "dataset_execute";
5
+ export interface FortemiDatasetLiveReceipt {
6
+ contract: typeof FORTEMI_DATASET_LIVE_CONTRACT;
7
+ outcome: "pending" | "supported";
8
+ diagnostic: "CONFORMANCE_FORTEMI_DATASET_CONTRACT_UNAVAILABLE" | "CONFORMANCE_FORTEMI_DATASET_PREFLIGHT_SUPPORTED";
9
+ receiptDigest: string;
10
+ bindings: {
11
+ aiwgCommit: string;
12
+ endpointFingerprint: string;
13
+ toolSchemaDigest: string;
14
+ };
15
+ observed: {
16
+ serverName: string;
17
+ serverVersion: string;
18
+ };
19
+ namespace: string;
20
+ operations: Array<{
21
+ tool: string;
22
+ compatible: boolean;
23
+ code: string;
24
+ }>;
25
+ mutation: {
26
+ authorized: false;
27
+ attempted: false;
28
+ };
29
+ resources: {
30
+ maxDurationMs: number;
31
+ durationMs: number;
32
+ maxToolCount: number;
33
+ observedToolCount: number;
34
+ maxSchemaBytes: number;
35
+ observedSchemaBytes: number;
36
+ networkAttempts: number;
37
+ toolCalls: number;
38
+ };
39
+ startedAt: string;
40
+ endedAt: string;
41
+ }
42
+ /** Read-only discovery. It never invokes a Fortemi tool, even when the proposed contract is present. */
43
+ export declare function qualifyFortemiDatasetLivePreflight(input: {
44
+ client: McpClientLike;
45
+ endpointUrl: string;
46
+ aiwgCommit: string;
47
+ maxDurationMs?: number;
48
+ now?: () => Date;
49
+ }): Promise<FortemiDatasetLiveReceipt>;
50
+ export declare function verifyFortemiDatasetLiveReceipt(value: unknown): string[];
51
+ /** Atomically creates private evidence without replacing an existing receipt. */
52
+ export declare function writeFortemiDatasetLiveReceipt(path: string, receipt: FortemiDatasetLiveReceipt): Promise<void>;
53
+ //# sourceMappingURL=fortemi-live-qualification.d.ts.map
@@ -0,0 +1,297 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { link, mkdir, unlink, writeFile } from "node:fs/promises";
3
+ import { dirname } from "node:path";
4
+ import { endpointFingerprint, fortemiReceiptDigest, } from "../storage/fortemi-qualification-receipt.js";
5
+ export const FORTEMI_DATASET_LIVE_CONTRACT = "aiwg.fortemi-dataset-live-qualification/v1";
6
+ export const FORTEMI_DATASET_CAPABILITIES_TOOL = "dataset_capabilities";
7
+ export const FORTEMI_DATASET_EXECUTE_TOOL = "dataset_execute";
8
+ const UUID_NAMESPACE = /^aiwg-dataset-qualification-[0-9a-f-]{36}$/u;
9
+ const COMMIT = /^[0-9a-f]{40}$/u;
10
+ const DIGEST = /^sha256:[0-9a-f]{64}$/u;
11
+ const SAFE = /^[A-Za-z0-9._:/-]+$/u;
12
+ const REQUIRED_TOOLS = [
13
+ FORTEMI_DATASET_CAPABILITIES_TOOL,
14
+ FORTEMI_DATASET_EXECUTE_TOOL,
15
+ ];
16
+ const MAX_TOOL_COUNT = 256;
17
+ const MAX_SCHEMA_BYTES = 1_048_576;
18
+ function record(value) {
19
+ return value !== null && typeof value === "object" && !Array.isArray(value)
20
+ ? value
21
+ : undefined;
22
+ }
23
+ function exactKeys(value, allowed) {
24
+ const keys = Object.keys(value);
25
+ return (keys.length === allowed.length && keys.every((key) => allowed.includes(key)));
26
+ }
27
+ function material(receipt) {
28
+ const { receiptDigest: _digest, ...rest } = receipt;
29
+ return rest;
30
+ }
31
+ function inputSchemaCompatible(schema, required) {
32
+ const candidate = record(schema);
33
+ const properties = record(candidate?.properties);
34
+ const declaredRequired = candidate?.required;
35
+ if (candidate?.type !== "object" ||
36
+ !properties ||
37
+ !Array.isArray(declaredRequired))
38
+ return false;
39
+ if (!declaredRequired.every((key) => typeof key === "string") ||
40
+ new Set(declaredRequired).size !== declaredRequired.length)
41
+ return false;
42
+ return Object.entries(required).every(([key, type]) => {
43
+ const property = record(properties[key]);
44
+ return declaredRequired.includes(key) && property?.type === type;
45
+ });
46
+ }
47
+ async function bounded(promise, timeoutMs) {
48
+ let timer;
49
+ return Promise.race([
50
+ promise,
51
+ new Promise((_, reject) => {
52
+ timer = setTimeout(() => reject(new Error("CONFORMANCE_FORTEMI_DATASET_PREFLIGHT_TIMEOUT")), timeoutMs);
53
+ }),
54
+ ]).finally(() => {
55
+ if (timer)
56
+ clearTimeout(timer);
57
+ });
58
+ }
59
+ /** Read-only discovery. It never invokes a Fortemi tool, even when the proposed contract is present. */
60
+ export async function qualifyFortemiDatasetLivePreflight(input) {
61
+ if (!COMMIT.test(input.aiwgCommit))
62
+ throw new Error("CONFORMANCE_INVALID_AIWG_COMMIT");
63
+ const requestedDuration = input.maxDurationMs ?? 5_000;
64
+ if (!Number.isFinite(requestedDuration))
65
+ throw new Error("CONFORMANCE_INVALID_RESOURCE_BOUND");
66
+ const maxDurationMs = Math.max(250, Math.min(Math.trunc(requestedDuration), 30_000));
67
+ const now = input.now ?? (() => new Date());
68
+ const startedAt = now().toISOString();
69
+ const namespace = `aiwg-dataset-qualification-${randomUUID()}`;
70
+ let schemas = [];
71
+ try {
72
+ if (!input.client.listTools)
73
+ throw new Error("CONFORMANCE_FORTEMI_TOOL_DISCOVERY_UNAVAILABLE");
74
+ const discovered = await bounded(input.client.listTools(), maxDurationMs);
75
+ schemas = discovered.tools ?? [];
76
+ if (!Array.isArray(schemas))
77
+ throw new Error("CONFORMANCE_FORTEMI_TOOL_INVENTORY_INVALID");
78
+ const inventory = schemas;
79
+ const observedSchemaBytes = Buffer.byteLength(JSON.stringify(inventory), "utf8");
80
+ if (inventory.length > MAX_TOOL_COUNT ||
81
+ observedSchemaBytes > MAX_SCHEMA_BYTES)
82
+ throw new Error("CONFORMANCE_RESOURCE_ENVELOPE_EXCEEDED");
83
+ const tools = new Map(inventory.map((tool) => [tool.name, tool]));
84
+ const specifications = [
85
+ {
86
+ tool: FORTEMI_DATASET_CAPABILITIES_TOOL,
87
+ required: { contract_version: "string" },
88
+ },
89
+ {
90
+ tool: FORTEMI_DATASET_EXECUTE_TOOL,
91
+ required: {
92
+ contract_version: "string",
93
+ namespace: "string",
94
+ plan: "object",
95
+ records: "array",
96
+ },
97
+ },
98
+ ];
99
+ const checks = specifications.map(({ tool, required }) => {
100
+ const unique = inventory.filter((candidate) => candidate.name === tool).length === 1;
101
+ const compatible = unique && inputSchemaCompatible(tools.get(tool)?.inputSchema, required);
102
+ return {
103
+ tool,
104
+ compatible,
105
+ code: compatible
106
+ ? "FORTEMI_DATASET_TOOL_SCHEMA_COMPATIBLE"
107
+ : tools.has(tool)
108
+ ? "FORTEMI_DATASET_TOOL_SCHEMA_DRIFT"
109
+ : "FORTEMI_DATASET_TOOL_MISSING",
110
+ };
111
+ });
112
+ const supported = checks.every((check) => check.compatible);
113
+ const endedAt = now().toISOString();
114
+ const server = input.client.serverVersion?.() ?? {};
115
+ const safeObserved = (value) => value && SAFE.test(value) ? value : "unreported";
116
+ const base = {
117
+ contract: FORTEMI_DATASET_LIVE_CONTRACT,
118
+ outcome: supported ? "supported" : "pending",
119
+ diagnostic: supported
120
+ ? "CONFORMANCE_FORTEMI_DATASET_PREFLIGHT_SUPPORTED"
121
+ : "CONFORMANCE_FORTEMI_DATASET_CONTRACT_UNAVAILABLE",
122
+ bindings: {
123
+ aiwgCommit: input.aiwgCommit,
124
+ endpointFingerprint: endpointFingerprint(input.endpointUrl),
125
+ toolSchemaDigest: fortemiReceiptDigest(schemas),
126
+ },
127
+ observed: {
128
+ serverName: safeObserved(server.name),
129
+ serverVersion: safeObserved(server.version),
130
+ },
131
+ namespace,
132
+ operations: checks,
133
+ mutation: { authorized: false, attempted: false },
134
+ resources: {
135
+ maxDurationMs,
136
+ durationMs: Date.parse(endedAt) - Date.parse(startedAt),
137
+ maxToolCount: MAX_TOOL_COUNT,
138
+ observedToolCount: inventory.length,
139
+ maxSchemaBytes: MAX_SCHEMA_BYTES,
140
+ observedSchemaBytes,
141
+ networkAttempts: 1,
142
+ toolCalls: 0,
143
+ },
144
+ startedAt,
145
+ endedAt,
146
+ };
147
+ const receipt = { ...base, receiptDigest: fortemiReceiptDigest(base) };
148
+ const errors = verifyFortemiDatasetLiveReceipt(receipt);
149
+ if (errors.length)
150
+ throw new Error(errors.join(","));
151
+ return receipt;
152
+ }
153
+ finally {
154
+ await input.client.close?.();
155
+ }
156
+ }
157
+ export function verifyFortemiDatasetLiveReceipt(value) {
158
+ const errors = [];
159
+ const top = record(value);
160
+ if (!top)
161
+ return ["CONFORMANCE_RECEIPT_SHAPE_INVALID"];
162
+ const receipt = value;
163
+ if (!record(receipt.bindings) ||
164
+ !record(receipt.observed) ||
165
+ !record(receipt.mutation) ||
166
+ !record(receipt.resources) ||
167
+ !Array.isArray(receipt.operations) ||
168
+ !receipt.operations.every((item) => Boolean(record(item)))) {
169
+ return ["CONFORMANCE_RECEIPT_SHAPE_INVALID"];
170
+ }
171
+ if (!exactKeys(top, [
172
+ "contract",
173
+ "outcome",
174
+ "diagnostic",
175
+ "receiptDigest",
176
+ "bindings",
177
+ "observed",
178
+ "namespace",
179
+ "operations",
180
+ "mutation",
181
+ "resources",
182
+ "startedAt",
183
+ "endedAt",
184
+ ]) ||
185
+ !exactKeys(receipt.bindings, [
186
+ "aiwgCommit",
187
+ "endpointFingerprint",
188
+ "toolSchemaDigest",
189
+ ]) ||
190
+ !exactKeys(receipt.observed, [
191
+ "serverName",
192
+ "serverVersion",
193
+ ]) ||
194
+ !exactKeys(receipt.mutation, [
195
+ "authorized",
196
+ "attempted",
197
+ ]) ||
198
+ !exactKeys(receipt.resources, [
199
+ "maxDurationMs",
200
+ "durationMs",
201
+ "maxToolCount",
202
+ "observedToolCount",
203
+ "maxSchemaBytes",
204
+ "observedSchemaBytes",
205
+ "networkAttempts",
206
+ "toolCalls",
207
+ ]) ||
208
+ !receipt.operations.every((item) => exactKeys(item, [
209
+ "tool",
210
+ "compatible",
211
+ "code",
212
+ ])))
213
+ errors.push("CONFORMANCE_RECEIPT_SHAPE_INVALID");
214
+ if (receipt.contract !== FORTEMI_DATASET_LIVE_CONTRACT)
215
+ errors.push("CONFORMANCE_RECEIPT_CONTRACT_MISMATCH");
216
+ if (!COMMIT.test(receipt.bindings.aiwgCommit) ||
217
+ !DIGEST.test(receipt.bindings.endpointFingerprint) ||
218
+ !DIGEST.test(receipt.bindings.toolSchemaDigest))
219
+ errors.push("CONFORMANCE_RECEIPT_BINDING_INVALID");
220
+ if (!UUID_NAMESPACE.test(receipt.namespace))
221
+ errors.push("CONFORMANCE_RECEIPT_NAMESPACE_INVALID");
222
+ if (![
223
+ receipt.observed.serverName,
224
+ receipt.observed.serverVersion,
225
+ ...receipt.operations.flatMap((item) => [item.tool, item.code]),
226
+ ].every((value) => SAFE.test(value)))
227
+ errors.push("CONFORMANCE_RECEIPT_UNSAFE_VALUE");
228
+ if (receipt.mutation.authorized !== false ||
229
+ receipt.mutation.attempted !== false ||
230
+ receipt.resources.toolCalls !== 0 ||
231
+ receipt.resources.networkAttempts !== 1)
232
+ errors.push("CONFORMANCE_RECEIPT_MUTATION_INVALID");
233
+ const operationNames = receipt.operations.map((operation) => operation.tool);
234
+ const inventoryValid = receipt.operations.length === REQUIRED_TOOLS.length &&
235
+ REQUIRED_TOOLS.every((tool) => operationNames.filter((name) => name === tool).length === 1);
236
+ if (!inventoryValid ||
237
+ receipt.operations.some((operation) => operation.code !==
238
+ (operation.compatible
239
+ ? "FORTEMI_DATASET_TOOL_SCHEMA_COMPATIBLE"
240
+ : operation.code === "FORTEMI_DATASET_TOOL_MISSING"
241
+ ? operation.code
242
+ : "FORTEMI_DATASET_TOOL_SCHEMA_DRIFT")))
243
+ errors.push("CONFORMANCE_RECEIPT_OPERATION_INVALID");
244
+ const supported = inventoryValid &&
245
+ receipt.operations.every((operation) => operation.compatible);
246
+ const expectedDiagnostic = supported
247
+ ? "CONFORMANCE_FORTEMI_DATASET_PREFLIGHT_SUPPORTED"
248
+ : "CONFORMANCE_FORTEMI_DATASET_CONTRACT_UNAVAILABLE";
249
+ if (receipt.outcome !== (supported ? "supported" : "pending") ||
250
+ receipt.diagnostic !== expectedDiagnostic)
251
+ errors.push("CONFORMANCE_RECEIPT_OUTCOME_INVALID");
252
+ const start = Date.parse(receipt.startedAt);
253
+ const end = Date.parse(receipt.endedAt);
254
+ if (!Number.isFinite(start) ||
255
+ !Number.isFinite(end) ||
256
+ end < start ||
257
+ receipt.resources.durationMs !== end - start)
258
+ errors.push("CONFORMANCE_RECEIPT_TIME_INVALID");
259
+ if (!Number.isInteger(receipt.resources.maxDurationMs) ||
260
+ receipt.resources.maxDurationMs < 250 ||
261
+ receipt.resources.maxDurationMs > 30_000 ||
262
+ !Number.isInteger(receipt.resources.durationMs) ||
263
+ receipt.resources.durationMs < 0 ||
264
+ receipt.resources.maxToolCount !== MAX_TOOL_COUNT ||
265
+ !Number.isInteger(receipt.resources.observedToolCount) ||
266
+ receipt.resources.observedToolCount < 0 ||
267
+ receipt.resources.observedToolCount > MAX_TOOL_COUNT ||
268
+ receipt.resources.maxSchemaBytes !== MAX_SCHEMA_BYTES ||
269
+ !Number.isInteger(receipt.resources.observedSchemaBytes) ||
270
+ receipt.resources.observedSchemaBytes < 0 ||
271
+ receipt.resources.observedSchemaBytes > MAX_SCHEMA_BYTES)
272
+ errors.push("CONFORMANCE_RECEIPT_RESOURCES_INVALID");
273
+ if (!DIGEST.test(receipt.receiptDigest) ||
274
+ receipt.receiptDigest !== fortemiReceiptDigest(material(receipt)))
275
+ errors.push("CONFORMANCE_RECEIPT_DIGEST_MISMATCH");
276
+ return errors;
277
+ }
278
+ /** Atomically creates private evidence without replacing an existing receipt. */
279
+ export async function writeFortemiDatasetLiveReceipt(path, receipt) {
280
+ const errors = verifyFortemiDatasetLiveReceipt(receipt);
281
+ if (errors.length)
282
+ throw new Error(errors.join(","));
283
+ await mkdir(dirname(path), { recursive: true });
284
+ const temporary = `${path}.tmp-${process.pid}`;
285
+ await writeFile(temporary, `${JSON.stringify(receipt, null, 2)}\n`, {
286
+ encoding: "utf8",
287
+ mode: 0o600,
288
+ flag: "wx",
289
+ });
290
+ try {
291
+ await link(temporary, path);
292
+ }
293
+ finally {
294
+ await unlink(temporary).catch(() => undefined);
295
+ }
296
+ }
297
+ //# sourceMappingURL=fortemi-live-qualification.js.map
@@ -14,6 +14,7 @@ export * from './orchestration-repository.js';
14
14
  export * from './file-orchestration-repository.js';
15
15
  export * from './local-execution-backend.js';
16
16
  export * from './fortemi-execution-bridge.js';
17
+ export * from './fortemi-live-qualification.js';
17
18
  export * from './orchestration-service.js';
18
19
  export * from './presentation.js';
19
20
  export * from './conformance-types.js';
@@ -14,6 +14,7 @@ export * from './orchestration-repository.js';
14
14
  export * from './file-orchestration-repository.js';
15
15
  export * from './local-execution-backend.js';
16
16
  export * from './fortemi-execution-bridge.js';
17
+ export * from './fortemi-live-qualification.js';
17
18
  export * from './orchestration-service.js';
18
19
  export * from './presentation.js';
19
20
  export * from './conformance-types.js';