@ixo/domain.md 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.
Files changed (70) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +6 -0
  3. package/README.md +30 -0
  4. package/assets/domain-md.schema.json +1103 -0
  5. package/assets/oracle-capsule-jcs-vectors.json +57 -0
  6. package/assets/oracle-capsule-manifest-contract.md +72 -0
  7. package/assets/oracle-capsule-source-lock.schema.json +45 -0
  8. package/assets/oracle-capsule.schema.json +265 -0
  9. package/assets/rules.json +275 -0
  10. package/assets/spec.md +1434 -0
  11. package/assets/template-manifest.schema.json +57 -0
  12. package/dist/capsule/index.d.ts +75 -0
  13. package/dist/capsule/index.js +20 -0
  14. package/dist/capsule/index.js.map +1 -0
  15. package/dist/chunk-BYBC2TSS.js +597 -0
  16. package/dist/chunk-BYBC2TSS.js.map +1 -0
  17. package/dist/chunk-G7PZJYAJ.js +31826 -0
  18. package/dist/chunk-G7PZJYAJ.js.map +1 -0
  19. package/dist/chunk-R6QZRNEX.js +164 -0
  20. package/dist/chunk-R6QZRNEX.js.map +1 -0
  21. package/dist/chunk-T3STBWXP.js +46 -0
  22. package/dist/chunk-T3STBWXP.js.map +1 -0
  23. package/dist/chunk-W7MRQP7I.js +862 -0
  24. package/dist/chunk-W7MRQP7I.js.map +1 -0
  25. package/dist/chunk-XDIWMZVF.js +1150 -0
  26. package/dist/chunk-XDIWMZVF.js.map +1 -0
  27. package/dist/cli-KcUIQEiE.d.ts +171 -0
  28. package/dist/cli.d.ts +2 -0
  29. package/dist/cli.js +400 -0
  30. package/dist/cli.js.map +1 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.js +83 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/resolver-B3mwnjJq.d.ts +96 -0
  35. package/dist/spec.d.ts +20 -0
  36. package/dist/spec.js +23 -0
  37. package/dist/spec.js.map +1 -0
  38. package/dist/templates/index.d.ts +7 -0
  39. package/dist/templates/index.js +24 -0
  40. package/dist/templates/index.js.map +1 -0
  41. package/dist/workers-Dr8B8a2O.d.ts +19 -0
  42. package/dist/workers.d.ts +3 -0
  43. package/dist/workers.js +31 -0
  44. package/dist/workers.js.map +1 -0
  45. package/package.json +97 -0
  46. package/src/capsule/canonical.ts +74 -0
  47. package/src/capsule/index.ts +10 -0
  48. package/src/capsule/json.ts +373 -0
  49. package/src/capsule/types.ts +63 -0
  50. package/src/capsule/validate.ts +551 -0
  51. package/src/cli.ts +405 -0
  52. package/src/compiled-validators.d.ts +7 -0
  53. package/src/compiled-validators.js +32512 -0
  54. package/src/constants.ts +250 -0
  55. package/src/diff.ts +111 -0
  56. package/src/export.ts +28 -0
  57. package/src/index.ts +36 -0
  58. package/src/lint.ts +42 -0
  59. package/src/parser.ts +174 -0
  60. package/src/sarif.ts +49 -0
  61. package/src/semantic.ts +1156 -0
  62. package/src/spec.ts +48 -0
  63. package/src/templates/errors.ts +3 -0
  64. package/src/templates/index.ts +11 -0
  65. package/src/templates/manifest.ts +94 -0
  66. package/src/templates/render.ts +435 -0
  67. package/src/templates/resolver.ts +200 -0
  68. package/src/templates/types.ts +98 -0
  69. package/src/types.ts +195 -0
  70. package/src/workers.ts +6 -0
