@aiwg/cli 2026.9.0 → 2026.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +41 -0
  2. package/bin/aiwg.mjs +15 -3
  3. package/dist/src/api/index.d.ts +3 -0
  4. package/dist/src/api/index.js +3 -0
  5. package/dist/src/channel/manager.mjs +2 -2
  6. package/dist/src/cli/handlers/dataset.js +186 -0
  7. package/dist/src/cli/handlers/help.js +2 -1
  8. package/dist/src/cli/handlers/index.js +5 -1
  9. package/dist/src/cli/handlers/init.js +1 -0
  10. package/dist/src/cli/handlers/output-mode.js +18 -1
  11. package/dist/src/cli/handlers/run.js +11 -3
  12. package/dist/src/cli/handlers/schema.js +221 -0
  13. package/dist/src/cli/handlers/steward.js +11 -1
  14. package/dist/src/cli/handlers/use.js +6 -2
  15. package/dist/src/cli/hooks/builtin/activity-log-hook.js +6 -0
  16. package/dist/src/cli/router.js +1 -1
  17. package/dist/src/cli/scope-resolver.js +22 -0
  18. package/dist/src/dataset/adapter-sdk.d.ts +41 -0
  19. package/dist/src/dataset/adapter-sdk.js +147 -0
  20. package/dist/src/dataset/adapter-types.d.ts +179 -0
  21. package/dist/src/dataset/adapter-types.js +2 -0
  22. package/dist/src/dataset/adapters.d.ts +104 -0
  23. package/dist/src/dataset/adapters.js +518 -0
  24. package/dist/src/dataset/conformance-types.d.ts +84 -0
  25. package/dist/src/dataset/conformance-types.js +3 -0
  26. package/dist/src/dataset/conformance.d.ts +13 -0
  27. package/dist/src/dataset/conformance.js +90 -0
  28. package/dist/src/dataset/contracts.d.ts +17 -0
  29. package/dist/src/dataset/contracts.js +236 -0
  30. package/dist/src/dataset/file-orchestration-repository.d.ts +19 -0
  31. package/dist/src/dataset/file-orchestration-repository.js +68 -0
  32. package/dist/src/dataset/fortemi-execution-bridge.d.ts +33 -0
  33. package/dist/src/dataset/fortemi-execution-bridge.js +35 -0
  34. package/dist/src/dataset/index.d.ts +21 -0
  35. package/dist/src/dataset/index.js +21 -0
  36. package/dist/src/dataset/ledger-types.d.ts +141 -0
  37. package/dist/src/dataset/ledger-types.js +2 -0
  38. package/dist/src/dataset/ledger.d.ts +27 -0
  39. package/dist/src/dataset/ledger.js +100 -0
  40. package/dist/src/dataset/local-execution-backend.d.ts +10 -0
  41. package/dist/src/dataset/local-execution-backend.js +32 -0
  42. package/dist/src/dataset/orchestration-repository.d.ts +29 -0
  43. package/dist/src/dataset/orchestration-repository.js +34 -0
  44. package/dist/src/dataset/orchestration-service.d.ts +56 -0
  45. package/dist/src/dataset/orchestration-service.js +466 -0
  46. package/dist/src/dataset/orchestration-types.d.ts +83 -0
  47. package/dist/src/dataset/orchestration-types.js +2 -0
  48. package/dist/src/dataset/presentation.d.ts +3 -0
  49. package/dist/src/dataset/presentation.js +8 -0
  50. package/dist/src/dataset/projections.d.ts +42 -0
  51. package/dist/src/dataset/projections.js +192 -0
  52. package/dist/src/dataset/schema-governance.d.ts +71 -0
  53. package/dist/src/dataset/schema-governance.js +135 -0
  54. package/dist/src/dataset/standards-types.d.ts +66 -0
  55. package/dist/src/dataset/standards-types.js +10 -0
  56. package/dist/src/dataset/standards.d.ts +13 -0
  57. package/dist/src/dataset/standards.js +291 -0
  58. package/dist/src/dataset/types.d.ts +258 -0
  59. package/dist/src/dataset/types.js +2 -0
  60. package/dist/src/extensions/commands/definitions.js +27 -1
  61. package/dist/src/installation/manager.mjs +5 -1
  62. package/dist/src/output-modes/index.js +4 -0
  63. package/dist/src/output-modes/registry.js +68 -24
  64. package/dist/src/output-modes/runtime.js +10 -8
  65. package/dist/src/plugin/skill-command-translator.js +1 -0
  66. package/dist/src/providers/capability-matrix.yaml +41 -0
  67. package/dist/src/providers/provider-definitions.js +72 -0
  68. package/dist/src/providers/provider-inventory.js +1 -0
  69. package/dist/src/schema/catalog.js +234 -0
  70. package/dist/src/schema/compatibility.js +42 -0
  71. package/dist/src/schema/diagnostics.js +36 -0
  72. package/dist/src/schema/index.js +8 -0
  73. package/dist/src/schema/policy.js +58 -0
  74. package/dist/src/schema/resolver.js +76 -0
  75. package/dist/src/schema/types.js +2 -0
  76. package/dist/src/schema/validator.js +82 -0
  77. package/dist/src/storage/backends/fortemi.js +6 -0
  78. package/dist/src/storage/config.js +18 -4
  79. package/dist/src/storage/fortemi-qualification.js +106 -0
  80. package/dist/src/storage/index.js +1 -0
  81. package/dist/src/storage/types.js +1 -1
  82. package/package.json +3 -1
  83. package/schemas/dataset/conformance-manifest.v1.schema.json +35 -0
  84. package/schemas/dataset/conformance-receipt.v1.schema.json +22 -0
  85. package/schemas/dataset/dataset-contracts.v1.schema.json +117 -0
  86. package/schemas/dataset/dataset-deprecations.v1.schema.json +37 -0
  87. package/schemas/dataset/dataset-schema-governance.v1.schema.json +92 -0
  88. package/schemas/dataset/dataset-standards-exchange.v1.schema.json +59 -0
  89. package/schemas/dataset/profiles/openlineage-1.0.0.schema.json +15 -0
  90. package/schemas/dataset/profiles/prov-json-20130430.schema.json +12 -0
  91. package/schemas/dataset/run-ledger.v1.schema.json +39 -0
  92. package/schemas/dataset/source-adapter.v1.schema.json +112 -0
  93. package/tools/agents/deploy-agents.mjs +9 -3
  94. package/tools/agents/providers/pi.mjs +164 -0
