@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.
- package/agentic/code/providers/capability-matrix.yaml +41 -0
- package/agentic/code/providers/model-capabilities.v1.json +11 -0
- package/agentic/code/providers/model-catalog.v1.json +8 -0
- package/agentic/code/providers/pi/aiwg-bridge.ts +26 -0
- package/bin/aiwg.mjs +15 -3
- package/dist/src/api/index.d.ts +3 -0
- package/dist/src/api/index.js +3 -0
- package/dist/src/auth/credential-store.js +6 -0
- package/dist/src/channel/manager.mjs +2 -2
- package/dist/src/cli/handlers/dataset.js +186 -0
- package/dist/src/cli/handlers/help.js +2 -1
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/init.js +1 -0
- package/dist/src/cli/handlers/output-mode.js +18 -1
- package/dist/src/cli/handlers/run.js +11 -3
- package/dist/src/cli/handlers/schema.js +221 -0
- package/dist/src/cli/handlers/sessions.js +30 -12
- package/dist/src/cli/handlers/steward.js +11 -1
- package/dist/src/cli/handlers/use.js +6 -2
- package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
- package/dist/src/cli/router.js +1 -1
- package/dist/src/cli/scope-resolver.js +22 -0
- package/dist/src/dataset/adapter-sdk.d.ts +41 -0
- package/dist/src/dataset/adapter-sdk.js +147 -0
- package/dist/src/dataset/adapter-types.d.ts +179 -0
- package/dist/src/dataset/adapter-types.js +2 -0
- package/dist/src/dataset/adapters.d.ts +104 -0
- package/dist/src/dataset/adapters.js +518 -0
- package/dist/src/dataset/conformance-types.d.ts +84 -0
- package/dist/src/dataset/conformance-types.js +3 -0
- package/dist/src/dataset/conformance.d.ts +13 -0
- package/dist/src/dataset/conformance.js +90 -0
- package/dist/src/dataset/contracts.d.ts +17 -0
- package/dist/src/dataset/contracts.js +236 -0
- package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
- package/dist/src/dataset/file-orchestration-repository.js +68 -0
- package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
- package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
- package/dist/src/dataset/index.d.ts +21 -0
- package/dist/src/dataset/index.js +21 -0
- package/dist/src/dataset/ledger-types.d.ts +141 -0
- package/dist/src/dataset/ledger-types.js +2 -0
- package/dist/src/dataset/ledger.d.ts +27 -0
- package/dist/src/dataset/ledger.js +100 -0
- package/dist/src/dataset/local-execution-backend.d.ts +10 -0
- package/dist/src/dataset/local-execution-backend.js +32 -0
- package/dist/src/dataset/orchestration-repository.d.ts +29 -0
- package/dist/src/dataset/orchestration-repository.js +34 -0
- package/dist/src/dataset/orchestration-service.d.ts +56 -0
- package/dist/src/dataset/orchestration-service.js +466 -0
- package/dist/src/dataset/orchestration-types.d.ts +83 -0
- package/dist/src/dataset/orchestration-types.js +2 -0
- package/dist/src/dataset/presentation.d.ts +3 -0
- package/dist/src/dataset/presentation.js +8 -0
- package/dist/src/dataset/projections.d.ts +42 -0
- package/dist/src/dataset/projections.js +192 -0
- package/dist/src/dataset/schema-governance.d.ts +71 -0
- package/dist/src/dataset/schema-governance.js +135 -0
- package/dist/src/dataset/standards-types.d.ts +66 -0
- package/dist/src/dataset/standards-types.js +10 -0
- package/dist/src/dataset/standards.d.ts +13 -0
- package/dist/src/dataset/standards.js +291 -0
- package/dist/src/dataset/types.d.ts +258 -0
- package/dist/src/dataset/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +27 -1
- package/dist/src/installation/manager.mjs +5 -1
- package/dist/src/models/model-capabilities.v1.json +11 -0
- package/dist/src/models/model-catalog.v1.json +8 -0
- package/dist/src/models/model-discovery.js +32 -0
- package/dist/src/models/provider-policy.js +3 -2
- package/dist/src/output-modes/index.js +4 -0
- package/dist/src/output-modes/registry.js +68 -24
- package/dist/src/output-modes/runtime.js +10 -8
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +41 -0
- package/dist/src/providers/provider-definitions.js +72 -0
- package/dist/src/providers/provider-inventory.js +1 -0
- package/dist/src/schema/catalog.js +234 -0
- package/dist/src/schema/compatibility.js +42 -0
- package/dist/src/schema/diagnostics.js +36 -0
- package/dist/src/schema/index.js +8 -0
- package/dist/src/schema/policy.js +58 -0
- package/dist/src/schema/resolver.js +76 -0
- package/dist/src/schema/types.js +2 -0
- package/dist/src/schema/validator.js +82 -0
- package/dist/src/sessions/adapters/pi.js +141 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/workspace-discovery.js +10 -0
- package/dist/src/storage/backends/fortemi.js +6 -0
- package/dist/src/storage/config.js +18 -4
- package/dist/src/storage/fortemi-qualification.js +106 -0
- package/dist/src/storage/index.js +1 -0
- package/dist/src/storage/types.js +1 -1
- package/package.json +3 -1
- package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
- package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
- package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
- package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
- package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
- package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
- package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
- package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
- package/schemas/dataset/run-ledger.v1.schema.json +39 -0
- package/schemas/dataset/source-adapter.v1.schema.json +112 -0
- package/tools/agents/deploy-agents.mjs +9 -3
- package/tools/agents/providers/pi.mjs +176 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
4
|
+
import { mkdirSync } from 'node:fs';
|
|
5
|
+
import { parse as parseYaml } from 'yaml';
|
|
6
|
+
import { analyzeBackwardCompatibility, loadSchemaCatalog, SchemaResolver, SchemaValidator } from '../../schema/index.js';
|
|
7
|
+
const ACTIONS = ['list', 'show', 'graph', 'policy', 'validate', 'lint', 'check-refs', 'diff', 'compatibility', 'generate', 'verify-projections'];
|
|
8
|
+
function value(args, flag) {
|
|
9
|
+
const index = args.indexOf(flag);
|
|
10
|
+
return index < 0 ? undefined : args[index + 1];
|
|
11
|
+
}
|
|
12
|
+
function positional(args) {
|
|
13
|
+
const flagsWithValues = new Set(['--catalog', '--domain', '--lifecycle', '--format', '--direction', '--schema', '--against']);
|
|
14
|
+
const result = [];
|
|
15
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
16
|
+
const arg = args[index];
|
|
17
|
+
if (!arg)
|
|
18
|
+
continue;
|
|
19
|
+
if (flagsWithValues.has(arg)) {
|
|
20
|
+
index += 1;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (!arg.startsWith('-'))
|
|
24
|
+
result.push(arg);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
function jsonResult(payload, exitCode = 0) {
|
|
29
|
+
return { exitCode, rawOutput: true, message: `${JSON.stringify(payload, null, 2)}\n` };
|
|
30
|
+
}
|
|
31
|
+
function digest(bytes) {
|
|
32
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
|
33
|
+
}
|
|
34
|
+
function readData(path) {
|
|
35
|
+
const source = readFileSync(path, 'utf8');
|
|
36
|
+
return ['.yaml', '.yml'].includes(extname(path).toLowerCase()) ? parseYaml(source) : JSON.parse(source);
|
|
37
|
+
}
|
|
38
|
+
function diagnostic(code, message, entry, path) {
|
|
39
|
+
return { code, severity: 'error', message, ...(entry ? { artifactId: entry.artifact.id } : {}), ...(path ? { path } : {}) };
|
|
40
|
+
}
|
|
41
|
+
function load(ctx) {
|
|
42
|
+
const catalogPath = value(ctx.args, '--catalog');
|
|
43
|
+
const result = loadSchemaCatalog({ rootDir: ctx.cwd, ...(catalogPath ? { catalogPath } : {}) });
|
|
44
|
+
return {
|
|
45
|
+
...(result.catalog ? { resolver: new SchemaResolver(result.catalog, { rootDir: ctx.cwd }) } : {}),
|
|
46
|
+
diagnostics: result.diagnostics,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function authorityPath(ctx, entry) {
|
|
50
|
+
const path = entry.artifact.authority.path;
|
|
51
|
+
if (!path)
|
|
52
|
+
throw new Error(`${entry.artifact.id} has no local canonical authority`);
|
|
53
|
+
return resolve(ctx.cwd, path);
|
|
54
|
+
}
|
|
55
|
+
function schemaRefs(node, refs = []) {
|
|
56
|
+
if (Array.isArray(node))
|
|
57
|
+
for (const child of node)
|
|
58
|
+
schemaRefs(child, refs);
|
|
59
|
+
else if (typeof node === 'object' && node !== null) {
|
|
60
|
+
for (const [key, child] of Object.entries(node)) {
|
|
61
|
+
if (key === '$ref' && typeof child === 'string')
|
|
62
|
+
refs.push(child);
|
|
63
|
+
else
|
|
64
|
+
schemaRefs(child, refs);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return refs;
|
|
68
|
+
}
|
|
69
|
+
function resolveBaseline(ctx, resolver, query) {
|
|
70
|
+
const entry = resolver.resolve(query);
|
|
71
|
+
return readData(entry ? authorityPath(ctx, entry) : resolve(ctx.cwd, query));
|
|
72
|
+
}
|
|
73
|
+
async function execute(ctx) {
|
|
74
|
+
const [action = 'list', ...terms] = positional(ctx.args);
|
|
75
|
+
if (!ACTIONS.includes(action))
|
|
76
|
+
return { exitCode: 1, message: `Unknown schema action '${action}'.` };
|
|
77
|
+
const loaded = load(ctx);
|
|
78
|
+
if (!loaded.resolver)
|
|
79
|
+
return jsonResult({ schema: 'aiwg.schema.diagnostics.v1', valid: false, diagnostics: loaded.diagnostics }, 1);
|
|
80
|
+
const resolver = loaded.resolver;
|
|
81
|
+
const query = terms[0];
|
|
82
|
+
if (action === 'list') {
|
|
83
|
+
const entries = resolver.list({ domain: value(ctx.args, '--domain'), lifecycle: value(ctx.args, '--lifecycle'), format: value(ctx.args, '--format') });
|
|
84
|
+
return jsonResult({ schema: 'aiwg.schema.list.v1', entries: entries.map(({ artifact, domain, digest: hash }) => ({ id: artifact.id, logicalName: artifact.logicalName, version: artifact.version, domain, format: artifact.format, lifecycle: artifact.lifecycle, digest: hash })) });
|
|
85
|
+
}
|
|
86
|
+
if (action === 'show') {
|
|
87
|
+
if (!query)
|
|
88
|
+
return { exitCode: 1, message: 'Usage: aiwg schema show <id|name@version|path>' };
|
|
89
|
+
const entry = resolver.require(query);
|
|
90
|
+
return jsonResult({ schema: 'aiwg.schema.show.v1', ...entry });
|
|
91
|
+
}
|
|
92
|
+
if (action === 'graph') {
|
|
93
|
+
const direction = value(ctx.args, '--direction');
|
|
94
|
+
if (direction && !['dependencies', 'dependents', 'both'].includes(direction))
|
|
95
|
+
throw new Error(`Invalid graph direction '${direction}'`);
|
|
96
|
+
return jsonResult({ schema: 'aiwg.schema.graph.v1', ...resolver.graph(query, { direction: direction }) });
|
|
97
|
+
}
|
|
98
|
+
if (action === 'policy') {
|
|
99
|
+
const effective = ctx.args.includes('--effective');
|
|
100
|
+
if (!effective || !query)
|
|
101
|
+
return { exitCode: 1, message: 'Usage: aiwg schema policy --effective <id>' };
|
|
102
|
+
return jsonResult({ schema: 'aiwg.schema.policy.v1', id: resolver.require(query).artifact.id, effective: resolver.policy(query) });
|
|
103
|
+
}
|
|
104
|
+
if (action === 'validate') {
|
|
105
|
+
const schemaQuery = value(ctx.args, '--schema');
|
|
106
|
+
const instance = schemaQuery ? query : terms[1];
|
|
107
|
+
const selected = schemaQuery ?? query;
|
|
108
|
+
if (!selected || !instance)
|
|
109
|
+
return { exitCode: 1, message: 'Usage: aiwg schema validate --schema <id> <instance>' };
|
|
110
|
+
const entry = resolver.require(selected);
|
|
111
|
+
if (entry.artifact.format !== 'json-schema')
|
|
112
|
+
throw new Error(`Validation currently requires a JSON Schema artifact; got ${entry.artifact.format}`);
|
|
113
|
+
const data = readData(resolve(ctx.cwd, instance));
|
|
114
|
+
const result = new SchemaValidator(resolver, { rootDir: ctx.cwd }).validate(entry.artifact.id, data);
|
|
115
|
+
return jsonResult({ schema: 'aiwg.schema.validation.v1', ...result, instance }, result.valid ? 0 : 1);
|
|
116
|
+
}
|
|
117
|
+
if (action === 'lint') {
|
|
118
|
+
const diagnostics = [...loaded.diagnostics];
|
|
119
|
+
const entries = resolver.list();
|
|
120
|
+
for (const entry of entries) {
|
|
121
|
+
if (entry.artifact.format !== 'json-schema')
|
|
122
|
+
continue;
|
|
123
|
+
try {
|
|
124
|
+
new SchemaValidator(resolver, { rootDir: ctx.cwd }).compile(entry.artifact.id);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
diagnostics.push(diagnostic('SCHEMA_COMPILE_FAILED', error instanceof Error ? error.message : String(error), entry, entry.artifact.authority.path));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const valid = !diagnostics.some(item => item.severity === 'error');
|
|
131
|
+
return jsonResult({ schema: 'aiwg.schema.diagnostics.v1', valid, diagnostics }, valid ? 0 : 1);
|
|
132
|
+
}
|
|
133
|
+
if (action === 'check-refs') {
|
|
134
|
+
const diagnostics = [];
|
|
135
|
+
for (const entry of resolver.list()) {
|
|
136
|
+
for (const dependency of entry.artifact.dependencies ?? [])
|
|
137
|
+
if (!resolver.resolve(dependency.id) && !dependency.optional)
|
|
138
|
+
diagnostics.push(diagnostic('SCHEMA_DEPENDENCY_UNRESOLVED', `Unresolved dependency ${dependency.id}`, entry));
|
|
139
|
+
if (entry.artifact.format !== 'json-schema' || !entry.artifact.authority.path)
|
|
140
|
+
continue;
|
|
141
|
+
for (const ref of schemaRefs(readData(authorityPath(ctx, entry)))) {
|
|
142
|
+
if (ref.startsWith('#'))
|
|
143
|
+
continue;
|
|
144
|
+
const base = ref.split('#')[0];
|
|
145
|
+
if (!base)
|
|
146
|
+
continue;
|
|
147
|
+
const local = resolve(dirname(authorityPath(ctx, entry)), base);
|
|
148
|
+
if (!resolver.resolve(base) && !resolver.resolve(ref) && !existsSync(local))
|
|
149
|
+
diagnostics.push(diagnostic('SCHEMA_REFERENCE_UNRESOLVED', `Unresolved $ref ${ref}`, entry, entry.artifact.authority.path));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return jsonResult({ schema: 'aiwg.schema.references.v1', valid: diagnostics.length === 0, diagnostics }, diagnostics.length ? 1 : 0);
|
|
153
|
+
}
|
|
154
|
+
if (action === 'diff' || action === 'compatibility') {
|
|
155
|
+
const against = value(ctx.args, '--against');
|
|
156
|
+
if (!query || !against)
|
|
157
|
+
return { exitCode: 1, message: `Usage: aiwg schema ${action} <id|path> --against <id|path>` };
|
|
158
|
+
const current = resolveBaseline(ctx, resolver, query);
|
|
159
|
+
const baseline = resolveBaseline(ctx, resolver, against);
|
|
160
|
+
const result = analyzeBackwardCompatibility(baseline, current);
|
|
161
|
+
return jsonResult({ schema: action === 'diff' ? 'aiwg.schema.diff.v1' : 'aiwg.schema.compatibility.v1', current: query, baseline: against, ...result }, result.status === 'breaking' ? 1 : 0);
|
|
162
|
+
}
|
|
163
|
+
if (action === 'verify-projections') {
|
|
164
|
+
const entries = query ? [resolver.require(query)] : resolver.list();
|
|
165
|
+
const diagnostics = [];
|
|
166
|
+
for (const entry of entries)
|
|
167
|
+
for (const projection of entry.artifact.projections ?? []) {
|
|
168
|
+
const path = resolve(ctx.cwd, projection.path);
|
|
169
|
+
if (!existsSync(path)) {
|
|
170
|
+
diagnostics.push(diagnostic('SCHEMA_PROJECTION_MISSING', `Projection does not exist: ${projection.path}`, entry, projection.path));
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const actual = digest(readFileSync(path));
|
|
174
|
+
if (projection.digest && projection.digest !== actual)
|
|
175
|
+
diagnostics.push(diagnostic('SCHEMA_PROJECTION_DRIFT', `Projection digest does not match: ${projection.path}`, entry, projection.path));
|
|
176
|
+
if (projection.kind === 'mirror' && readFileSync(path).compare(readFileSync(authorityPath(ctx, entry))) !== 0)
|
|
177
|
+
diagnostics.push(diagnostic('SCHEMA_PROJECTION_DRIFT', `Mirror differs from canonical authority: ${projection.path}`, entry, projection.path));
|
|
178
|
+
}
|
|
179
|
+
return jsonResult({ schema: 'aiwg.schema.projections.v1', valid: diagnostics.length === 0, diagnostics }, diagnostics.length ? 1 : 0);
|
|
180
|
+
}
|
|
181
|
+
const entries = query ? [resolver.require(query)] : resolver.list();
|
|
182
|
+
const projections = entries.flatMap(entry => (entry.artifact.projections ?? []).map(projection => ({ artifactId: entry.artifact.id, source: entry.artifact.authority.path, ...projection })));
|
|
183
|
+
if (ctx.args.includes('--write'))
|
|
184
|
+
for (const item of projections) {
|
|
185
|
+
if (item.kind !== 'mirror' || !item.source)
|
|
186
|
+
continue;
|
|
187
|
+
const target = resolve(ctx.cwd, item.path);
|
|
188
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
189
|
+
writeFileSync(target, readFileSync(resolve(ctx.cwd, item.source)));
|
|
190
|
+
}
|
|
191
|
+
return jsonResult({ schema: 'aiwg.schema.generation-plan.v1', write: ctx.args.includes('--write'), projections });
|
|
192
|
+
}
|
|
193
|
+
export const schemaHandler = {
|
|
194
|
+
id: 'schema',
|
|
195
|
+
name: 'Schema Control Plane',
|
|
196
|
+
description: 'Discover, validate, compare, and verify governed schema artifacts',
|
|
197
|
+
category: 'utility',
|
|
198
|
+
aliases: ['schemas'],
|
|
199
|
+
async help() {
|
|
200
|
+
return { exitCode: 0, rawOutput: true, message: [
|
|
201
|
+
'Usage: aiwg schema <action> [options]',
|
|
202
|
+
'',
|
|
203
|
+
'Discovery: list, show <id>, graph [id], policy --effective <id>',
|
|
204
|
+
'Quality: lint, check-refs, validate --schema <id> <instance>',
|
|
205
|
+
'Evolution: diff <id|path> --against <id|path>, compatibility <id|path> --against <id|path>',
|
|
206
|
+
'Projections: generate [id] [--write], verify-projections [id]',
|
|
207
|
+
'',
|
|
208
|
+
'Common options: --catalog <path>, --domain <id>, --lifecycle <state>, --format <format>',
|
|
209
|
+
'All command results use stable versioned JSON envelopes.',
|
|
210
|
+
].join('\n') };
|
|
211
|
+
},
|
|
212
|
+
async execute(ctx) {
|
|
213
|
+
try {
|
|
214
|
+
return await execute(ctx);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
return jsonResult({ schema: 'aiwg.schema.error.v1', error: error instanceof Error ? error.message : String(error) }, 1);
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
|
|
2
2
|
import { dirname, isAbsolute, resolve, } from 'node:path';
|
|
3
3
|
import { pathToFileURL } from 'node:url';
|
|
4
|
-
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
|
|
4
|
+
import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, PI_ADAPTER_VERSION, PiSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
|
|
5
5
|
const JSON_CONTRACT_VERSION = '1.0.0';
|
|
6
6
|
async function createLineMemoryPromotionDestination(projectRoot, manifestPath) {
|
|
7
7
|
const modulePath = resolve(dirname(manifestPath), 'commands', 'line-memory.mjs');
|
|
@@ -622,7 +622,7 @@ async function importSource(ctx, args) {
|
|
|
622
622
|
if (provider !== 'generic' && provider !== 'claude' && provider !== 'codex'
|
|
623
623
|
&& provider !== 'copilot' && provider !== 'cursor' && provider !== 'factory'
|
|
624
624
|
&& provider !== 'hermes' && provider !== 'opencode' && provider !== 'openclaw'
|
|
625
|
-
&& provider !== 'openhuman' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
625
|
+
&& provider !== 'openhuman' && provider !== 'pi' && provider !== 'warp' && provider !== 'devin-desktop') {
|
|
626
626
|
throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
|
|
627
627
|
}
|
|
628
628
|
const sourceId = requiredValue(args, '--source-id');
|
|
@@ -638,6 +638,7 @@ async function importSource(ctx, args) {
|
|
|
638
638
|
const isOpenCode = provider === 'opencode';
|
|
639
639
|
const isOpenClaw = provider === 'openclaw';
|
|
640
640
|
const isOpenHuman = provider === 'openhuman';
|
|
641
|
+
const isPi = provider === 'pi';
|
|
641
642
|
const isWarp = provider === 'warp';
|
|
642
643
|
const isDevinDesktop = provider === 'devin-desktop';
|
|
643
644
|
const adapter = isClaude
|
|
@@ -658,11 +659,13 @@ async function importSource(ctx, args) {
|
|
|
658
659
|
? new OpenClawSessionAdapter()
|
|
659
660
|
: isOpenHuman
|
|
660
661
|
? new OpenHumanSessionAdapter()
|
|
661
|
-
:
|
|
662
|
-
? new
|
|
663
|
-
:
|
|
664
|
-
? new
|
|
665
|
-
:
|
|
662
|
+
: isPi
|
|
663
|
+
? new PiSessionAdapter()
|
|
664
|
+
: isWarp
|
|
665
|
+
? new WarpSessionAdapter()
|
|
666
|
+
: isDevinDesktop
|
|
667
|
+
? new DevinDesktopSessionAdapter()
|
|
668
|
+
: new GenericSessionInterchangeAdapter();
|
|
666
669
|
const locatorClass = isClaude
|
|
667
670
|
? (input.endsWith('.hooks.jsonl') ? 'claude-hook-jsonl' : 'claude-transcript-jsonl')
|
|
668
671
|
: isCodex
|
|
@@ -681,11 +684,13 @@ async function importSource(ctx, args) {
|
|
|
681
684
|
? 'openclaw-consistent-snapshot-jsonl'
|
|
682
685
|
: isOpenHuman
|
|
683
686
|
? 'openhuman-enriched-jsonl'
|
|
684
|
-
:
|
|
685
|
-
? '
|
|
686
|
-
:
|
|
687
|
-
? '
|
|
688
|
-
:
|
|
687
|
+
: isPi
|
|
688
|
+
? 'pi-session-v3-jsonl'
|
|
689
|
+
: isWarp
|
|
690
|
+
? 'warp-markdown-export'
|
|
691
|
+
: isDevinDesktop
|
|
692
|
+
? 'devin-desktop-cascade-hook-jsonl'
|
|
693
|
+
: 'manual-export';
|
|
689
694
|
const selectedSource = {
|
|
690
695
|
provider, locator: input, locatorClass, sourceId,
|
|
691
696
|
authorizedScope: { workspaceId, allowedRoots: [dirname(input)] },
|
|
@@ -1007,6 +1012,19 @@ function providerDisposition(provider) {
|
|
|
1007
1012
|
},
|
|
1008
1013
|
};
|
|
1009
1014
|
}
|
|
1015
|
+
if (provider === 'pi') {
|
|
1016
|
+
return {
|
|
1017
|
+
provider, disposition: 'implemented', operationalState: 'available',
|
|
1018
|
+
supportedOperations: ['discover', 'inspect', 'stream'],
|
|
1019
|
+
acquisitionModes: ['jsonl'], reasonCode: null,
|
|
1020
|
+
remediation: 'Authorize PI_CODING_AGENT_SESSION_DIR, the default Pi sessions root, or an explicit v3 JSONL export.',
|
|
1021
|
+
evidence: {
|
|
1022
|
+
adapterVersion: PI_ADAPTER_VERSION,
|
|
1023
|
+
verifiedAt: '2026-09-04',
|
|
1024
|
+
documentation: 'https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/session-manager.ts',
|
|
1025
|
+
},
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1010
1028
|
if (provider === 'warp') {
|
|
1011
1029
|
return {
|
|
1012
1030
|
provider, disposition: 'manual-only', operationalState: 'available',
|
|
@@ -29,6 +29,9 @@ import { buildWrapperRouteEnvelope } from '../../models/wrapper-route.js';
|
|
|
29
29
|
import { loadProviderModelCatalog, } from '../../models/provider-policy.js';
|
|
30
30
|
import { CapabilityResolutionError, resolveRoutableCapability, } from '../../artifacts/capability-resolver.js';
|
|
31
31
|
const BASELINE_PROVIDER = 'claude-code';
|
|
32
|
+
function isModelPolicyProvider(provider) {
|
|
33
|
+
return Object.hasOwn(loadProviderModelCatalog().providers, provider);
|
|
34
|
+
}
|
|
32
35
|
// ── Feature name normalization ────────────────────────────────────────────────
|
|
33
36
|
/**
|
|
34
37
|
* Accept both hyphenated (agent-teams) and underscored (agent_teams) feature
|
|
@@ -192,7 +195,7 @@ async function handleSteward(args, ctx) {
|
|
|
192
195
|
aiwg steward permissions migrate --apply Back up and atomically normalize config
|
|
193
196
|
|
|
194
197
|
Providers:
|
|
195
|
-
claude-code, codex, copilot, cursor, factory, opencode, warp, windsurf, hermes, openclaw
|
|
198
|
+
claude-code, codex, copilot, cursor, factory, opencode, pi, warp, windsurf, hermes, openclaw
|
|
196
199
|
|
|
197
200
|
Features:
|
|
198
201
|
cron, agent_teams, tasks, mcp, behaviors, mission_control, daemon
|
|
@@ -455,6 +458,13 @@ async function handleSteward(args, ctx) {
|
|
|
455
458
|
hint: 'Pass --provider with a supported provider id.',
|
|
456
459
|
exitCode: EXIT_CODES.USAGE,
|
|
457
460
|
});
|
|
461
|
+
if (!isModelPolicyProvider(provider))
|
|
462
|
+
throw new AiwgError({
|
|
463
|
+
code: 'ERR_USAGE_UNSUPPORTED_PROVIDER',
|
|
464
|
+
message: `Model wrapper routing is not implemented for provider: ${provider}`,
|
|
465
|
+
hint: 'Use `aiwg steward capabilities --provider pi` for current Pi capability routing; model routing is tracked separately.',
|
|
466
|
+
exitCode: EXIT_CODES.USAGE,
|
|
467
|
+
});
|
|
458
468
|
const capabilityType = flagValue('--capability-type');
|
|
459
469
|
const capability = flagValue('--capability');
|
|
460
470
|
const assignment = flagValue('--assignment');
|
|
@@ -2922,7 +2922,7 @@ export class UseHandler {
|
|
|
2922
2922
|
catch {
|
|
2923
2923
|
// Profile selection is optional — don't fail deployment
|
|
2924
2924
|
}
|
|
2925
|
-
if (framework === 'aiwg-utils' && !remainingArgs.includes('--dry-run')) {
|
|
2925
|
+
if (framework === 'aiwg-utils' && provider !== 'pi' && !remainingArgs.includes('--dry-run')) {
|
|
2926
2926
|
const wrapperValidation = await validateDeployedModelWrappers({
|
|
2927
2927
|
provider: normalizeProviderDefinitionId(provider) ?? provider,
|
|
2928
2928
|
target,
|
|
@@ -3025,6 +3025,7 @@ export class UseHandler {
|
|
|
3025
3025
|
? withProviderOverride(deployFilteredArgs, provider)
|
|
3026
3026
|
: deployFilteredArgs;
|
|
3027
3027
|
const bulkKernelOnly = framework === 'all'
|
|
3028
|
+
&& provider !== 'pi'
|
|
3028
3029
|
&& !remainingArgs.includes('--copy-all')
|
|
3029
3030
|
&& !remainingArgs.includes('--copy-standard-skills');
|
|
3030
3031
|
if (bulkKernelOnly)
|
|
@@ -3230,7 +3231,10 @@ export class UseHandler {
|
|
|
3230
3231
|
}
|
|
3231
3232
|
await ensureProviderGeneratedDirsIgnored(target, provider, { dryRun, verbose });
|
|
3232
3233
|
const paths = getProviderPaths(provider);
|
|
3233
|
-
|
|
3234
|
+
// Pi model selection/headless routing is delivered by #2151. Until that
|
|
3235
|
+
// adapter exists, do not require model-wrapper artifacts that Pi cannot
|
|
3236
|
+
// load; resource deployment remains independently valid.
|
|
3237
|
+
if (!dryRun && !skipUtils && !bulkKernelOnly && provider !== 'pi') {
|
|
3234
3238
|
const wrapperValidation = await validateDeployedModelWrappers({
|
|
3235
3239
|
provider,
|
|
3236
3240
|
target,
|
|
@@ -74,6 +74,12 @@ export const activityLogPostCommandHook = {
|
|
|
74
74
|
commands: Object.keys(COMMAND_OPERATION_MAP),
|
|
75
75
|
},
|
|
76
76
|
async execute(ctx) {
|
|
77
|
+
// A dry-run is a strict no-write contract. The command handler may preview
|
|
78
|
+
// mutations, but the post-command hook must not create activity.log as a
|
|
79
|
+
// side effect of that preview.
|
|
80
|
+
if (ctx.args.includes('--dry-run')) {
|
|
81
|
+
return { action: 'continue' };
|
|
82
|
+
}
|
|
77
83
|
// Skip on env-var opt-out
|
|
78
84
|
if (isActivityLogSkipped()) {
|
|
79
85
|
return { action: 'continue' };
|
package/dist/src/cli/router.js
CHANGED
|
@@ -316,7 +316,7 @@ async function logCommandInvocation(input) {
|
|
|
316
316
|
* @returns Handler context
|
|
317
317
|
*/
|
|
318
318
|
async function buildContext(args, rawArgs, options) {
|
|
319
|
-
const frameworkRoot = await getFrameworkRoot();
|
|
319
|
+
const frameworkRoot = await getFrameworkRoot({ createIfMissing: !args.includes('--dry-run') });
|
|
320
320
|
const ctx = {
|
|
321
321
|
args,
|
|
322
322
|
rawArgs,
|
|
@@ -13,6 +13,16 @@ import { homedir } from 'node:os';
|
|
|
13
13
|
import * as path from 'node:path';
|
|
14
14
|
import { resolveHermesHome, resolveHermesHomePath } from '../providers/hermes-home.js';
|
|
15
15
|
export const hermesHome = resolveHermesHome;
|
|
16
|
+
function piAgentDir() {
|
|
17
|
+
const configured = process.env.PI_CODING_AGENT_DIR;
|
|
18
|
+
if (!configured)
|
|
19
|
+
return path.join(homedir(), '.pi', 'agent');
|
|
20
|
+
if (configured === '~')
|
|
21
|
+
return homedir();
|
|
22
|
+
if (configured.startsWith('~/'))
|
|
23
|
+
return path.join(homedir(), configured.slice(2));
|
|
24
|
+
return configured;
|
|
25
|
+
}
|
|
16
26
|
/**
|
|
17
27
|
* User-scope deploy paths per provider per ADR-4 §2. Each path is absolute
|
|
18
28
|
* (rooted in os.homedir()) so the orchestrator's existing path-join logic
|
|
@@ -47,6 +57,18 @@ export const USER_SCOPE_PATHS = {
|
|
|
47
57
|
rules: '',
|
|
48
58
|
behaviors: '',
|
|
49
59
|
},
|
|
60
|
+
pi: {
|
|
61
|
+
// Pi's global resource root is configurable. Unlike project deployment,
|
|
62
|
+
// user-scope resources belong under the effective agent directory. Keep
|
|
63
|
+
// skills on this single native path instead of also copying them to
|
|
64
|
+
// ~/.agents/skills, which would create duplicate Pi discovery entries.
|
|
65
|
+
// PI_CODING_AGENT_DIR is configuration only and is not runtime evidence.
|
|
66
|
+
agents: '',
|
|
67
|
+
skills: path.join(piAgentDir(), 'skills'),
|
|
68
|
+
commands: path.join(piAgentDir(), 'prompts'),
|
|
69
|
+
rules: '',
|
|
70
|
+
behaviors: path.join(piAgentDir(), 'extensions'),
|
|
71
|
+
},
|
|
50
72
|
copilot: {
|
|
51
73
|
// #1160 — Non-applicable for filesystem user-scope discovery.
|
|
52
74
|
//
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AdapterCheckpoint, AdapterConfigurationResult, AdapterDiagnostic, AdapterDiagnosticCode, AdapterLimits, AdapterManifest, AdapterOperation, AdapterQualificationCell, AdapterQualificationReport, AdapterRequest, AdapterSchemaDeclaration, CredentialLocator, DatasetSourceAdapter } from './adapter-types.js';
|
|
2
|
+
export declare const DEFAULT_ADAPTER_LIMITS: AdapterLimits;
|
|
3
|
+
export declare function sha256Digest(value: string | Uint8Array): {
|
|
4
|
+
algorithm: 'sha256';
|
|
5
|
+
value: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function canonicalJson(value: unknown): string;
|
|
8
|
+
export declare function diagnostic(operation: AdapterOperation, code: AdapterDiagnosticCode, message: string, options?: {
|
|
9
|
+
path?: string;
|
|
10
|
+
retryable?: boolean;
|
|
11
|
+
severity?: AdapterDiagnostic['severity'];
|
|
12
|
+
}): AdapterDiagnostic;
|
|
13
|
+
export declare class AdapterFailure extends Error {
|
|
14
|
+
readonly diagnostic: AdapterDiagnostic;
|
|
15
|
+
constructor(diagnostic: AdapterDiagnostic);
|
|
16
|
+
}
|
|
17
|
+
export declare function assertActive(signal: AbortSignal | undefined, operation: AdapterOperation): void;
|
|
18
|
+
export declare function effectiveLimits(manifest: AdapterManifest, requested: AdapterLimits): AdapterLimits;
|
|
19
|
+
export declare function isCredentialLocator(value: unknown): value is CredentialLocator;
|
|
20
|
+
export declare function containsRawSecret(value: unknown, key?: string): boolean;
|
|
21
|
+
export declare function redactAdapterValue(value: unknown, key?: string): unknown;
|
|
22
|
+
export declare function schemaDeclaration(id: string, schema: Record<string, unknown>): AdapterSchemaDeclaration;
|
|
23
|
+
export declare function configureObject<T extends Record<string, unknown>>(operation: AdapterOperation, value: unknown, validate: (candidate: Record<string, unknown>) => candidate is T): AdapterConfigurationResult<T>;
|
|
24
|
+
export declare function validateCheckpoint(checkpoint: AdapterCheckpoint | undefined, manifest: AdapterManifest, sourceIdentity: string): AdapterDiagnostic[];
|
|
25
|
+
export declare class AdapterRegistry {
|
|
26
|
+
#private;
|
|
27
|
+
private readonly trust;
|
|
28
|
+
constructor(trust: {
|
|
29
|
+
allowIds: Set<string>;
|
|
30
|
+
allowUntrusted?: boolean;
|
|
31
|
+
});
|
|
32
|
+
register(adapter: DatasetSourceAdapter): void;
|
|
33
|
+
require(id: string, version: string): DatasetSourceAdapter;
|
|
34
|
+
}
|
|
35
|
+
export declare function qualifyAdapter(adapter: DatasetSourceAdapter, options: {
|
|
36
|
+
fixtureRevision: string;
|
|
37
|
+
qualifiedAt: string;
|
|
38
|
+
cells: AdapterQualificationCell[];
|
|
39
|
+
}): Promise<AdapterQualificationReport>;
|
|
40
|
+
export declare function request<T extends Record<string, unknown>>(requestId: string, config: T, policy: AdapterRequest<T>['policy'], limits?: Partial<AdapterLimits>): AdapterRequest<T>;
|
|
41
|
+
//# sourceMappingURL=adapter-sdk.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { DATASET_ADAPTER_CONTRACT_VERSION } from './adapter-types.js';
|
|
3
|
+
export const DEFAULT_ADAPTER_LIMITS = Object.freeze({
|
|
4
|
+
maxRecords: 10_000,
|
|
5
|
+
maxBytes: 16 * 1024 * 1024,
|
|
6
|
+
maxRecordBytes: 1024 * 1024,
|
|
7
|
+
timeoutMs: 30_000,
|
|
8
|
+
maxRedirects: 3,
|
|
9
|
+
maxDepth: 16,
|
|
10
|
+
});
|
|
11
|
+
export function sha256Digest(value) {
|
|
12
|
+
return { algorithm: 'sha256', value: createHash('sha256').update(value).digest('hex') };
|
|
13
|
+
}
|
|
14
|
+
export function canonicalJson(value) {
|
|
15
|
+
if (value === null || typeof value !== 'object')
|
|
16
|
+
return JSON.stringify(value);
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
19
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(',')}}`;
|
|
20
|
+
}
|
|
21
|
+
export function diagnostic(operation, code, message, options = {}) {
|
|
22
|
+
return { code, message, operation, retryable: options.retryable ?? false, severity: options.severity ?? 'error', ...(options.path ? { path: options.path } : {}) };
|
|
23
|
+
}
|
|
24
|
+
export class AdapterFailure extends Error {
|
|
25
|
+
diagnostic;
|
|
26
|
+
constructor(diagnostic) {
|
|
27
|
+
super(diagnostic.message);
|
|
28
|
+
this.diagnostic = diagnostic;
|
|
29
|
+
this.name = 'AdapterFailure';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function assertActive(signal, operation) {
|
|
33
|
+
if (signal?.aborted)
|
|
34
|
+
throw new AdapterFailure(diagnostic(operation, 'ADAPTER_CANCELLED', 'The adapter operation was cancelled.'));
|
|
35
|
+
}
|
|
36
|
+
export function effectiveLimits(manifest, requested) {
|
|
37
|
+
const positive = (value, ceiling) => Math.max(1, Math.min(Number.isFinite(value) ? Math.trunc(value) : 1, ceiling));
|
|
38
|
+
return {
|
|
39
|
+
maxRecords: positive(requested.maxRecords, manifest.limits.maxRecords),
|
|
40
|
+
maxBytes: positive(requested.maxBytes, manifest.limits.maxBytes),
|
|
41
|
+
maxRecordBytes: positive(requested.maxRecordBytes, manifest.limits.maxRecordBytes),
|
|
42
|
+
timeoutMs: positive(requested.timeoutMs, manifest.limits.timeoutMs),
|
|
43
|
+
maxRedirects: Math.max(0, Math.min(Math.trunc(requested.maxRedirects), manifest.limits.maxRedirects)),
|
|
44
|
+
maxDepth: positive(requested.maxDepth, manifest.limits.maxDepth),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function isCredentialLocator(value) {
|
|
48
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
49
|
+
return false;
|
|
50
|
+
const candidate = value;
|
|
51
|
+
return ['opaque', 'environment', 'keychain', 'vault'].includes(String(candidate.kind))
|
|
52
|
+
&& typeof candidate.locator === 'string' && candidate.locator.length > 0 && candidate.locator.length <= 512;
|
|
53
|
+
}
|
|
54
|
+
const SECRET_KEY = /(?:secret|password|passwd|token|api[-_]?key|credential)/iu;
|
|
55
|
+
export function containsRawSecret(value, key = '') {
|
|
56
|
+
if (SECRET_KEY.test(key))
|
|
57
|
+
return !isCredentialLocator(value);
|
|
58
|
+
if (Array.isArray(value))
|
|
59
|
+
return value.some(item => containsRawSecret(item));
|
|
60
|
+
if (value && typeof value === 'object')
|
|
61
|
+
return Object.entries(value).some(([childKey, child]) => containsRawSecret(child, childKey));
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
export function redactAdapterValue(value, key = '') {
|
|
65
|
+
if (SECRET_KEY.test(key))
|
|
66
|
+
return '[credential-locator-redacted]';
|
|
67
|
+
if (Array.isArray(value))
|
|
68
|
+
return value.map(item => redactAdapterValue(item));
|
|
69
|
+
if (value && typeof value === 'object')
|
|
70
|
+
return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, redactAdapterValue(child, childKey)]));
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
export function schemaDeclaration(id, schema) {
|
|
74
|
+
const version = '1.0.0';
|
|
75
|
+
return { id, version, digest: sha256Digest(canonicalJson(schema)), dialect: 'https://json-schema.org/draft/2020-12/schema', schema };
|
|
76
|
+
}
|
|
77
|
+
export function configureObject(operation, value, validate) {
|
|
78
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || containsRawSecret(value)) {
|
|
79
|
+
return { ok: false, diagnostics: [diagnostic(operation, containsRawSecret(value) ? 'ADAPTER_SECRET_REJECTED' : 'ADAPTER_INVALID_CONFIGURATION', containsRawSecret(value) ? 'Secret-like fields must contain an opaque credential locator, never credential material.' : 'Configuration must be an object accepted by the adapter schema.')] };
|
|
80
|
+
}
|
|
81
|
+
const candidate = structuredClone(value);
|
|
82
|
+
if (!validate(candidate))
|
|
83
|
+
return { ok: false, diagnostics: [diagnostic(operation, 'ADAPTER_INVALID_CONFIGURATION', 'Configuration does not satisfy the adapter contract.')] };
|
|
84
|
+
return { ok: true, config: candidate, configDigest: sha256Digest(canonicalJson(redactAdapterValue(candidate))), diagnostics: [] };
|
|
85
|
+
}
|
|
86
|
+
export function validateCheckpoint(checkpoint, manifest, sourceIdentity) {
|
|
87
|
+
if (!checkpoint)
|
|
88
|
+
return [];
|
|
89
|
+
const valid = checkpoint.contractVersion === DATASET_ADAPTER_CONTRACT_VERSION
|
|
90
|
+
&& checkpoint.kind === 'AdapterCheckpoint'
|
|
91
|
+
&& checkpoint.adapter.id === manifest.id
|
|
92
|
+
&& checkpoint.adapter.version === manifest.version
|
|
93
|
+
&& checkpoint.sourceIdentity === sourceIdentity
|
|
94
|
+
&& /^-?\d+$/u.test(checkpoint.cursor)
|
|
95
|
+
&& checkpoint.schema.id === manifest.schemas.checkpoint.id
|
|
96
|
+
&& checkpoint.schema.version === manifest.schemas.checkpoint.version
|
|
97
|
+
&& checkpoint.schema.digest?.value === manifest.schemas.checkpoint.digest.value;
|
|
98
|
+
return valid ? [] : [diagnostic('read', 'ADAPTER_CHECKPOINT_INCOMPATIBLE', 'Checkpoint identity, adapter version, or schema binding is incompatible; no records were read.')];
|
|
99
|
+
}
|
|
100
|
+
export class AdapterRegistry {
|
|
101
|
+
trust;
|
|
102
|
+
#adapters = new Map();
|
|
103
|
+
constructor(trust) {
|
|
104
|
+
this.trust = trust;
|
|
105
|
+
}
|
|
106
|
+
register(adapter) {
|
|
107
|
+
const manifest = adapter.describe();
|
|
108
|
+
if (!this.trust.allowIds.has(manifest.id))
|
|
109
|
+
throw new Error(`ADAPTER_NOT_ALLOWLISTED: ${manifest.id}`);
|
|
110
|
+
if (manifest.trust.state === 'untrusted' && !this.trust.allowUntrusted)
|
|
111
|
+
throw new Error(`ADAPTER_TRUST_REQUIRED: ${manifest.id}`);
|
|
112
|
+
if (manifest.permissions.credentials !== 'none' && manifest.permissions.credentials !== 'locator-only')
|
|
113
|
+
throw new Error(`ADAPTER_PERMISSION_INVALID: ${manifest.id}`);
|
|
114
|
+
this.#adapters.set(`${manifest.id}@${manifest.version}`, adapter);
|
|
115
|
+
}
|
|
116
|
+
require(id, version) {
|
|
117
|
+
const adapter = this.#adapters.get(`${id}@${version}`);
|
|
118
|
+
if (!adapter)
|
|
119
|
+
throw new Error(`ADAPTER_NOT_REGISTERED: ${id}@${version}`);
|
|
120
|
+
return adapter;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
export async function qualifyAdapter(adapter, options) {
|
|
124
|
+
const manifest = adapter.describe();
|
|
125
|
+
const required = new Set(['configuration-schema', 'preview-purity', 'deterministic-read', 'stable-errors', 'cancellation', 'checkpoints', 'version-upgrade', 'secret-leakage']);
|
|
126
|
+
const passed = new Set(options.cells.filter(cell => cell.passed).map(cell => cell.name));
|
|
127
|
+
const qualified = [...required].every(name => passed.has(name));
|
|
128
|
+
const realSource = options.cells.some(cell => cell.source === 'real-source' && cell.passed);
|
|
129
|
+
return {
|
|
130
|
+
contractVersion: DATASET_ADAPTER_CONTRACT_VERSION,
|
|
131
|
+
kind: 'AdapterQualificationReport',
|
|
132
|
+
adapter: { id: manifest.id, version: manifest.version, packageDigest: manifest.packageDigest },
|
|
133
|
+
schemas: {
|
|
134
|
+
config: binding(manifest.schemas.config), record: binding(manifest.schemas.discoveredRecord), checkpoint: binding(manifest.schemas.checkpoint),
|
|
135
|
+
},
|
|
136
|
+
fixtureRevision: options.fixtureRevision,
|
|
137
|
+
cells: structuredClone(options.cells), qualifiedAt: options.qualifiedAt,
|
|
138
|
+
qualified, stableEligible: qualified && realSource,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function binding(schema) {
|
|
142
|
+
return { id: schema.id, version: schema.version, digest: schema.digest };
|
|
143
|
+
}
|
|
144
|
+
export function request(requestId, config, policy, limits = {}) {
|
|
145
|
+
return { contractVersion: DATASET_ADAPTER_CONTRACT_VERSION, requestId, config, policy, limits: { ...DEFAULT_ADAPTER_LIMITS, ...limits } };
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=adapter-sdk.js.map
|