@@ -0,0 +1,200 @@
1
+ import { lookup } from 'node:dns/promises';
2
+ import { lstat, readFile, realpath } from 'node:fs/promises';
3
+ import { request } from 'node:https';
4
+ import { relative, resolve } from 'node:path';
5
+ import { fileURLToPath, pathToFileURL } from 'node:url';
6
+
7
+ import ipaddr from 'ipaddr.js';
8
+
9
+ import type {
10
+ ResolvedTemplateResource,
11
+ TemplateResolver,
12
+ TemplateResolverContext,
13
+ } from './types.js';
14
+
15
+ function isPublicAddress(address: string): boolean {
16
+ const parsed = ipaddr.parse(address);
17
+ const range = parsed.range();
18
+ return range === 'unicast';
19
+ }
20
+
21
+ export function validateHttpsTarget(url: URL, addresses: string[] = []): void {
22
+ if (url.protocol !== 'https:') throw new Error('Only HTTPS template retrieval is allowed.');
23
+ if (url.username || url.password) throw new Error('Credentials in template URLs are forbidden.');
24
+ if (!url.hostname) throw new Error('Template URL hostname is required.');
25
+ if (addresses.some((address) => !isPublicAddress(address))) {
26
+ throw new Error(`Template hostname ${url.hostname} resolves to a non-public address.`);
27
+ }
28
+ }
29
+
30
+ export class FileTemplateResolver implements TemplateResolver {
31
+ supports(uri: string): boolean {
32
+ return uri.startsWith('file:') || !/^[A-Za-z][A-Za-z0-9+.-]*:/.test(uri);
33
+ }
34
+
35
+ async resolve(uri: string, context: TemplateResolverContext): Promise<ResolvedTemplateResource> {
36
+ const path = resolve(uri.startsWith('file:') ? fileURLToPath(uri) : uri);
37
+ const stat = await lstat(path);
38
+ if (!stat.isFile() || stat.isSymbolicLink())
39
+ throw new Error('Template resources must be regular files, not symlinks.');
40
+ if (stat.size > context.maxBytes)
41
+ throw new Error(`Template resource exceeds ${context.maxBytes} bytes.`);
42
+ const canonical = await realpath(path);
43
+ if (canonical !== path) throw new Error('Template resource paths must not contain symlinks.');
44
+ return { uri: pathToFileURL(canonical).href, bytes: await readFile(canonical) };
45
+ }
46
+
47
+ static async resolveRelative(manifestUri: string, logicalPath: string): Promise<string> {
48
+ const manifestPath = fileURLToPath(manifestUri);
49
+ const root = await realpath(resolve(manifestPath, '..'));
50
+ const requested = resolve(root, logicalPath);
51
+ const lexicalTraversal = relative(root, requested);
52
+ if (
53
+ lexicalTraversal.startsWith('..') ||
54
+ lexicalTraversal.startsWith('/') ||
55
+ lexicalTraversal === ''
56
+ ) {
57
+ throw new Error(`Template path escapes or aliases the manifest root: ${logicalPath}`);
58
+ }
59
+ let current = root;
60
+ for (const component of lexicalTraversal.split(/[\\/]/)) {
61
+ current = resolve(current, component);
62
+ if ((await lstat(current)).isSymbolicLink())
63
+ throw new Error(`Template path contains a symlink: ${logicalPath}`);
64
+ }
65
+ const target = await realpath(requested);
66
+ const traversal = relative(root, target);
67
+ if (traversal.startsWith('..') || traversal.startsWith('/') || traversal === '') {
68
+ throw new Error(`Template path escapes or aliases the manifest root: ${logicalPath}`);
69
+ }
70
+ return pathToFileURL(target).href;
71
+ }
72
+ }
73
+
74
+ interface HttpsResolverOptions {
75
+ maxRedirects?: number;
76
+ }
77
+
78
+ export class HttpsTemplateResolver implements TemplateResolver {
79
+ readonly maxRedirects: number;
80
+
81
+ constructor(options: HttpsResolverOptions = {}) {
82
+ this.maxRedirects = options.maxRedirects ?? 3;
83
+ }
84
+
85
+ supports(uri: string): boolean {
86
+ return uri.startsWith('https:');
87
+ }
88
+
89
+ async resolve(uri: string, context: TemplateResolverContext): Promise<ResolvedTemplateResource> {
90
+ return this.fetch(new URL(uri), context, 0);
91
+ }
92
+
93
+ private async fetch(
94
+ url: URL,
95
+ context: TemplateResolverContext,
96
+ redirects: number,
97
+ ): Promise<ResolvedTemplateResource> {
98
+ validateHttpsTarget(url);
99
+ const addresses = await lookup(url.hostname, { all: true, verbatim: true });
100
+ if (addresses.length === 0)
101
+ throw new Error(`Template hostname ${url.hostname} did not resolve.`);
102
+ validateHttpsTarget(
103
+ url,
104
+ addresses.map((entry) => entry.address),
105
+ );
106
+ const approved = addresses[0];
107
+ if (!approved) throw new Error(`Template hostname ${url.hostname} did not resolve.`);
108
+
109
+ return new Promise((resolvePromise, reject) => {
110
+ const req = request(
111
+ url,
112
+ {
113
+ headers: { accept: 'application/yaml, application/json, text/markdown, text/plain' },
114
+ lookup(_hostname, _options, callback) {
115
+ callback(null, approved.address, approved.family);
116
+ },
117
+ servername: url.hostname,
118
+ },
119
+ (response) => {
120
+ const status = response.statusCode ?? 0;
121
+ const location = response.headers.location;
122
+ if (status >= 300 && status < 400 && location) {
123
+ response.resume();
124
+ if (redirects >= this.maxRedirects) {
125
+ reject(new Error('Template retrieval exceeded the redirect limit.'));
126
+ return;
127
+ }
128
+ const redirected = new URL(location, url);
129
+ this.fetch(redirected, context, redirects + 1).then(resolvePromise, reject);
130
+ return;
131
+ }
132
+ if (status !== 200) {
133
+ response.resume();
134
+ reject(new Error(`Template retrieval returned HTTP ${status}.`));
135
+ return;
136
+ }
137
+ const chunks: Buffer[] = [];
138
+ let size = 0;
139
+ response.on('data', (chunk: Buffer) => {
140
+ size += chunk.length;
141
+ if (size > context.maxBytes) {
142
+ req.destroy(new Error(`Template resource exceeds ${context.maxBytes} bytes.`));
143
+ return;
144
+ }
145
+ chunks.push(chunk);
146
+ });
147
+ response.on('end', () => {
148
+ resolvePromise({
149
+ uri: url.href,
150
+ bytes: Buffer.concat(chunks),
151
+ ...(typeof response.headers['content-type'] === 'string'
152
+ ? { mediaType: response.headers['content-type'] }
153
+ : {}),
154
+ });
155
+ });
156
+ },
157
+ );
158
+ req.setTimeout(context.timeoutMs, () =>
159
+ req.destroy(new Error('Template retrieval timed out.')),
160
+ );
161
+ req.on('error', reject);
162
+ req.end();
163
+ });
164
+ }
165
+ }
166
+
167
+ export class IpfsTemplateResolver implements TemplateResolver {
168
+ private readonly gateway: URL;
169
+ private readonly https: HttpsTemplateResolver;
170
+
171
+ constructor(gateway: string, httpsResolver = new HttpsTemplateResolver()) {
172
+ this.gateway = new URL(gateway.endsWith('/') ? gateway : `${gateway}/`);
173
+ validateHttpsTarget(this.gateway);
174
+ this.https = httpsResolver;
175
+ }
176
+
177
+ supports(uri: string): boolean {
178
+ return uri.startsWith('ipfs:');
179
+ }
180
+
181
+ async resolve(uri: string, context: TemplateResolverContext): Promise<ResolvedTemplateResource> {
182
+ const source = new URL(uri);
183
+ const cid = source.hostname;
184
+ if (!cid) throw new Error('IPFS URI must include a CID host.');
185
+ const path = source.pathname.replace(/^\/+/, '');
186
+ const target = new URL(`ipfs/${cid}/${path}`, this.gateway);
187
+ const result = await this.https.resolve(target.href, context);
188
+ return { ...result, uri };
189
+ }
190
+ }
191
+
192
+ export function relativeTemplateUri(manifestUri: string, logicalPath: string): string {
193
+ if (manifestUri.startsWith('ipfs:')) {
194
+ const base = new URL(manifestUri);
195
+ const root = base.pathname.slice(0, base.pathname.lastIndexOf('/') + 1);
196
+ base.pathname = `${root}${logicalPath}`;
197
+ return base.href;
198
+ }
199
+ return new URL(logicalPath, manifestUri).href;
200
+ }
@@ -0,0 +1,98 @@
1
+ import type { LintReport } from '../types.js';
2
+
3
+ export type TemplateRole =
4
+ | 'domain.md'
5
+ | 'description'
6
+ | 'changelog'
7
+ | 'manifest'
8
+ | 'playbook'
9
+ | 'schema'
10
+ | 'resource'
11
+ | 'policy'
12
+ | 'supporting';
13
+
14
+ export type TemplateDerivedType =
15
+ | 'dao'
16
+ | 'organisation'
17
+ | 'project'
18
+ | 'asset'
19
+ | 'deed'
20
+ | 'investment'
21
+ | 'oracle'
22
+ | 'service'
23
+ | 'dataset'
24
+ | 'device'
25
+ | 'place'
26
+ | 'portfolio'
27
+ | 'marketplace'
28
+ | 'pod'
29
+ | 'claim_collection'
30
+ | 'custom';
31
+
32
+ export interface TemplateFileRecord {
33
+ role: TemplateRole;
34
+ path: string;
35
+ media_type: 'text/markdown' | 'application/yaml' | 'application/json' | 'text/plain';
36
+ max_bytes: number;
37
+ uri?: string;
38
+ cid?: string | null;
39
+ sha256?: string | null;
40
+ required: boolean;
41
+ }
42
+
43
+ export interface TemplateBundleRecord {
44
+ derived_type: TemplateDerivedType;
45
+ bundle_version: string;
46
+ files: TemplateFileRecord[];
47
+ }
48
+
49
+ export interface TemplateManifest {
50
+ version: '1.0.0-rc.3';
51
+ kind: 'domain.md/template-manifest';
52
+ protocol: string;
53
+ protocol_version: string;
54
+ bundles: TemplateBundleRecord[];
55
+ }
56
+
57
+ export interface ResolvedTemplateResource {
58
+ uri: string;
59
+ bytes: Uint8Array;
60
+ mediaType?: string;
61
+ }
62
+
63
+ export interface TemplateResolverContext {
64
+ maxBytes: number;
65
+ timeoutMs: number;
66
+ }
67
+
68
+ export interface TemplateResolver {
69
+ supports(uri: string): boolean;
70
+ resolve(uri: string, context: TemplateResolverContext): Promise<ResolvedTemplateResource>;
71
+ }
72
+
73
+ export interface RenderTemplateRequest {
74
+ manifestUri: string;
75
+ expectedProtocol: string;
76
+ derivedType: TemplateDerivedType;
77
+ parameters: Record<string, unknown>;
78
+ manifestSha256?: string;
79
+ manifestCid?: string;
80
+ ipfsGateway?: string;
81
+ resolvers?: TemplateResolver[];
82
+ timeoutMs?: number;
83
+ }
84
+
85
+ export interface RenderedFile {
86
+ role: string;
87
+ path: string;
88
+ bytes: Uint8Array;
89
+ sha256: string;
90
+ sourceUri: string;
91
+ }
92
+
93
+ export interface RenderTemplateResult {
94
+ files: RenderedFile[];
95
+ provenance: string;
96
+ report: LintReport;
97
+ manifest: TemplateManifest;
98
+ }
package/src/types.ts ADDED
@@ -0,0 +1,195 @@
1
+ export type Severity = 'error' | 'warning' | 'info';
2
+ export type ConformanceProfile = 'authoring_draft' | 'persisted_draft' | 'anchored' | 'runtime';
3
+ export type DomainMode = 'derived' | 'protocol' | 'standalone';
4
+ export type DomainRecord = Record<string, unknown>;
5
+
6
+ export interface SourceLocation {
7
+ line: number;
8
+ column: number;
9
+ endLine?: number;
10
+ endColumn?: number;
11
+ }
12
+
13
+ export interface Finding {
14
+ severity: Severity;
15
+ code: string;
16
+ message: string;
17
+ path: string;
18
+ location: SourceLocation;
19
+ remediation?: string;
20
+ }
21
+
22
+ export interface DomainSection {
23
+ heading: string;
24
+ content: string;
25
+ location: SourceLocation;
26
+ }
27
+
28
+ export interface DomainDocument {
29
+ frontmatter: DomainRecord;
30
+ sections: DomainSection[];
31
+ sourceName: string;
32
+ raw: string;
33
+ }
34
+
35
+ export interface ParseOptions {
36
+ sourceName?: string;
37
+ maxBytes?: number;
38
+ }
39
+
40
+ export interface ParseResult {
41
+ ok: boolean;
42
+ document?: DomainDocument;
43
+ findings: Finding[];
44
+ }
45
+
46
+ export interface LintOptions extends ParseOptions {
47
+ expectedProfile?: ConformanceProfile;
48
+ }
49
+
50
+ export interface FindingSummary {
51
+ errors: number;
52
+ warnings: number;
53
+ infos: number;
54
+ }
55
+
56
+ export interface ExternalCheck {
57
+ code: string;
58
+ category: 'integrity' | 'identity' | 'authorization' | 'time' | 'chain';
59
+ reason: string;
60
+ }
61
+
62
+ export type ConstitutionStatus =
63
+ 'not_applicable' | 'draft' | 'adopted' | 'in_force' | 'suspended' | 'superseded';
64
+
65
+ export type ConstitutionalInstrumentFunction =
66
+ 'constitutive' | 'governing' | 'amending' | 'interpretive' | 'executable';
67
+
68
+ export interface LegalEffect {
69
+ status: 'none' | 'claimed' | 'verified' | 'unknown';
70
+ jurisdiction: string | null;
71
+ authority_evidence: string[];
72
+ }
73
+
74
+ export interface ConstitutionalInstrument {
75
+ document_ref: string;
76
+ type: string;
77
+ functions: ConstitutionalInstrumentFunction[];
78
+ canonical: boolean;
79
+ effective_from: string | null;
80
+ effective_until: string | null;
81
+ }
82
+
83
+ export interface ConstitutionalGovernance {
84
+ authority_sources: string[];
85
+ decision_procedure: string | null;
86
+ amendment_procedure: string | null;
87
+ interpretation_procedure: string | null;
88
+ dispute_resolution_procedure: string | null;
89
+ suspension_procedure: string | null;
90
+ dissolution_procedure: string | null;
91
+ }
92
+
93
+ export interface ConstitutionalExecution {
94
+ mode: 'human_interpreted' | 'machine_assisted' | 'machine_executable' | 'hybrid';
95
+ implementations: string[];
96
+ conformance_tests: string[];
97
+ enforcement_points: string[];
98
+ failure_policy: 'deny' | 'pause_and_escalate';
99
+ human_review_required_for: string[];
100
+ }
101
+
102
+ export interface ConstitutionalAI {
103
+ mode: 'none' | 'context_only' | 'critique_and_revise' | 'policy_evaluate' | 'hybrid';
104
+ applies_to_agents: string[];
105
+ principles: string[];
106
+ critique_procedure: string | null;
107
+ revision_procedure: string | null;
108
+ decision_procedure: string | null;
109
+ model_profile: string | null;
110
+ conflict_policy: 'canonical_authority_prevails';
111
+ audit_record: string | null;
112
+ }
113
+
114
+ export interface ConstitutionalSubjectProfile {
115
+ subject_types: string[];
116
+ archetypes: string[];
117
+ identity: string[];
118
+ purposes: string[];
119
+ interests: string[];
120
+ values: string[];
121
+ rights: string[];
122
+ obligations: string[];
123
+ capabilities: string[];
124
+ claims: string[];
125
+ wallets: string[];
126
+ authorities: string[];
127
+ memory: string[];
128
+ evidence_policies: string[];
129
+ evaluation_policies: string[];
130
+ decision_policies: string[];
131
+ settlement_policies: string[];
132
+ governance: string[];
133
+ custodians: string[];
134
+ stewards: string[];
135
+ owners: string[];
136
+ beneficiaries: string[];
137
+ oracles: string[];
138
+ agentic_twins: string[];
139
+ }
140
+
141
+ export interface ConstitutionDeclaration {
142
+ status: ConstitutionStatus;
143
+ reason: string | null;
144
+ subject: string;
145
+ type: string;
146
+ subject_profile: ConstitutionalSubjectProfile;
147
+ legal_effect?: LegalEffect;
148
+ norms?: string[];
149
+ instruments?: ConstitutionalInstrument[];
150
+ governance?: ConstitutionalGovernance;
151
+ execution?: ConstitutionalExecution;
152
+ constitutional_ai?: ConstitutionalAI;
153
+ }
154
+
155
+ export interface LintReport {
156
+ tool: '@ixo/domain.md';
157
+ toolVersion: string;
158
+ specVersion: string;
159
+ sourceName: string;
160
+ mode: DomainMode | null;
161
+ profile: ConformanceProfile | null;
162
+ status: 'static-pass' | 'fail';
163
+ ok: boolean;
164
+ summary: FindingSummary;
165
+ findings: Finding[];
166
+ externalChecksRequired: ExternalCheck[];
167
+ document?: DomainDocument;
168
+ }
169
+
170
+ export type ChangeClassification = 'security-sensitive' | 'operational' | 'narrative';
171
+
172
+ export interface DomainChange {
173
+ path: string;
174
+ classification: ChangeClassification;
175
+ before?: unknown;
176
+ after?: unknown;
177
+ }
178
+
179
+ export interface DiffOptions {
180
+ failOn?: 'regression' | 'review';
181
+ }
182
+
183
+ export interface DiffReport {
184
+ before: FindingSummary;
185
+ after: FindingSummary;
186
+ changes: DomainChange[];
187
+ conformanceRegression: boolean;
188
+ requiresReview: boolean;
189
+ regression: boolean;
190
+ }
191
+
192
+ export interface ExportOptions {
193
+ format: 'json' | 'sarif';
194
+ sourceName?: string;
195
+ }
package/src/workers.ts ADDED
@@ -0,0 +1,6 @@
1
+ /** Workers nodejs_compat entry: no filesystem, network resolvers or dynamic compilation. */
2
+ export { lint } from './lint.js';
3
+ export { parseDomain } from './parser.js';
4
+ export * from './capsule/index.js';
5
+ export { SPEC_VERSION, PACKAGE_VERSION } from './constants.js';
6
+ export type * from './types.js';