@contractspec/lib.contracts 1.49.0 → 1.51.0
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/dist/app-config/contracts.d.ts +51 -51
- package/dist/app-config/events.d.ts +27 -27
- package/dist/app-config/lifecycle-contracts.d.ts +55 -55
- package/dist/app-config/runtime.d.ts +1 -1
- package/dist/app-config/spec.d.ts +1 -1
- package/dist/capabilities/capabilities.d.ts +64 -5
- package/dist/capabilities/capabilities.js +125 -0
- package/dist/capabilities/context.d.ts +88 -0
- package/dist/capabilities/context.js +87 -0
- package/dist/capabilities/docs/capabilities.docblock.js +191 -2
- package/dist/capabilities/guards.d.ts +110 -0
- package/dist/capabilities/guards.js +146 -0
- package/dist/capabilities/index.d.ts +4 -1
- package/dist/capabilities/index.js +4 -1
- package/dist/capabilities/validation.d.ts +76 -0
- package/dist/capabilities/validation.js +141 -0
- package/dist/client/react/feature-render.d.ts +2 -2
- package/dist/data-views/runtime.d.ts +1 -1
- package/dist/events.d.ts +79 -13
- package/dist/events.js +33 -3
- package/dist/examples/schema.d.ts +10 -10
- package/dist/experiments/spec.d.ts +7 -4
- package/dist/features/install.d.ts +4 -4
- package/dist/features/types.d.ts +28 -32
- package/dist/index.d.ts +21 -12
- package/dist/index.js +12 -3
- package/dist/install.d.ts +1 -1
- package/dist/integrations/openbanking/contracts/accounts.d.ts +67 -67
- package/dist/integrations/openbanking/contracts/balances.d.ts +35 -35
- package/dist/integrations/openbanking/contracts/transactions.d.ts +49 -49
- package/dist/integrations/openbanking/models.d.ts +55 -55
- package/dist/integrations/operations.d.ts +103 -103
- package/dist/integrations/spec.d.ts +1 -1
- package/dist/knowledge/operations.d.ts +67 -67
- package/dist/llm/exporters.d.ts +2 -2
- package/dist/markdown.d.ts +1 -1
- package/dist/onboarding-base.d.ts +29 -29
- package/dist/operations/operation.d.ts +6 -0
- package/dist/ownership.d.ts +133 -8
- package/dist/ownership.js +25 -0
- package/dist/policy/context.d.ts +237 -0
- package/dist/policy/context.js +227 -0
- package/dist/policy/guards.d.ts +145 -0
- package/dist/policy/guards.js +254 -0
- package/dist/policy/index.d.ts +12 -1
- package/dist/policy/index.js +11 -1
- package/dist/policy/spec.d.ts +7 -4
- package/dist/policy/validation.d.ts +67 -0
- package/dist/policy/validation.js +307 -0
- package/dist/presentations/presentations.d.ts +6 -0
- package/dist/tests/spec.d.ts +17 -12
- package/dist/themes.d.ts +7 -4
- package/dist/translations/index.d.ts +6 -0
- package/dist/translations/index.js +5 -0
- package/dist/translations/registry.d.ts +144 -0
- package/dist/translations/registry.js +223 -0
- package/dist/translations/spec.d.ts +126 -0
- package/dist/translations/spec.js +31 -0
- package/dist/translations/validation.d.ts +85 -0
- package/dist/translations/validation.js +328 -0
- package/dist/types.d.ts +140 -14
- package/dist/versioning/index.d.ts +2 -1
- package/dist/versioning/index.js +2 -1
- package/dist/versioning/refs.d.ts +179 -0
- package/dist/versioning/refs.js +161 -0
- package/dist/workflow/context.d.ts +191 -0
- package/dist/workflow/context.js +227 -0
- package/dist/workflow/index.d.ts +4 -2
- package/dist/workflow/index.js +4 -2
- package/dist/workflow/spec.d.ts +1 -1
- package/dist/workflow/validation.d.ts +64 -2
- package/dist/workflow/validation.js +194 -1
- package/package.json +19 -6
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/capabilities/context.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when a required capability is missing.
|
|
4
|
+
*/
|
|
5
|
+
var CapabilityMissingError = class extends Error {
|
|
6
|
+
capabilityKey;
|
|
7
|
+
requiredVersion;
|
|
8
|
+
constructor(capabilityKey, requiredVersion) {
|
|
9
|
+
const versionSuffix = requiredVersion ? `.v${requiredVersion}` : "";
|
|
10
|
+
super(`Missing required capability: ${capabilityKey}${versionSuffix}`);
|
|
11
|
+
this.name = "CapabilityMissingError";
|
|
12
|
+
this.capabilityKey = capabilityKey;
|
|
13
|
+
this.requiredVersion = requiredVersion;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var CapabilityContextImpl = class {
|
|
17
|
+
capabilities;
|
|
18
|
+
capabilityVersions;
|
|
19
|
+
constructor(enabledCapabilities) {
|
|
20
|
+
const capSet = /* @__PURE__ */ new Set();
|
|
21
|
+
const versionMap = /* @__PURE__ */ new Map();
|
|
22
|
+
for (const cap of enabledCapabilities) {
|
|
23
|
+
capSet.add(cap.key);
|
|
24
|
+
versionMap.set(cap.key, cap.version);
|
|
25
|
+
}
|
|
26
|
+
this.capabilities = capSet;
|
|
27
|
+
this.capabilityVersions = versionMap;
|
|
28
|
+
}
|
|
29
|
+
hasCapability(key, version) {
|
|
30
|
+
if (!this.capabilities.has(key)) return false;
|
|
31
|
+
if (version != null) return this.capabilityVersions.get(key) === version;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
requireCapability(key, version) {
|
|
35
|
+
if (!this.hasCapability(key, version)) throw new CapabilityMissingError(key, version);
|
|
36
|
+
}
|
|
37
|
+
hasAllCapabilities(keys) {
|
|
38
|
+
return keys.every((k) => this.capabilities.has(k));
|
|
39
|
+
}
|
|
40
|
+
hasAnyCapability(keys) {
|
|
41
|
+
return keys.some((k) => this.capabilities.has(k));
|
|
42
|
+
}
|
|
43
|
+
getMatchingCapabilities(pattern) {
|
|
44
|
+
if (!pattern.includes("*")) return this.capabilities.has(pattern) ? [pattern] : [];
|
|
45
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
|
|
46
|
+
const regex = /* @__PURE__ */ new RegExp(`^${regexPattern}$`);
|
|
47
|
+
return [...this.capabilities].filter((key) => regex.test(key));
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Creates a capability context from enabled capabilities.
|
|
52
|
+
*
|
|
53
|
+
* @param enabledCapabilities - Array of capability refs that are enabled
|
|
54
|
+
* @returns CapabilityContext for checking/requiring capabilities
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* // From user subscription capabilities
|
|
59
|
+
* const userCaps = await getUserCapabilities(userId);
|
|
60
|
+
* const ctx = createCapabilityContext(userCaps);
|
|
61
|
+
*
|
|
62
|
+
* // In handler
|
|
63
|
+
* ctx.requireCapability('premium-features');
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
function createCapabilityContext(enabledCapabilities) {
|
|
67
|
+
return new CapabilityContextImpl(enabledCapabilities);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates an empty capability context (no capabilities enabled).
|
|
71
|
+
* Useful for anonymous users or testing.
|
|
72
|
+
*/
|
|
73
|
+
function createEmptyCapabilityContext() {
|
|
74
|
+
return new CapabilityContextImpl([]);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a capability context with all capabilities enabled (bypass).
|
|
78
|
+
* Useful for admin users or internal services.
|
|
79
|
+
*
|
|
80
|
+
* @param allCapabilities - Array of all capability refs to enable
|
|
81
|
+
*/
|
|
82
|
+
function createBypassCapabilityContext(allCapabilities) {
|
|
83
|
+
return new CapabilityContextImpl(allCapabilities);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
export { CapabilityMissingError, createBypassCapabilityContext, createCapabilityContext, createEmptyCapabilityContext };
|
|
@@ -4,7 +4,7 @@ import { registerDocBlocks } from "../../docs/registry.js";
|
|
|
4
4
|
const tech_contracts_capabilities_DocBlocks = [{
|
|
5
5
|
id: "docs.tech.contracts.capabilities",
|
|
6
6
|
title: "CapabilitySpec Overview",
|
|
7
|
-
summary: "Capability specs
|
|
7
|
+
summary: "Capability specs define what a module provides (operations, events, presentations) and requires (dependencies). They enable bidirectional linking, inheritance, runtime enforcement, and automated validation.",
|
|
8
8
|
kind: "reference",
|
|
9
9
|
visibility: "public",
|
|
10
10
|
route: "/docs/tech/contracts/capabilities",
|
|
@@ -13,7 +13,196 @@ const tech_contracts_capabilities_DocBlocks = [{
|
|
|
13
13
|
"contracts",
|
|
14
14
|
"capabilities"
|
|
15
15
|
],
|
|
16
|
-
body:
|
|
16
|
+
body: `# CapabilitySpec Overview
|
|
17
|
+
|
|
18
|
+
## Purpose
|
|
19
|
+
|
|
20
|
+
Capabilities are **module interfaces** that define:
|
|
21
|
+
1. What operations, events, and presentations a module exposes (\`provides\`)
|
|
22
|
+
2. What other capabilities it depends on (\`requires\`)
|
|
23
|
+
3. Inheritance hierarchies via \`extends\`
|
|
24
|
+
|
|
25
|
+
They enable:
|
|
26
|
+
- **Bidirectional linking**: Specs reference capabilities, capabilities list their specs
|
|
27
|
+
- **Dependency validation**: Features can't install without satisfying requirements
|
|
28
|
+
- **Runtime enforcement**: Check capabilities before executing operations
|
|
29
|
+
- **Inheritance**: Build capability hierarchies with shared requirements
|
|
30
|
+
|
|
31
|
+
## Schema
|
|
32
|
+
|
|
33
|
+
\`\`\`ts
|
|
34
|
+
export interface CapabilitySpec {
|
|
35
|
+
meta: CapabilityMeta; // ownership metadata + { key, version, kind }
|
|
36
|
+
extends?: CapabilityRef; // NEW: inherit from parent capability
|
|
37
|
+
provides?: CapabilitySurfaceRef[]; // surfaces this capability exposes
|
|
38
|
+
requires?: CapabilityRequirement[];// capabilities that must exist
|
|
39
|
+
}
|
|
40
|
+
\`\`\`
|
|
41
|
+
|
|
42
|
+
### Bidirectional Linking
|
|
43
|
+
|
|
44
|
+
Operations, events, and presentations can now declare their capability:
|
|
45
|
+
|
|
46
|
+
\`\`\`ts
|
|
47
|
+
// In OperationSpec
|
|
48
|
+
{
|
|
49
|
+
meta: { key: 'payments.charge.create', ... },
|
|
50
|
+
capability: { key: 'payments', version: '1.0.0' }, // Links to capability
|
|
51
|
+
io: { ... }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// In CapabilitySpec
|
|
55
|
+
{
|
|
56
|
+
meta: { key: 'payments', version: '1.0.0', ... },
|
|
57
|
+
provides: [
|
|
58
|
+
{ surface: 'operation', key: 'payments.charge.create' }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
Validation ensures both sides match via \`validateCapabilityConsistency()\`.
|
|
64
|
+
|
|
65
|
+
## Registry Query Methods
|
|
66
|
+
|
|
67
|
+
The \`CapabilityRegistry\` now provides rich query capabilities:
|
|
68
|
+
|
|
69
|
+
\`\`\`ts
|
|
70
|
+
// Forward lookups: Capability → Specs
|
|
71
|
+
registry.getOperationsFor('payments'); // ['payments.charge.create', ...]
|
|
72
|
+
registry.getEventsFor('payments'); // ['payments.charge.succeeded', ...]
|
|
73
|
+
registry.getPresentationsFor('payments'); // ['payments.dashboard', ...]
|
|
74
|
+
|
|
75
|
+
// Reverse lookups: Spec → Capabilities
|
|
76
|
+
registry.getCapabilitiesForOperation('payments.charge.create');
|
|
77
|
+
registry.getCapabilitiesForEvent('payments.charge.succeeded');
|
|
78
|
+
registry.getCapabilitiesForPresentation('payments.dashboard');
|
|
79
|
+
|
|
80
|
+
// Inheritance
|
|
81
|
+
registry.getAncestors('payments.stripe'); // Parent chain
|
|
82
|
+
registry.getEffectiveRequirements('payments.stripe'); // Includes inherited
|
|
83
|
+
registry.getEffectiveSurfaces('payments.stripe'); // Includes inherited
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
## Inheritance
|
|
87
|
+
|
|
88
|
+
Capabilities can extend other capabilities:
|
|
89
|
+
|
|
90
|
+
\`\`\`ts
|
|
91
|
+
// Base capability
|
|
92
|
+
defineCapability({
|
|
93
|
+
meta: { key: 'payments.base', version: '1.0.0', ... },
|
|
94
|
+
requires: [{ key: 'auth', version: '1.0.0' }],
|
|
95
|
+
provides: [{ surface: 'operation', key: 'payments.list' }]
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Child capability inherits requirements
|
|
99
|
+
defineCapability({
|
|
100
|
+
meta: { key: 'payments.stripe', version: '1.0.0', ... },
|
|
101
|
+
extends: { key: 'payments.base', version: '1.0.0' },
|
|
102
|
+
requires: [{ key: 'encryption', version: '1.0.0' }], // Added
|
|
103
|
+
provides: [{ surface: 'operation', key: 'payments.stripe.charge' }]
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// getEffectiveRequirements('payments.stripe') returns:
|
|
107
|
+
// [{ key: 'auth', ... }, { key: 'encryption', ... }]
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
## Runtime Enforcement
|
|
111
|
+
|
|
112
|
+
Use \`CapabilityContext\` for opt-in runtime checks:
|
|
113
|
+
|
|
114
|
+
\`\`\`ts
|
|
115
|
+
import { createCapabilityContext, assertCapabilityForOperation } from '@contractspec/lib.contracts';
|
|
116
|
+
|
|
117
|
+
// Create context from user's enabled capabilities
|
|
118
|
+
const ctx = createCapabilityContext(user.capabilities);
|
|
119
|
+
|
|
120
|
+
// Check capability
|
|
121
|
+
if (ctx.hasCapability('payments')) {
|
|
122
|
+
// User can access payments features
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Assert capability (throws if missing)
|
|
126
|
+
ctx.requireCapability('payments');
|
|
127
|
+
|
|
128
|
+
// Guard an operation
|
|
129
|
+
assertCapabilityForOperation(ctx, paymentOperation);
|
|
130
|
+
|
|
131
|
+
// Filter operations by enabled capabilities
|
|
132
|
+
const allowedOps = filterOperationsByCapability(ctx, allOperations);
|
|
133
|
+
\`\`\`
|
|
134
|
+
|
|
135
|
+
## Validation
|
|
136
|
+
|
|
137
|
+
Validate bidirectional consistency between capabilities and specs:
|
|
138
|
+
|
|
139
|
+
\`\`\`ts
|
|
140
|
+
import { validateCapabilityConsistency, findOrphanSpecs } from '@contractspec/lib.contracts';
|
|
141
|
+
|
|
142
|
+
const result = validateCapabilityConsistency({
|
|
143
|
+
capabilities: capabilityRegistry,
|
|
144
|
+
operations: operationRegistry,
|
|
145
|
+
events: eventRegistry,
|
|
146
|
+
presentations: presentationRegistry,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (!result.valid) {
|
|
150
|
+
console.error('Validation errors:', result.errors);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Find specs without capability assignment (informational)
|
|
154
|
+
const orphans = findOrphanSpecs({ capabilities, operations });
|
|
155
|
+
\`\`\`
|
|
156
|
+
|
|
157
|
+
## Feature Integration
|
|
158
|
+
|
|
159
|
+
During \`installFeature()\`:
|
|
160
|
+
1. \`provides\` capabilities must exist in the registry
|
|
161
|
+
2. \`requires\` must be satisfied by registered capabilities or local \`provides\`
|
|
162
|
+
3. Referenced operations/events/presentations must exist
|
|
163
|
+
|
|
164
|
+
## Authoring Guidelines
|
|
165
|
+
|
|
166
|
+
1. **Register capabilities first** before referencing them in features
|
|
167
|
+
2. **Use bidirectional linking** - set \`capability\` on specs and list them in \`provides\`
|
|
168
|
+
3. **Version consciously** - bump versions on breaking changes
|
|
169
|
+
4. **Document dependencies** with \`reason\` strings
|
|
170
|
+
5. **Use inheritance** for capability families with shared requirements
|
|
171
|
+
6. **Validate during CI** with \`validateCapabilityConsistency()\`
|
|
172
|
+
|
|
173
|
+
## Error Handling
|
|
174
|
+
|
|
175
|
+
\`\`\`ts
|
|
176
|
+
import { CapabilityMissingError } from '@contractspec/lib.contracts';
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
ctx.requireCapability('premium-features');
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof CapabilityMissingError) {
|
|
182
|
+
console.log('Missing:', err.capabilityKey);
|
|
183
|
+
console.log('Required version:', err.requiredVersion);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
\`\`\`
|
|
187
|
+
|
|
188
|
+
## API Reference
|
|
189
|
+
|
|
190
|
+
### Types
|
|
191
|
+
- \`CapabilitySpec\` - Capability definition
|
|
192
|
+
- \`CapabilityRef\` - Reference to a capability (key + version)
|
|
193
|
+
- \`CapabilitySurfaceRef\` - Reference to a provided surface
|
|
194
|
+
- \`CapabilityRequirement\` - Dependency requirement
|
|
195
|
+
- \`CapabilityContext\` - Runtime capability context
|
|
196
|
+
- \`CapabilityValidationResult\` - Validation result
|
|
197
|
+
|
|
198
|
+
### Functions
|
|
199
|
+
- \`defineCapability(spec)\` - Define a capability spec
|
|
200
|
+
- \`createCapabilityContext(caps)\` - Create runtime context
|
|
201
|
+
- \`validateCapabilityConsistency(deps)\` - Validate bidirectional links
|
|
202
|
+
- \`findOrphanSpecs(deps)\` - Find specs without capability assignment
|
|
203
|
+
- \`assertCapabilityForOperation/Event/Presentation(ctx, spec)\` - Guards
|
|
204
|
+
- \`filterOperationsByCapability(ctx, ops)\` - Filter by enabled capabilities
|
|
205
|
+
`
|
|
17
206
|
}];
|
|
18
207
|
registerDocBlocks(tech_contracts_capabilities_DocBlocks);
|
|
19
208
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { PresentationSpec } from "../presentations/presentations.js";
|
|
2
|
+
import { AnyOperationSpec } from "../operations/operation.js";
|
|
3
|
+
import { CapabilityContext } from "./context.js";
|
|
4
|
+
import { AnyEventSpec } from "../events.js";
|
|
5
|
+
|
|
6
|
+
//#region src/capabilities/guards.d.ts
|
|
7
|
+
|
|
8
|
+
/** Result of a capability guard check. */
|
|
9
|
+
interface CapabilityGuardResult {
|
|
10
|
+
/** Whether the guard passed. */
|
|
11
|
+
allowed: boolean;
|
|
12
|
+
/** Missing capability if guard failed. */
|
|
13
|
+
missingCapability?: {
|
|
14
|
+
key: string;
|
|
15
|
+
version: string;
|
|
16
|
+
};
|
|
17
|
+
/** Reason for denial if guard failed. */
|
|
18
|
+
reason?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if an operation's capability is enabled in the context.
|
|
22
|
+
*
|
|
23
|
+
* @param ctx - Capability context to check against
|
|
24
|
+
* @param operation - Operation spec to check
|
|
25
|
+
* @returns Guard result indicating if operation is allowed
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const result = checkCapabilityForOperation(ctx, myOperation);
|
|
30
|
+
* if (!result.allowed) {
|
|
31
|
+
* console.log('Denied:', result.reason);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
declare function checkCapabilityForOperation(ctx: CapabilityContext, operation: AnyOperationSpec): CapabilityGuardResult;
|
|
36
|
+
/**
|
|
37
|
+
* Assert that an operation's capability is enabled, throwing if not.
|
|
38
|
+
*
|
|
39
|
+
* @param ctx - Capability context to check against
|
|
40
|
+
* @param operation - Operation spec to check
|
|
41
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Throws if capability missing
|
|
46
|
+
* assertCapabilityForOperation(ctx, myOperation);
|
|
47
|
+
*
|
|
48
|
+
* // Safe to proceed with operation
|
|
49
|
+
* await handler(input);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function assertCapabilityForOperation(ctx: CapabilityContext, operation: AnyOperationSpec): void;
|
|
53
|
+
/**
|
|
54
|
+
* Check if an event's capability is enabled in the context.
|
|
55
|
+
*
|
|
56
|
+
* @param ctx - Capability context to check against
|
|
57
|
+
* @param event - Event spec to check
|
|
58
|
+
* @returns Guard result indicating if event is allowed
|
|
59
|
+
*/
|
|
60
|
+
declare function checkCapabilityForEvent(ctx: CapabilityContext, event: AnyEventSpec): CapabilityGuardResult;
|
|
61
|
+
/**
|
|
62
|
+
* Assert that an event's capability is enabled, throwing if not.
|
|
63
|
+
*
|
|
64
|
+
* @param ctx - Capability context to check against
|
|
65
|
+
* @param event - Event spec to check
|
|
66
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
67
|
+
*/
|
|
68
|
+
declare function assertCapabilityForEvent(ctx: CapabilityContext, event: AnyEventSpec): void;
|
|
69
|
+
/**
|
|
70
|
+
* Check if a presentation's capability is enabled in the context.
|
|
71
|
+
*
|
|
72
|
+
* @param ctx - Capability context to check against
|
|
73
|
+
* @param presentation - Presentation spec to check
|
|
74
|
+
* @returns Guard result indicating if presentation is allowed
|
|
75
|
+
*/
|
|
76
|
+
declare function checkCapabilityForPresentation(ctx: CapabilityContext, presentation: PresentationSpec): CapabilityGuardResult;
|
|
77
|
+
/**
|
|
78
|
+
* Assert that a presentation's capability is enabled, throwing if not.
|
|
79
|
+
*
|
|
80
|
+
* @param ctx - Capability context to check against
|
|
81
|
+
* @param presentation - Presentation spec to check
|
|
82
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
83
|
+
*/
|
|
84
|
+
declare function assertCapabilityForPresentation(ctx: CapabilityContext, presentation: PresentationSpec): void;
|
|
85
|
+
/**
|
|
86
|
+
* Filter operations to only those with enabled capabilities.
|
|
87
|
+
*
|
|
88
|
+
* @param ctx - Capability context to check against
|
|
89
|
+
* @param operations - Operations to filter
|
|
90
|
+
* @returns Operations that have their capabilities enabled (or no capability requirement)
|
|
91
|
+
*/
|
|
92
|
+
declare function filterOperationsByCapability(ctx: CapabilityContext, operations: AnyOperationSpec[]): AnyOperationSpec[];
|
|
93
|
+
/**
|
|
94
|
+
* Filter events to only those with enabled capabilities.
|
|
95
|
+
*
|
|
96
|
+
* @param ctx - Capability context to check against
|
|
97
|
+
* @param events - Events to filter
|
|
98
|
+
* @returns Events that have their capabilities enabled (or no capability requirement)
|
|
99
|
+
*/
|
|
100
|
+
declare function filterEventsByCapability(ctx: CapabilityContext, events: AnyEventSpec[]): AnyEventSpec[];
|
|
101
|
+
/**
|
|
102
|
+
* Filter presentations to only those with enabled capabilities.
|
|
103
|
+
*
|
|
104
|
+
* @param ctx - Capability context to check against
|
|
105
|
+
* @param presentations - Presentations to filter
|
|
106
|
+
* @returns Presentations that have their capabilities enabled (or no capability requirement)
|
|
107
|
+
*/
|
|
108
|
+
declare function filterPresentationsByCapability(ctx: CapabilityContext, presentations: PresentationSpec[]): PresentationSpec[];
|
|
109
|
+
//#endregion
|
|
110
|
+
export { CapabilityGuardResult, assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { CapabilityMissingError } from "./context.js";
|
|
2
|
+
|
|
3
|
+
//#region src/capabilities/guards.ts
|
|
4
|
+
/**
|
|
5
|
+
* Check if an operation's capability is enabled in the context.
|
|
6
|
+
*
|
|
7
|
+
* @param ctx - Capability context to check against
|
|
8
|
+
* @param operation - Operation spec to check
|
|
9
|
+
* @returns Guard result indicating if operation is allowed
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const result = checkCapabilityForOperation(ctx, myOperation);
|
|
14
|
+
* if (!result.allowed) {
|
|
15
|
+
* console.log('Denied:', result.reason);
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
function checkCapabilityForOperation(ctx, operation) {
|
|
20
|
+
if (!operation.capability) return { allowed: true };
|
|
21
|
+
const { key, version } = operation.capability;
|
|
22
|
+
if (ctx.hasCapability(key, version)) return { allowed: true };
|
|
23
|
+
return {
|
|
24
|
+
allowed: false,
|
|
25
|
+
missingCapability: {
|
|
26
|
+
key,
|
|
27
|
+
version
|
|
28
|
+
},
|
|
29
|
+
reason: `Operation "${operation.meta.key}" requires capability "${key}.v${version}"`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Assert that an operation's capability is enabled, throwing if not.
|
|
34
|
+
*
|
|
35
|
+
* @param ctx - Capability context to check against
|
|
36
|
+
* @param operation - Operation spec to check
|
|
37
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Throws if capability missing
|
|
42
|
+
* assertCapabilityForOperation(ctx, myOperation);
|
|
43
|
+
*
|
|
44
|
+
* // Safe to proceed with operation
|
|
45
|
+
* await handler(input);
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
function assertCapabilityForOperation(ctx, operation) {
|
|
49
|
+
const result = checkCapabilityForOperation(ctx, operation);
|
|
50
|
+
if (!result.allowed && result.missingCapability) throw new CapabilityMissingError(result.missingCapability.key, result.missingCapability.version);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Check if an event's capability is enabled in the context.
|
|
54
|
+
*
|
|
55
|
+
* @param ctx - Capability context to check against
|
|
56
|
+
* @param event - Event spec to check
|
|
57
|
+
* @returns Guard result indicating if event is allowed
|
|
58
|
+
*/
|
|
59
|
+
function checkCapabilityForEvent(ctx, event) {
|
|
60
|
+
if (!event.capability) return { allowed: true };
|
|
61
|
+
const { key, version } = event.capability;
|
|
62
|
+
if (ctx.hasCapability(key, version)) return { allowed: true };
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
missingCapability: {
|
|
66
|
+
key,
|
|
67
|
+
version
|
|
68
|
+
},
|
|
69
|
+
reason: `Event "${event.meta.key}" requires capability "${key}.v${version}"`
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Assert that an event's capability is enabled, throwing if not.
|
|
74
|
+
*
|
|
75
|
+
* @param ctx - Capability context to check against
|
|
76
|
+
* @param event - Event spec to check
|
|
77
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
78
|
+
*/
|
|
79
|
+
function assertCapabilityForEvent(ctx, event) {
|
|
80
|
+
const result = checkCapabilityForEvent(ctx, event);
|
|
81
|
+
if (!result.allowed && result.missingCapability) throw new CapabilityMissingError(result.missingCapability.key, result.missingCapability.version);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if a presentation's capability is enabled in the context.
|
|
85
|
+
*
|
|
86
|
+
* @param ctx - Capability context to check against
|
|
87
|
+
* @param presentation - Presentation spec to check
|
|
88
|
+
* @returns Guard result indicating if presentation is allowed
|
|
89
|
+
*/
|
|
90
|
+
function checkCapabilityForPresentation(ctx, presentation) {
|
|
91
|
+
if (!presentation.capability) return { allowed: true };
|
|
92
|
+
const { key, version } = presentation.capability;
|
|
93
|
+
if (ctx.hasCapability(key, version)) return { allowed: true };
|
|
94
|
+
return {
|
|
95
|
+
allowed: false,
|
|
96
|
+
missingCapability: {
|
|
97
|
+
key,
|
|
98
|
+
version
|
|
99
|
+
},
|
|
100
|
+
reason: `Presentation "${presentation.meta.key}" requires capability "${key}.v${version}"`
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Assert that a presentation's capability is enabled, throwing if not.
|
|
105
|
+
*
|
|
106
|
+
* @param ctx - Capability context to check against
|
|
107
|
+
* @param presentation - Presentation spec to check
|
|
108
|
+
* @throws {CapabilityMissingError} If capability is not enabled
|
|
109
|
+
*/
|
|
110
|
+
function assertCapabilityForPresentation(ctx, presentation) {
|
|
111
|
+
const result = checkCapabilityForPresentation(ctx, presentation);
|
|
112
|
+
if (!result.allowed && result.missingCapability) throw new CapabilityMissingError(result.missingCapability.key, result.missingCapability.version);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Filter operations to only those with enabled capabilities.
|
|
116
|
+
*
|
|
117
|
+
* @param ctx - Capability context to check against
|
|
118
|
+
* @param operations - Operations to filter
|
|
119
|
+
* @returns Operations that have their capabilities enabled (or no capability requirement)
|
|
120
|
+
*/
|
|
121
|
+
function filterOperationsByCapability(ctx, operations) {
|
|
122
|
+
return operations.filter((op) => checkCapabilityForOperation(ctx, op).allowed);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Filter events to only those with enabled capabilities.
|
|
126
|
+
*
|
|
127
|
+
* @param ctx - Capability context to check against
|
|
128
|
+
* @param events - Events to filter
|
|
129
|
+
* @returns Events that have their capabilities enabled (or no capability requirement)
|
|
130
|
+
*/
|
|
131
|
+
function filterEventsByCapability(ctx, events) {
|
|
132
|
+
return events.filter((ev) => checkCapabilityForEvent(ctx, ev).allowed);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Filter presentations to only those with enabled capabilities.
|
|
136
|
+
*
|
|
137
|
+
* @param ctx - Capability context to check against
|
|
138
|
+
* @param presentations - Presentations to filter
|
|
139
|
+
* @returns Presentations that have their capabilities enabled (or no capability requirement)
|
|
140
|
+
*/
|
|
141
|
+
function filterPresentationsByCapability(ctx, presentations) {
|
|
142
|
+
return presentations.filter((pres) => checkCapabilityForPresentation(ctx, pres).allowed);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//#endregion
|
|
146
|
+
export { assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability };
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { CapabilityKind, CapabilityMeta, CapabilityRef, CapabilityRegistry, CapabilityRequirement, CapabilitySpec, CapabilitySurface, CapabilitySurfaceRef, capabilityKey, defineCapability } from "./capabilities.js";
|
|
2
|
+
import { CapabilityValidationDeps, CapabilityValidationError, CapabilityValidationResult, findOrphanSpecs, validateCapabilityConsistency } from "./validation.js";
|
|
3
|
+
import { CapabilityContext, CapabilityMissingError, createBypassCapabilityContext, createCapabilityContext, createEmptyCapabilityContext } from "./context.js";
|
|
4
|
+
import { CapabilityGuardResult, assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability } from "./guards.js";
|
|
2
5
|
import { openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities } from "./openbanking.js";
|
|
3
|
-
export { CapabilityKind, CapabilityMeta, CapabilityRef, CapabilityRegistry, CapabilityRequirement, CapabilitySpec, CapabilitySurface, CapabilitySurfaceRef, capabilityKey, defineCapability, openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities };
|
|
6
|
+
export { CapabilityContext, CapabilityGuardResult, CapabilityKind, CapabilityMeta, CapabilityMissingError, CapabilityRef, CapabilityRegistry, CapabilityRequirement, CapabilitySpec, CapabilitySurface, CapabilitySurfaceRef, CapabilityValidationDeps, CapabilityValidationError, CapabilityValidationResult, assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, capabilityKey, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, createBypassCapabilityContext, createCapabilityContext, createEmptyCapabilityContext, defineCapability, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability, findOrphanSpecs, openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities, validateCapabilityConsistency };
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { CapabilityRegistry, capabilityKey, defineCapability } from "./capabilities.js";
|
|
2
|
+
import { findOrphanSpecs, validateCapabilityConsistency } from "./validation.js";
|
|
3
|
+
import { CapabilityMissingError, createBypassCapabilityContext, createCapabilityContext, createEmptyCapabilityContext } from "./context.js";
|
|
4
|
+
import { assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability } from "./guards.js";
|
|
2
5
|
import { openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities } from "./openbanking.js";
|
|
3
6
|
|
|
4
|
-
export { CapabilityRegistry, capabilityKey, defineCapability, openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities };
|
|
7
|
+
export { CapabilityMissingError, CapabilityRegistry, assertCapabilityForEvent, assertCapabilityForOperation, assertCapabilityForPresentation, capabilityKey, checkCapabilityForEvent, checkCapabilityForOperation, checkCapabilityForPresentation, createBypassCapabilityContext, createCapabilityContext, createEmptyCapabilityContext, defineCapability, filterEventsByCapability, filterOperationsByCapability, filterPresentationsByCapability, findOrphanSpecs, openBankingAccountsReadCapability, openBankingBalancesReadCapability, openBankingTransactionsReadCapability, registerOpenBankingCapabilities, validateCapabilityConsistency };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CapabilityRegistry, CapabilitySurface } from "./capabilities.js";
|
|
2
|
+
import { OperationSpecRegistry } from "../operations/registry.js";
|
|
3
|
+
import { PresentationRegistry } from "../presentations/registry.js";
|
|
4
|
+
import "../presentations/index.js";
|
|
5
|
+
import { EventRegistry } from "../events.js";
|
|
6
|
+
|
|
7
|
+
//#region src/capabilities/validation.d.ts
|
|
8
|
+
|
|
9
|
+
/** Single validation error describing an inconsistency. */
|
|
10
|
+
interface CapabilityValidationError {
|
|
11
|
+
/** Type of validation error. */
|
|
12
|
+
type: 'missing_surface_spec' | 'orphan_spec' | 'capability_not_found' | 'surface_not_in_provides';
|
|
13
|
+
/** Human-readable error message. */
|
|
14
|
+
message: string;
|
|
15
|
+
/** Capability key involved (if applicable). */
|
|
16
|
+
capabilityKey?: string;
|
|
17
|
+
/** Surface type involved (if applicable). */
|
|
18
|
+
surface?: CapabilitySurface;
|
|
19
|
+
/** Spec key involved (if applicable). */
|
|
20
|
+
specKey?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Result of capability consistency validation. */
|
|
23
|
+
interface CapabilityValidationResult {
|
|
24
|
+
/** Whether validation passed with no errors. */
|
|
25
|
+
valid: boolean;
|
|
26
|
+
/** List of validation errors found. */
|
|
27
|
+
errors: CapabilityValidationError[];
|
|
28
|
+
/** List of warnings (non-blocking issues). */
|
|
29
|
+
warnings: CapabilityValidationError[];
|
|
30
|
+
}
|
|
31
|
+
/** Registries needed for full bidirectional validation. */
|
|
32
|
+
interface CapabilityValidationDeps {
|
|
33
|
+
capabilities: CapabilityRegistry;
|
|
34
|
+
operations?: OperationSpecRegistry;
|
|
35
|
+
events?: EventRegistry;
|
|
36
|
+
presentations?: PresentationRegistry;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Validates bidirectional consistency between capabilities and their surfaces.
|
|
40
|
+
*
|
|
41
|
+
* Checks:
|
|
42
|
+
* 1. Forward validation: Every surface ref in capability `provides` exists
|
|
43
|
+
* 2. Reverse validation: Every spec with `capability` field is in that capability's `provides`
|
|
44
|
+
*
|
|
45
|
+
* @param deps - Registries to validate against
|
|
46
|
+
* @returns Validation result with errors and warnings
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const result = validateCapabilityConsistency({
|
|
51
|
+
* capabilities: capabilityRegistry,
|
|
52
|
+
* operations: operationRegistry,
|
|
53
|
+
* events: eventRegistry,
|
|
54
|
+
* });
|
|
55
|
+
*
|
|
56
|
+
* if (!result.valid) {
|
|
57
|
+
* console.error('Capability validation failed:', result.errors);
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function validateCapabilityConsistency(deps: CapabilityValidationDeps): CapabilityValidationResult;
|
|
62
|
+
/**
|
|
63
|
+
* Finds specs that have no capability assignment (orphan specs).
|
|
64
|
+
* This is informational - orphan specs are allowed but may indicate
|
|
65
|
+
* incomplete capability modeling.
|
|
66
|
+
*
|
|
67
|
+
* @param deps - Registries to check
|
|
68
|
+
* @returns List of spec keys without capability assignment
|
|
69
|
+
*/
|
|
70
|
+
declare function findOrphanSpecs(deps: CapabilityValidationDeps): {
|
|
71
|
+
operations: string[];
|
|
72
|
+
events: string[];
|
|
73
|
+
presentations: string[];
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
export { CapabilityValidationDeps, CapabilityValidationError, CapabilityValidationResult, findOrphanSpecs, validateCapabilityConsistency };
|