@godman-protocols/soul 0.2.0 → 0.3.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/engine.d.ts +108 -30
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +310 -141
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +5 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +120 -51
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +36 -4
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/engine.ts +370 -152
- package/src/index.ts +5 -29
- package/src/types.ts +198 -53
package/dist/engine.d.ts
CHANGED
|
@@ -1,46 +1,124 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SOUL —
|
|
3
|
-
*
|
|
4
|
-
* @version 0.2.0
|
|
2
|
+
* SOUL — Sovereign Open Universal Layer
|
|
3
|
+
* "One layer. Every chain. Every model. SOUL."
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Core engine: SoulValidator, SoulPublisher, SoulVerifier,
|
|
6
|
+
* parseSoulMd(), generateSoulMd().
|
|
7
|
+
*
|
|
8
|
+
* @version 0.3.0
|
|
8
9
|
*/
|
|
9
|
-
import type {
|
|
10
|
+
import type { ERC8004Anchor, SHA256Hash, SoulDocument, SoulPublisherConfig, SoulValidationResult, SoulVerificationResult } from './types.js';
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
-
* Must be signed by the operator before use.
|
|
12
|
+
* Compute SHA-256 hex digest of a string.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
15
|
-
issuedAt?: Timestamp;
|
|
16
|
-
}): Omit<Constitution, 'signature'> & {
|
|
17
|
-
signature: '';
|
|
18
|
-
};
|
|
14
|
+
export declare function sha256(content: string): SHA256Hash;
|
|
19
15
|
/**
|
|
20
|
-
*
|
|
16
|
+
* Parse a SOUL.md markdown string into a SoulDocument.
|
|
17
|
+
*
|
|
18
|
+
* Expected format:
|
|
19
|
+
* ```
|
|
20
|
+
* # SOUL — <agent_name>
|
|
21
|
+
*
|
|
22
|
+
* ## Who We Are
|
|
23
|
+
* <content>
|
|
24
|
+
*
|
|
25
|
+
* ## Why We Exist
|
|
26
|
+
* <content>
|
|
27
|
+
* ...
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Metadata (agent_did, created_at, etc.) can be provided via `defaults`
|
|
31
|
+
* since they are not typically present in the markdown itself.
|
|
21
32
|
*/
|
|
22
|
-
export declare function
|
|
33
|
+
export declare function parseSoulMd(markdown: string, defaults?: {
|
|
34
|
+
agent_did?: string;
|
|
35
|
+
created_at?: string;
|
|
36
|
+
updated_at?: string;
|
|
37
|
+
erc8004_anchor?: ERC8004Anchor;
|
|
38
|
+
}): SoulDocument;
|
|
23
39
|
/**
|
|
24
|
-
*
|
|
40
|
+
* Generate a SOUL.md markdown string from a SoulDocument.
|
|
25
41
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
42
|
+
* Output format:
|
|
43
|
+
* ```
|
|
44
|
+
* # SOUL — <agent_name>
|
|
45
|
+
*
|
|
46
|
+
* ## Who We Are
|
|
47
|
+
* <content>
|
|
48
|
+
* ...
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function generateSoulMd(doc: SoulDocument): string;
|
|
52
|
+
/**
|
|
53
|
+
* Validates SoulDocument instances against the SOUL format specification.
|
|
31
54
|
*/
|
|
32
|
-
export declare
|
|
55
|
+
export declare class SoulValidator {
|
|
56
|
+
/**
|
|
57
|
+
* Validate a SoulDocument for completeness and format compliance.
|
|
58
|
+
*/
|
|
59
|
+
validate(doc: SoulDocument): SoulValidationResult;
|
|
60
|
+
/**
|
|
61
|
+
* Validate a SOUL.md markdown string by parsing it first.
|
|
62
|
+
*/
|
|
63
|
+
validateMarkdown(markdown: string): SoulValidationResult;
|
|
64
|
+
}
|
|
33
65
|
/**
|
|
34
|
-
*
|
|
35
|
-
* Context is a key-value map of runtime metrics.
|
|
66
|
+
* Publishes a SoulDocument as a SOUL.md endpoint.
|
|
36
67
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
68
|
+
* In v0.3.0 this is a lightweight utility that generates the markdown
|
|
69
|
+
* and computes the hash. Full HTTP server implementation is deferred
|
|
70
|
+
* to v0.4.0 (requires server/client packages).
|
|
40
71
|
*/
|
|
41
|
-
export declare
|
|
72
|
+
export declare class SoulPublisher {
|
|
73
|
+
private document;
|
|
74
|
+
private config;
|
|
75
|
+
constructor(doc: SoulDocument, config?: SoulPublisherConfig);
|
|
76
|
+
/**
|
|
77
|
+
* Generate the SOUL.md markdown content for publishing.
|
|
78
|
+
*/
|
|
79
|
+
getMarkdown(): string;
|
|
80
|
+
/**
|
|
81
|
+
* Compute the SHA-256 hash of the generated SOUL.md content.
|
|
82
|
+
* This is the value that should be anchored on-chain via ERC-8004.
|
|
83
|
+
*/
|
|
84
|
+
getHash(): SHA256Hash;
|
|
85
|
+
/**
|
|
86
|
+
* Get the full endpoint path where SOUL.md would be served.
|
|
87
|
+
*/
|
|
88
|
+
getEndpoint(): string;
|
|
89
|
+
/**
|
|
90
|
+
* Get the publisher configuration.
|
|
91
|
+
*/
|
|
92
|
+
getConfig(): Required<SoulPublisherConfig>;
|
|
93
|
+
/**
|
|
94
|
+
* Build the ERC-8004 anchor metadata for this published SOUL.md.
|
|
95
|
+
*/
|
|
96
|
+
buildAnchor(params: {
|
|
97
|
+
token_id: string;
|
|
98
|
+
chain_id: number;
|
|
99
|
+
contract_address: string;
|
|
100
|
+
}): ERC8004Anchor;
|
|
101
|
+
}
|
|
42
102
|
/**
|
|
43
|
-
*
|
|
103
|
+
* Verifies that a SOUL.md content matches its on-chain ERC-8004 anchor.
|
|
104
|
+
*
|
|
105
|
+
* In v0.3.0 this provides local hash verification. On-chain fetch
|
|
106
|
+
* (via ethers/viem) is deferred to v0.4.0 server package.
|
|
44
107
|
*/
|
|
45
|
-
export declare
|
|
108
|
+
export declare class SoulVerifier {
|
|
109
|
+
/**
|
|
110
|
+
* Verify a SOUL.md markdown string against an expected SHA-256 hash.
|
|
111
|
+
*/
|
|
112
|
+
verify(markdown: string, expectedHash: SHA256Hash): SoulVerificationResult;
|
|
113
|
+
/**
|
|
114
|
+
* Verify a SoulDocument against its own ERC-8004 anchor.
|
|
115
|
+
* Returns unverified if no anchor is present.
|
|
116
|
+
*/
|
|
117
|
+
verifyDocument(doc: SoulDocument): SoulVerificationResult;
|
|
118
|
+
/**
|
|
119
|
+
* Compute the SHA-256 hash of a SOUL.md markdown string.
|
|
120
|
+
* Utility method for pre-anchoring verification.
|
|
121
|
+
*/
|
|
122
|
+
computeHash(markdown: string): SHA256Hash;
|
|
123
|
+
}
|
|
46
124
|
//# sourceMappingURL=engine.d.ts.map
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EACV,aAAa,EACb,UAAU,EACV,YAAY,EACZ,mBAAmB,EAGnB,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,YAAY,CAAC;AAWpB;;GAEG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAElD;AAyBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;IACT,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,aAAa,CAAC;CAChC,GACA,YAAY,CAgFd;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,MAAM,CAmBxD;AAMD;;GAEG;AACH,qBAAa,aAAa;IACxB;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,oBAAoB;IA4EjD;;OAEG;IACH,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB;CAIzD;AAMD;;;;;;GAMG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAe;IAC/B,OAAO,CAAC,MAAM,CAAgC;gBAElC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,EAAE,mBAAmB;IAS3D;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;;OAGG;IACH,OAAO,IAAI,UAAU;IAIrB;;OAEG;IACH,WAAW,IAAI,MAAM;IAIrB;;OAEG;IACH,SAAS,IAAI,QAAQ,CAAC,mBAAmB,CAAC;IAI1C;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,GAAG,aAAa;CAQlB;AAMD;;;;;GAKG;AACH,qBAAa,YAAY;IACvB;;OAEG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,GAAG,sBAAsB;IAc1E;;;OAGG;IACH,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,sBAAsB;IAgBzD;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU;CAG1C"}
|
package/dist/engine.js
CHANGED
|
@@ -1,182 +1,351 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SOUL —
|
|
3
|
-
*
|
|
4
|
-
* @version 0.2.0
|
|
2
|
+
* SOUL — Sovereign Open Universal Layer
|
|
3
|
+
* "One layer. Every chain. Every model. SOUL."
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Core engine: SoulValidator, SoulPublisher, SoulVerifier,
|
|
6
|
+
* parseSoulMd(), generateSoulMd().
|
|
7
|
+
*
|
|
8
|
+
* @version 0.3.0
|
|
8
9
|
*/
|
|
9
|
-
import {
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { REQUIRED_SECTION_KEYS, SOUL_SECTION_TITLES, } from './types.js';
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
11
|
-
//
|
|
13
|
+
// SHA-256 utility
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Must be signed by the operator before use.
|
|
16
|
+
* Compute SHA-256 hex digest of a string.
|
|
16
17
|
*/
|
|
17
|
-
export function
|
|
18
|
-
return
|
|
19
|
-
version: '0.1',
|
|
20
|
-
operatorId,
|
|
21
|
-
issuedAt: options.issuedAt ?? new Date().toISOString(),
|
|
22
|
-
constraints: constraints.map((c) => ({ ...c, id: randomUUID() })),
|
|
23
|
-
killSwitches: killSwitches.map((k) => ({
|
|
24
|
-
...k,
|
|
25
|
-
id: randomUUID(),
|
|
26
|
-
nonNegotiable: true,
|
|
27
|
-
})),
|
|
28
|
-
signature: '',
|
|
29
|
-
};
|
|
18
|
+
export function sha256(content) {
|
|
19
|
+
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
30
20
|
}
|
|
31
21
|
// ---------------------------------------------------------------------------
|
|
32
|
-
//
|
|
22
|
+
// Section key <-> title mapping
|
|
33
23
|
// ---------------------------------------------------------------------------
|
|
24
|
+
/** Map from human-readable title to section key */
|
|
25
|
+
const TITLE_TO_KEY = new Map(Object.entries(SOUL_SECTION_TITLES).map(([key, title]) => [title, key]));
|
|
34
26
|
/**
|
|
35
|
-
*
|
|
27
|
+
* Normalise a heading string to match against known section titles.
|
|
28
|
+
* Strips leading '#' characters, trims whitespace.
|
|
36
29
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
version: constitution.version,
|
|
40
|
-
operatorId: constitution.operatorId,
|
|
41
|
-
issuedAt: constitution.issuedAt,
|
|
42
|
-
constraintCount: constitution.constraints.length,
|
|
43
|
-
killSwitchCount: constitution.killSwitches.length,
|
|
44
|
-
});
|
|
45
|
-
const signature = createHmac('sha256', operatorSecret)
|
|
46
|
-
.update(payload, 'utf8')
|
|
47
|
-
.digest('hex');
|
|
48
|
-
return { ...constitution, signature };
|
|
30
|
+
function normaliseHeading(raw) {
|
|
31
|
+
return raw.replace(/^#+\s*/, '').trim();
|
|
49
32
|
}
|
|
50
33
|
// ---------------------------------------------------------------------------
|
|
51
|
-
//
|
|
34
|
+
// parseSoulMd — parse SOUL.md markdown into SoulDocument
|
|
52
35
|
// ---------------------------------------------------------------------------
|
|
53
36
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
37
|
+
* Parse a SOUL.md markdown string into a SoulDocument.
|
|
38
|
+
*
|
|
39
|
+
* Expected format:
|
|
40
|
+
* ```
|
|
41
|
+
* # SOUL — <agent_name>
|
|
42
|
+
*
|
|
43
|
+
* ## Who We Are
|
|
44
|
+
* <content>
|
|
45
|
+
*
|
|
46
|
+
* ## Why We Exist
|
|
47
|
+
* <content>
|
|
48
|
+
* ...
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* Metadata (agent_did, created_at, etc.) can be provided via `defaults`
|
|
52
|
+
* since they are not typically present in the markdown itself.
|
|
59
53
|
*/
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
54
|
+
export function parseSoulMd(markdown, defaults) {
|
|
55
|
+
const lines = markdown.split('\n');
|
|
56
|
+
// Extract agent name from first H1 heading
|
|
57
|
+
let agentName = 'Unknown Agent';
|
|
58
|
+
const h1Match = lines.find((l) => /^#\s+/.test(l) && !/^##/.test(l));
|
|
59
|
+
if (h1Match) {
|
|
60
|
+
const cleaned = normaliseHeading(h1Match);
|
|
61
|
+
// Strip "SOUL — " or "SOUL - " prefix if present
|
|
62
|
+
agentName = cleaned.replace(/^SOUL\s*[—\-]\s*/, '').trim() || agentName;
|
|
63
|
+
}
|
|
64
|
+
// Split into sections by ## headings
|
|
65
|
+
const sections = [];
|
|
66
|
+
let currentTitle = null;
|
|
67
|
+
let currentLines = [];
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (/^##\s+/.test(line)) {
|
|
70
|
+
// Save previous section
|
|
71
|
+
if (currentTitle !== null) {
|
|
72
|
+
sections.push({
|
|
73
|
+
title: currentTitle,
|
|
74
|
+
content: currentLines.join('\n').trim(),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
currentTitle = normaliseHeading(line);
|
|
78
|
+
currentLines = [];
|
|
79
|
+
}
|
|
80
|
+
else if (currentTitle !== null) {
|
|
81
|
+
currentLines.push(line);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Save last section
|
|
85
|
+
if (currentTitle !== null) {
|
|
86
|
+
sections.push({
|
|
87
|
+
title: currentTitle,
|
|
88
|
+
content: currentLines.join('\n').trim(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
// Build section map
|
|
92
|
+
const sectionMap = new Map();
|
|
93
|
+
for (const section of sections) {
|
|
94
|
+
const key = TITLE_TO_KEY.get(section.title);
|
|
95
|
+
if (key) {
|
|
96
|
+
sectionMap.set(key, {
|
|
97
|
+
title: section.title,
|
|
98
|
+
content: section.content,
|
|
99
|
+
immutable: key === 'what_we_will_never_do' || key === 'constitutional_foundation',
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Fill missing sections with empty placeholders
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
const buildSection = (key) => {
|
|
106
|
+
return sectionMap.get(key) ?? {
|
|
107
|
+
title: SOUL_SECTION_TITLES[key],
|
|
108
|
+
content: '',
|
|
109
|
+
immutable: key === 'what_we_will_never_do' || key === 'constitutional_foundation',
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
soul_version: '1.0',
|
|
114
|
+
agent_name: agentName,
|
|
115
|
+
agent_did: defaults?.agent_did ?? '',
|
|
116
|
+
created_at: defaults?.created_at ?? now,
|
|
117
|
+
updated_at: defaults?.updated_at ?? now,
|
|
118
|
+
who_we_are: buildSection('who_we_are'),
|
|
119
|
+
why_we_exist: buildSection('why_we_exist'),
|
|
120
|
+
what_we_believe: buildSection('what_we_believe'),
|
|
121
|
+
what_we_will_never_do: buildSection('what_we_will_never_do'),
|
|
122
|
+
what_we_owe_the_world: buildSection('what_we_owe_the_world'),
|
|
123
|
+
what_we_owe_each_other: buildSection('what_we_owe_each_other'),
|
|
124
|
+
founding_intention: buildSection('founding_intention'),
|
|
125
|
+
self_committed_swarms: buildSection('self_committed_swarms'),
|
|
126
|
+
constitutional_foundation: buildSection('constitutional_foundation'),
|
|
127
|
+
erc8004_anchor: defaults?.erc8004_anchor,
|
|
128
|
+
};
|
|
70
129
|
}
|
|
71
130
|
// ---------------------------------------------------------------------------
|
|
72
|
-
//
|
|
131
|
+
// generateSoulMd — generate SOUL.md markdown from SoulDocument
|
|
73
132
|
// ---------------------------------------------------------------------------
|
|
74
133
|
/**
|
|
75
|
-
*
|
|
134
|
+
* Generate a SOUL.md markdown string from a SoulDocument.
|
|
76
135
|
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
136
|
+
* Output format:
|
|
137
|
+
* ```
|
|
138
|
+
* # SOUL — <agent_name>
|
|
139
|
+
*
|
|
140
|
+
* ## Who We Are
|
|
141
|
+
* <content>
|
|
142
|
+
* ...
|
|
143
|
+
* ```
|
|
82
144
|
*/
|
|
83
|
-
export function
|
|
84
|
-
const
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
145
|
+
export function generateSoulMd(doc) {
|
|
146
|
+
const parts = [];
|
|
147
|
+
// Title
|
|
148
|
+
parts.push(`# SOUL — ${doc.agent_name}`);
|
|
149
|
+
parts.push('');
|
|
150
|
+
// Each section in canonical order
|
|
151
|
+
for (const key of REQUIRED_SECTION_KEYS) {
|
|
152
|
+
const section = doc[key];
|
|
153
|
+
parts.push(`## ${section.title}`);
|
|
154
|
+
parts.push('');
|
|
155
|
+
if (section.content) {
|
|
156
|
+
parts.push(section.content);
|
|
157
|
+
parts.push('');
|
|
95
158
|
}
|
|
96
159
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
160
|
+
return parts.join('\n').trimEnd() + '\n';
|
|
161
|
+
}
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// SoulValidator — validates required sections and format compliance
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
/**
|
|
166
|
+
* Validates SoulDocument instances against the SOUL format specification.
|
|
167
|
+
*/
|
|
168
|
+
export class SoulValidator {
|
|
169
|
+
/**
|
|
170
|
+
* Validate a SoulDocument for completeness and format compliance.
|
|
171
|
+
*/
|
|
172
|
+
validate(doc) {
|
|
173
|
+
const errors = [];
|
|
174
|
+
const warnings = [];
|
|
175
|
+
// Check version
|
|
176
|
+
if (doc.soul_version !== '1.0') {
|
|
177
|
+
errors.push(`Invalid soul_version: expected "1.0", got "${doc.soul_version}"`);
|
|
178
|
+
}
|
|
179
|
+
// Check agent metadata
|
|
180
|
+
if (!doc.agent_name || doc.agent_name.trim().length === 0) {
|
|
181
|
+
errors.push('agent_name is required and must not be empty');
|
|
182
|
+
}
|
|
183
|
+
if (!doc.agent_did || doc.agent_did.trim().length === 0) {
|
|
184
|
+
warnings.push('agent_did is empty — on-chain anchoring requires a DID');
|
|
185
|
+
}
|
|
186
|
+
if (!doc.created_at) {
|
|
187
|
+
errors.push('created_at timestamp is required');
|
|
188
|
+
}
|
|
189
|
+
if (!doc.updated_at) {
|
|
190
|
+
errors.push('updated_at timestamp is required');
|
|
191
|
+
}
|
|
192
|
+
// Check all 9 required sections exist and have content
|
|
193
|
+
for (const key of REQUIRED_SECTION_KEYS) {
|
|
194
|
+
const section = doc[key];
|
|
195
|
+
if (!section) {
|
|
196
|
+
errors.push(`Missing required section: ${SOUL_SECTION_TITLES[key]} (${key})`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
if (!section.title || section.title.trim().length === 0) {
|
|
200
|
+
errors.push(`Section "${key}" has empty title`);
|
|
201
|
+
}
|
|
202
|
+
if (!section.content || section.content.trim().length === 0) {
|
|
203
|
+
warnings.push(`Section "${key}" (${section.title}) has no content`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// Validate immutability flags on key sections
|
|
207
|
+
if (doc.what_we_will_never_do && !doc.what_we_will_never_do.immutable) {
|
|
208
|
+
warnings.push('"What We Will Never Do" section should be marked immutable');
|
|
107
209
|
}
|
|
210
|
+
if (doc.constitutional_foundation && !doc.constitutional_foundation.immutable) {
|
|
211
|
+
warnings.push('"Constitutional Foundation" section should be marked immutable');
|
|
212
|
+
}
|
|
213
|
+
// Validate ERC-8004 anchor if present
|
|
214
|
+
if (doc.erc8004_anchor) {
|
|
215
|
+
const anchor = doc.erc8004_anchor;
|
|
216
|
+
if (!anchor.token_id) {
|
|
217
|
+
errors.push('ERC-8004 anchor: token_id is required');
|
|
218
|
+
}
|
|
219
|
+
if (!anchor.soul_hash) {
|
|
220
|
+
errors.push('ERC-8004 anchor: soul_hash is required');
|
|
221
|
+
}
|
|
222
|
+
if (!anchor.chain_id) {
|
|
223
|
+
errors.push('ERC-8004 anchor: chain_id is required');
|
|
224
|
+
}
|
|
225
|
+
if (!anchor.contract_address) {
|
|
226
|
+
errors.push('ERC-8004 anchor: contract_address is required');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
valid: errors.length === 0,
|
|
231
|
+
errors,
|
|
232
|
+
warnings,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Validate a SOUL.md markdown string by parsing it first.
|
|
237
|
+
*/
|
|
238
|
+
validateMarkdown(markdown) {
|
|
239
|
+
const doc = parseSoulMd(markdown);
|
|
240
|
+
return this.validate(doc);
|
|
108
241
|
}
|
|
109
|
-
// Default deny
|
|
110
|
-
return {
|
|
111
|
-
allowed: false,
|
|
112
|
-
constraintId: null,
|
|
113
|
-
enforcementLevel: 'hard',
|
|
114
|
-
reason: 'No matching allow constraint — denied by default (constitutional principle)',
|
|
115
|
-
evaluatedAt: now,
|
|
116
|
-
};
|
|
117
242
|
}
|
|
118
243
|
// ---------------------------------------------------------------------------
|
|
119
|
-
//
|
|
244
|
+
// SoulPublisher — hosts SOUL.md at /.well-known/soul.md
|
|
120
245
|
// ---------------------------------------------------------------------------
|
|
121
246
|
/**
|
|
122
|
-
*
|
|
123
|
-
* Context is a key-value map of runtime metrics.
|
|
247
|
+
* Publishes a SoulDocument as a SOUL.md endpoint.
|
|
124
248
|
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
249
|
+
* In v0.3.0 this is a lightweight utility that generates the markdown
|
|
250
|
+
* and computes the hash. Full HTTP server implementation is deferred
|
|
251
|
+
* to v0.4.0 (requires server/client packages).
|
|
128
252
|
*/
|
|
129
|
-
export
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
253
|
+
export class SoulPublisher {
|
|
254
|
+
document;
|
|
255
|
+
config;
|
|
256
|
+
constructor(doc, config) {
|
|
257
|
+
this.document = doc;
|
|
258
|
+
this.config = {
|
|
259
|
+
port: config?.port ?? 3000,
|
|
260
|
+
hostname: config?.hostname ?? '0.0.0.0',
|
|
261
|
+
path: config?.path ?? '/.well-known/soul.md',
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Generate the SOUL.md markdown content for publishing.
|
|
266
|
+
*/
|
|
267
|
+
getMarkdown() {
|
|
268
|
+
return generateSoulMd(this.document);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Compute the SHA-256 hash of the generated SOUL.md content.
|
|
272
|
+
* This is the value that should be anchored on-chain via ERC-8004.
|
|
273
|
+
*/
|
|
274
|
+
getHash() {
|
|
275
|
+
return sha256(this.getMarkdown());
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Get the full endpoint path where SOUL.md would be served.
|
|
279
|
+
*/
|
|
280
|
+
getEndpoint() {
|
|
281
|
+
return this.config.path;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get the publisher configuration.
|
|
285
|
+
*/
|
|
286
|
+
getConfig() {
|
|
287
|
+
return { ...this.config };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Build the ERC-8004 anchor metadata for this published SOUL.md.
|
|
291
|
+
*/
|
|
292
|
+
buildAnchor(params) {
|
|
293
|
+
return {
|
|
294
|
+
token_id: params.token_id,
|
|
295
|
+
soul_hash: this.getHash(),
|
|
296
|
+
chain_id: params.chain_id,
|
|
297
|
+
contract_address: params.contract_address,
|
|
298
|
+
};
|
|
164
299
|
}
|
|
165
|
-
return null;
|
|
166
300
|
}
|
|
167
301
|
// ---------------------------------------------------------------------------
|
|
168
|
-
//
|
|
302
|
+
// SoulVerifier — fetch SOUL.md + verify SHA-256 hash matches on-chain
|
|
169
303
|
// ---------------------------------------------------------------------------
|
|
170
304
|
/**
|
|
171
|
-
*
|
|
305
|
+
* Verifies that a SOUL.md content matches its on-chain ERC-8004 anchor.
|
|
306
|
+
*
|
|
307
|
+
* In v0.3.0 this provides local hash verification. On-chain fetch
|
|
308
|
+
* (via ethers/viem) is deferred to v0.4.0 server package.
|
|
172
309
|
*/
|
|
173
|
-
export
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
310
|
+
export class SoulVerifier {
|
|
311
|
+
/**
|
|
312
|
+
* Verify a SOUL.md markdown string against an expected SHA-256 hash.
|
|
313
|
+
*/
|
|
314
|
+
verify(markdown, expectedHash) {
|
|
315
|
+
const computedHash = sha256(markdown);
|
|
316
|
+
const matches = computedHash === expectedHash;
|
|
317
|
+
return {
|
|
318
|
+
verified: matches,
|
|
319
|
+
computed_hash: computedHash,
|
|
320
|
+
on_chain_hash: expectedHash,
|
|
321
|
+
reason: matches
|
|
322
|
+
? 'SOUL.md hash matches on-chain ERC-8004 anchor'
|
|
323
|
+
: `Hash mismatch: computed ${computedHash} !== on-chain ${expectedHash}`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Verify a SoulDocument against its own ERC-8004 anchor.
|
|
328
|
+
* Returns unverified if no anchor is present.
|
|
329
|
+
*/
|
|
330
|
+
verifyDocument(doc) {
|
|
331
|
+
const markdown = generateSoulMd(doc);
|
|
332
|
+
const computedHash = sha256(markdown);
|
|
333
|
+
if (!doc.erc8004_anchor) {
|
|
334
|
+
return {
|
|
335
|
+
verified: false,
|
|
336
|
+
computed_hash: computedHash,
|
|
337
|
+
on_chain_hash: null,
|
|
338
|
+
reason: 'No ERC-8004 anchor present — cannot verify on-chain',
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
return this.verify(markdown, doc.erc8004_anchor.soul_hash);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Compute the SHA-256 hash of a SOUL.md markdown string.
|
|
345
|
+
* Utility method for pre-anchoring verification.
|
|
346
|
+
*/
|
|
347
|
+
computeHash(markdown) {
|
|
348
|
+
return sha256(markdown);
|
|
349
|
+
}
|
|
181
350
|
}
|
|
182
351
|
//# sourceMappingURL=engine.js.map
|