@agenticprimitives/contracts 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/AUDIT.md +67 -0
  2. package/CLAUDE.md +40 -0
  3. package/LICENSE +21 -0
  4. package/README.md +45 -0
  5. package/deployments-anvil.json +1 -0
  6. package/deployments-base-sepolia.json +1 -0
  7. package/dist/abi/AgentNameAttributeResolver.json +798 -0
  8. package/dist/abi/AgentNamePredicates.json +1 -0
  9. package/dist/abi/AgentNameRegistry.json +826 -0
  10. package/dist/abi/AgentNameUniversalResolver.json +222 -0
  11. package/dist/abi/AgentProfilePredicates.json +1 -0
  12. package/dist/abi/AgentProfileResolver.json +1044 -0
  13. package/dist/abi/AgentRelationship.json +583 -0
  14. package/dist/abi/AgentRelationshipPredicates.json +1 -0
  15. package/dist/abi/AgenticGovernance.json +259 -0
  16. package/dist/abi/AllowedMethodsEnforcer.json +108 -0
  17. package/dist/abi/AllowedTargetsEnforcer.json +103 -0
  18. package/dist/abi/ApprovedHashRegistry.json +114 -0
  19. package/dist/abi/AttributeStorage.json +557 -0
  20. package/dist/abi/CaveatEnforcerBase.json +130 -0
  21. package/dist/abi/GovernanceManaged.json +43 -0
  22. package/dist/abi/IAttributeReader.json +98 -0
  23. package/dist/abi/ICaveatEnforcer.json +98 -0
  24. package/dist/abi/IDelegationManager.json +211 -0
  25. package/dist/abi/IERC7579Module.json +34 -0
  26. package/dist/abi/IERC7579ModuleLifecycle.json +60 -0
  27. package/dist/abi/IGovernanceView.json +34 -0
  28. package/dist/abi/MultiSendCallOnly.json +29 -0
  29. package/dist/abi/MultiSendCallOnlyHarness.json +42 -0
  30. package/dist/abi/OntologyTermRegistry.json +397 -0
  31. package/dist/abi/P256Verifier.json +1 -0
  32. package/dist/abi/PermissionlessSubregistry.json +207 -0
  33. package/dist/abi/RelationshipTypeRegistry.json +455 -0
  34. package/dist/abi/ShapeRegistry.json +627 -0
  35. package/dist/abi/SmartAgentModuleTypes.json +1 -0
  36. package/dist/abi/TimestampEnforcer.json +108 -0
  37. package/dist/abi/ValueEnforcer.json +103 -0
  38. package/dist/abi/WebAuthnLib.json +1 -0
  39. package/dist/abi/index.d.ts +35 -0
  40. package/dist/abi/index.js +35 -0
  41. package/package.json +48 -0
  42. package/spec.md +52 -0
  43. package/src/AgentAccount.sol +1374 -0
  44. package/src/AgentAccountFactory.sol +274 -0
  45. package/src/ApprovedHashRegistry.sol +57 -0
  46. package/src/IAgentAccount.sol +138 -0
  47. package/src/SmartAgentPaymaster.sol +281 -0
  48. package/src/UniversalSignatureValidator.sol +136 -0
  49. package/src/agency/DelegationManager.sol +374 -0
  50. package/src/agency/ICaveatEnforcer.sol +62 -0
  51. package/src/agency/IDelegationManager.sol +69 -0
  52. package/src/custody/CustodyPolicy.sol +892 -0
  53. package/src/custody/IERC7579Module.sol +60 -0
  54. package/src/enforcers/AllowedMethodsEnforcer.AUDIT.md +51 -0
  55. package/src/enforcers/AllowedMethodsEnforcer.sol +48 -0
  56. package/src/enforcers/AllowedTargetsEnforcer.AUDIT.md +49 -0
  57. package/src/enforcers/AllowedTargetsEnforcer.sol +44 -0
  58. package/src/enforcers/CaveatEnforcerBase.sol +19 -0
  59. package/src/enforcers/QuorumEnforcer.AUDIT.md +71 -0
  60. package/src/enforcers/QuorumEnforcer.sol +191 -0
  61. package/src/enforcers/TimestampEnforcer.AUDIT.md +50 -0
  62. package/src/enforcers/TimestampEnforcer.sol +43 -0
  63. package/src/enforcers/ValueEnforcer.AUDIT.md +51 -0
  64. package/src/enforcers/ValueEnforcer.sol +41 -0
  65. package/src/governance/AgenticGovernance.sol +140 -0
  66. package/src/governance/GovernanceManaged.sol +75 -0
  67. package/src/governance/IGovernance.sol +15 -0
  68. package/src/identity/AgentProfilePredicates.sol +40 -0
  69. package/src/identity/AgentProfileResolver.sol +194 -0
  70. package/src/libraries/MultiSendCallOnly.sol +95 -0
  71. package/src/libraries/P256Verifier.sol +47 -0
  72. package/src/libraries/SignatureSlotRecovery.sol +196 -0
  73. package/src/libraries/WebAuthnLib.sol +164 -0
  74. package/src/naming/AgentNameAttributeResolver.sol +95 -0
  75. package/src/naming/AgentNamePredicates.sol +74 -0
  76. package/src/naming/AgentNameRegistry.sol +362 -0
  77. package/src/naming/AgentNameUniversalResolver.sol +210 -0
  78. package/src/naming/PermissionlessSubregistry.sol +98 -0
  79. package/src/ontology/AttributeStorage.sol +289 -0
  80. package/src/ontology/OntologyTermRegistry.sol +146 -0
  81. package/src/ontology/ShapeRegistry.sol +240 -0
  82. package/src/relationships/AgentRelationship.sol +289 -0
  83. package/src/relationships/AgentRelationshipPredicates.sol +44 -0
  84. package/src/relationships/RelationshipTypeRegistry.sol +143 -0
