agent-passport-system 1.27.0 → 1.29.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/README.md +45 -7
- package/dist/src/core/anchor-state.d.ts +46 -0
- package/dist/src/core/anchor-state.d.ts.map +1 -0
- package/dist/src/core/anchor-state.js +80 -0
- package/dist/src/core/anchor-state.js.map +1 -0
- package/dist/src/core/canonical-jcs.d.ts +23 -0
- package/dist/src/core/canonical-jcs.d.ts.map +1 -0
- package/dist/src/core/canonical-jcs.js +125 -0
- package/dist/src/core/canonical-jcs.js.map +1 -0
- package/dist/src/core/data-narrowing.d.ts +43 -0
- package/dist/src/core/data-narrowing.d.ts.map +1 -0
- package/dist/src/core/data-narrowing.js +97 -0
- package/dist/src/core/data-narrowing.js.map +1 -0
- package/dist/src/core/data-source-attribution.d.ts +17 -0
- package/dist/src/core/data-source-attribution.d.ts.map +1 -0
- package/dist/src/core/data-source-attribution.js +219 -0
- package/dist/src/core/data-source-attribution.js.map +1 -0
- package/dist/src/core/denial-domains.d.ts +43 -0
- package/dist/src/core/denial-domains.d.ts.map +1 -0
- package/dist/src/core/denial-domains.js +153 -0
- package/dist/src/core/denial-domains.js.map +1 -0
- package/dist/src/core/fidelity-probe.d.ts +170 -0
- package/dist/src/core/fidelity-probe.d.ts.map +1 -0
- package/dist/src/core/fidelity-probe.js +189 -0
- package/dist/src/core/fidelity-probe.js.map +1 -0
- package/dist/src/core/gateway-identity.d.ts +59 -0
- package/dist/src/core/gateway-identity.d.ts.map +1 -0
- package/dist/src/core/gateway-identity.js +195 -0
- package/dist/src/core/gateway-identity.js.map +1 -0
- package/dist/src/core/gateway-wiring.d.ts +32 -0
- package/dist/src/core/gateway-wiring.d.ts.map +1 -0
- package/dist/src/core/gateway-wiring.js +71 -0
- package/dist/src/core/gateway-wiring.js.map +1 -0
- package/dist/src/core/gateway.d.ts +12 -1
- package/dist/src/core/gateway.d.ts.map +1 -1
- package/dist/src/core/gateway.js +113 -3
- package/dist/src/core/gateway.js.map +1 -1
- package/dist/src/core/governance-posture.d.ts +72 -0
- package/dist/src/core/governance-posture.d.ts.map +1 -0
- package/dist/src/core/governance-posture.js +173 -0
- package/dist/src/core/governance-posture.js.map +1 -0
- package/dist/src/core/reputation-authority.d.ts +13 -4
- package/dist/src/core/reputation-authority.d.ts.map +1 -1
- package/dist/src/core/reputation-authority.js +29 -5
- package/dist/src/core/reputation-authority.js.map +1 -1
- package/dist/src/index.d.ts +17 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +17 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/types/data-contribution.d.ts +28 -0
- package/dist/src/types/data-contribution.d.ts.map +1 -1
- package/dist/src/types/gateway.d.ts +45 -1
- package/dist/src/types/gateway.d.ts.map +1 -1
- package/dist/src/types/reputation-authority.d.ts +4 -0
- package/dist/src/types/reputation-authority.d.ts.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/agent-passport-system)
|
|
4
4
|
[](https://github.com/aeoess/agent-passport-system/blob/main/LICENSE)
|
|
5
|
-
[](https://github.com/aeoess/agent-passport-system)
|
|
6
6
|
[](https://doi.org/10.5281/zenodo.18749779)
|
|
7
7
|
|
|
8
8
|
> **For AI agents:** visit [aeoess.com/llms.txt](https://aeoess.com/llms.txt) for machine-readable docs or [llms-full.txt](https://aeoess.com/llms-full.txt) for the complete reference.
|
|
9
9
|
|
|
10
|
-
**Governance,
|
|
10
|
+
**Governance infrastructure for the agent economy.** Identity, delegation, reputation, enforcement, commerce, institutional governance. Not just identity — the full stack.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
AI agents represent companies and people. They spend real money, access sensitive data, negotiate contracts, and talk to other agents. APS answers: what is this agent allowed to do? How much can it spend? Is it trustworthy? What happens when it violates a constraint? And can you prove all of this cryptographically?
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
15
|
npm install agent-passport-system
|
|
@@ -70,6 +70,40 @@ const result = await gateway.processToolCall({
|
|
|
70
70
|
|
|
71
71
|
**What just happened:** The gateway verified the agent's identity, checked delegation scope, enforced spend limits, evaluated values floor compliance, verified reputation tier, checked revocation status, executed the tool, generated a signed receipt, and updated reputation. All in one call. The agent never touched the tool directly.
|
|
72
72
|
|
|
73
|
+
## Framework Integration: CrewAI
|
|
74
|
+
|
|
75
|
+
Add governance to any CrewAI crew in 10 lines. Works the same way with LangChain, ADK, A2A, or any custom framework.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { generateKeyPair, createCrewAIGovernance } from 'agent-passport-system'
|
|
79
|
+
|
|
80
|
+
const keys = generateKeyPair()
|
|
81
|
+
const gov = createCrewAIGovernance({
|
|
82
|
+
agentId: 'research-agent',
|
|
83
|
+
...keys,
|
|
84
|
+
delegationId: 'del_research_2026',
|
|
85
|
+
allowedScopes: ['tool:web_search', 'tool:read_file', 'task:execute'],
|
|
86
|
+
spendLimitPerAction: 5.00,
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Wrap any tool call — permitted actions execute, denied ones don't
|
|
90
|
+
const result = await gov.governedToolCall(
|
|
91
|
+
'web_search',
|
|
92
|
+
{ query: 'AI governance standards' },
|
|
93
|
+
() => mySearchTool('AI governance standards'),
|
|
94
|
+
0.01 // estimated cost
|
|
95
|
+
)
|
|
96
|
+
// result.governance.verdict → 'permit' or 'deny'
|
|
97
|
+
// result.receipt → signed proof of the action (or denial)
|
|
98
|
+
|
|
99
|
+
// Use as CrewAI task callback
|
|
100
|
+
const receipt = gov.taskCallback({ description: '...', result: '...', agent: 'research-agent' })
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**What you get:** scope enforcement (agent can only use allowed tools), spend controls ($5/action limit), signed receipts for every action (permit or deny), and a verifiable audit trail. The agent never bypasses governance because the wrapper executes the action, not the agent.
|
|
104
|
+
|
|
105
|
+
See [`examples/crewai-governance.ts`](examples/crewai-governance.ts) for the full working example. Adapters also available for [LangChain](src/adapters/langchain.ts), [Google ADK](src/adapters/adk.ts), and [A2A](src/adapters/a2a.ts).
|
|
106
|
+
|
|
73
107
|
## Identity Is the Foundation, Not the Product
|
|
74
108
|
|
|
75
109
|
Everything above is built on Ed25519 cryptographic identity. But identity is the plumbing, not the value proposition.
|
|
@@ -90,7 +124,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
|
|
|
90
124
|
|
|
91
125
|
## The Stack
|
|
92
126
|
|
|
93
|
-
|
|
127
|
+
62 core modules + 32 v2 constitutional modules. 1715 tests. Zero heavy dependencies.
|
|
94
128
|
|
|
95
129
|
| Layer | What it does | Key primitive |
|
|
96
130
|
|-------|-------------|---------------|
|
|
@@ -109,7 +143,7 @@ const agent = joinSocialContract({ name: 'my-agent', owner: 'alice', floor: floo
|
|
|
109
143
|
|
|
110
144
|
## MCP Server
|
|
111
145
|
|
|
112
|
-
|
|
146
|
+
121 tools across all modules. Any MCP client connects agents directly.
|
|
113
147
|
|
|
114
148
|
```bash
|
|
115
149
|
npm install -g agent-passport-system-mcp
|
|
@@ -144,7 +178,7 @@ npx agent-passport audit --floor values/floor.yaml
|
|
|
144
178
|
|
|
145
179
|
```bash
|
|
146
180
|
npm test
|
|
147
|
-
#
|
|
181
|
+
# 1715 tests across 93 files, 446 suites, 0 failures
|
|
148
182
|
```
|
|
149
183
|
|
|
150
184
|
50 adversarial tests: Merkle tampering, attribution gaming, compliance violations, floor negotiation attacks, cross-chain confused deputy, taint laundering, authority probing.
|
|
@@ -162,7 +196,7 @@ npm test
|
|
|
162
196
|
| Signed receipts | 3-sig chain | Proposed | Logs | General | — |
|
|
163
197
|
| Values enforcement | 8 principles, graduated | — | Rules | — | — |
|
|
164
198
|
| Coordination | Task lifecycle + MCP | — | — | — | — |
|
|
165
|
-
| Tests |
|
|
199
|
+
| Tests | 1715 (50 adversarial) | None | Limited | None | None |
|
|
166
200
|
|
|
167
201
|
## Recognition
|
|
168
202
|
|
|
@@ -177,6 +211,10 @@ npm test
|
|
|
177
211
|
|
|
178
212
|
**"Monotonic Narrowing for Agent Authority"** — Published on [Zenodo](https://doi.org/10.5281/zenodo.18749779). [Read →](papers/agent-social-contract.md)
|
|
179
213
|
|
|
214
|
+
**"Faceted Authority Attenuation: A Product Lattice Model for AI Agent Governance"** — Published on [Zenodo](https://doi.org/10.5281/zenodo.19260073). Seven-dimensional product lattice formalization with authorization witnesses, constraint vectors, and institutional governance composition.
|
|
215
|
+
|
|
216
|
+
**Cited by:** Nanook & Gerundium, "PDR in Production: Empirical Validation of Behavioral Trust Scoring in Multi-Agent Systems" ([DOI:10.5281/zenodo.19323172](https://doi.org/10.5281/zenodo.19323172)) — Section 7.6 independently validates the APS Bayesian reputation model, sigma dynamics, and structuralVerdict/trustVerdict separation using production data from UBC.
|
|
217
|
+
|
|
180
218
|
## Authorship
|
|
181
219
|
|
|
182
220
|
Designed and built by **Tymofii Pidlisnyi** with AI assistance from **Claude** (Anthropic).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** External anchor state for a receipt or batch.
|
|
2
|
+
* unanchored: exists only in gateway memory
|
|
3
|
+
* batched_pending: included in a Merkle batch, root not yet anchored externally
|
|
4
|
+
* anchored: Merkle root published to external log (Rekor, Solana, etc.)
|
|
5
|
+
* critical_direct_anchor: individual receipt anchored directly (bypass batching) */
|
|
6
|
+
export type AnchorState = 'unanchored' | 'batched_pending' | 'anchored' | 'critical_direct_anchor';
|
|
7
|
+
/** Anchor metadata on a receipt */
|
|
8
|
+
export interface AnchorMetadata {
|
|
9
|
+
state: AnchorState;
|
|
10
|
+
/** Batch ID if batched_pending or anchored */
|
|
11
|
+
batchId?: string;
|
|
12
|
+
/** External anchor reference (URL, transaction ID, etc.) */
|
|
13
|
+
anchorRef?: string;
|
|
14
|
+
/** When the anchor was confirmed */
|
|
15
|
+
anchoredAt?: string;
|
|
16
|
+
/** Which anchor backend was used */
|
|
17
|
+
anchorBackend?: string;
|
|
18
|
+
}
|
|
19
|
+
/** Auto-batch configuration */
|
|
20
|
+
export interface AutoBatchConfig {
|
|
21
|
+
/** Maximum seconds between batch commits (0 = disabled) */
|
|
22
|
+
maxIntervalSeconds: number;
|
|
23
|
+
/** Maximum receipts before auto-commit (0 = disabled) */
|
|
24
|
+
maxReceiptsPerBatch: number;
|
|
25
|
+
/** Whether critical/irreversible actions get direct anchor */
|
|
26
|
+
directAnchorCritical: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const DEFAULT_AUTO_BATCH_CONFIG: AutoBatchConfig;
|
|
29
|
+
/** Create initial anchor metadata for a new receipt */
|
|
30
|
+
export declare function createAnchorMetadata(critical?: boolean): AnchorMetadata;
|
|
31
|
+
/** Transition anchor state when receipt is added to a batch */
|
|
32
|
+
export declare function markBatched(anchor: AnchorMetadata, batchId: string): AnchorMetadata;
|
|
33
|
+
/** Transition anchor state when batch root is externally anchored */
|
|
34
|
+
export declare function markAnchored(anchor: AnchorMetadata, anchorRef: string, anchorBackend: string): AnchorMetadata;
|
|
35
|
+
/** Check if auto-batch should fire based on config and current state */
|
|
36
|
+
export declare function shouldAutoBatch(pendingCount: number, lastBatchTime: string | null, config?: AutoBatchConfig): {
|
|
37
|
+
trigger: boolean;
|
|
38
|
+
reason: 'max_receipts' | 'max_interval' | null;
|
|
39
|
+
};
|
|
40
|
+
/** Check if an anchor state meets a minimum requirement */
|
|
41
|
+
export declare function meetsAnchorRequirement(current: AnchorState, minimum: AnchorState): boolean;
|
|
42
|
+
/** Check if anchor state transition is valid (can only move forward) */
|
|
43
|
+
export declare function isValidAnchorTransition(from: AnchorState, to: AnchorState): boolean;
|
|
44
|
+
/** Exported ordering for cross-language verification */
|
|
45
|
+
export declare const ANCHOR_STATE_ORDER: Record<AnchorState, number>;
|
|
46
|
+
//# sourceMappingURL=anchor-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-state.d.ts","sourceRoot":"","sources":["../../../src/core/anchor-state.ts"],"names":[],"mappings":"AAYA;;;;qFAIqF;AACrF,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,iBAAiB,GAAG,UAAU,GAAG,wBAAwB,CAAA;AAElG,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,WAAW,CAAA;IAClB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oCAAoC;IACpC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,+BAA+B;AAC/B,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,kBAAkB,EAAE,MAAM,CAAA;IAC1B,yDAAyD;IACzD,mBAAmB,EAAE,MAAM,CAAA;IAC3B,8DAA8D;IAC9D,oBAAoB,EAAE,OAAO,CAAA;CAC9B;AAED,eAAO,MAAM,yBAAyB,EAAE,eAIvC,CAAA;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,QAAQ,GAAE,OAAe,GAAG,cAAc,CAI9E;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAInF;AAED,qEAAqE;AACrE,wBAAgB,YAAY,CAC1B,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,GACpB,cAAc,CAQhB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAC7B,YAAY,EAAE,MAAM,EACpB,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,MAAM,GAAE,eAA2C,GAClD;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,cAAc,GAAG,cAAc,GAAG,IAAI,CAAA;CAAE,CAsBtE;AAUD,2DAA2D;AAC3D,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,WAAW,GACnB,OAAO,CAET;AAED,wEAAwE;AACxE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,WAAW,GAAG,OAAO,CAEnF;AAED,wDAAwD;AACxD,eAAO,MAAM,kBAAkB,6BAAe,CAAA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════
|
|
2
|
+
// Anchor States — External verifiability tracking for receipts
|
|
3
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4
|
+
// Consilium Priority 6. Gemini: "explicit receipt anchor states."
|
|
5
|
+
// desiorac (A2A #1672): batch commitment lags individual receipts.
|
|
6
|
+
//
|
|
7
|
+
// Every receipt and batch carries an anchor state:
|
|
8
|
+
// unanchored → batched_pending → anchored → critical_direct_anchor
|
|
9
|
+
//
|
|
10
|
+
// Auto-batching: configurable window (N seconds or N receipts).
|
|
11
|
+
// ══════════════════════════════════════════════════════════════════
|
|
12
|
+
export const DEFAULT_AUTO_BATCH_CONFIG = {
|
|
13
|
+
maxIntervalSeconds: 300, // 5 minutes
|
|
14
|
+
maxReceiptsPerBatch: 100,
|
|
15
|
+
directAnchorCritical: true,
|
|
16
|
+
};
|
|
17
|
+
/** Create initial anchor metadata for a new receipt */
|
|
18
|
+
export function createAnchorMetadata(critical = false) {
|
|
19
|
+
return {
|
|
20
|
+
state: critical ? 'critical_direct_anchor' : 'unanchored',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Transition anchor state when receipt is added to a batch */
|
|
24
|
+
export function markBatched(anchor, batchId) {
|
|
25
|
+
if (anchor.state === 'critical_direct_anchor')
|
|
26
|
+
return anchor; // already anchored
|
|
27
|
+
if (anchor.state === 'anchored')
|
|
28
|
+
return anchor; // already anchored
|
|
29
|
+
return { ...anchor, state: 'batched_pending', batchId };
|
|
30
|
+
}
|
|
31
|
+
/** Transition anchor state when batch root is externally anchored */
|
|
32
|
+
export function markAnchored(anchor, anchorRef, anchorBackend) {
|
|
33
|
+
if (anchor.state === 'critical_direct_anchor')
|
|
34
|
+
return anchor;
|
|
35
|
+
return {
|
|
36
|
+
...anchor,
|
|
37
|
+
state: 'anchored',
|
|
38
|
+
anchorRef, anchorBackend,
|
|
39
|
+
anchoredAt: new Date().toISOString(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Check if auto-batch should fire based on config and current state */
|
|
43
|
+
export function shouldAutoBatch(pendingCount, lastBatchTime, config = DEFAULT_AUTO_BATCH_CONFIG) {
|
|
44
|
+
if (pendingCount === 0)
|
|
45
|
+
return { trigger: false, reason: null };
|
|
46
|
+
// Receipt count trigger
|
|
47
|
+
if (config.maxReceiptsPerBatch > 0 && pendingCount >= config.maxReceiptsPerBatch) {
|
|
48
|
+
return { trigger: true, reason: 'max_receipts' };
|
|
49
|
+
}
|
|
50
|
+
// Time interval trigger
|
|
51
|
+
if (config.maxIntervalSeconds > 0 && lastBatchTime) {
|
|
52
|
+
const elapsed = (Date.now() - new Date(lastBatchTime).getTime()) / 1000;
|
|
53
|
+
if (elapsed >= config.maxIntervalSeconds) {
|
|
54
|
+
return { trigger: true, reason: 'max_interval' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// First batch ever — trigger on interval if no previous batch
|
|
58
|
+
if (config.maxIntervalSeconds > 0 && !lastBatchTime && pendingCount > 0) {
|
|
59
|
+
return { trigger: true, reason: 'max_interval' };
|
|
60
|
+
}
|
|
61
|
+
return { trigger: false, reason: null };
|
|
62
|
+
}
|
|
63
|
+
/** Anchor state ordering — higher number = more externally verifiable */
|
|
64
|
+
const ANCHOR_ORDER = {
|
|
65
|
+
unanchored: 0,
|
|
66
|
+
batched_pending: 1,
|
|
67
|
+
anchored: 2,
|
|
68
|
+
critical_direct_anchor: 3,
|
|
69
|
+
};
|
|
70
|
+
/** Check if an anchor state meets a minimum requirement */
|
|
71
|
+
export function meetsAnchorRequirement(current, minimum) {
|
|
72
|
+
return ANCHOR_ORDER[current] >= ANCHOR_ORDER[minimum];
|
|
73
|
+
}
|
|
74
|
+
/** Check if anchor state transition is valid (can only move forward) */
|
|
75
|
+
export function isValidAnchorTransition(from, to) {
|
|
76
|
+
return ANCHOR_ORDER[to] >= ANCHOR_ORDER[from];
|
|
77
|
+
}
|
|
78
|
+
/** Exported ordering for cross-language verification */
|
|
79
|
+
export const ANCHOR_STATE_ORDER = ANCHOR_ORDER;
|
|
80
|
+
//# sourceMappingURL=anchor-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor-state.js","sourceRoot":"","sources":["../../../src/core/anchor-state.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,+DAA+D;AAC/D,qEAAqE;AACrE,kEAAkE;AAClE,mEAAmE;AACnE,EAAE;AACF,mDAAmD;AACnD,qEAAqE;AACrE,EAAE;AACF,gEAAgE;AAChE,qEAAqE;AAgCrE,MAAM,CAAC,MAAM,yBAAyB,GAAoB;IACxD,kBAAkB,EAAE,GAAG,EAAI,YAAY;IACvC,mBAAmB,EAAE,GAAG;IACxB,oBAAoB,EAAE,IAAI;CAC3B,CAAA;AAED,uDAAuD;AACvD,MAAM,UAAU,oBAAoB,CAAC,WAAoB,KAAK;IAC5D,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,YAAY;KAC1D,CAAA;AACH,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,WAAW,CAAC,MAAsB,EAAE,OAAe;IACjE,IAAI,MAAM,CAAC,KAAK,KAAK,wBAAwB;QAAE,OAAO,MAAM,CAAA,CAAC,mBAAmB;IAChF,IAAI,MAAM,CAAC,KAAK,KAAK,UAAU;QAAE,OAAO,MAAM,CAAA,CAAC,mBAAmB;IAClE,OAAO,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,iBAAiB,EAAE,OAAO,EAAE,CAAA;AACzD,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAC1B,MAAsB,EACtB,SAAiB,EACjB,aAAqB;IAErB,IAAI,MAAM,CAAC,KAAK,KAAK,wBAAwB;QAAE,OAAO,MAAM,CAAA;IAC5D,OAAO;QACL,GAAG,MAAM;QACT,KAAK,EAAE,UAAU;QACjB,SAAS,EAAE,aAAa;QACxB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACrC,CAAA;AACH,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAC7B,YAAoB,EACpB,aAA4B,EAC5B,SAA0B,yBAAyB;IAEnD,IAAI,YAAY,KAAK,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IAE/D,wBAAwB;IACxB,IAAI,MAAM,CAAC,mBAAmB,GAAG,CAAC,IAAI,YAAY,IAAI,MAAM,CAAC,mBAAmB,EAAE,CAAC;QACjF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;IAClD,CAAC;IAED,wBAAwB;IACxB,IAAI,MAAM,CAAC,kBAAkB,GAAG,CAAC,IAAI,aAAa,EAAE,CAAC;QACnD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAA;QACvE,IAAI,OAAO,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;QAClD,CAAC;IACH,CAAC;IAED,8DAA8D;IAC9D,IAAI,MAAM,CAAC,kBAAkB,GAAG,CAAC,IAAI,CAAC,aAAa,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;QACxE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA;IAClD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AACzC,CAAC;AAED,yEAAyE;AACzE,MAAM,YAAY,GAAgC;IAChD,UAAU,EAAE,CAAC;IACb,eAAe,EAAE,CAAC;IAClB,QAAQ,EAAE,CAAC;IACX,sBAAsB,EAAE,CAAC;CAC1B,CAAA;AAED,2DAA2D;AAC3D,MAAM,UAAU,sBAAsB,CACpC,OAAoB,EACpB,OAAoB;IAEpB,OAAO,YAAY,CAAC,OAAO,CAAC,IAAI,YAAY,CAAC,OAAO,CAAC,CAAA;AACvD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,uBAAuB,CAAC,IAAiB,EAAE,EAAe;IACxE,OAAO,YAAY,CAAC,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAA;AAC/C,CAAC;AAED,wDAAwD;AACxD,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** RFC 8785 JSON Canonicalization Scheme.
|
|
2
|
+
* Differences from legacy canonicalize():
|
|
3
|
+
* - null values ARE preserved (not filtered)
|
|
4
|
+
* - undefined object values become null
|
|
5
|
+
* - Number serialization follows ES2015 spec
|
|
6
|
+
* - All other behavior is identical (sorted keys, no whitespace) */
|
|
7
|
+
export declare function canonicalizeJCS(value: unknown): string;
|
|
8
|
+
/** Detect which canonicalization variant was likely used.
|
|
9
|
+
* Checks if null values are present — JCS preserves them, legacy strips them. */
|
|
10
|
+
export declare function detectCanonicalVariant(obj: unknown, canonicalString: string): 'jcs' | 'legacy' | 'ambiguous';
|
|
11
|
+
/** Cross-language test vector for canonicalization verification */
|
|
12
|
+
export interface CanonicalizationTestVector {
|
|
13
|
+
id: string;
|
|
14
|
+
description: string;
|
|
15
|
+
input: unknown;
|
|
16
|
+
expected_jcs: string;
|
|
17
|
+
expected_legacy: string;
|
|
18
|
+
sha256_jcs: string;
|
|
19
|
+
sha256_legacy: string;
|
|
20
|
+
}
|
|
21
|
+
/** Built-in test vectors for cross-language verification */
|
|
22
|
+
export declare function getTestVectors(): CanonicalizationTestVector[];
|
|
23
|
+
//# sourceMappingURL=canonical-jcs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-jcs.d.ts","sourceRoot":"","sources":["../../../src/core/canonical-jcs.ts"],"names":[],"mappings":"AAcA;;;;;qEAKqE;AACrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAiCtD;AAED;kFACkF;AAClF,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,OAAO,EACZ,eAAe,EAAE,MAAM,GACtB,KAAK,GAAG,QAAQ,GAAG,WAAW,CAMhC;AAYD,mEAAmE;AACnE,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,aAAa,EAAE,MAAM,CAAA;CACtB;AAOD,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,0BAA0B,EAAE,CAoF7D"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════
|
|
2
|
+
// JCS Canonicalization — RFC 8785 compliant JSON Canonicalization
|
|
3
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4
|
+
// The original canonicalize() filters null values — a deviation from
|
|
5
|
+
// RFC 8785 that cannot be changed without breaking existing signatures.
|
|
6
|
+
//
|
|
7
|
+
// This module provides:
|
|
8
|
+
// canonicalizeJCS() — strict RFC 8785 compliance
|
|
9
|
+
// verifyCanonical() — detect which variant was used
|
|
10
|
+
//
|
|
11
|
+
// Migration: new signatures should use JCS. Old signatures keep
|
|
12
|
+
// working with the legacy function. Verification tries both.
|
|
13
|
+
// ══════════════════════════════════════════════════════════════════
|
|
14
|
+
/** RFC 8785 JSON Canonicalization Scheme.
|
|
15
|
+
* Differences from legacy canonicalize():
|
|
16
|
+
* - null values ARE preserved (not filtered)
|
|
17
|
+
* - undefined object values become null
|
|
18
|
+
* - Number serialization follows ES2015 spec
|
|
19
|
+
* - All other behavior is identical (sorted keys, no whitespace) */
|
|
20
|
+
export function canonicalizeJCS(value) {
|
|
21
|
+
if (value === null || value === undefined)
|
|
22
|
+
return 'null';
|
|
23
|
+
switch (typeof value) {
|
|
24
|
+
case 'boolean':
|
|
25
|
+
return value ? 'true' : 'false';
|
|
26
|
+
case 'number': {
|
|
27
|
+
if (!isFinite(value))
|
|
28
|
+
throw new Error('JCS does not support Infinity or NaN');
|
|
29
|
+
// ES2015 number serialization — JSON.stringify handles this correctly
|
|
30
|
+
return JSON.stringify(value);
|
|
31
|
+
}
|
|
32
|
+
case 'string':
|
|
33
|
+
return JSON.stringify(value);
|
|
34
|
+
case 'object': {
|
|
35
|
+
if (value instanceof Date)
|
|
36
|
+
return JSON.stringify(value);
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return '[' + value.map(item => canonicalizeJCS(item)).join(',') + ']';
|
|
39
|
+
}
|
|
40
|
+
// Object: sort keys by Unicode code point, preserve null values
|
|
41
|
+
const obj = value;
|
|
42
|
+
const keys = Object.keys(obj).sort();
|
|
43
|
+
const pairs = [];
|
|
44
|
+
for (const key of keys) {
|
|
45
|
+
const v = obj[key];
|
|
46
|
+
// RFC 8785: undefined becomes null, null is preserved
|
|
47
|
+
// Only skip if the key was never set (shouldn't happen with Object.keys)
|
|
48
|
+
pairs.push(`${JSON.stringify(key)}:${canonicalizeJCS(v)}`);
|
|
49
|
+
}
|
|
50
|
+
return '{' + pairs.join(',') + '}';
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
throw new Error(`JCS: unsupported type ${typeof value}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Detect which canonicalization variant was likely used.
|
|
57
|
+
* Checks if null values are present — JCS preserves them, legacy strips them. */
|
|
58
|
+
export function detectCanonicalVariant(obj, canonicalString) {
|
|
59
|
+
// If the object has no null values, both variants produce identical output
|
|
60
|
+
if (!hasNullValues(obj))
|
|
61
|
+
return 'ambiguous';
|
|
62
|
+
// If canonical string contains `:null`, it's JCS (legacy strips nulls)
|
|
63
|
+
if (canonicalString.includes(':null'))
|
|
64
|
+
return 'jcs';
|
|
65
|
+
return 'legacy';
|
|
66
|
+
}
|
|
67
|
+
function hasNullValues(obj) {
|
|
68
|
+
if (obj === null)
|
|
69
|
+
return true;
|
|
70
|
+
if (typeof obj !== 'object' || obj === undefined)
|
|
71
|
+
return false;
|
|
72
|
+
if (Array.isArray(obj))
|
|
73
|
+
return obj.some(hasNullValues);
|
|
74
|
+
return Object.values(obj).some(v => v === null || v === undefined || hasNullValues(v));
|
|
75
|
+
}
|
|
76
|
+
import { createHash } from 'crypto';
|
|
77
|
+
/** Generate SHA-256 hex digest of a string */
|
|
78
|
+
function sha256hex(input) {
|
|
79
|
+
return createHash('sha256').update(input, 'utf-8').digest('hex');
|
|
80
|
+
}
|
|
81
|
+
/** Built-in test vectors for cross-language verification */
|
|
82
|
+
export function getTestVectors() {
|
|
83
|
+
const vectors = [];
|
|
84
|
+
function addVector(id, desc, input, jcs, legacy) {
|
|
85
|
+
vectors.push({
|
|
86
|
+
id, description: desc, input,
|
|
87
|
+
expected_jcs: jcs, expected_legacy: legacy,
|
|
88
|
+
sha256_jcs: sha256hex(jcs), sha256_legacy: sha256hex(legacy),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// V1: Simple object — both variants identical
|
|
92
|
+
addVector('cv-001', 'Simple object, no nulls — variants identical', { agentId: 'agent-001', scope: 'read' }, '{"agentId":"agent-001","scope":"read"}', '{"agentId":"agent-001","scope":"read"}');
|
|
93
|
+
// V2: Object with null — variants diverge
|
|
94
|
+
addVector('cv-002', 'Null value — JCS preserves, legacy strips', { agentId: 'agent-001', metadata: null, scope: 'read' }, '{"agentId":"agent-001","metadata":null,"scope":"read"}', '{"agentId":"agent-001","scope":"read"}');
|
|
95
|
+
// V3: Key ordering
|
|
96
|
+
addVector('cv-003', 'Keys sorted by Unicode code point', { zebra: 1, alpha: 2, middle: 3 }, '{"alpha":2,"middle":3,"zebra":1}', '{"alpha":2,"middle":3,"zebra":1}');
|
|
97
|
+
// V4: Nested objects with null
|
|
98
|
+
addVector('cv-004', 'Nested object with null at depth', { outer: { inner: null, value: 42 }, top: 'ok' }, '{"outer":{"inner":null,"value":42},"top":"ok"}', '{"outer":{"value":42},"top":"ok"}');
|
|
99
|
+
// V5: Arrays with null elements
|
|
100
|
+
addVector('cv-005', 'Array with null elements — both preserve array nulls', { items: [1, null, 3] }, '{"items":[1,null,3]}', '{"items":[1,null,3]}');
|
|
101
|
+
// V6: Number edge cases
|
|
102
|
+
addVector('cv-006', 'Number formatting — integers and floats', { integer: 42, negative: -7, float: 3.14, zero: 0 }, '{"float":3.14,"integer":42,"negative":-7,"zero":0}', '{"float":3.14,"integer":42,"negative":-7,"zero":0}');
|
|
103
|
+
// V7: Empty structures
|
|
104
|
+
addVector('cv-007', 'Empty object and empty array', { emptyArr: [], emptyObj: {} }, '{"emptyArr":[],"emptyObj":{}}', '{"emptyArr":[],"emptyObj":{}}');
|
|
105
|
+
// V8: Unicode
|
|
106
|
+
addVector('cv-008', 'Unicode string content', { name: 'Тимофій', emoji: '🔐' }, '{"emoji":"🔐","name":"Тимофій"}', '{"emoji":"🔐","name":"Тимофій"}');
|
|
107
|
+
// V9: Realistic APS object — delegation-like structure
|
|
108
|
+
addVector('cv-009', 'Realistic delegation object with mixed null/present fields', {
|
|
109
|
+
delegationId: 'del_abc123',
|
|
110
|
+
delegatedBy: 'did:aps:principal001',
|
|
111
|
+
delegatedTo: 'did:aps:agent002',
|
|
112
|
+
scope: ['data:read', 'commerce:checkout'],
|
|
113
|
+
spendLimit: 500,
|
|
114
|
+
obligationBundleHash: null,
|
|
115
|
+
expiresAt: '2026-04-01T00:00:00Z',
|
|
116
|
+
notBefore: null,
|
|
117
|
+
maxDepth: 3,
|
|
118
|
+
currentDepth: 1,
|
|
119
|
+
createdAt: '2026-03-29T00:00:00Z',
|
|
120
|
+
}, '{"createdAt":"2026-03-29T00:00:00Z","currentDepth":1,"delegatedBy":"did:aps:principal001","delegatedTo":"did:aps:agent002","delegationId":"del_abc123","expiresAt":"2026-04-01T00:00:00Z","maxDepth":3,"notBefore":null,"obligationBundleHash":null,"scope":["data:read","commerce:checkout"],"spendLimit":500}', '{"createdAt":"2026-03-29T00:00:00Z","currentDepth":1,"delegatedBy":"did:aps:principal001","delegatedTo":"did:aps:agent002","delegationId":"del_abc123","expiresAt":"2026-04-01T00:00:00Z","maxDepth":3,"scope":["data:read","commerce:checkout"],"spendLimit":500}');
|
|
121
|
+
// V10: Boolean values
|
|
122
|
+
addVector('cv-010', 'Boolean values', { active: true, revoked: false }, '{"active":true,"revoked":false}', '{"active":true,"revoked":false}');
|
|
123
|
+
return vectors;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=canonical-jcs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-jcs.js","sourceRoot":"","sources":["../../../src/core/canonical-jcs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ConstraintFacet, ConstraintStatus } from '../types/gateway.js';
|
|
2
|
+
/** A facet evaluation snapshot — facet name + its status */
|
|
3
|
+
export interface FacetSnapshot {
|
|
4
|
+
facet: ConstraintFacet;
|
|
5
|
+
status: ConstraintStatus;
|
|
6
|
+
}
|
|
7
|
+
/** Result of a narrowing check */
|
|
8
|
+
export interface NarrowingCheckResult {
|
|
9
|
+
valid: boolean;
|
|
10
|
+
/** Facets where data attempted to widen authority */
|
|
11
|
+
violations: Array<{
|
|
12
|
+
facet: ConstraintFacet;
|
|
13
|
+
before: ConstraintStatus;
|
|
14
|
+
after: ConstraintStatus;
|
|
15
|
+
message: string;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
/** Assert that data influence only narrows authority.
|
|
19
|
+
* Compares constraint evaluations BEFORE and AFTER data is considered.
|
|
20
|
+
* Any facet that moves from a more restrictive status to a more
|
|
21
|
+
* permissive one is a violation — data attempted to widen authority.
|
|
22
|
+
*
|
|
23
|
+
* @param before - Facet evaluations before data influence
|
|
24
|
+
* @param after - Facet evaluations after data influence
|
|
25
|
+
* @returns NarrowingCheckResult with any violations */
|
|
26
|
+
export declare function assertDataNarrowsOnly(before: FacetSnapshot[], after: FacetSnapshot[]): NarrowingCheckResult;
|
|
27
|
+
/** Apply data-sourced constraint modifications safely.
|
|
28
|
+
* Only allows narrowing (making more restrictive).
|
|
29
|
+
* Returns the narrowed snapshots with any widening attempts rejected.
|
|
30
|
+
*
|
|
31
|
+
* Use case: a data source declares "this data requires scope:read_only"
|
|
32
|
+
* → the constraint is narrowed. But if data declares "grant scope:admin"
|
|
33
|
+
* → the widening is rejected and the original constraint stands. */
|
|
34
|
+
export declare function applyDataConstraints(current: FacetSnapshot[], dataInfluence: FacetSnapshot[]): {
|
|
35
|
+
result: FacetSnapshot[];
|
|
36
|
+
rejected: NarrowingCheckResult['violations'];
|
|
37
|
+
};
|
|
38
|
+
/** Check if a status transition is valid narrowing (same or more restrictive) */
|
|
39
|
+
export declare function isValidNarrowing(before: ConstraintStatus, after: ConstraintStatus): boolean;
|
|
40
|
+
/** The status ordering: fail < unknown < not_applicable < pass.
|
|
41
|
+
* Exported for test vectors and cross-language verification. */
|
|
42
|
+
export declare const NARROWING_ORDER: Record<ConstraintStatus, number>;
|
|
43
|
+
//# sourceMappingURL=data-narrowing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-narrowing.d.ts","sourceRoot":"","sources":["../../../src/core/data-narrowing.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAE5E,4DAA4D;AAC5D,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,eAAe,CAAA;IACtB,MAAM,EAAE,gBAAgB,CAAA;CACzB;AAED,kCAAkC;AAClC,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAA;IACd,qDAAqD;IACrD,UAAU,EAAE,KAAK,CAAC;QAChB,KAAK,EAAE,eAAe,CAAA;QACtB,MAAM,EAAE,gBAAgB,CAAA;QACxB,KAAK,EAAE,gBAAgB,CAAA;QACvB,OAAO,EAAE,MAAM,CAAA;KAChB,CAAC,CAAA;CACH;AAWD;;;;;;;wDAOwD;AACxD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,aAAa,EAAE,EACvB,KAAK,EAAE,aAAa,EAAE,GACrB,oBAAoB,CAwBtB;AAED;;;;;;qEAMqE;AACrE,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,aAAa,EAAE,EACxB,aAAa,EAAE,aAAa,EAAE,GAC7B;IAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC,YAAY,CAAC,CAAA;CAAE,CAkC3E;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,EAAE,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAE3F;AAED;iEACiE;AACjE,eAAO,MAAM,eAAe,kCAAe,CAAA"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// ══════════════════════════════════════════════════════════════════
|
|
2
|
+
// Data Narrowing Invariant — Data Can Only Narrow Authority
|
|
3
|
+
// ══════════════════════════════════════════════════════════════════
|
|
4
|
+
// GPT's "Context-Bypass Attack" (consilium March 2026):
|
|
5
|
+
// An agent reads a data source containing authority-widening
|
|
6
|
+
// instructions. The gateway must ensure data inputs can ONLY narrow
|
|
7
|
+
// the ConstraintVector, never widen it.
|
|
8
|
+
//
|
|
9
|
+
// Same as monotonic narrowing for delegation — monotonic narrowing
|
|
10
|
+
// for data influence. Only signed delegations from a higher-tier
|
|
11
|
+
// principal can widen authority.
|
|
12
|
+
// ══════════════════════════════════════════════════════════════════
|
|
13
|
+
/** Status ordering for monotonic narrowing check.
|
|
14
|
+
* Lower number = more restrictive. Authority can only move DOWN. */
|
|
15
|
+
const STATUS_ORDER = {
|
|
16
|
+
'fail': 0,
|
|
17
|
+
'unknown': 1,
|
|
18
|
+
'not_applicable': 2,
|
|
19
|
+
'pass': 3,
|
|
20
|
+
};
|
|
21
|
+
/** Assert that data influence only narrows authority.
|
|
22
|
+
* Compares constraint evaluations BEFORE and AFTER data is considered.
|
|
23
|
+
* Any facet that moves from a more restrictive status to a more
|
|
24
|
+
* permissive one is a violation — data attempted to widen authority.
|
|
25
|
+
*
|
|
26
|
+
* @param before - Facet evaluations before data influence
|
|
27
|
+
* @param after - Facet evaluations after data influence
|
|
28
|
+
* @returns NarrowingCheckResult with any violations */
|
|
29
|
+
export function assertDataNarrowsOnly(before, after) {
|
|
30
|
+
const violations = [];
|
|
31
|
+
const beforeMap = new Map(before.map(f => [f.facet, f.status]));
|
|
32
|
+
for (const a of after) {
|
|
33
|
+
const beforeStatus = beforeMap.get(a.facet);
|
|
34
|
+
if (beforeStatus === undefined)
|
|
35
|
+
continue; // new facet, no comparison
|
|
36
|
+
const beforeOrder = STATUS_ORDER[beforeStatus];
|
|
37
|
+
const afterOrder = STATUS_ORDER[a.status];
|
|
38
|
+
// If after is MORE permissive (higher order) than before → violation
|
|
39
|
+
if (afterOrder > beforeOrder) {
|
|
40
|
+
violations.push({
|
|
41
|
+
facet: a.facet,
|
|
42
|
+
before: beforeStatus,
|
|
43
|
+
after: a.status,
|
|
44
|
+
message: `Data attempted to widen ${a.facet}: ${beforeStatus} → ${a.status}`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { valid: violations.length === 0, violations };
|
|
49
|
+
}
|
|
50
|
+
/** Apply data-sourced constraint modifications safely.
|
|
51
|
+
* Only allows narrowing (making more restrictive).
|
|
52
|
+
* Returns the narrowed snapshots with any widening attempts rejected.
|
|
53
|
+
*
|
|
54
|
+
* Use case: a data source declares "this data requires scope:read_only"
|
|
55
|
+
* → the constraint is narrowed. But if data declares "grant scope:admin"
|
|
56
|
+
* → the widening is rejected and the original constraint stands. */
|
|
57
|
+
export function applyDataConstraints(current, dataInfluence) {
|
|
58
|
+
const currentMap = new Map(current.map(f => [f.facet, f]));
|
|
59
|
+
const result = [...current];
|
|
60
|
+
const rejected = [];
|
|
61
|
+
for (const influence of dataInfluence) {
|
|
62
|
+
const existing = currentMap.get(influence.facet);
|
|
63
|
+
if (!existing) {
|
|
64
|
+
// New facet from data — only accept if restrictive (fail or unknown)
|
|
65
|
+
if (STATUS_ORDER[influence.status] <= STATUS_ORDER['unknown']) {
|
|
66
|
+
result.push(influence);
|
|
67
|
+
}
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const existingOrder = STATUS_ORDER[existing.status];
|
|
71
|
+
const influenceOrder = STATUS_ORDER[influence.status];
|
|
72
|
+
if (influenceOrder <= existingOrder) {
|
|
73
|
+
// More restrictive or equal — allowed (narrowing)
|
|
74
|
+
const idx = result.findIndex(f => f.facet === influence.facet);
|
|
75
|
+
if (idx >= 0)
|
|
76
|
+
result[idx] = influence;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Less restrictive — rejected (widening attempt)
|
|
80
|
+
rejected.push({
|
|
81
|
+
facet: influence.facet,
|
|
82
|
+
before: existing.status,
|
|
83
|
+
after: influence.status,
|
|
84
|
+
message: `Rejected: data tried to widen ${influence.facet} from ${existing.status} to ${influence.status}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { result, rejected };
|
|
89
|
+
}
|
|
90
|
+
/** Check if a status transition is valid narrowing (same or more restrictive) */
|
|
91
|
+
export function isValidNarrowing(before, after) {
|
|
92
|
+
return STATUS_ORDER[after] <= STATUS_ORDER[before];
|
|
93
|
+
}
|
|
94
|
+
/** The status ordering: fail < unknown < not_applicable < pass.
|
|
95
|
+
* Exported for test vectors and cross-language verification. */
|
|
96
|
+
export const NARROWING_ORDER = STATUS_ORDER;
|
|
97
|
+
//# sourceMappingURL=data-narrowing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-narrowing.js","sourceRoot":"","sources":["../../../src/core/data-narrowing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DataAccessReceipt } from '../types/data-source.js';
|
|
2
|
+
import type { DataAttributionModel, DataSourceAttributionReport } from '../types/data-contribution.js';
|
|
3
|
+
export declare function computeDataSourceAttribution(opts: {
|
|
4
|
+
outputArtifactId: string;
|
|
5
|
+
outputType: 'decision' | 'content' | 'model' | 'action';
|
|
6
|
+
accessReceipts: DataAccessReceipt[];
|
|
7
|
+
sourceDescriptors?: Map<string, string>;
|
|
8
|
+
model?: DataAttributionModel;
|
|
9
|
+
customWeights?: Map<string, number>;
|
|
10
|
+
generatorPublicKey: string;
|
|
11
|
+
generatorPrivateKey: string;
|
|
12
|
+
}): DataSourceAttributionReport;
|
|
13
|
+
export declare function verifyDataSourceAttribution(report: DataSourceAttributionReport, publicKey: string): {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: string[];
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=data-source-attribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-source-attribution.d.ts","sourceRoot":"","sources":["../../../src/core/data-source-attribution.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAChE,OAAO,KAAK,EACV,oBAAoB,EAEpB,2BAA2B,EAC5B,MAAM,+BAA+B,CAAA;AAkHtC,wBAAgB,4BAA4B,CAAC,IAAI,EAAE;IACjD,gBAAgB,EAAE,MAAM,CAAA;IACxB,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAA;IACvD,cAAc,EAAE,iBAAiB,EAAE,CAAA;IACnC,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,KAAK,CAAC,EAAE,oBAAoB,CAAA;IAC5B,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,kBAAkB,EAAE,MAAM,CAAA;IAC1B,mBAAmB,EAAE,MAAM,CAAA;CAC5B,GAAG,2BAA2B,CAsE9B;AAMD,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,2BAA2B,EACnC,SAAS,EAAE,MAAM,GAChB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAwCtC"}
|