@@ -0,0 +1,518 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { lookup } from 'node:dns/promises';
3
+ import { lstat, opendir, realpath, stat } from 'node:fs/promises';
4
+ import { isIP } from 'node:net';
5
+ import { isAbsolute, relative, resolve, sep } from 'node:path';
6
+ import { createInterface } from 'node:readline';
7
+ import { DATASET_ADAPTER_CONTRACT_VERSION } from './adapter-types.js';
8
+ import { AdapterFailure, assertActive, configureObject, diagnostic, effectiveLimits, isCredentialLocator, schemaDeclaration, sha256Digest, validateCheckpoint, } from './adapter-sdk.js';
9
+ const fileConfigSchema = {
10
+ type: 'object', additionalProperties: false, required: ['path'],
11
+ properties: { path: { type: 'string', minLength: 1 }, encoding: { const: 'utf8' } },
12
+ };
13
+ const directoryConfigSchema = {
14
+ type: 'object', additionalProperties: false, required: ['path'],
15
+ properties: { path: { type: 'string', minLength: 1 }, encoding: { const: 'utf8' }, recursive: { type: 'boolean' } },
16
+ };
17
+ const httpConfigSchema = {
18
+ type: 'object', additionalProperties: false, required: ['url'],
19
+ properties: {
20
+ url: { type: 'string', format: 'uri' },
21
+ headers: { type: 'object', additionalProperties: { oneOf: [{ type: 'string' }, { type: 'object', required: ['kind', 'locator'] }] } },
22
+ },
23
+ };
24
+ const checkpointSchema = {
25
+ type: 'object', additionalProperties: false,
26
+ required: ['contractVersion', 'kind', 'adapter', 'sourceIdentity', 'cursor', 'schema', 'createdAt'],
27
+ properties: { contractVersion: { const: DATASET_ADAPTER_CONTRACT_VERSION }, kind: { const: 'AdapterCheckpoint' }, cursor: { type: 'string' } },
28
+ };
29
+ const recordSchema = { type: 'object', required: ['logicalId', 'ordinal', 'value', 'sourceLocator', 'contentDigest'] };
30
+ function manifest(id, sourceKinds, locality, capabilities, configSchema) {
31
+ const config = schemaDeclaration(`https://aiwg.io/schemas/dataset/adapters/${id}.config.v1`, configSchema);
32
+ const discoveredRecord = schemaDeclaration('https://aiwg.io/schemas/dataset/adapters/discovered-record.v1', recordSchema);
33
+ const checkpoint = schemaDeclaration('https://aiwg.io/schemas/dataset/adapters/checkpoint.v1', checkpointSchema);
34
+ return {
35
+ contractVersion: DATASET_ADAPTER_CONTRACT_VERSION, kind: 'AdapterManifest', id: `aiwg.adapter.${id}`, version: '1.0.0',
36
+ packageDigest: sha256Digest(`aiwg.adapter.${id}@1.0.0`), datasetContractRange: '^1.0.0', sourceKinds,
37
+ schemas: { config, discoveredRecord, checkpoint }, capabilities,
38
+ limits: { maxRecords: 100_000, maxBytes: 64 * 1024 * 1024, maxRecordBytes: 4 * 1024 * 1024, timeoutMs: 60_000, maxRedirects: 5, maxDepth: 32 },
39
+ maturity: 'qualified', publisher: { id: 'aiwg-maintainers', url: 'https://aiwg.io' }, trust: { state: 'builtin' },
40
+ permissions: { locality, network: locality === 'remote' ? 'https-allowlisted' : 'none', credentials: locality === 'remote' ? 'locator-only' : 'none' },
41
+ incremental: {
42
+ ordering: 'ascending zero-based record ordinal', sameCursorTieHandling: 'logicalId lexical order',
43
+ lateArrivals: 'new source revision requires discovery and a new checkpoint lineage',
44
+ tombstones: 'reference adapters do not synthesize deletes', checkpointCompatibility: 'exact adapter id, version, source identity, and checkpoint schema digest',
45
+ },
46
+ };
47
+ }
48
+ function isFileConfig(value) {
49
+ return Object.keys(value).every(key => key === 'path' || key === 'encoding')
50
+ && typeof value.path === 'string' && value.path.length > 0 && (value.encoding === undefined || value.encoding === 'utf8');
51
+ }
52
+ function isDirectoryConfig(value) {
53
+ return Object.keys(value).every(key => key === 'path' || key === 'encoding' || key === 'recursive')
54
+ && typeof value.path === 'string' && value.path.length > 0
55
+ && (value.encoding === undefined || value.encoding === 'utf8')
56
+ && (value.recursive === undefined || typeof value.recursive === 'boolean');
57
+ }
58
+ function isHttpConfig(value) {
59
+ if (!Object.keys(value).every(key => key === 'url' || key === 'headers') || typeof value.url !== 'string' || value.url.length === 0)
60
+ return false;
61
+ if (value.headers === undefined)
62
+ return true;
63
+ if (!value.headers || typeof value.headers !== 'object' || Array.isArray(value.headers))
64
+ return false;
65
+ return Object.values(value.headers).every(header => typeof header === 'string' || isCredentialLocator(header));
66
+ }
67
+ async function safeLocalPath(configured, policy, expected, operation) {
68
+ if (!policy.allowedRoot)
69
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_INVALID_CONFIGURATION', 'Local adapters require an explicit allowedRoot.'));
70
+ const root = await realpath(policy.allowedRoot).catch(() => { throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', 'The allowed root is unavailable.')); });
71
+ const candidate = resolve(root, configured);
72
+ if (isAbsolute(configured) && candidate !== resolve(configured))
73
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_PATH_ESCAPE', 'Absolute source paths must remain inside allowedRoot.'));
74
+ const lexical = relative(root, candidate);
75
+ if (lexical === '..' || lexical.startsWith(`..${sep}`) || isAbsolute(lexical))
76
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_PATH_ESCAPE', 'Source path escapes allowedRoot.'));
77
+ const linkInfo = await lstat(candidate).catch(() => { throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', 'The source path is unavailable.', { path: configured })); });
78
+ if (linkInfo.isSymbolicLink())
79
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_UNSAFE_SYMLINK', 'Symbolic links are not accepted as dataset sources.', { path: configured }));
80
+ const resolved = await realpath(candidate);
81
+ const physical = relative(root, resolved);
82
+ if (physical === '..' || physical.startsWith(`..${sep}`) || isAbsolute(physical))
83
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_PATH_ESCAPE', 'Resolved source path escapes allowedRoot.'));
84
+ const info = await stat(resolved);
85
+ if ((expected === 'file' && !info.isFile()) || (expected === 'directory' && !info.isDirectory()))
86
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SPECIAL_FILE', `Expected a regular ${expected}; special files are prohibited.`, { path: configured }));
87
+ return { path: resolved, size: info.size, mtimeMs: info.mtimeMs };
88
+ }
89
+ function caught(error, operation) {
90
+ if (error instanceof AdapterFailure)
91
+ return { ok: false, diagnostics: [{ ...error.diagnostic, operation }] };
92
+ if (error instanceof Error && error.name === 'AbortError')
93
+ return { ok: false, diagnostics: [diagnostic(operation, 'ADAPTER_CANCELLED', 'The adapter operation was cancelled.')] };
94
+ return { ok: false, diagnostics: [diagnostic(operation, 'ADAPTER_INTERNAL_ERROR', 'The adapter could not complete the operation.')] };
95
+ }
96
+ function identity(kind, locator, size, revision = '') {
97
+ return `${kind}:sha256:${sha256Digest(`${locator}\0${size}\0${revision}`).value}`;
98
+ }
99
+ function decodeUtf8(value, operation) {
100
+ try {
101
+ return new TextDecoder('utf-8', { fatal: true }).decode(value);
102
+ }
103
+ catch {
104
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SCHEMA_DRIFT', 'Source content is not valid UTF-8.'));
105
+ }
106
+ }
107
+ function record(value, ordinal, sourceLocator) {
108
+ const serialized = typeof value === 'string' ? value : JSON.stringify(value);
109
+ return { logicalId: `sha256:${sha256Digest(`${sourceLocator}\0${ordinal}\0${serialized}`).value}`, ordinal, value, sourceLocator, contentDigest: sha256Digest(serialized) };
110
+ }
111
+ function checkpoint(manifestValue, sourceIdentity, cursor) {
112
+ return {
113
+ contractVersion: DATASET_ADAPTER_CONTRACT_VERSION, kind: 'AdapterCheckpoint', adapter: { id: manifestValue.id, version: manifestValue.version },
114
+ sourceIdentity, cursor, schema: { id: manifestValue.schemas.checkpoint.id, version: manifestValue.schemas.checkpoint.version, digest: manifestValue.schemas.checkpoint.digest },
115
+ createdAt: new Date().toISOString(),
116
+ };
117
+ }
118
+ class LocalAdapter {
119
+ async configure(config) { return configureObject('configure', config, value => this.valid(value)); }
120
+ async check(request) {
121
+ try {
122
+ assertActive(request.signal, 'check');
123
+ const value = await this.discoverPath(request.config, request);
124
+ return { ok: true, value: { sourceIdentity: value.sourceIdentity }, diagnostics: [] };
125
+ }
126
+ catch (error) {
127
+ return caught(error, 'check');
128
+ }
129
+ }
130
+ async discover(request) {
131
+ try {
132
+ assertActive(request.signal, 'discover');
133
+ return { ok: true, value: await this.discoverPath(request.config, request), diagnostics: [] };
134
+ }
135
+ catch (error) {
136
+ return caught(error, 'discover');
137
+ }
138
+ }
139
+ async preview(request) {
140
+ try {
141
+ assertActive(request.signal, 'preview');
142
+ const values = [];
143
+ if (request.count <= 0)
144
+ return { ok: true, value: values, diagnostics: [] };
145
+ for await (const item of this.records(request.config, request)) {
146
+ values.push(item);
147
+ if (values.length >= Math.max(0, Math.min(request.count, request.limits.maxRecords)))
148
+ break;
149
+ }
150
+ return { ok: true, value: values, diagnostics: [] };
151
+ }
152
+ catch (error) {
153
+ return caught(error, 'preview');
154
+ }
155
+ }
156
+ async *read(request) {
157
+ try {
158
+ assertActive(request.signal, 'read');
159
+ if (request.checkpoint) {
160
+ const binding = validateCheckpoint(request.checkpoint, this.describe(), request.checkpoint.sourceIdentity);
161
+ if (binding[0]) {
162
+ yield { kind: 'diagnostic', diagnostic: binding[0] };
163
+ return;
164
+ }
165
+ }
166
+ const found = await this.discoverPath(request.config, request);
167
+ const invalid = validateCheckpoint(request.checkpoint, this.describe(), found.sourceIdentity);
168
+ if (invalid[0]) {
169
+ yield { kind: 'diagnostic', diagnostic: invalid[0] };
170
+ return;
171
+ }
172
+ const after = request.checkpoint ? Number.parseInt(request.checkpoint.cursor, 10) + 1 : 0;
173
+ let last = after - 1;
174
+ for await (const item of this.records(request.config, request)) {
175
+ if (item.ordinal < after)
176
+ continue;
177
+ yield { kind: 'record', record: item };
178
+ last = item.ordinal;
179
+ }
180
+ yield { kind: 'checkpoint', checkpoint: checkpoint(this.describe(), found.sourceIdentity, String(last)) };
181
+ }
182
+ catch (error) {
183
+ const found = error instanceof AdapterFailure
184
+ ? { ...error.diagnostic, operation: 'read' }
185
+ : diagnostic('read', error instanceof Error && error.name === 'AbortError' ? 'ADAPTER_CANCELLED' : 'ADAPTER_INTERNAL_ERROR', error instanceof Error && error.name === 'AbortError' ? 'The adapter operation was cancelled.' : 'The adapter could not complete the read.');
186
+ yield { kind: 'diagnostic', diagnostic: found };
187
+ }
188
+ }
189
+ }
190
+ export class FileAdapter extends LocalAdapter {
191
+ #manifest = manifest('file', ['file'], 'local', ['discovery', 'preview', 'streaming-read', 'checkpoint'], fileConfigSchema);
192
+ describe() { return structuredClone(this.#manifest); }
193
+ valid(value) { return isFileConfig(value); }
194
+ async discoverPath(config, request) {
195
+ const found = await safeLocalPath(config.path, request.policy, 'file', 'discover');
196
+ return { sourceIdentity: identity('file', found.path, found.size, String(found.mtimeMs)), proposedSchema: { type: 'string' }, schemaBasis: 'observed', estimates: { records: 1, bytes: found.size }, privacySignals: [], licenseSignals: [], cursorSupport: true, checkpointSupport: true, identityStability: 'stable', limitations: ['Content is decoded as strict UTF-8.'] };
197
+ }
198
+ async *records(config, request) {
199
+ const limits = effectiveLimits(this.#manifest, request.limits);
200
+ const found = await safeLocalPath(config.path, request.policy, 'file', request.checkpoint ? 'read' : 'preview');
201
+ assertActive(request.signal, request.checkpoint ? 'read' : 'preview');
202
+ if (found.size > limits.maxBytes || found.size > limits.maxRecordBytes)
203
+ throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_RESOURCE_LIMIT', 'The file exceeds the configured byte limit.'));
204
+ const chunks = [];
205
+ for await (const chunk of createReadStream(found.path, { signal: request.signal }))
206
+ chunks.push(Buffer.from(chunk));
207
+ yield record(decodeUtf8(Buffer.concat(chunks), request.checkpoint ? 'read' : 'preview'), 0, config.path);
208
+ }
209
+ }
210
+ class LineAdapter extends LocalAdapter {
211
+ async lineDiscovery(kind, config, request, schema) {
212
+ const found = await safeLocalPath(config.path, request.policy, 'file', 'discover');
213
+ return { sourceIdentity: identity(kind, found.path, found.size, String(found.mtimeMs)), proposedSchema: schema, schemaBasis: 'inferred', estimates: { bytes: found.size }, privacySignals: [], licenseSignals: [], cursorSupport: true, checkpointSupport: true, identityStability: 'stable', limitations: ['Record count is established during bounded read.'] };
214
+ }
215
+ async *lines(config, request, parse) {
216
+ const limits = effectiveLimits(this.describe(), request.limits);
217
+ const operation = request.checkpoint ? 'read' : 'preview';
218
+ const found = await safeLocalPath(config.path, request.policy, 'file', operation);
219
+ if (found.size > limits.maxBytes)
220
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'The source exceeds the configured byte limit.'));
221
+ const input = createReadStream(found.path, { signal: request.signal, encoding: 'utf8' });
222
+ const lines = createInterface({ input, crlfDelay: Infinity });
223
+ let ordinal = 0;
224
+ try {
225
+ for await (const line of lines) {
226
+ assertActive(request.signal, operation);
227
+ if (Buffer.byteLength(line) > limits.maxRecordBytes || ordinal >= limits.maxRecords)
228
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'The record or record count exceeds configured limits.'));
229
+ if (line.trim() !== '')
230
+ yield record(parse(line, ordinal), ordinal, config.path);
231
+ ordinal += 1;
232
+ }
233
+ }
234
+ finally {
235
+ lines.close();
236
+ input.destroy();
237
+ }
238
+ }
239
+ }
240
+ export class JsonlAdapter extends LineAdapter {
241
+ #manifest = manifest('jsonl', ['jsonl'], 'local', ['discovery', 'preview', 'streaming-read', 'checkpoint'], fileConfigSchema);
242
+ describe() { return structuredClone(this.#manifest); }
243
+ valid(value) { return isFileConfig(value); }
244
+ discoverPath(config, request) { return this.lineDiscovery('jsonl', config, request, {}); }
245
+ records(config, request) {
246
+ return this.lines(config, request, (line, ordinal) => { try {
247
+ return JSON.parse(line);
248
+ }
249
+ catch {
250
+ throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_SCHEMA_DRIFT', `Malformed JSONL record at ordinal ${ordinal}.`));
251
+ } });
252
+ }
253
+ }
254
+ function parseCsvLine(line) {
255
+ const fields = [];
256
+ let value = '';
257
+ let quoted = false;
258
+ for (let index = 0; index < line.length; index += 1) {
259
+ const char = line[index];
260
+ if (char === '"' && quoted && line[index + 1] === '"') {
261
+ value += '"';
262
+ index += 1;
263
+ }
264
+ else if (char === '"')
265
+ quoted = !quoted;
266
+ else if (char === ',' && !quoted) {
267
+ fields.push(value);
268
+ value = '';
269
+ }
270
+ else
271
+ value += char;
272
+ }
273
+ if (quoted)
274
+ throw new Error('unterminated quoted field');
275
+ fields.push(value);
276
+ return fields;
277
+ }
278
+ export class CsvAdapter extends LineAdapter {
279
+ #manifest = manifest('csv', ['csv'], 'local', ['discovery', 'preview', 'streaming-read', 'checkpoint'], fileConfigSchema);
280
+ describe() { return structuredClone(this.#manifest); }
281
+ valid(value) { return isFileConfig(value); }
282
+ discoverPath(config, request) { return this.lineDiscovery('csv', config, request, { type: 'array', items: { type: 'string' } }); }
283
+ records(config, request) {
284
+ return this.lines(config, request, (line, ordinal) => { try {
285
+ return parseCsvLine(line);
286
+ }
287
+ catch {
288
+ throw new AdapterFailure(diagnostic(request.checkpoint ? 'read' : 'preview', 'ADAPTER_SCHEMA_DRIFT', `Malformed CSV record at ordinal ${ordinal}.`));
289
+ } });
290
+ }
291
+ }
292
+ export class DirectoryAdapter extends LocalAdapter {
293
+ #manifest = manifest('directory', ['directory'], 'local', ['discovery', 'preview', 'streaming-read', 'checkpoint'], directoryConfigSchema);
294
+ describe() { return structuredClone(this.#manifest); }
295
+ valid(value) { return isDirectoryConfig(value); }
296
+ async #scan(config, request, operation) {
297
+ const limits = effectiveLimits(this.#manifest, request.limits);
298
+ const root = (await safeLocalPath(config.path, request.policy, 'directory', operation)).path;
299
+ const paths = [];
300
+ let total = 0;
301
+ const walk = async (directory, depth) => {
302
+ if (depth > limits.maxDepth)
303
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'Directory depth exceeds the configured limit.'));
304
+ const handle = await opendir(directory);
305
+ for await (const entry of handle) {
306
+ assertActive(request.signal, operation);
307
+ const path = resolve(directory, entry.name);
308
+ if (entry.isSymbolicLink())
309
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_UNSAFE_SYMLINK', 'Directory sources may not contain symbolic links.', { path: relative(root, path) }));
310
+ if (entry.isDirectory()) {
311
+ if (config.recursive)
312
+ await walk(path, depth + 1);
313
+ }
314
+ else if (entry.isFile()) {
315
+ const info = await stat(path);
316
+ total += info.size;
317
+ if (paths.length >= limits.maxRecords || info.size > limits.maxRecordBytes || total > limits.maxBytes)
318
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'Directory content exceeds a configured record or byte limit.'));
319
+ paths.push(path);
320
+ }
321
+ else
322
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SPECIAL_FILE', 'Directory sources may contain only regular files and directories.', { path: relative(root, path) }));
323
+ }
324
+ };
325
+ await walk(root, 0);
326
+ paths.sort();
327
+ const metadata = await Promise.all(paths.map(async (path) => { const info = await stat(path); return `${relative(root, path)}\0${info.size}\0${info.mtimeMs}`; }));
328
+ return { root, paths, total, revision: sha256Digest(metadata.join('\n')).value };
329
+ }
330
+ async discoverPath(config, request) {
331
+ const found = await this.#scan(config, request, 'discover');
332
+ return { sourceIdentity: identity('directory', found.root, found.total, found.revision), proposedSchema: { type: 'object', properties: { path: { type: 'string' }, content: { type: 'string' } } }, schemaBasis: 'observed', estimates: { records: found.paths.length, bytes: found.total }, privacySignals: [], licenseSignals: [], cursorSupport: true, checkpointSupport: true, identityStability: 'stable', limitations: ['Symlinks and special files are rejected.'] };
333
+ }
334
+ async *records(config, request) {
335
+ const operation = request.checkpoint ? 'read' : 'preview';
336
+ const { root, paths } = await this.#scan(config, request, operation);
337
+ for (const [ordinal, path] of paths.entries()) {
338
+ const chunks = [];
339
+ for await (const chunk of createReadStream(path, { signal: request.signal }))
340
+ chunks.push(Buffer.from(chunk));
341
+ const source = relative(root, path).split(sep).join('/');
342
+ yield record({ path: source, content: decodeUtf8(Buffer.concat(chunks), operation) }, ordinal, source);
343
+ }
344
+ }
345
+ }
346
+ export class HttpAdapter {
347
+ fetchImpl;
348
+ resolveHost;
349
+ #manifest = manifest('http', ['https'], 'remote', ['discovery', 'preview', 'streaming-read', 'checkpoint'], httpConfigSchema);
350
+ constructor(fetchImpl = fetch, resolveHost = async (hostname) => lookup(hostname, { all: true, verbatim: true })) {
351
+ this.fetchImpl = fetchImpl;
352
+ this.resolveHost = resolveHost;
353
+ }
354
+ describe() { return structuredClone(this.#manifest); }
355
+ async configure(config) { return configureObject('configure', config, isHttpConfig); }
356
+ async #fetch(request, operation) {
357
+ assertActive(request.signal, operation);
358
+ if (request.policy.offline)
359
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_OFFLINE_PROHIBITED', 'HTTP access is refused in offline mode.'));
360
+ const limits = effectiveLimits(this.#manifest, request.limits);
361
+ let url = new URL(request.config.url);
362
+ const deadline = AbortSignal.timeout(limits.timeoutMs);
363
+ const signal = request.signal ? AbortSignal.any([request.signal, deadline]) : deadline;
364
+ for (let redirects = 0;; redirects += 1) {
365
+ await validateHttpUrl(url, request.policy, operation, this.resolveHost);
366
+ const headers = new Headers();
367
+ for (const [name, value] of Object.entries(request.config.headers ?? {})) {
368
+ if (typeof value !== 'string')
369
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_AUTHORIZATION_UNAVAILABLE', `Credential locator for header ${name} requires an external authorized resolver.`));
370
+ if (/authorization|cookie|token|key/iu.test(name))
371
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SECRET_REJECTED', `Header ${name} must use an opaque credential locator.`));
372
+ headers.set(name, value);
373
+ }
374
+ let response;
375
+ try {
376
+ response = await this.fetchImpl(url, { method: 'GET', redirect: 'manual', headers, signal });
377
+ }
378
+ catch (error) {
379
+ if (signal.aborted)
380
+ throw new AdapterFailure(diagnostic(operation, request.signal?.aborted ? 'ADAPTER_CANCELLED' : 'ADAPTER_RESOURCE_LIMIT', request.signal?.aborted ? 'The HTTP operation was cancelled.' : 'The HTTP operation exceeded its time limit.'));
381
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', 'The HTTP source is unavailable.', { retryable: true }));
382
+ }
383
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
384
+ if (redirects >= limits.maxRedirects)
385
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_REDIRECT_PROHIBITED', 'HTTP redirect limit exceeded.'));
386
+ const location = response.headers.get('location');
387
+ if (!location)
388
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', 'Redirect response omitted a Location header.'));
389
+ url = new URL(location, url);
390
+ continue;
391
+ }
392
+ if (!response.ok)
393
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', `HTTP source returned status ${response.status}.`, { retryable: response.status >= 500 }));
394
+ const length = Number(response.headers.get('content-length') ?? '0');
395
+ if (length > limits.maxBytes)
396
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'HTTP response exceeds the configured byte limit.'));
397
+ if (!response.body)
398
+ return { body: '', url: url.toString() };
399
+ const reader = response.body.getReader();
400
+ const chunks = [];
401
+ let total = 0;
402
+ try {
403
+ while (true) {
404
+ const item = await reader.read();
405
+ if (item.done)
406
+ break;
407
+ total += item.value.byteLength;
408
+ if (total > limits.maxBytes) {
409
+ await reader.cancel();
410
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_RESOURCE_LIMIT', 'HTTP response exceeds the configured byte limit.'));
411
+ }
412
+ ;
413
+ chunks.push(item.value);
414
+ }
415
+ }
416
+ finally {
417
+ reader.releaseLock();
418
+ }
419
+ const joined = new Uint8Array(total);
420
+ let offset = 0;
421
+ for (const chunk of chunks) {
422
+ joined.set(chunk, offset);
423
+ offset += chunk.byteLength;
424
+ }
425
+ return { body: new TextDecoder().decode(joined), url: url.toString() };
426
+ }
427
+ }
428
+ async check(request) {
429
+ try {
430
+ const found = await this.#fetch(request, 'check');
431
+ return { ok: true, value: { sourceIdentity: identity('https', found.url, Buffer.byteLength(found.body)) }, diagnostics: [] };
432
+ }
433
+ catch (error) {
434
+ return caught(error, 'check');
435
+ }
436
+ }
437
+ async discover(request) {
438
+ try {
439
+ const found = await this.#fetch(request, 'discover');
440
+ return { ok: true, value: { sourceIdentity: identity('https', found.url, Buffer.byteLength(found.body)), proposedSchema: { type: 'string' }, schemaBasis: 'observed', estimates: { records: 1, bytes: Buffer.byteLength(found.body) }, privacySignals: [], licenseSignals: [], cursorSupport: true, checkpointSupport: true, identityStability: 'source-unstable', identityLimitation: 'Remote content may change without a stable validator.', limitations: ['One bounded response is one logical record.'] }, diagnostics: [] };
441
+ }
442
+ catch (error) {
443
+ return caught(error, 'discover');
444
+ }
445
+ }
446
+ async preview(request) {
447
+ try {
448
+ const found = await this.#fetch(request, 'preview');
449
+ return { ok: true, value: request.count > 0 ? [record(found.body, 0, found.url)] : [], diagnostics: [] };
450
+ }
451
+ catch (error) {
452
+ return caught(error, 'preview');
453
+ }
454
+ }
455
+ async *read(request) {
456
+ try {
457
+ if (request.checkpoint) {
458
+ const binding = validateCheckpoint(request.checkpoint, this.#manifest, request.checkpoint.sourceIdentity);
459
+ if (binding[0]) {
460
+ yield { kind: 'diagnostic', diagnostic: binding[0] };
461
+ return;
462
+ }
463
+ }
464
+ const found = await this.#fetch(request, 'read');
465
+ const sourceIdentity = identity('https', found.url, Buffer.byteLength(found.body));
466
+ const invalid = validateCheckpoint(request.checkpoint, this.#manifest, sourceIdentity);
467
+ if (invalid[0]) {
468
+ yield { kind: 'diagnostic', diagnostic: invalid[0] };
469
+ return;
470
+ }
471
+ if (!request.checkpoint)
472
+ yield { kind: 'record', record: record(found.body, 0, found.url) };
473
+ yield { kind: 'checkpoint', checkpoint: checkpoint(this.#manifest, sourceIdentity, '0') };
474
+ }
475
+ catch (error) {
476
+ yield { kind: 'diagnostic', diagnostic: error instanceof AdapterFailure ? error.diagnostic : diagnostic('read', 'ADAPTER_INTERNAL_ERROR', 'The HTTP adapter could not complete the read.') };
477
+ }
478
+ }
479
+ }
480
+ async function validateHttpUrl(url, policy, operation, resolveHost) {
481
+ if (url.protocol !== 'https:')
482
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_NETWORK_PROHIBITED', 'Only HTTPS dataset sources are permitted.'));
483
+ const hostname = url.hostname.toLowerCase().replace(/\.$/u, '');
484
+ const allowlisted = (policy.allowedHosts ?? []).some(value => value.toLowerCase().replace(/\.$/u, '') === hostname);
485
+ if (!allowlisted)
486
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_NETWORK_PROHIBITED', 'HTTP source host is not allowlisted.'));
487
+ if (!policy.allowPrivateNetwork && privateHost(hostname))
488
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_NETWORK_PROHIBITED', 'Private, loopback, link-local, and metadata endpoints are prohibited.'));
489
+ if (url.username || url.password)
490
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SECRET_REJECTED', 'Credentials in source URLs are prohibited.'));
491
+ if (!policy.allowPrivateNetwork && isIP(hostname) === 0) {
492
+ let addresses;
493
+ try {
494
+ addresses = await resolveHost(hostname);
495
+ }
496
+ catch {
497
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_SOURCE_UNAVAILABLE', 'HTTP source host could not be resolved.', { retryable: true }));
498
+ }
499
+ if (addresses.length === 0 || addresses.some(item => privateHost(item.address)))
500
+ throw new AdapterFailure(diagnostic(operation, 'ADAPTER_NETWORK_PROHIBITED', 'HTTP source resolves to a private, loopback, link-local, or prohibited address.'));
501
+ }
502
+ }
503
+ function privateHost(hostname) {
504
+ if (hostname === 'localhost' || hostname.endsWith('.localhost') || hostname === '169.254.169.254')
505
+ return true;
506
+ if (isIP(hostname) === 4) {
507
+ const [a = 0, b = 0] = hostname.split('.').map(Number);
508
+ return a === 10 || a === 127 || a === 0 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
509
+ }
510
+ if (isIP(hostname) === 6)
511
+ return hostname === '::1' || hostname.startsWith('fe80:') || hostname.startsWith('fc') || hostname.startsWith('fd');
512
+ return false;
513
+ }
514
+ export function createBuiltinAdapterRegistry() {
515
+ const adapters = [new FileAdapter(), new DirectoryAdapter(), new JsonlAdapter(), new CsvAdapter(), new HttpAdapter()];
516
+ return { adapters, manifests: adapters.map(adapter => adapter.describe()) };
517
+ }
518
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +1,84 @@
1
+ export declare const DATASET_CONFORMANCE_CONTRACT: "aiwg.dataset.conformance/v1";
2
+ export declare const DATASET_CONFORMANCE_SCHEMA_VERSION: "1.0.0";
3
+ export type ConformanceCellStatus = 'passed' | 'failed' | 'pending';
4
+ export type ConformanceMaturity = 'experimental' | 'qualified' | 'stable';
5
+ export type ConformanceEvidenceKind = 'fixture' | 'real-source' | 'cross-repo' | 'live-qualification';
6
+ export interface DatasetConformanceCell {
7
+ id: string;
8
+ area: 'adapter' | 'capability' | 'replay' | 'checkpoint' | 'provenance' | 'security' | 'offline' | 'parity' | 'standards' | 'migration';
9
+ requiredCapabilities: string[];
10
+ sourceClass: 'synthetic' | 'file' | 'directory' | 'jsonl' | 'csv' | 'http' | 'cache' | 'fortemi';
11
+ runtimeClass: 'local' | 'fortemi-core' | 'fortemi-server';
12
+ fixture: {
13
+ path: string;
14
+ digest: string;
15
+ revision: string;
16
+ };
17
+ expected: {
18
+ result: 'pass' | 'reject' | 'pending';
19
+ diagnostic?: string;
20
+ };
21
+ maturity: ConformanceMaturity;
22
+ evidence: ConformanceEvidenceKind[];
23
+ resourceEnvelope: {
24
+ maxBytes: number;
25
+ maxRecords: number;
26
+ maxDurationMs: number;
27
+ };
28
+ liveAuthorizationRequired?: boolean;
29
+ }
30
+ export interface DatasetConformanceManifest {
31
+ contract: typeof DATASET_CONFORMANCE_CONTRACT;
32
+ schemaVersion: string;
33
+ corpusVersion: string;
34
+ cells: DatasetConformanceCell[];
35
+ }
36
+ export interface DatasetConformanceBinding {
37
+ aiwgCommit: string;
38
+ fortemiCommit?: string;
39
+ packageDigests: Record<string, string>;
40
+ schemaDigests: Record<string, string>;
41
+ fixtureDigest: string;
42
+ descriptorDigest?: string;
43
+ configurationDigest: string;
44
+ }
45
+ export interface DatasetConformanceCellResult {
46
+ cellId: string;
47
+ status: ConformanceCellStatus;
48
+ diagnostic?: string;
49
+ evidence: Array<{
50
+ kind: ConformanceEvidenceKind;
51
+ reference: string;
52
+ digest: string;
53
+ }>;
54
+ observed: {
55
+ records?: number;
56
+ bytes?: number;
57
+ durationMs?: number;
58
+ networkAttempts: number;
59
+ };
60
+ }
61
+ export interface DatasetConformanceReceipt {
62
+ contract: typeof DATASET_CONFORMANCE_CONTRACT;
63
+ schemaVersion: string;
64
+ corpusVersion: string;
65
+ manifestDigest: string;
66
+ resultDigest: string;
67
+ bindings: DatasetConformanceBinding;
68
+ startedAt: string;
69
+ endedAt: string;
70
+ results: DatasetConformanceCellResult[];
71
+ summary: {
72
+ passed: number;
73
+ failed: number;
74
+ pending: number;
75
+ stableEligible: boolean;
76
+ };
77
+ }
78
+ export type DatasetConformanceDiagnosticCode = 'CONFORMANCE_MANIFEST_INVALID' | 'CONFORMANCE_CELL_DUPLICATE' | 'CONFORMANCE_REQUIRED_CELL_MISSING' | 'CONFORMANCE_FIXTURE_DIGEST_MISMATCH' | 'CONFORMANCE_RECEIPT_STALE' | 'CONFORMANCE_RECEIPT_UNVERIFIABLE' | 'CONFORMANCE_EVIDENCE_WEAKENED' | 'CONFORMANCE_MOCK_ONLY_STABLE' | 'CONFORMANCE_RESOURCE_ENVELOPE_MISSING' | 'CONFORMANCE_PENDING_STABLE_CELL' | 'CONFORMANCE_RESULT_FAILED' | 'CONFORMANCE_RESULT_DIGEST_MISMATCH' | 'CONFORMANCE_SENSITIVE_VALUE';
79
+ export interface DatasetConformanceDiagnostic {
80
+ code: DatasetConformanceDiagnosticCode;
81
+ path: string;
82
+ message: string;
83
+ }
84
+ //# sourceMappingURL=conformance-types.d.ts.map
@@ -0,0 +1,3 @@
1
+ export const DATASET_CONFORMANCE_CONTRACT = 'aiwg.dataset.conformance/v1';
2
+ export const DATASET_CONFORMANCE_SCHEMA_VERSION = '1.0.0';
3
+ //# sourceMappingURL=conformance-types.js.map
@@ -0,0 +1,13 @@
1
+ import type { DatasetConformanceCellResult, DatasetConformanceDiagnostic, DatasetConformanceManifest, DatasetConformanceReceipt } from './conformance-types.js';
2
+ export declare function canonicalConformanceJson(value: unknown): string;
3
+ export declare function conformanceDigest(value: unknown): string;
4
+ export declare function resultDigest(results: DatasetConformanceCellResult[]): string;
5
+ export declare function validateConformanceManifest(manifest: DatasetConformanceManifest): DatasetConformanceDiagnostic[];
6
+ export declare function summarizeConformance(manifest: DatasetConformanceManifest, results: DatasetConformanceCellResult[]): {
7
+ passed: number;
8
+ failed: number;
9
+ pending: number;
10
+ stableEligible: boolean;
11
+ };
12
+ export declare function verifyConformanceReceipt(manifest: DatasetConformanceManifest, receipt: DatasetConformanceReceipt): DatasetConformanceDiagnostic[];
13
+ //# sourceMappingURL=conformance.d.ts.map