@@ -0,0 +1,289 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import "./OntologyTermRegistry.sol";
5
+
6
+ /**
7
+ * @title AttributeStorage
8
+ * @notice Abstract base for typed, ontology-governed attribute storage.
9
+ * Each contract that inherits gets its OWN copy of the mappings
10
+ * (no shared backend). Subclasses choose their auth model; the
11
+ * setters are `internal` so the subclass enforces preconditions.
12
+ *
13
+ * Eight typed value families, identified by `uint8` datatype
14
+ * discriminators:
15
+ * 1 string, 2 address, 3 bool, 4 uint256,
16
+ * 5 bytes32, 6 string[], 7 address[], 8 bytes32[]
17
+ *
18
+ * Predicate enumeration: `predicatesOf(subject)` returns the list of
19
+ * predicates ever set on a subject (insertion order, no duplicates).
20
+ * `subjectVersion(subject)` is bumped on every write — off-chain syncs
21
+ * (RDF mirrors, indexers) use it as a watermark for diff-aware emission.
22
+ *
23
+ * Predicate governance: every internal setter checks the predicate is
24
+ * ACTIVE in the bound `ONTOLOGY` (an OntologyTermRegistry instance
25
+ * passed at construction). Bypass-the-SDK writes with unknown
26
+ * predicates revert at the chain edge.
27
+ *
28
+ * Adapted from smart-agent
29
+ * (`packages/contracts/src/AttributeStorage.sol`, 283 LOC) —
30
+ * structurally identical.
31
+ */
32
+ abstract contract AttributeStorage {
33
+ OntologyTermRegistry public immutable ONTOLOGY;
34
+
35
+ // ─── Datatype discriminators ────────────────────────────────────
36
+ uint8 internal constant DT_STRING = 1;
37
+ uint8 internal constant DT_ADDRESS = 2;
38
+ uint8 internal constant DT_BOOL = 3;
39
+ uint8 internal constant DT_UINT256 = 4;
40
+ uint8 internal constant DT_BYTES32 = 5;
41
+ uint8 internal constant DT_STRING_ARR = 6;
42
+ uint8 internal constant DT_ADDRESS_ARR = 7;
43
+ uint8 internal constant DT_BYTES32_ARR = 8;
44
+
45
+ // ─── Typed value families ───────────────────────────────────────
46
+ mapping(bytes32 => mapping(bytes32 => string)) private _string;
47
+ mapping(bytes32 => mapping(bytes32 => address)) private _address;
48
+ mapping(bytes32 => mapping(bytes32 => bool)) private _bool;
49
+ mapping(bytes32 => mapping(bytes32 => uint256)) private _uint;
50
+ mapping(bytes32 => mapping(bytes32 => bytes32)) private _bytes32;
51
+ mapping(bytes32 => mapping(bytes32 => string[])) private _stringArr;
52
+ mapping(bytes32 => mapping(bytes32 => address[])) private _addressArr;
53
+ mapping(bytes32 => mapping(bytes32 => bytes32[])) private _bytes32Arr;
54
+
55
+ // ─── Indexing / metadata ────────────────────────────────────────
56
+ mapping(bytes32 => bytes32[]) private _predicates;
57
+ mapping(bytes32 => mapping(bytes32 => bool)) private _isSet;
58
+ mapping(bytes32 => mapping(bytes32 => uint8)) private _datatype;
59
+ mapping(bytes32 => mapping(bytes32 => uint64)) private _updatedAt;
60
+ mapping(bytes32 => uint64) private _subjectVersion;
61
+ bytes32[] private _allSubjects;
62
+ mapping(bytes32 => bool) private _subjectKnown;
63
+
64
+ // ─── Events ─────────────────────────────────────────────────────
65
+ event AttributeSet(bytes32 indexed subject, bytes32 indexed predicate, uint8 datatype, uint64 version);
66
+ event AttributeUnset(bytes32 indexed subject, bytes32 indexed predicate, uint64 version);
67
+ event AttributeAppended(bytes32 indexed subject, bytes32 indexed predicate, uint8 datatype, uint64 version);
68
+ event SubjectFirstSeen(bytes32 indexed subject);
69
+
70
+ // ─── Errors ─────────────────────────────────────────────────────
71
+ error PredicateNotActive();
72
+ error AttributeNotSet();
73
+
74
+ constructor(address ontologyRegistry) {
75
+ ONTOLOGY = OntologyTermRegistry(ontologyRegistry);
76
+ }
77
+
78
+ // ─── Public datatype discriminator readers (for ShapeRegistry) ──
79
+
80
+ function DT_STRING_PUB() external pure returns (uint8) { return DT_STRING; }
81
+ function DT_ADDRESS_PUB() external pure returns (uint8) { return DT_ADDRESS; }
82
+ function DT_BOOL_PUB() external pure returns (uint8) { return DT_BOOL; }
83
+ function DT_UINT256_PUB() external pure returns (uint8) { return DT_UINT256; }
84
+ function DT_BYTES32_PUB() external pure returns (uint8) { return DT_BYTES32; }
85
+ function DT_STRING_ARR_PUB() external pure returns (uint8) { return DT_STRING_ARR; }
86
+ function DT_ADDRESS_ARR_PUB() external pure returns (uint8) { return DT_ADDRESS_ARR; }
87
+ function DT_BYTES32_ARR_PUB() external pure returns (uint8) { return DT_BYTES32_ARR; }
88
+
89
+ // ─── Internal setters (subclass-only) ───────────────────────────
90
+
91
+ function _setString(bytes32 subject, bytes32 predicate, string memory value) internal {
92
+ _requirePredicate(predicate);
93
+ _string[subject][predicate] = value;
94
+ _record(subject, predicate, DT_STRING);
95
+ }
96
+
97
+ function _setAddress(bytes32 subject, bytes32 predicate, address value) internal {
98
+ _requirePredicate(predicate);
99
+ _address[subject][predicate] = value;
100
+ _record(subject, predicate, DT_ADDRESS);
101
+ }
102
+
103
+ function _setBool(bytes32 subject, bytes32 predicate, bool value) internal {
104
+ _requirePredicate(predicate);
105
+ _bool[subject][predicate] = value;
106
+ _record(subject, predicate, DT_BOOL);
107
+ }
108
+
109
+ function _setUint(bytes32 subject, bytes32 predicate, uint256 value) internal {
110
+ _requirePredicate(predicate);
111
+ _uint[subject][predicate] = value;
112
+ _record(subject, predicate, DT_UINT256);
113
+ }
114
+
115
+ function _setBytes32(bytes32 subject, bytes32 predicate, bytes32 value) internal {
116
+ _requirePredicate(predicate);
117
+ _bytes32[subject][predicate] = value;
118
+ _record(subject, predicate, DT_BYTES32);
119
+ }
120
+
121
+ function _setStringArr(bytes32 subject, bytes32 predicate, string[] memory values) internal {
122
+ _requirePredicate(predicate);
123
+ delete _stringArr[subject][predicate];
124
+ for (uint256 i = 0; i < values.length; i++) {
125
+ _stringArr[subject][predicate].push(values[i]);
126
+ }
127
+ _record(subject, predicate, DT_STRING_ARR);
128
+ }
129
+
130
+ function _setAddressArr(bytes32 subject, bytes32 predicate, address[] memory values) internal {
131
+ _requirePredicate(predicate);
132
+ delete _addressArr[subject][predicate];
133
+ for (uint256 i = 0; i < values.length; i++) {
134
+ _addressArr[subject][predicate].push(values[i]);
135
+ }
136
+ _record(subject, predicate, DT_ADDRESS_ARR);
137
+ }
138
+
139
+ function _setBytes32Arr(bytes32 subject, bytes32 predicate, bytes32[] memory values) internal {
140
+ _requirePredicate(predicate);
141
+ delete _bytes32Arr[subject][predicate];
142
+ for (uint256 i = 0; i < values.length; i++) {
143
+ _bytes32Arr[subject][predicate].push(values[i]);
144
+ }
145
+ _record(subject, predicate, DT_BYTES32_ARR);
146
+ }
147
+
148
+ function _appendString(bytes32 subject, bytes32 predicate, string memory value) internal {
149
+ _requirePredicate(predicate);
150
+ _stringArr[subject][predicate].push(value);
151
+ _recordAppend(subject, predicate, DT_STRING_ARR);
152
+ }
153
+
154
+ function _appendAddress(bytes32 subject, bytes32 predicate, address value) internal {
155
+ _requirePredicate(predicate);
156
+ _addressArr[subject][predicate].push(value);
157
+ _recordAppend(subject, predicate, DT_ADDRESS_ARR);
158
+ }
159
+
160
+ function _appendBytes32(bytes32 subject, bytes32 predicate, bytes32 value) internal {
161
+ _requirePredicate(predicate);
162
+ _bytes32Arr[subject][predicate].push(value);
163
+ _recordAppend(subject, predicate, DT_BYTES32_ARR);
164
+ }
165
+
166
+ function _unset(bytes32 subject, bytes32 predicate) internal {
167
+ if (!_isSet[subject][predicate]) revert AttributeNotSet();
168
+ uint8 dt = _datatype[subject][predicate];
169
+ if (dt == DT_STRING) delete _string[subject][predicate];
170
+ else if (dt == DT_ADDRESS) delete _address[subject][predicate];
171
+ else if (dt == DT_BOOL) delete _bool[subject][predicate];
172
+ else if (dt == DT_UINT256) delete _uint[subject][predicate];
173
+ else if (dt == DT_BYTES32) delete _bytes32[subject][predicate];
174
+ else if (dt == DT_STRING_ARR) delete _stringArr[subject][predicate];
175
+ else if (dt == DT_ADDRESS_ARR) delete _addressArr[subject][predicate];
176
+ else if (dt == DT_BYTES32_ARR) delete _bytes32Arr[subject][predicate];
177
+ _isSet[subject][predicate] = false;
178
+ delete _datatype[subject][predicate];
179
+ delete _updatedAt[subject][predicate];
180
+ uint64 v = _bumpVersion(subject);
181
+ emit AttributeUnset(subject, predicate, v);
182
+ }
183
+
184
+ // ─── Public getters ─────────────────────────────────────────────
185
+
186
+ function getString(bytes32 subject, bytes32 predicate) external view returns (string memory) {
187
+ return _string[subject][predicate];
188
+ }
189
+ function getAddress(bytes32 subject, bytes32 predicate) external view returns (address) {
190
+ return _address[subject][predicate];
191
+ }
192
+ function getBool(bytes32 subject, bytes32 predicate) external view returns (bool) {
193
+ return _bool[subject][predicate];
194
+ }
195
+ function getUint(bytes32 subject, bytes32 predicate) external view returns (uint256) {
196
+ return _uint[subject][predicate];
197
+ }
198
+ function getBytes32(bytes32 subject, bytes32 predicate) external view returns (bytes32) {
199
+ return _bytes32[subject][predicate];
200
+ }
201
+ function getStringArr(bytes32 subject, bytes32 predicate) external view returns (string[] memory) {
202
+ return _stringArr[subject][predicate];
203
+ }
204
+ function getAddressArr(bytes32 subject, bytes32 predicate) external view returns (address[] memory) {
205
+ return _addressArr[subject][predicate];
206
+ }
207
+ function getBytes32Arr(bytes32 subject, bytes32 predicate) external view returns (bytes32[] memory) {
208
+ return _bytes32Arr[subject][predicate];
209
+ }
210
+
211
+ function predicatesOf(bytes32 subject) external view returns (bytes32[] memory) {
212
+ return _predicates[subject];
213
+ }
214
+ function datatypeOf(bytes32 subject, bytes32 predicate) external view returns (uint8) {
215
+ return _datatype[subject][predicate];
216
+ }
217
+ function updatedAt(bytes32 subject, bytes32 predicate) external view returns (uint64) {
218
+ return _updatedAt[subject][predicate];
219
+ }
220
+ function isSet(bytes32 subject, bytes32 predicate) external view returns (bool) {
221
+ return _isSet[subject][predicate];
222
+ }
223
+ function subjectVersion(bytes32 subject) external view returns (uint64) {
224
+ return _subjectVersion[subject];
225
+ }
226
+ function allSubjects() external view returns (bytes32[] memory) {
227
+ return _allSubjects;
228
+ }
229
+ function subjectCount() external view returns (uint256) {
230
+ return _allSubjects.length;
231
+ }
232
+
233
+ // ─── Internal helpers ───────────────────────────────────────────
234
+
235
+ function _requirePredicate(bytes32 predicate) internal view {
236
+ if (!ONTOLOGY.isActive(predicate)) revert PredicateNotActive();
237
+ }
238
+
239
+ function _record(bytes32 subject, bytes32 predicate, uint8 dt) internal {
240
+ _trackSubject(subject);
241
+ if (!_isSet[subject][predicate]) {
242
+ _predicates[subject].push(predicate);
243
+ _isSet[subject][predicate] = true;
244
+ }
245
+ _datatype[subject][predicate] = dt;
246
+ uint64 v = _bumpVersion(subject);
247
+ _updatedAt[subject][predicate] = v;
248
+ emit AttributeSet(subject, predicate, dt, v);
249
+ }
250
+
251
+ function _recordAppend(bytes32 subject, bytes32 predicate, uint8 dt) internal {
252
+ _trackSubject(subject);
253
+ if (!_isSet[subject][predicate]) {
254
+ _predicates[subject].push(predicate);
255
+ _isSet[subject][predicate] = true;
256
+ }
257
+ _datatype[subject][predicate] = dt;
258
+ uint64 v = _bumpVersion(subject);
259
+ _updatedAt[subject][predicate] = v;
260
+ emit AttributeAppended(subject, predicate, dt, v);
261
+ }
262
+
263
+ function _trackSubject(bytes32 subject) internal {
264
+ if (!_subjectKnown[subject]) {
265
+ _subjectKnown[subject] = true;
266
+ _allSubjects.push(subject);
267
+ emit SubjectFirstSeen(subject);
268
+ }
269
+ }
270
+
271
+ function _bumpVersion(bytes32 subject) internal returns (uint64) {
272
+ uint64 next = _subjectVersion[subject] + 1;
273
+ _subjectVersion[subject] = next;
274
+ return next;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * @title IAttributeReader
280
+ * @notice Read-only interface every AttributeStorage subclass exposes.
281
+ * ShapeRegistry takes this so a single shape definition can
282
+ * validate any registry's subject.
283
+ */
284
+ interface IAttributeReader {
285
+ function isSet(bytes32 subject, bytes32 predicate) external view returns (bool);
286
+ function datatypeOf(bytes32 subject, bytes32 predicate) external view returns (uint8);
287
+ function getBytes32(bytes32 subject, bytes32 predicate) external view returns (bytes32);
288
+ function getBytes32Arr(bytes32 subject, bytes32 predicate) external view returns (bytes32[] memory);
289
+ }
@@ -0,0 +1,146 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ /**
5
+ * @title OntologyTermRegistry
6
+ * @notice Governed registry of valid ontology predicates for the
7
+ * agenticprimitives trust fabric.
8
+ *
9
+ * Controls which predicate bytes32 ids may appear on any
10
+ * `AttributeStorage` subclass (naming records, future relationships /
11
+ * identity records). Each term maps a `bytes32` id (keccak256 of a
12
+ * CURIE like `"atl:displayName"`) to its full URI, human label, and
13
+ * expected datatype family.
14
+ *
15
+ * Governance: only `governor` may register or deactivate terms.
16
+ * Deployer is the bootstrap governor; rotation to a multi-sig /
17
+ * Smart Agent CustodyPolicy is a single `transferGovernor` call.
18
+ *
19
+ * Adapted from smart-agent
20
+ * (`packages/contracts/src/OntologyTermRegistry.sol`, 136 LOC) —
21
+ * structurally identical; copied here to keep the agenticprimitives
22
+ * trust fabric self-contained.
23
+ */
24
+ contract OntologyTermRegistry {
25
+ struct Term {
26
+ bytes32 id; // keccak256("atl:displayName")
27
+ string curie; // "atl:displayName"
28
+ string uri; // "https://agentictrust.io/ontology/core#displayName"
29
+ string label; // "Display Name"
30
+ string datatype; // "string" | "address" | "bool" | "uint256" | "bytes32" | "string[]" | "address[]" | "bytes32[]"
31
+ bool active;
32
+ uint256 registeredAt;
33
+ }
34
+
35
+ address public governor;
36
+ mapping(bytes32 => Term) private _terms;
37
+ bytes32[] private _termIds;
38
+
39
+ event TermRegistered(bytes32 indexed id, string curie, string uri);
40
+ event TermDeactivated(bytes32 indexed id);
41
+ event TermActivated(bytes32 indexed id);
42
+ event GovernorTransferred(address indexed oldGovernor, address indexed newGovernor);
43
+
44
+ error NotGovernor();
45
+ error TermExists();
46
+ error TermNotFound();
47
+ error ZeroGovernor();
48
+
49
+ modifier onlyGovernor() {
50
+ if (msg.sender != governor) revert NotGovernor();
51
+ _;
52
+ }
53
+
54
+ constructor(address governor_) {
55
+ if (governor_ == address(0)) revert ZeroGovernor();
56
+ governor = governor_;
57
+ }
58
+
59
+ function transferGovernor(address newGovernor) external onlyGovernor {
60
+ if (newGovernor == address(0)) revert ZeroGovernor();
61
+ emit GovernorTransferred(governor, newGovernor);
62
+ governor = newGovernor;
63
+ }
64
+
65
+ function registerTerm(
66
+ bytes32 id,
67
+ string calldata curie,
68
+ string calldata uri,
69
+ string calldata label,
70
+ string calldata datatype
71
+ ) external onlyGovernor {
72
+ if (_terms[id].registeredAt != 0) revert TermExists();
73
+ _terms[id] = Term({
74
+ id: id,
75
+ curie: curie,
76
+ uri: uri,
77
+ label: label,
78
+ datatype: datatype,
79
+ active: true,
80
+ registeredAt: block.timestamp
81
+ });
82
+ _termIds.push(id);
83
+ emit TermRegistered(id, curie, uri);
84
+ }
85
+
86
+ function registerTermBatch(
87
+ bytes32[] calldata ids,
88
+ string[] calldata curies,
89
+ string[] calldata uris,
90
+ string[] calldata labels,
91
+ string[] calldata datatypes
92
+ ) external onlyGovernor {
93
+ for (uint256 i = 0; i < ids.length; i++) {
94
+ if (_terms[ids[i]].registeredAt != 0) continue; // skip existing
95
+ _terms[ids[i]] = Term({
96
+ id: ids[i],
97
+ curie: curies[i],
98
+ uri: uris[i],
99
+ label: labels[i],
100
+ datatype: datatypes[i],
101
+ active: true,
102
+ registeredAt: block.timestamp
103
+ });
104
+ _termIds.push(ids[i]);
105
+ emit TermRegistered(ids[i], curies[i], uris[i]);
106
+ }
107
+ }
108
+
109
+ function deactivateTerm(bytes32 id) external onlyGovernor {
110
+ if (_terms[id].registeredAt == 0) revert TermNotFound();
111
+ _terms[id].active = false;
112
+ emit TermDeactivated(id);
113
+ }
114
+
115
+ function activateTerm(bytes32 id) external onlyGovernor {
116
+ if (_terms[id].registeredAt == 0) revert TermNotFound();
117
+ _terms[id].active = true;
118
+ emit TermActivated(id);
119
+ }
120
+
121
+ // ─── Queries ────────────────────────────────────────────────────
122
+
123
+ function getTerm(bytes32 id) external view returns (Term memory) {
124
+ return _terms[id];
125
+ }
126
+
127
+ function isRegistered(bytes32 id) external view returns (bool) {
128
+ return _terms[id].registeredAt != 0;
129
+ }
130
+
131
+ function isActive(bytes32 id) external view returns (bool) {
132
+ return _terms[id].active;
133
+ }
134
+
135
+ function termCount() external view returns (uint256) {
136
+ return _termIds.length;
137
+ }
138
+
139
+ function getTermAt(uint256 index) external view returns (Term memory) {
140
+ return _terms[_termIds[index]];
141
+ }
142
+
143
+ function getAllTermIds() external view returns (bytes32[] memory) {
144
+ return _termIds;
145
+ }
146
+ }
@@ -0,0 +1,240 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import "./AttributeStorage.sol";
5
+
6
+ /**
7
+ * @title ShapeRegistry
8
+ * @notice SHACL-inspired class shape constraints. Decoupled from any
9
+ * specific store: every validation call takes the reader's
10
+ * address, so a single registry can hold shapes that validate
11
+ * against any AttributeStorage subclass (naming records today,
12
+ * relationship + identity records in Phase 3+).
13
+ *
14
+ * Supported SHACL subset:
15
+ * `sh:path` → predicate id
16
+ * `sh:datatype` → AttributeStorage datatype family discriminator (`DT_*`)
17
+ * `sh:minCount` / `sh:maxCount` → cardinality enum
18
+ * `sh:in` → enumSetId referencing the contract's stored allowed values
19
+ * `sh:class` → expectedClass id (informational; off-chain class IRI hash)
20
+ *
21
+ * Adapted from smart-agent
22
+ * (`packages/contracts/src/ShapeRegistry.sol`, 231 LOC) —
23
+ * structurally identical.
24
+ */
25
+ contract ShapeRegistry {
26
+ address public governor;
27
+
28
+ enum Cardinality {
29
+ OPTIONAL, // 0..1
30
+ REQUIRED_ONE, // 1..1
31
+ REQUIRED_MANY, // 1..*
32
+ OPTIONAL_MANY // 0..*
33
+ }
34
+
35
+ struct PropertyConstraint {
36
+ bytes32 predicate;
37
+ uint8 expectedDatatype;
38
+ Cardinality cardinality;
39
+ bytes32 enumSetId;
40
+ bytes32 expectedClass;
41
+ }
42
+
43
+ struct Shape {
44
+ bytes32 classId;
45
+ string shapeURI;
46
+ bytes32 shapeHash;
47
+ uint16 version;
48
+ bool active;
49
+ bool exists;
50
+ }
51
+
52
+ mapping(bytes32 => Shape) private _shapes;
53
+ mapping(bytes32 => PropertyConstraint[]) private _props;
54
+ mapping(bytes32 => bytes32[]) private _enumValues;
55
+ mapping(bytes32 => mapping(bytes32 => bool)) private _enumContains;
56
+ bytes32[] private _classIds;
57
+
58
+ event ShapeDefined(bytes32 indexed classId, uint16 version, string shapeURI, bytes32 shapeHash);
59
+ event ShapeUpdated(bytes32 indexed classId, uint16 version, bytes32 shapeHash);
60
+ event ShapeDeactivated(bytes32 indexed classId);
61
+ event ShapeActivated(bytes32 indexed classId);
62
+ event EnumSetDefined(bytes32 indexed enumSetId, uint256 valueCount);
63
+ event GovernorTransferred(address indexed previousGovernor, address indexed newGovernor);
64
+
65
+ error NotGovernor();
66
+ error ShapeAlreadyDefined();
67
+ error ShapeNotDefined();
68
+ error ShapeNotActive();
69
+ error MissingRequiredProperty(bytes32 predicate);
70
+ error WrongDatatype(bytes32 predicate, uint8 actual, uint8 expected);
71
+ error EnumValueNotAllowed(bytes32 predicate, bytes32 actualValue);
72
+ error EnumSetEmpty();
73
+ error ZeroGovernor();
74
+
75
+ modifier onlyGovernor() {
76
+ if (msg.sender != governor) revert NotGovernor();
77
+ _;
78
+ }
79
+
80
+ constructor(address governor_) {
81
+ if (governor_ == address(0)) revert ZeroGovernor();
82
+ governor = governor_;
83
+ }
84
+
85
+ function transferGovernor(address newGovernor) external onlyGovernor {
86
+ if (newGovernor == address(0)) revert ZeroGovernor();
87
+ emit GovernorTransferred(governor, newGovernor);
88
+ governor = newGovernor;
89
+ }
90
+
91
+ // ─── Shape definition ───────────────────────────────────────────
92
+
93
+ function defineShape(
94
+ bytes32 classId,
95
+ PropertyConstraint[] calldata props,
96
+ string calldata shapeURI,
97
+ bytes32 shapeHash
98
+ ) external onlyGovernor returns (uint16) {
99
+ if (_shapes[classId].exists) revert ShapeAlreadyDefined();
100
+ _shapes[classId] = Shape({
101
+ classId: classId,
102
+ shapeURI: shapeURI,
103
+ shapeHash: shapeHash,
104
+ version: 1,
105
+ active: true,
106
+ exists: true
107
+ });
108
+ _classIds.push(classId);
109
+ for (uint256 i = 0; i < props.length; i++) {
110
+ _props[classId].push(props[i]);
111
+ }
112
+ emit ShapeDefined(classId, 1, shapeURI, shapeHash);
113
+ return 1;
114
+ }
115
+
116
+ function updateShape(
117
+ bytes32 classId,
118
+ PropertyConstraint[] calldata props,
119
+ string calldata shapeURI,
120
+ bytes32 shapeHash
121
+ ) external onlyGovernor returns (uint16) {
122
+ Shape storage s = _shapes[classId];
123
+ if (!s.exists) revert ShapeNotDefined();
124
+ s.version += 1;
125
+ s.shapeURI = shapeURI;
126
+ s.shapeHash = shapeHash;
127
+ delete _props[classId];
128
+ for (uint256 i = 0; i < props.length; i++) {
129
+ _props[classId].push(props[i]);
130
+ }
131
+ emit ShapeUpdated(classId, s.version, shapeHash);
132
+ return s.version;
133
+ }
134
+
135
+ function deactivateShape(bytes32 classId) external onlyGovernor {
136
+ Shape storage s = _shapes[classId];
137
+ if (!s.exists) revert ShapeNotDefined();
138
+ s.active = false;
139
+ emit ShapeDeactivated(classId);
140
+ }
141
+
142
+ function activateShape(bytes32 classId) external onlyGovernor {
143
+ Shape storage s = _shapes[classId];
144
+ if (!s.exists) revert ShapeNotDefined();
145
+ s.active = true;
146
+ emit ShapeActivated(classId);
147
+ }
148
+
149
+ function defineEnumSet(bytes32 enumSetId, bytes32[] calldata allowedValues) external onlyGovernor {
150
+ if (allowedValues.length == 0) revert EnumSetEmpty();
151
+ bytes32[] storage existing = _enumValues[enumSetId];
152
+ for (uint256 i = 0; i < existing.length; i++) {
153
+ _enumContains[enumSetId][existing[i]] = false;
154
+ }
155
+ delete _enumValues[enumSetId];
156
+ for (uint256 i = 0; i < allowedValues.length; i++) {
157
+ _enumValues[enumSetId].push(allowedValues[i]);
158
+ _enumContains[enumSetId][allowedValues[i]] = true;
159
+ }
160
+ emit EnumSetDefined(enumSetId, allowedValues.length);
161
+ }
162
+
163
+ // ─── Validation ─────────────────────────────────────────────────
164
+
165
+ /// @notice Validate `subject` in `store` against the shape `classId`.
166
+ /// Reverts with a specific error if any constraint fails.
167
+ function validateSubject(bytes32 classId, bytes32 subject, address store) external view {
168
+ _validate(classId, subject, IAttributeReader(store));
169
+ }
170
+
171
+ /// @notice Try-validate variant: returns false instead of reverting.
172
+ function isValid(bytes32 classId, bytes32 subject, address store) external view returns (bool) {
173
+ try this.validateSubject(classId, subject, store) {
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+
180
+ function _validate(bytes32 classId, bytes32 subject, IAttributeReader store) internal view {
181
+ Shape storage s = _shapes[classId];
182
+ if (!s.exists) revert ShapeNotDefined();
183
+ if (!s.active) revert ShapeNotActive();
184
+
185
+ PropertyConstraint[] storage props = _props[classId];
186
+ for (uint256 i = 0; i < props.length; i++) {
187
+ PropertyConstraint storage p = props[i];
188
+ bool present = store.isSet(subject, p.predicate);
189
+ bool required = p.cardinality == Cardinality.REQUIRED_ONE
190
+ || p.cardinality == Cardinality.REQUIRED_MANY;
191
+
192
+ if (!present) {
193
+ if (required) revert MissingRequiredProperty(p.predicate);
194
+ continue;
195
+ }
196
+
197
+ uint8 actualDt = store.datatypeOf(subject, p.predicate);
198
+ if (actualDt != p.expectedDatatype) {
199
+ revert WrongDatatype(p.predicate, actualDt, p.expectedDatatype);
200
+ }
201
+
202
+ if (p.enumSetId != bytes32(0)) {
203
+ if (p.expectedDatatype == 5) { // DT_BYTES32
204
+ bytes32 v = store.getBytes32(subject, p.predicate);
205
+ if (!_enumContains[p.enumSetId][v]) {
206
+ revert EnumValueNotAllowed(p.predicate, v);
207
+ }
208
+ } else if (p.expectedDatatype == 8) { // DT_BYTES32_ARR
209
+ bytes32[] memory arr = store.getBytes32Arr(subject, p.predicate);
210
+ for (uint256 j = 0; j < arr.length; j++) {
211
+ if (!_enumContains[p.enumSetId][arr[j]]) {
212
+ revert EnumValueNotAllowed(p.predicate, arr[j]);
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ // ─── Queries ────────────────────────────────────────────────────
221
+
222
+ function getShape(bytes32 classId) external view returns (Shape memory) {
223
+ return _shapes[classId];
224
+ }
225
+ function getProperties(bytes32 classId) external view returns (PropertyConstraint[] memory) {
226
+ return _props[classId];
227
+ }
228
+ function getEnumValues(bytes32 enumSetId) external view returns (bytes32[] memory) {
229
+ return _enumValues[enumSetId];
230
+ }
231
+ function isInEnumSet(bytes32 enumSetId, bytes32 value) external view returns (bool) {
232
+ return _enumContains[enumSetId][value];
233
+ }
234
+ function shapeCount() external view returns (uint256) {
235
+ return _classIds.length;
236
+ }
237
+ function getClassIdAt(uint256 index) external view returns (bytes32) {
238
+ return _classIds[index];
239
+ }
240
+ }