@aiwg/cli 2026.8.28 → 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.
- package/agentic/code/providers/capability-matrix.yaml +41 -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/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/steward.js +11 -1
- package/dist/src/cli/handlers/use.js +46 -9
- 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/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/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 +4 -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 +164 -0
|
@@ -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');
|
|
@@ -1022,12 +1022,13 @@ async function countBundleDeployedArtifacts(bundlePath, target, provider) {
|
|
|
1022
1022
|
}
|
|
1023
1023
|
const SKILL_SUPPORT_REFERENCE = /(?:^|[\s`('"\[])((?:templates|references|scripts|assets)\/[A-Za-z0-9._@/+\-]+)(?=$|[\s`)'"\],:;])/gm;
|
|
1024
1024
|
/**
|
|
1025
|
-
*
|
|
1026
|
-
*
|
|
1025
|
+
* Skill-relative support files may live beside the skill or at the bundle root
|
|
1026
|
+
* (plugin payloads commonly share report templates). Materialize
|
|
1027
1027
|
* only paths explicitly named by SKILL.md, and fail closed on missing or
|
|
1028
1028
|
* unsafe sources so a deployed instruction can never point at absent assets.
|
|
1029
1029
|
*/
|
|
1030
|
-
async function
|
|
1030
|
+
async function reconcileDeployedSkillAssets(bundlePath, target, provider, options = {}) {
|
|
1031
|
+
const strictReferences = options.strictReferences ?? true;
|
|
1031
1032
|
const skillsRoot = path.join(bundlePath, 'skills');
|
|
1032
1033
|
let skillDirs;
|
|
1033
1034
|
try {
|
|
@@ -1054,7 +1055,13 @@ async function reconcileProjectLocalSkillAssets(bundlePath, target, provider) {
|
|
|
1054
1055
|
catch {
|
|
1055
1056
|
continue;
|
|
1056
1057
|
}
|
|
1057
|
-
const
|
|
1058
|
+
const frontmatter = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)?.[1] ?? '';
|
|
1059
|
+
const declaredEntrypoint = frontmatter
|
|
1060
|
+
.match(/^[ \t]+entrypoint:\s*["']?([^"'\s]+)["']?\s*$/m)?.[1];
|
|
1061
|
+
const declaredEntrypoints = new Set(declaredEntrypoint ? [declaredEntrypoint] : []);
|
|
1062
|
+
const references = [...new Set([
|
|
1063
|
+
...content.matchAll(SKILL_SUPPORT_REFERENCE),
|
|
1064
|
+
].map(match => match[1]).concat([...declaredEntrypoints]))];
|
|
1058
1065
|
for (const relative of references) {
|
|
1059
1066
|
const normalized = path.posix.normalize(relative);
|
|
1060
1067
|
if (normalized !== relative || normalized.startsWith('../') || path.isAbsolute(normalized)) {
|
|
@@ -1072,8 +1079,12 @@ async function reconcileProjectLocalSkillAssets(bundlePath, target, provider) {
|
|
|
1072
1079
|
}
|
|
1073
1080
|
catch { /* try bundle-root fallback */ }
|
|
1074
1081
|
}
|
|
1075
|
-
if (!source)
|
|
1076
|
-
|
|
1082
|
+
if (!source) {
|
|
1083
|
+
if (strictReferences || declaredEntrypoints.has(relative)) {
|
|
1084
|
+
throw new Error(`missing skill support asset '${relative}' referenced by ${sourceSkillMd}`);
|
|
1085
|
+
}
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1077
1088
|
let deployedSkillRoot;
|
|
1078
1089
|
for (const root of deployRoots) {
|
|
1079
1090
|
// The deployer may select the bulk or kernel tier; use the tier that
|
|
@@ -1162,7 +1173,7 @@ async function deployOneProjectLocalBundle(opts) {
|
|
|
1162
1173
|
exitCode = result.exitCode;
|
|
1163
1174
|
if (exitCode === 0 && !dryRun) {
|
|
1164
1175
|
try {
|
|
1165
|
-
await
|
|
1176
|
+
await reconcileDeployedSkillAssets(bundle.artifactPath, target, provider);
|
|
1166
1177
|
}
|
|
1167
1178
|
catch (error) {
|
|
1168
1179
|
ui.warn(`Project-local skill asset deployment failed for '${bundle.id}': ${error.message}`);
|
|
@@ -2733,6 +2744,17 @@ export class UseHandler {
|
|
|
2733
2744
|
|| `Failed to deploy required addon '${dependency}'`,
|
|
2734
2745
|
};
|
|
2735
2746
|
}
|
|
2747
|
+
if (!dryRunAddon) {
|
|
2748
|
+
try {
|
|
2749
|
+
await reconcileDeployedSkillAssets(dependencySource, target, provider, { strictReferences: false });
|
|
2750
|
+
}
|
|
2751
|
+
catch (error) {
|
|
2752
|
+
return {
|
|
2753
|
+
exitCode: 1,
|
|
2754
|
+
message: `Required addon '${dependency}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2736
2758
|
try {
|
|
2737
2759
|
await registerSourceCliCommands({
|
|
2738
2760
|
source: dependencySource,
|
|
@@ -2764,6 +2786,17 @@ export class UseHandler {
|
|
|
2764
2786
|
if (addonResult.exitCode !== 0) {
|
|
2765
2787
|
return addonResult;
|
|
2766
2788
|
}
|
|
2789
|
+
if (!dryRunAddon) {
|
|
2790
|
+
try {
|
|
2791
|
+
await reconcileDeployedSkillAssets(addonSource, target, provider, { strictReferences: false });
|
|
2792
|
+
}
|
|
2793
|
+
catch (error) {
|
|
2794
|
+
return {
|
|
2795
|
+
exitCode: 1,
|
|
2796
|
+
message: `${kind} '${framework}' skill asset deployment failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2767
2800
|
// Register only artifacts actually written by a confirmed deployment.
|
|
2768
2801
|
if (!dryRunAddon) {
|
|
2769
2802
|
try {
|
|
@@ -2889,7 +2922,7 @@ export class UseHandler {
|
|
|
2889
2922
|
catch {
|
|
2890
2923
|
// Profile selection is optional — don't fail deployment
|
|
2891
2924
|
}
|
|
2892
|
-
if (framework === 'aiwg-utils' && !remainingArgs.includes('--dry-run')) {
|
|
2925
|
+
if (framework === 'aiwg-utils' && provider !== 'pi' && !remainingArgs.includes('--dry-run')) {
|
|
2893
2926
|
const wrapperValidation = await validateDeployedModelWrappers({
|
|
2894
2927
|
provider: normalizeProviderDefinitionId(provider) ?? provider,
|
|
2895
2928
|
target,
|
|
@@ -2992,6 +3025,7 @@ export class UseHandler {
|
|
|
2992
3025
|
? withProviderOverride(deployFilteredArgs, provider)
|
|
2993
3026
|
: deployFilteredArgs;
|
|
2994
3027
|
const bulkKernelOnly = framework === 'all'
|
|
3028
|
+
&& provider !== 'pi'
|
|
2995
3029
|
&& !remainingArgs.includes('--copy-all')
|
|
2996
3030
|
&& !remainingArgs.includes('--copy-standard-skills');
|
|
2997
3031
|
if (bulkKernelOnly)
|
|
@@ -3197,7 +3231,10 @@ export class UseHandler {
|
|
|
3197
3231
|
}
|
|
3198
3232
|
await ensureProviderGeneratedDirsIgnored(target, provider, { dryRun, verbose });
|
|
3199
3233
|
const paths = getProviderPaths(provider);
|
|
3200
|
-
|
|
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') {
|
|
3201
3238
|
const wrapperValidation = await validateDeployedModelWrappers({
|
|
3202
3239
|
provider,
|
|
3203
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
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { Digest, PrivacyClassification, SchemaBinding } from './types.js';
|
|
2
|
+
export declare const DATASET_ADAPTER_CONTRACT_VERSION: "aiwg.dataset.adapter/v1";
|
|
3
|
+
export type DatasetAdapterContractVersion = typeof DATASET_ADAPTER_CONTRACT_VERSION;
|
|
4
|
+
export type AdapterOperation = 'describe' | 'configure' | 'check' | 'discover' | 'preview' | 'read';
|
|
5
|
+
export type AdapterDiagnosticCode = 'ADAPTER_INVALID_CONFIGURATION' | 'ADAPTER_AUTHORIZATION_UNAVAILABLE' | 'ADAPTER_OFFLINE_PROHIBITED' | 'ADAPTER_SOURCE_UNAVAILABLE' | 'ADAPTER_RESOURCE_LIMIT' | 'ADAPTER_SCHEMA_DRIFT' | 'ADAPTER_CHECKPOINT_INCOMPATIBLE' | 'ADAPTER_CANCELLED' | 'ADAPTER_PATH_ESCAPE' | 'ADAPTER_UNSAFE_SYMLINK' | 'ADAPTER_SPECIAL_FILE' | 'ADAPTER_NETWORK_PROHIBITED' | 'ADAPTER_REDIRECT_PROHIBITED' | 'ADAPTER_SECRET_REJECTED' | 'ADAPTER_INTERNAL_ERROR';
|
|
6
|
+
export interface AdapterDiagnostic {
|
|
7
|
+
code: AdapterDiagnosticCode;
|
|
8
|
+
severity: 'info' | 'warning' | 'error';
|
|
9
|
+
message: string;
|
|
10
|
+
operation: AdapterOperation;
|
|
11
|
+
path?: string;
|
|
12
|
+
retryable: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface AdapterSchemaDeclaration extends SchemaBinding {
|
|
15
|
+
digest: Digest;
|
|
16
|
+
dialect: 'https://json-schema.org/draft/2020-12/schema';
|
|
17
|
+
schema: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
export interface AdapterManifest {
|
|
20
|
+
contractVersion: DatasetAdapterContractVersion;
|
|
21
|
+
kind: 'AdapterManifest';
|
|
22
|
+
id: string;
|
|
23
|
+
version: string;
|
|
24
|
+
packageDigest: Digest;
|
|
25
|
+
datasetContractRange: string;
|
|
26
|
+
sourceKinds: string[];
|
|
27
|
+
schemas: {
|
|
28
|
+
config: AdapterSchemaDeclaration;
|
|
29
|
+
discoveredRecord: AdapterSchemaDeclaration;
|
|
30
|
+
checkpoint: AdapterSchemaDeclaration;
|
|
31
|
+
};
|
|
32
|
+
capabilities: Array<'discovery' | 'preview' | 'streaming-read' | 'checkpoint' | 'tombstone'>;
|
|
33
|
+
limits: AdapterLimits;
|
|
34
|
+
maturity: 'experimental' | 'qualified' | 'stable';
|
|
35
|
+
publisher: {
|
|
36
|
+
id: string;
|
|
37
|
+
url?: string;
|
|
38
|
+
};
|
|
39
|
+
trust: {
|
|
40
|
+
state: 'builtin' | 'verified' | 'untrusted';
|
|
41
|
+
signature?: string;
|
|
42
|
+
};
|
|
43
|
+
permissions: {
|
|
44
|
+
locality: 'local' | 'remote';
|
|
45
|
+
network: 'none' | 'https-allowlisted';
|
|
46
|
+
credentials: 'none' | 'locator-only';
|
|
47
|
+
};
|
|
48
|
+
incremental: {
|
|
49
|
+
ordering: string;
|
|
50
|
+
sameCursorTieHandling: string;
|
|
51
|
+
lateArrivals: string;
|
|
52
|
+
tombstones: string;
|
|
53
|
+
checkpointCompatibility: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface CredentialLocator {
|
|
57
|
+
kind: 'opaque' | 'environment' | 'keychain' | 'vault';
|
|
58
|
+
locator: string;
|
|
59
|
+
}
|
|
60
|
+
export interface AdapterLimits {
|
|
61
|
+
maxRecords: number;
|
|
62
|
+
maxBytes: number;
|
|
63
|
+
maxRecordBytes: number;
|
|
64
|
+
timeoutMs: number;
|
|
65
|
+
maxRedirects: number;
|
|
66
|
+
maxDepth: number;
|
|
67
|
+
}
|
|
68
|
+
export interface AdapterPolicy {
|
|
69
|
+
offline: boolean;
|
|
70
|
+
allowedRoot?: string;
|
|
71
|
+
allowedHosts?: string[];
|
|
72
|
+
allowPrivateNetwork?: boolean;
|
|
73
|
+
}
|
|
74
|
+
export interface AdapterRequest<TConfig extends Record<string, unknown> = Record<string, unknown>> {
|
|
75
|
+
contractVersion: DatasetAdapterContractVersion;
|
|
76
|
+
requestId: string;
|
|
77
|
+
config: TConfig;
|
|
78
|
+
policy: AdapterPolicy;
|
|
79
|
+
limits: AdapterLimits;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}
|
|
82
|
+
export interface AdapterCheckpoint {
|
|
83
|
+
contractVersion: DatasetAdapterContractVersion;
|
|
84
|
+
kind: 'AdapterCheckpoint';
|
|
85
|
+
adapter: {
|
|
86
|
+
id: string;
|
|
87
|
+
version: string;
|
|
88
|
+
};
|
|
89
|
+
sourceIdentity: string;
|
|
90
|
+
cursor: string;
|
|
91
|
+
tieBreaker?: string;
|
|
92
|
+
schema: SchemaBinding;
|
|
93
|
+
createdAt: string;
|
|
94
|
+
}
|
|
95
|
+
export interface AdapterConfigurationResult<TConfig extends Record<string, unknown> = Record<string, unknown>> {
|
|
96
|
+
ok: boolean;
|
|
97
|
+
config?: TConfig;
|
|
98
|
+
configDigest?: Digest;
|
|
99
|
+
diagnostics: AdapterDiagnostic[];
|
|
100
|
+
}
|
|
101
|
+
export interface AdapterDiscovery {
|
|
102
|
+
sourceIdentity: string;
|
|
103
|
+
proposedSchema: Record<string, unknown>;
|
|
104
|
+
schemaBasis: 'declared' | 'observed' | 'inferred';
|
|
105
|
+
estimates: {
|
|
106
|
+
records?: number;
|
|
107
|
+
bytes?: number;
|
|
108
|
+
};
|
|
109
|
+
privacySignals: PrivacyClassification[];
|
|
110
|
+
licenseSignals: string[];
|
|
111
|
+
cursorSupport: boolean;
|
|
112
|
+
checkpointSupport: boolean;
|
|
113
|
+
identityStability: 'stable' | 'source-unstable';
|
|
114
|
+
identityLimitation?: string;
|
|
115
|
+
limitations: string[];
|
|
116
|
+
}
|
|
117
|
+
export interface AdapterResult<T> {
|
|
118
|
+
ok: boolean;
|
|
119
|
+
value?: T;
|
|
120
|
+
diagnostics: AdapterDiagnostic[];
|
|
121
|
+
}
|
|
122
|
+
export interface AdapterRecord {
|
|
123
|
+
logicalId: string;
|
|
124
|
+
ordinal: number;
|
|
125
|
+
value: unknown;
|
|
126
|
+
sourceLocator: string;
|
|
127
|
+
contentDigest: Digest;
|
|
128
|
+
tombstone?: boolean;
|
|
129
|
+
}
|
|
130
|
+
export type AdapterReadEvent = {
|
|
131
|
+
kind: 'record';
|
|
132
|
+
record: AdapterRecord;
|
|
133
|
+
} | {
|
|
134
|
+
kind: 'checkpoint';
|
|
135
|
+
checkpoint: AdapterCheckpoint;
|
|
136
|
+
} | {
|
|
137
|
+
kind: 'diagnostic';
|
|
138
|
+
diagnostic: AdapterDiagnostic;
|
|
139
|
+
};
|
|
140
|
+
export interface DatasetSourceAdapter<TConfig extends Record<string, unknown> = Record<string, unknown>> {
|
|
141
|
+
describe(): AdapterManifest;
|
|
142
|
+
configure(config: unknown): Promise<AdapterConfigurationResult<TConfig>>;
|
|
143
|
+
check(request: AdapterRequest<TConfig>): Promise<AdapterResult<{
|
|
144
|
+
sourceIdentity: string;
|
|
145
|
+
}>>;
|
|
146
|
+
discover(request: AdapterRequest<TConfig>): Promise<AdapterResult<AdapterDiscovery>>;
|
|
147
|
+
preview(request: AdapterRequest<TConfig> & {
|
|
148
|
+
count: number;
|
|
149
|
+
}): Promise<AdapterResult<AdapterRecord[]>>;
|
|
150
|
+
read(request: AdapterRequest<TConfig> & {
|
|
151
|
+
checkpoint?: AdapterCheckpoint;
|
|
152
|
+
}): AsyncIterable<AdapterReadEvent>;
|
|
153
|
+
}
|
|
154
|
+
export interface AdapterQualificationCell {
|
|
155
|
+
name: string;
|
|
156
|
+
source: 'fixture' | 'real-source';
|
|
157
|
+
passed: boolean;
|
|
158
|
+
evidence: string[];
|
|
159
|
+
}
|
|
160
|
+
export interface AdapterQualificationReport {
|
|
161
|
+
contractVersion: DatasetAdapterContractVersion;
|
|
162
|
+
kind: 'AdapterQualificationReport';
|
|
163
|
+
adapter: {
|
|
164
|
+
id: string;
|
|
165
|
+
version: string;
|
|
166
|
+
packageDigest: Digest;
|
|
167
|
+
};
|
|
168
|
+
schemas: {
|
|
169
|
+
config: SchemaBinding;
|
|
170
|
+
record: SchemaBinding;
|
|
171
|
+
checkpoint: SchemaBinding;
|
|
172
|
+
};
|
|
173
|
+
fixtureRevision: string;
|
|
174
|
+
cells: AdapterQualificationCell[];
|
|
175
|
+
qualifiedAt: string;
|
|
176
|
+
qualified: boolean;
|
|
177
|
+
stableEligible: boolean;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=adapter-types.d.ts.map
|