@metamynd/mmt-graph 0.2.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 +264 -0
- package/mmt-graph.accountability.mjs +128 -0
- package/mmt-graph.anchor-live-demo.mjs +122 -0
- package/mmt-graph.anchor.mjs +31 -0
- package/mmt-graph.auditor-cli.mjs +41 -0
- package/mmt-graph.auditor.mjs +129 -0
- package/mmt-graph.blast-radius.mjs +166 -0
- package/mmt-graph.changelog.mjs +80 -0
- package/mmt-graph.contagion.mjs +105 -0
- package/mmt-graph.engine.mjs +153 -0
- package/mmt-graph.evidence-path.mjs +65 -0
- package/mmt-graph.merkle.mjs +67 -0
- package/mmt-graph.project.mjs +236 -0
- package/mmt-graph.self-host-quickstart.mjs +78 -0
- package/mmt-graph.shapes.ttl +39 -0
- package/mmt-graph.types.mjs +207 -0
- package/package.json +75 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mutation-event vocabulary the engine consumes. Anchor-compatible from day one: a
|
|
3
|
+
* discriminated `{op, ...}` envelope (matching magp-hcs2's convention) carrying only the
|
|
4
|
+
* fields the projector needs — not full DB rows — the same additive-only discipline
|
|
5
|
+
* evidence leaves already follow (see evidence-batch.ts's canonical()). Where an event
|
|
6
|
+
* comes from (a Postgres trigger, a migration script, HCS replay) is Phase 2/3, not here.
|
|
7
|
+
*
|
|
8
|
+
* These map onto real `Policy`/`AgentIdentity`/`Principal` rows one-for-one — see
|
|
9
|
+
* docs/design/metamynd-trust-ontology.md domains A/C for the grounding. Notably, a
|
|
10
|
+
* delegated mandate is NOT a separate concept from a root mandate: `Policy` carries
|
|
11
|
+
* delegation fields (`delegatedFromPolicyId`, `delegationDepth`) on the same row as the
|
|
12
|
+
* mandate itself, so `MandateGranted` carries them as optional fields rather than being a
|
|
13
|
+
* distinct event type.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* `principalDid` on AgentRegistered is the agent's HOME organisation (AgentIdentity.
|
|
18
|
+
* organizationPrincipalId in accountability.ts) — a structural fact independent of any
|
|
19
|
+
* mandate, distinct from `mmt:actsFor` (which domain A already defines as the root
|
|
20
|
+
* mandate's principalDid). Phase 5's fifth slice (mmt-graph.accountability.mjs) projects
|
|
21
|
+
* this onto `mmt:memberOf`, reusing the exact relationship the fourth slice's
|
|
22
|
+
* organization/unit cohorts already established for the same underlying field — see that
|
|
23
|
+
* slice's note in mmt-graph.project.mjs for why memberOf and actsFor must not be conflated.
|
|
24
|
+
*/
|
|
25
|
+
/** @typedef {{ op: 'agent-registered', agentDid: string, kind?: string, principalDid?: string }} AgentRegistered */
|
|
26
|
+
/**
|
|
27
|
+
* The verification/`authorizedRepUserId` fields are a snapshot of the Principal row at
|
|
28
|
+
* event time, not a separate lifecycle event — the same "current-state field on a
|
|
29
|
+
* recording event" shape evidenceRecorded/batchAnchored already use for anchorStatus,
|
|
30
|
+
* chosen for the same reason: this package proves graph shape, not full temporal
|
|
31
|
+
* event-sourcing of every state transition.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {{
|
|
35
|
+
* op: 'principal-registered',
|
|
36
|
+
* principalDid: string,
|
|
37
|
+
* principalType?: string,
|
|
38
|
+
* verificationStatus?: string,
|
|
39
|
+
* assuranceLevel?: string,
|
|
40
|
+
* verificationExpiresAt?: string,
|
|
41
|
+
* authorizedRepUserId?: string,
|
|
42
|
+
* }} PrincipalRegistered
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* `expiresAt`, added in Phase 5's sixth slice, is Policy.expiresAt — needed for
|
|
46
|
+
* blast-radius.ts's live/expired split (`isLive`), which the earlier slices never needed
|
|
47
|
+
* (evidence-path and accountability don't gate on mandate liveness). Absent means no
|
|
48
|
+
* expiry, not expired — same rule as the real `isLive`.
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {{
|
|
52
|
+
* op: 'mandate-granted',
|
|
53
|
+
* policyId: string,
|
|
54
|
+
* agentDid: string,
|
|
55
|
+
* principalDid?: string, // set on a root mandate (Policy.principalDid)
|
|
56
|
+
* delegatedFromPolicyId?: string, // set on a delegated mandate (Policy.delegatedFromPolicyId)
|
|
57
|
+
* delegationDepth?: number, // set alongside delegatedFromPolicyId, 1..MAX_DELEGATION_DEPTH
|
|
58
|
+
* expiresAt?: string, // Policy.expiresAt, ISO — absent means no expiry
|
|
59
|
+
* }} MandateGranted
|
|
60
|
+
*/
|
|
61
|
+
/** @typedef {{ op: 'mandate-revoked', policyId: string }} MandateRevoked */
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Domain H (runtime activity/evidence) — Phase 5's first slice, proving the engine can
|
|
65
|
+
* answer evidence-path.ts's "independently verifiable" question, not a full replacement
|
|
66
|
+
* of it (see mmt-graph.evidence-path.mjs's header). Grounded in the real fields
|
|
67
|
+
* evidence-path.ts/evidence.model.ts actually use, same additive-only discipline as the
|
|
68
|
+
* other event types above — not the full DB row.
|
|
69
|
+
*/
|
|
70
|
+
/**
|
|
71
|
+
* `agentDid`, added in Phase 5's fifth slice, is the decision's actor — the edge
|
|
72
|
+
* accountability.ts's own graph builder already calls `decided-for`, adopted here as
|
|
73
|
+
* `mmt:decidedFor` since Domain H names no equivalent predicate (see
|
|
74
|
+
* mmt-graph.accountability.mjs).
|
|
75
|
+
*/
|
|
76
|
+
/** @typedef {{ op: 'decision-recorded', decisionId: string, decisionDigest?: string, agentDid?: string }} DecisionRecorded */
|
|
77
|
+
/** @typedef {{ op: 'evidence-recorded', eventId: string, decisionDigest?: string, anchorStatus?: string }} EvidenceRecorded */
|
|
78
|
+
/** @typedef {{ op: 'batch-anchored', batchId: string, memberEventIds: string[], anchorStatus?: string }} BatchAnchored */
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Domain F (governance) — Phase 5's second slice, proving the engine can answer
|
|
82
|
+
* contagion.ts's cohort-specificity question (see mmt-graph.contagion.mjs's header).
|
|
83
|
+
* `sop`/`standard` share one pattern in the real algorithm (`buildKeyed` in contagion.ts,
|
|
84
|
+
* keyed via a junction table). `organization`/`unit` (Phase 5's fourth slice) use a
|
|
85
|
+
* different shape (`buildSingle`, keyed off
|
|
86
|
+
* AgentIdentity.organizationPrincipalId/operatingUnitId — a direct FK, not a junction
|
|
87
|
+
* table) but the SAME event/projector mechanism — see mmt-graph.project.mjs's
|
|
88
|
+
* projectCohortShared for why `organization`/`unit` additionally assert `mmt:memberOf`,
|
|
89
|
+
* the real Domain A relationship, alongside the mechanical `mmt:sharesCohort` edge.
|
|
90
|
+
*/
|
|
91
|
+
/** @typedef {{ op: 'cohort-shared', agentDid: string, kind: 'sop' | 'standard' | 'organization' | 'unit', key: string, label: string, version?: number }} CohortShared */
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Domain A — Phase 5's fifth slice, proving the engine can answer accountability.ts's "who
|
|
95
|
+
* is answerable" question (see mmt-graph.accountability.mjs's header). `mmt:Human` is named
|
|
96
|
+
* in domain A's actor taxonomy but was `[PROPOSED]`/unimplemented until now; humans have no
|
|
97
|
+
* DID, so `userId` mints a synthetic IRI the same way `mandateIri`/`decisionIri` do for
|
|
98
|
+
* their own non-DID identifiers.
|
|
99
|
+
*/
|
|
100
|
+
/** @typedef {{ op: 'human-registered', userId: string, email: string, firstName?: string, lastName?: string }} HumanRegistered */
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Domain A/B — Phase 5's sixth slice, proving the engine can answer blast-radius.ts's
|
|
104
|
+
* "who does this issuer's tier depend on, and what breaks if it lapses" question (see
|
|
105
|
+
* mmt-graph.blast-radius.mjs's header). `mmt:Issuer` is the one genuinely new class this
|
|
106
|
+
* package adds — the W3C VC Data Model's own term, not invented, since `issuer.model.ts`'s
|
|
107
|
+
* `did` field means an issuer is exactly as IRI-able as an agent or a principal. `did` is
|
|
108
|
+
* required, not `issuerId`: same reasoning as agentIri/principalIri using the DID itself as
|
|
109
|
+
* the node's IRI rather than minting a synthetic one.
|
|
110
|
+
*/
|
|
111
|
+
/** @typedef {{ op: 'issuer-registered', did: string, name?: string, principalDid?: string }} IssuerRegistered */
|
|
112
|
+
|
|
113
|
+
const COHORT_KINDS = ['sop', 'standard', 'organization', 'unit'];
|
|
114
|
+
|
|
115
|
+
/** The bound this engine enforces on delegationDepth — mirrors policy-core/delegation.ts's MAX_DELEGATION_DEPTH. */
|
|
116
|
+
export const MAX_DELEGATION_DEPTH = 3;
|
|
117
|
+
|
|
118
|
+
function requireFields(event, fields) {
|
|
119
|
+
const missing = fields.filter((f) => event[f] === undefined || event[f] === null);
|
|
120
|
+
if (missing.length > 0) throw new Error(`${event.op}: missing required field(s): ${missing.join(', ')}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @returns {AgentRegistered} */
|
|
124
|
+
export function agentRegistered({ agentDid, kind, principalDid }) {
|
|
125
|
+
const ev = { op: 'agent-registered', agentDid, kind, principalDid };
|
|
126
|
+
requireFields(ev, ['agentDid']);
|
|
127
|
+
return ev;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @returns {PrincipalRegistered} */
|
|
131
|
+
export function principalRegistered({
|
|
132
|
+
principalDid,
|
|
133
|
+
principalType,
|
|
134
|
+
verificationStatus,
|
|
135
|
+
assuranceLevel,
|
|
136
|
+
verificationExpiresAt,
|
|
137
|
+
authorizedRepUserId,
|
|
138
|
+
}) {
|
|
139
|
+
const ev = { op: 'principal-registered', principalDid, principalType, verificationStatus, assuranceLevel, verificationExpiresAt, authorizedRepUserId };
|
|
140
|
+
requireFields(ev, ['principalDid']);
|
|
141
|
+
return ev;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** @returns {MandateGranted} */
|
|
145
|
+
export function mandateGranted({ policyId, agentDid, principalDid, delegatedFromPolicyId, delegationDepth, expiresAt }) {
|
|
146
|
+
const ev = { op: 'mandate-granted', policyId, agentDid, principalDid, delegatedFromPolicyId, delegationDepth, expiresAt };
|
|
147
|
+
requireFields(ev, ['policyId', 'agentDid']);
|
|
148
|
+
if (delegatedFromPolicyId && (delegationDepth === undefined || delegationDepth === null)) {
|
|
149
|
+
throw new Error(`mandate-granted for ${policyId}: delegatedFromPolicyId set without delegationDepth`);
|
|
150
|
+
}
|
|
151
|
+
if (delegationDepth !== undefined && (delegationDepth < 1 || delegationDepth > MAX_DELEGATION_DEPTH)) {
|
|
152
|
+
throw new Error(`mandate-granted for ${policyId}: delegationDepth ${delegationDepth} outside 1..${MAX_DELEGATION_DEPTH}`);
|
|
153
|
+
}
|
|
154
|
+
return ev;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** @returns {MandateRevoked} */
|
|
158
|
+
export function mandateRevoked({ policyId }) {
|
|
159
|
+
const ev = { op: 'mandate-revoked', policyId };
|
|
160
|
+
requireFields(ev, ['policyId']);
|
|
161
|
+
return ev;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @returns {DecisionRecorded} */
|
|
165
|
+
export function decisionRecorded({ decisionId, decisionDigest, agentDid }) {
|
|
166
|
+
const ev = { op: 'decision-recorded', decisionId, decisionDigest, agentDid };
|
|
167
|
+
requireFields(ev, ['decisionId']);
|
|
168
|
+
return ev;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** @returns {EvidenceRecorded} */
|
|
172
|
+
export function evidenceRecorded({ eventId, decisionDigest, anchorStatus }) {
|
|
173
|
+
const ev = { op: 'evidence-recorded', eventId, decisionDigest, anchorStatus };
|
|
174
|
+
requireFields(ev, ['eventId']);
|
|
175
|
+
return ev;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** @returns {BatchAnchored} */
|
|
179
|
+
export function batchAnchored({ batchId, memberEventIds, anchorStatus }) {
|
|
180
|
+
const ev = { op: 'batch-anchored', batchId, memberEventIds, anchorStatus };
|
|
181
|
+
requireFields(ev, ['batchId', 'memberEventIds']);
|
|
182
|
+
return ev;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** @returns {CohortShared} */
|
|
186
|
+
export function cohortShared({ agentDid, kind, key, label, version }) {
|
|
187
|
+
const ev = { op: 'cohort-shared', agentDid, kind, key, label, version };
|
|
188
|
+
requireFields(ev, ['agentDid', 'kind', 'key', 'label']);
|
|
189
|
+
if (!COHORT_KINDS.includes(kind)) {
|
|
190
|
+
throw new Error(`cohort-shared: kind must be one of ${COHORT_KINDS.join(', ')} (got "${kind}")`);
|
|
191
|
+
}
|
|
192
|
+
return ev;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** @returns {HumanRegistered} */
|
|
196
|
+
export function humanRegistered({ userId, email, firstName, lastName }) {
|
|
197
|
+
const ev = { op: 'human-registered', userId, email, firstName, lastName };
|
|
198
|
+
requireFields(ev, ['userId', 'email']);
|
|
199
|
+
return ev;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** @returns {IssuerRegistered} */
|
|
203
|
+
export function issuerRegistered({ did, name, principalDid }) {
|
|
204
|
+
const ev = { op: 'issuer-registered', did, name, principalDid };
|
|
205
|
+
requireFields(ev, ['did']);
|
|
206
|
+
return ev;
|
|
207
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metamynd/mmt-graph",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Reference trust-graph engine (Oxigraph/SPARQL + SHACL) + HCS-anchor contract: apply mutation events, validate the MMTO §3 authority-chain invariant, batch the changelog into a Merkle root ready to anchor, and verify a disclosed tenant graph with MetaMynd fully offline. Hosting-mode-agnostic — no MetaMynd backend, no Postgres, no MetaMynd credentials anywhere in the code path. See docs/design/metamynd-trust-ontology.md Appendix C.",
|
|
6
|
+
"main": "mmt-graph.engine.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./mmt-graph.engine.mjs",
|
|
9
|
+
"./types": "./mmt-graph.types.mjs",
|
|
10
|
+
"./project": "./mmt-graph.project.mjs",
|
|
11
|
+
"./changelog": "./mmt-graph.changelog.mjs",
|
|
12
|
+
"./anchor": "./mmt-graph.anchor.mjs",
|
|
13
|
+
"./evidence-path": "./mmt-graph.evidence-path.mjs",
|
|
14
|
+
"./contagion": "./mmt-graph.contagion.mjs",
|
|
15
|
+
"./accountability": "./mmt-graph.accountability.mjs",
|
|
16
|
+
"./blast-radius": "./mmt-graph.blast-radius.mjs",
|
|
17
|
+
"./auditor": "./mmt-graph.auditor.mjs"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"mmt-graph-auditor": "mmt-graph.auditor-cli.mjs"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"mmt-graph.types.mjs",
|
|
24
|
+
"mmt-graph.project.mjs",
|
|
25
|
+
"mmt-graph.shapes.ttl",
|
|
26
|
+
"mmt-graph.engine.mjs",
|
|
27
|
+
"mmt-graph.merkle.mjs",
|
|
28
|
+
"mmt-graph.changelog.mjs",
|
|
29
|
+
"mmt-graph.anchor.mjs",
|
|
30
|
+
"mmt-graph.anchor-live-demo.mjs",
|
|
31
|
+
"mmt-graph.evidence-path.mjs",
|
|
32
|
+
"mmt-graph.contagion.mjs",
|
|
33
|
+
"mmt-graph.accountability.mjs",
|
|
34
|
+
"mmt-graph.blast-radius.mjs",
|
|
35
|
+
"mmt-graph.auditor.mjs",
|
|
36
|
+
"mmt-graph.auditor-cli.mjs",
|
|
37
|
+
"mmt-graph.self-host-quickstart.mjs",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"spike": "node mmt-graph.spike.mjs",
|
|
42
|
+
"test": "node mmt-graph.test.mjs && node mmt-graph.merkle.test.mjs && node mmt-graph.changelog.test.mjs && node mmt-graph.anchor.test.mjs && node mmt-graph.evidence-path.test.mjs && node mmt-graph.contagion.test.mjs && node mmt-graph.accountability.test.mjs && node mmt-graph.blast-radius.test.mjs && node mmt-graph.auditor.test.mjs",
|
|
43
|
+
"anchor-live-demo": "node mmt-graph.anchor-live-demo.mjs",
|
|
44
|
+
"auditor": "node mmt-graph.auditor-cli.mjs",
|
|
45
|
+
"self-host-quickstart": "node mmt-graph.self-host-quickstart.mjs"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@zazuko/env-node": "^3.1.0",
|
|
49
|
+
"oxigraph": "^0.5.5",
|
|
50
|
+
"rdf-validate-shacl": "^0.6.5"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@hashgraph/sdk": "^2.81.0",
|
|
54
|
+
"dotenv": "^17.4.2"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"metamynd",
|
|
61
|
+
"trust-graph",
|
|
62
|
+
"rdf",
|
|
63
|
+
"sparql",
|
|
64
|
+
"shacl",
|
|
65
|
+
"ontology",
|
|
66
|
+
"mmto",
|
|
67
|
+
"hedera",
|
|
68
|
+
"hcs",
|
|
69
|
+
"agent-governance",
|
|
70
|
+
"self-hosted",
|
|
71
|
+
"verifiable"
|
|
72
|
+
],
|
|
73
|
+
"author": "MetaMynd",
|
|
74
|
+
"license": "MIT"
|
|
75
|
+
}
|