@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
package/dist/spec.js ADDED
@@ -0,0 +1,23 @@
1
+ import {
2
+ getOracleCapsuleContract,
3
+ getOracleCapsuleJcsVectors,
4
+ getOracleCapsuleSchema,
5
+ getOracleCapsuleSourceLockSchema,
6
+ getRules,
7
+ getSchema,
8
+ getSchemaSource,
9
+ getSpecification,
10
+ getTemplateManifestSchema
11
+ } from "./chunk-T3STBWXP.js";
12
+ export {
13
+ getOracleCapsuleContract,
14
+ getOracleCapsuleJcsVectors,
15
+ getOracleCapsuleSchema,
16
+ getOracleCapsuleSourceLockSchema,
17
+ getRules,
18
+ getSchema,
19
+ getSchemaSource,
20
+ getSpecification,
21
+ getTemplateManifestSchema
22
+ };
23
+ //# sourceMappingURL=spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,7 @@
1
+ import { T as TemplateManifest } from '../resolver-B3mwnjJq.js';
2
+ export { F as FileTemplateResolver, H as HttpsTemplateResolver, I as IpfsTemplateResolver, R as RenderTemplateRequest, a as RenderTemplateResult, b as RenderedFile, c as ResolvedTemplateResource, d as TemplateBundleRecord, e as TemplateDerivedType, f as TemplateFileRecord, g as TemplateResolver, h as TemplateResolverContext, i as TemplateRole, j as TemplateValidationError, r as relativeTemplateUri, k as renderTemplateBundle, v as validateHttpsTarget } from '../resolver-B3mwnjJq.js';
3
+ import '../cli-KcUIQEiE.js';
4
+
5
+ declare function parseTemplateManifest(bytes: Uint8Array): TemplateManifest;
6
+
7
+ export { TemplateManifest, parseTemplateManifest };
@@ -0,0 +1,24 @@
1
+ import {
2
+ FileTemplateResolver,
3
+ HttpsTemplateResolver,
4
+ IpfsTemplateResolver,
5
+ TemplateValidationError,
6
+ parseTemplateManifest,
7
+ relativeTemplateUri,
8
+ renderTemplateBundle,
9
+ validateHttpsTarget
10
+ } from "../chunk-BYBC2TSS.js";
11
+ import "../chunk-T3STBWXP.js";
12
+ import "../chunk-XDIWMZVF.js";
13
+ import "../chunk-G7PZJYAJ.js";
14
+ export {
15
+ FileTemplateResolver,
16
+ HttpsTemplateResolver,
17
+ IpfsTemplateResolver,
18
+ TemplateValidationError,
19
+ parseTemplateManifest,
20
+ relativeTemplateUri,
21
+ renderTemplateBundle,
22
+ validateHttpsTarget
23
+ };
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,19 @@
1
+ import { r as LintOptions, s as LintReport, P as ParseOptions, t as ParseResult } from './cli-KcUIQEiE.js';
2
+ import './capsule/index.js';
3
+
4
+ declare function lint(content: string, options?: LintOptions): LintReport;
5
+
6
+ declare function parseDomain(content: string, options?: ParseOptions): ParseResult;
7
+
8
+ /** The published package version; semantic-release sets it at release time. */
9
+ declare const PACKAGE_VERSION: string;
10
+ declare const SPEC_VERSION = "1.0.0-rc.3";
11
+ declare const CONSTITUTION_STATUSES: readonly ["not_applicable", "draft", "adopted", "in_force", "suspended", "superseded"];
12
+ declare const CONSTITUTION_TYPES: readonly ["con:OperationalConstitution", "con:PersonalConstitution", "con:AssetConstitution", "con:FinancialSubjectConstitution", "con:WorkConstitution", "con:ServiceConstitution", "con:OracleConstitution", "con:InformationSubjectConstitution", "con:PlaceConstitution", "con:BiologicalSubjectConstitution", "con:NetworkConstitution", "con:StateConstitution", "con:InternationalOrganizationConstitution", "con:OrganizationalConstitution", "con:CorporateConstitution", "con:TrustConstitution", "con:CooperativeConstitution", "con:PartnershipConstitution", "con:FoundationConstitution", "con:PublicBodyConstitution", "con:ProjectConstitution", "con:ProtocolConstitution", "con:DAOConstitution", "con:AgenticConstitution", "con:SchemeConstitution"];
13
+ declare const CONSTITUTIONAL_SUBJECT_TYPES: readonly ["con:Person", "con:Organization", "con:Asset", "con:Commodity", "con:FinancialInstrument", "con:PropertyRight", "con:Agreement", "con:Deed", "con:Project", "con:Work", "con:Protocol", "con:Service", "con:Oracle", "con:Claim", "con:Credential", "con:Evidence", "con:Decision", "con:Outcome", "con:Place", "con:BiologicalEntity", "con:Network", "con:AgenticTwin"];
14
+ declare const CONSTITUTIONAL_ARCHETYPES: readonly ["con:Stewarded", "con:Owned", "con:Managed", "con:Governed", "con:Regulated", "con:Verified", "con:Settled"];
15
+ declare const CONSTITUTION_INSTRUMENT_FUNCTIONS: readonly ["constitutive", "governing", "amending", "interpretive", "executable"];
16
+ declare const CONSTITUTION_EXECUTION_MODES: readonly ["human_interpreted", "machine_assisted", "machine_executable", "hybrid"];
17
+ declare const CONSTITUTION_AI_MODES: readonly ["none", "context_only", "critique_and_revise", "policy_evaluate", "hybrid"];
18
+
19
+ export { CONSTITUTIONAL_ARCHETYPES as C, PACKAGE_VERSION as P, SPEC_VERSION as S, CONSTITUTIONAL_SUBJECT_TYPES as a, CONSTITUTION_AI_MODES as b, CONSTITUTION_EXECUTION_MODES as c, CONSTITUTION_INSTRUMENT_FUNCTIONS as d, CONSTITUTION_STATUSES as e, CONSTITUTION_TYPES as f, lint as l, parseDomain as p };
@@ -0,0 +1,3 @@
1
+ export { P as PACKAGE_VERSION, S as SPEC_VERSION, l as lint, p as parseDomain } from './workers-Dr8B8a2O.js';
2
+ export { CapsuleCanonicalizationError, CapsuleExternalCheck, CapsuleExternalCheckCategory, CapsuleValidationOptions, CapsuleValidationReport, JcsGoldenVector, JsonPrimitive, JsonValue, StrictJsonDocument, StrictJsonOptions, StrictJsonResult, canonicalizeCapsuleJson, capsuleJcsSha256, oracleCapsuleReleaseDigest, oracleCapsuleReleaseProjection, parseCapsuleJson, validateOracleCapsule } from './capsule/index.js';
3
+ export { C as ChangeClassification, c as ConformanceProfile, d as ConstitutionDeclaration, e as ConstitutionStatus, f as ConstitutionalAI, g as ConstitutionalExecution, h as ConstitutionalGovernance, i as ConstitutionalInstrument, j as ConstitutionalInstrumentFunction, k as ConstitutionalSubjectProfile, a as DiffOptions, b as DiffReport, l as DomainChange, m as DomainDocument, n as DomainMode, D as DomainRecord, o as DomainSection, E as ExportOptions, p as ExternalCheck, F as Finding, q as FindingSummary, L as LegalEffect, r as LintOptions, s as LintReport, P as ParseOptions, t as ParseResult, S as Severity, u as SourceLocation } from './cli-KcUIQEiE.js';
@@ -0,0 +1,31 @@
1
+ import {
2
+ CapsuleCanonicalizationError,
3
+ canonicalizeCapsuleJson,
4
+ capsuleJcsSha256,
5
+ oracleCapsuleReleaseDigest,
6
+ oracleCapsuleReleaseProjection,
7
+ parseCapsuleJson,
8
+ validateOracleCapsule
9
+ } from "./chunk-W7MRQP7I.js";
10
+ import {
11
+ lint,
12
+ parseDomain
13
+ } from "./chunk-XDIWMZVF.js";
14
+ import {
15
+ PACKAGE_VERSION,
16
+ SPEC_VERSION
17
+ } from "./chunk-G7PZJYAJ.js";
18
+ export {
19
+ CapsuleCanonicalizationError,
20
+ PACKAGE_VERSION,
21
+ SPEC_VERSION,
22
+ canonicalizeCapsuleJson,
23
+ capsuleJcsSha256,
24
+ lint,
25
+ oracleCapsuleReleaseDigest,
26
+ oracleCapsuleReleaseProjection,
27
+ parseCapsuleJson,
28
+ parseDomain,
29
+ validateOracleCapsule
30
+ };
31
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "@ixo/domain.md",
3
+ "version": "0.3.0",
4
+ "description": "Authority-aware domain.md specification, validator, authoring library, and CLI",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "private": false,
8
+ "sideEffects": false,
9
+ "engines": {
10
+ "node": ">=20.10.0"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ixoworld/domain.md.git",
15
+ "directory": "packages/cli"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/ixoworld/domain.md/issues"
19
+ },
20
+ "homepage": "https://github.com/ixoworld/domain.md#readme",
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "provenance": true
24
+ },
25
+ "keywords": [
26
+ "domain.md",
27
+ "ixo",
28
+ "ai-agents",
29
+ "did",
30
+ "governance",
31
+ "validation",
32
+ "oracle-capsule",
33
+ "rfc8785"
34
+ ],
35
+ "bin": {
36
+ "domain.md": "./dist/cli.js",
37
+ "domainmd": "./dist/cli.js"
38
+ },
39
+ "main": "./dist/index.js",
40
+ "types": "./dist/index.d.ts",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/index.js"
45
+ },
46
+ "./spec": {
47
+ "types": "./dist/spec.d.ts",
48
+ "import": "./dist/spec.js"
49
+ },
50
+ "./templates": {
51
+ "types": "./dist/templates/index.d.ts",
52
+ "import": "./dist/templates/index.js"
53
+ },
54
+ "./capsule": {
55
+ "types": "./dist/capsule/index.d.ts",
56
+ "import": "./dist/capsule/index.js"
57
+ },
58
+ "./workers": {
59
+ "types": "./src/workers.ts",
60
+ "import": "./src/workers.ts"
61
+ }
62
+ },
63
+ "files": [
64
+ "assets",
65
+ "dist",
66
+ "README.md",
67
+ "LICENSE",
68
+ "NOTICE",
69
+ "src"
70
+ ],
71
+ "scripts": {
72
+ "build": "tsup",
73
+ "test": "vitest run",
74
+ "test:coverage": "vitest run --coverage",
75
+ "typecheck": "tsc --noEmit",
76
+ "lint": "eslint src test tsup.config.ts vitest.config.ts",
77
+ "package:check": "npm pack --dry-run --json"
78
+ },
79
+ "dependencies": {
80
+ "ajv": "8.20.0",
81
+ "ajv-formats": "3.0.1",
82
+ "citty": "0.2.2",
83
+ "ipaddr.js": "2.4.0",
84
+ "multiformats": "14.0.4",
85
+ "remark-frontmatter": "5.0.0",
86
+ "remark-parse": "11.0.0",
87
+ "unified": "11.0.5",
88
+ "unist-util-visit": "5.1.0",
89
+ "yaml": "2.9.0"
90
+ },
91
+ "devDependencies": {
92
+ "@types/mdast": "4.0.4",
93
+ "@vitest/coverage-v8": "4.1.10",
94
+ "tsup": "8.5.1",
95
+ "vitest": "4.1.10"
96
+ }
97
+ }
@@ -0,0 +1,74 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ import type { JsonValue } from './types.js';
4
+
5
+ export class CapsuleCanonicalizationError extends Error {
6
+ readonly code = 'capsule-canonicalization';
7
+ }
8
+
9
+ function canonical(value: unknown, path: string): string {
10
+ if (value === null) return 'null';
11
+ if (typeof value === 'boolean') return value ? 'true' : 'false';
12
+ if (typeof value === 'string') {
13
+ for (let index = 0; index < value.length; index += 1) {
14
+ const code = value.charCodeAt(index);
15
+ if (code >= 0xd800 && code <= 0xdbff) {
16
+ const low = value.charCodeAt(index + 1);
17
+ if (!Number.isInteger(low) || low < 0xdc00 || low > 0xdfff)
18
+ throw new CapsuleCanonicalizationError(`Lone high surrogate at ${path}.`);
19
+ index += 1;
20
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
21
+ throw new CapsuleCanonicalizationError(`Lone low surrogate at ${path}.`);
22
+ }
23
+ }
24
+ return JSON.stringify(value);
25
+ }
26
+ if (typeof value === 'number') {
27
+ if (!Number.isFinite(value))
28
+ throw new CapsuleCanonicalizationError(`Non-finite number at ${path}.`);
29
+ if (Number.isInteger(value) && !Number.isSafeInteger(value))
30
+ throw new CapsuleCanonicalizationError(`Unsafe integer at ${path}.`);
31
+ return JSON.stringify(value);
32
+ }
33
+ if (Array.isArray(value)) {
34
+ return `[${value.map((child, index) => canonical(child, `${path}/${index}`)).join(',')}]`;
35
+ }
36
+ if (typeof value === 'object' && value !== undefined) {
37
+ const record = value as Record<string, unknown>;
38
+ return `{${Object.keys(record)
39
+ .sort()
40
+ .map((key) => `${canonical(key, path)}:${canonical(record[key], `${path}/${key}`)}`)
41
+ .join(',')}}`;
42
+ }
43
+ throw new CapsuleCanonicalizationError(`Unsupported JSON value at ${path}.`);
44
+ }
45
+
46
+ /** RFC 8785 JCS serialization for already parsed I-JSON-compatible values. */
47
+ export function canonicalizeCapsuleJson(value: JsonValue): string {
48
+ return canonical(value, '/');
49
+ }
50
+
51
+ export function capsuleJcsSha256(value: JsonValue): string {
52
+ return createHash('sha256').update(canonicalizeCapsuleJson(value), 'utf8').digest('hex');
53
+ }
54
+
55
+ /**
56
+ * Release digests omit only metadata.release_digest, the one self-referential
57
+ * field. Exact fetched-byte SHA/CID verification remains a separate operation.
58
+ */
59
+ export function oracleCapsuleReleaseProjection(value: JsonValue): JsonValue {
60
+ if (typeof value !== 'object' || value === null || Array.isArray(value))
61
+ throw new CapsuleCanonicalizationError('A capsule release must be a JSON object.');
62
+ const root = structuredClone(value) as Record<string, JsonValue>;
63
+ const metadata = root.metadata;
64
+ if (typeof metadata !== 'object' || metadata === null || Array.isArray(metadata))
65
+ throw new CapsuleCanonicalizationError('A capsule release must contain metadata.');
66
+ const projectedMetadata = { ...metadata } as Record<string, JsonValue>;
67
+ delete projectedMetadata.release_digest;
68
+ root.metadata = projectedMetadata;
69
+ return root;
70
+ }
71
+
72
+ export function oracleCapsuleReleaseDigest(value: JsonValue): string {
73
+ return capsuleJcsSha256(oracleCapsuleReleaseProjection(value));
74
+ }
@@ -0,0 +1,10 @@
1
+ export {
2
+ canonicalizeCapsuleJson,
3
+ CapsuleCanonicalizationError,
4
+ capsuleJcsSha256,
5
+ oracleCapsuleReleaseDigest,
6
+ oracleCapsuleReleaseProjection,
7
+ } from './canonical.js';
8
+ export { parseCapsuleJson } from './json.js';
9
+ export { validateOracleCapsule } from './validate.js';
10
+ export type * from './types.js';
@@ -0,0 +1,373 @@
1
+ import { ORACLE_CAPSULE_LIMITS } from '../constants.js';
2
+ import type { Finding, SourceLocation } from '../types.js';
3
+ import type {
4
+ JsonValue,
5
+ StrictJsonDocument,
6
+ StrictJsonOptions,
7
+ StrictJsonResult,
8
+ } from './types.js';
9
+
10
+ class JsonFailure extends Error {
11
+ constructor(
12
+ readonly code: string,
13
+ message: string,
14
+ readonly path: string,
15
+ readonly offset: number,
16
+ ) {
17
+ super(message);
18
+ }
19
+ }
20
+
21
+ function pointerSegment(value: string): string {
22
+ return value.replace(/~/g, '~0').replace(/\//g, '~1');
23
+ }
24
+
25
+ function decodeInput(input: string | Uint8Array): string {
26
+ if (typeof input === 'string') return input;
27
+ if (input[0] === 0xef && input[1] === 0xbb && input[2] === 0xbf)
28
+ throw new JsonFailure('capsule-encoding', 'UTF-8 byte-order marks are forbidden.', '/', 0);
29
+ try {
30
+ return new TextDecoder('utf-8', { fatal: true, ignoreBOM: false }).decode(input);
31
+ } catch {
32
+ throw new JsonFailure('capsule-encoding', 'Input must be valid UTF-8.', '/', 0);
33
+ }
34
+ }
35
+
36
+ class StrictJsonParser {
37
+ private offset = 0;
38
+ private nodes = 0;
39
+ private readonly lineStarts = [0];
40
+ readonly locations = new Map<string, SourceLocation>();
41
+
42
+ constructor(
43
+ private readonly source: string,
44
+ private readonly options: Required<
45
+ Pick<StrictJsonOptions, 'maxDepth' | 'maxNodes' | 'maxScalarLength'>
46
+ >,
47
+ ) {
48
+ for (let index = 0; index < source.length; index += 1) {
49
+ if (source.charCodeAt(index) === 0x0a) this.lineStarts.push(index + 1);
50
+ }
51
+ }
52
+
53
+ parse(): JsonValue {
54
+ this.skipWhitespace();
55
+ const value = this.parseValue('/', 0);
56
+ this.skipWhitespace();
57
+ if (this.offset !== this.source.length)
58
+ this.fail('capsule-json-syntax', 'Unexpected trailing content.', '/');
59
+ return value;
60
+ }
61
+
62
+ private location(offset: number): SourceLocation {
63
+ let low = 0;
64
+ let high = this.lineStarts.length;
65
+ while (low + 1 < high) {
66
+ const middle = Math.floor((low + high) / 2);
67
+ if ((this.lineStarts[middle] ?? 0) <= offset) low = middle;
68
+ else high = middle;
69
+ }
70
+ return { line: low + 1, column: offset - (this.lineStarts[low] ?? 0) + 1 };
71
+ }
72
+
73
+ failureLocation(offset: number): SourceLocation {
74
+ return this.location(offset);
75
+ }
76
+
77
+ private fail(code: string, message: string, path: string, offset = this.offset): never {
78
+ throw new JsonFailure(code, message, path, offset);
79
+ }
80
+
81
+ private node(path: string, depth: number): void {
82
+ if (depth > this.options.maxDepth)
83
+ this.fail('capsule-limit', `JSON exceeds maximum depth ${this.options.maxDepth}.`, path);
84
+ this.nodes += 1;
85
+ if (this.nodes > this.options.maxNodes)
86
+ this.fail('capsule-limit', `JSON exceeds maximum node count ${this.options.maxNodes}.`, path);
87
+ this.locations.set(path, this.location(this.offset));
88
+ }
89
+
90
+ private skipWhitespace(): void {
91
+ while (/[\t\n\r ]/.test(this.source[this.offset] ?? '')) this.offset += 1;
92
+ }
93
+
94
+ private parseValue(path: string, depth: number): JsonValue {
95
+ this.node(path, depth);
96
+ const character = this.source[this.offset];
97
+ if (character === '{') return this.parseObject(path, depth);
98
+ if (character === '[') return this.parseArray(path, depth);
99
+ if (character === '"') return this.parseString(path);
100
+ if (character === '-' || (character !== undefined && /[0-9]/.test(character)))
101
+ return this.parseNumber(path);
102
+ if (this.source.startsWith('true', this.offset)) {
103
+ this.offset += 4;
104
+ return true;
105
+ }
106
+ if (this.source.startsWith('false', this.offset)) {
107
+ this.offset += 5;
108
+ return false;
109
+ }
110
+ if (this.source.startsWith('null', this.offset)) {
111
+ this.offset += 4;
112
+ return null;
113
+ }
114
+ return this.fail('capsule-json-syntax', 'Expected a JSON value.', path);
115
+ }
116
+
117
+ private parseObject(path: string, depth: number): { [key: string]: JsonValue } {
118
+ const result: { [key: string]: JsonValue } = {};
119
+ const seen = new Set<string>();
120
+ this.offset += 1;
121
+ this.skipWhitespace();
122
+ if (this.source[this.offset] === '}') {
123
+ this.offset += 1;
124
+ return result;
125
+ }
126
+ while (true) {
127
+ if (this.source[this.offset] !== '"')
128
+ this.fail('capsule-json-syntax', 'Object names must be JSON strings.', path);
129
+ const keyOffset = this.offset;
130
+ const key = this.parseString(path);
131
+ const childPath = path === '/' ? `/${pointerSegment(key)}` : `${path}/${pointerSegment(key)}`;
132
+ if (seen.has(key))
133
+ this.fail(
134
+ 'capsule-duplicate-key',
135
+ `Duplicate object name ${JSON.stringify(key)}.`,
136
+ childPath,
137
+ keyOffset,
138
+ );
139
+ seen.add(key);
140
+ this.skipWhitespace();
141
+ if (this.source[this.offset] !== ':')
142
+ this.fail('capsule-json-syntax', 'Expected a colon after the object name.', childPath);
143
+ this.offset += 1;
144
+ this.skipWhitespace();
145
+ result[key] = this.parseValue(childPath, depth + 1);
146
+ this.skipWhitespace();
147
+ const separator = this.source[this.offset];
148
+ if (separator === '}') {
149
+ this.offset += 1;
150
+ return result;
151
+ }
152
+ if (separator !== ',')
153
+ this.fail('capsule-json-syntax', 'Expected a comma or closing brace.', path);
154
+ this.offset += 1;
155
+ this.skipWhitespace();
156
+ }
157
+ }
158
+
159
+ private parseArray(path: string, depth: number): JsonValue[] {
160
+ const result: JsonValue[] = [];
161
+ this.offset += 1;
162
+ this.skipWhitespace();
163
+ if (this.source[this.offset] === ']') {
164
+ this.offset += 1;
165
+ return result;
166
+ }
167
+ while (true) {
168
+ const childPath = path === '/' ? `/${result.length}` : `${path}/${result.length}`;
169
+ result.push(this.parseValue(childPath, depth + 1));
170
+ this.skipWhitespace();
171
+ const separator = this.source[this.offset];
172
+ if (separator === ']') {
173
+ this.offset += 1;
174
+ return result;
175
+ }
176
+ if (separator !== ',')
177
+ this.fail('capsule-json-syntax', 'Expected a comma or closing bracket.', path);
178
+ this.offset += 1;
179
+ this.skipWhitespace();
180
+ }
181
+ }
182
+
183
+ private parseString(path: string): string {
184
+ this.offset += 1;
185
+ let value = '';
186
+ while (this.offset < this.source.length) {
187
+ const code = this.source.charCodeAt(this.offset);
188
+ if (code === 0x22) {
189
+ this.offset += 1;
190
+ if (value.length > this.options.maxScalarLength)
191
+ this.fail(
192
+ 'capsule-limit',
193
+ `String exceeds maximum length ${this.options.maxScalarLength}.`,
194
+ path,
195
+ );
196
+ return value;
197
+ }
198
+ if (code < 0x20)
199
+ this.fail('capsule-json-syntax', 'Unescaped control character in string.', path);
200
+ if (code === 0x5c) {
201
+ value += this.parseEscape(path);
202
+ continue;
203
+ }
204
+ if (code >= 0xd800 && code <= 0xdbff) {
205
+ const low = this.source.charCodeAt(this.offset + 1);
206
+ if (!Number.isInteger(low) || low < 0xdc00 || low > 0xdfff)
207
+ this.fail('capsule-unicode', 'Lone high surrogate is not I-JSON compatible.', path);
208
+ value += this.source.slice(this.offset, this.offset + 2);
209
+ this.offset += 2;
210
+ continue;
211
+ }
212
+ if (code >= 0xdc00 && code <= 0xdfff)
213
+ this.fail('capsule-unicode', 'Lone low surrogate is not I-JSON compatible.', path);
214
+ value += this.source[this.offset] ?? '';
215
+ this.offset += 1;
216
+ }
217
+ return this.fail('capsule-json-syntax', 'Unterminated JSON string.', path);
218
+ }
219
+
220
+ private parseEscape(path: string): string {
221
+ this.offset += 1;
222
+ const escape = this.source[this.offset];
223
+ const simple: Record<string, string> = {
224
+ '"': '"',
225
+ '\\': '\\',
226
+ '/': '/',
227
+ b: '\b',
228
+ f: '\f',
229
+ n: '\n',
230
+ r: '\r',
231
+ t: '\t',
232
+ };
233
+ if (escape !== undefined && escape in simple) {
234
+ this.offset += 1;
235
+ return simple[escape] ?? '';
236
+ }
237
+ if (escape !== 'u') return this.fail('capsule-json-syntax', 'Invalid JSON escape.', path);
238
+ const first = this.readUnicodeEscape(path);
239
+ if (first >= 0xd800 && first <= 0xdbff) {
240
+ if (this.source.slice(this.offset, this.offset + 2) !== '\\u')
241
+ return this.fail(
242
+ 'capsule-unicode',
243
+ 'Escaped high surrogate must be followed by a low surrogate.',
244
+ path,
245
+ );
246
+ const second = this.readUnicodeEscape(path);
247
+ if (second < 0xdc00 || second > 0xdfff)
248
+ return this.fail(
249
+ 'capsule-unicode',
250
+ 'Escaped high surrogate must be followed by a low surrogate.',
251
+ path,
252
+ );
253
+ return String.fromCharCode(first, second);
254
+ }
255
+ if (first >= 0xdc00 && first <= 0xdfff)
256
+ return this.fail('capsule-unicode', 'Lone escaped low surrogate is forbidden.', path);
257
+ return String.fromCharCode(first);
258
+ }
259
+
260
+ private readUnicodeEscape(path: string): number {
261
+ const digitsAt =
262
+ this.source[this.offset] === 'u'
263
+ ? this.offset + 1
264
+ : this.source.slice(this.offset, this.offset + 2) === '\\u'
265
+ ? this.offset + 2
266
+ : -1;
267
+ if (digitsAt < 0) return this.fail('capsule-json-syntax', 'Invalid Unicode escape.', path);
268
+ const hex = this.source.slice(digitsAt, digitsAt + 4);
269
+ if (!/^[0-9a-fA-F]{4}$/.test(hex))
270
+ return this.fail(
271
+ 'capsule-json-syntax',
272
+ 'Unicode escapes require four hexadecimal digits.',
273
+ path,
274
+ );
275
+ this.offset = digitsAt + 4;
276
+ return Number.parseInt(hex, 16);
277
+ }
278
+
279
+ private parseNumber(path: string): number {
280
+ const remaining = this.source.slice(this.offset);
281
+ const match = /^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?/.exec(remaining);
282
+ if (!match) return this.fail('capsule-json-syntax', 'Invalid JSON number.', path);
283
+ const token = match[0];
284
+ const following = remaining[token.length];
285
+ if (following !== undefined && !/[\t\n\r ,\]}]/.test(following))
286
+ return this.fail('capsule-json-syntax', 'Invalid character after JSON number.', path);
287
+ this.offset += token.length;
288
+ const value = Number(token);
289
+ if (!Number.isFinite(value))
290
+ return this.fail('capsule-number', 'Non-finite numbers are not I-JSON compatible.', path);
291
+ if (Number.isInteger(value) && !Number.isSafeInteger(value))
292
+ return this.fail(
293
+ 'capsule-number',
294
+ 'Integers must be within the interoperable safe-integer range.',
295
+ path,
296
+ );
297
+ return value;
298
+ }
299
+ }
300
+
301
+ function finding(error: JsonFailure, location: SourceLocation): Finding {
302
+ return {
303
+ severity: 'error',
304
+ code: error.code,
305
+ message: error.message,
306
+ path: error.path,
307
+ location,
308
+ };
309
+ }
310
+
311
+ export function parseCapsuleJson(
312
+ input: string | Uint8Array,
313
+ options: StrictJsonOptions = {},
314
+ ): StrictJsonResult {
315
+ const sourceName = options.sourceName ?? '<memory>';
316
+ let source: string;
317
+ try {
318
+ source = decodeInput(input);
319
+ } catch (error) {
320
+ const failure = error as JsonFailure;
321
+ return { ok: false, findings: [finding(failure, { line: 1, column: 1 })] };
322
+ }
323
+ const maxBytes = options.maxBytes ?? ORACLE_CAPSULE_LIMITS.maxBytes;
324
+ if (Buffer.byteLength(source, 'utf8') > maxBytes) {
325
+ return {
326
+ ok: false,
327
+ findings: [
328
+ {
329
+ severity: 'error',
330
+ code: 'capsule-file-too-large',
331
+ message: `Input exceeds ${maxBytes} bytes.`,
332
+ path: '/',
333
+ location: { line: 1, column: 1 },
334
+ },
335
+ ],
336
+ };
337
+ }
338
+ if (source.charCodeAt(0) === 0xfeff) {
339
+ return {
340
+ ok: false,
341
+ findings: [
342
+ {
343
+ severity: 'error',
344
+ code: 'capsule-encoding',
345
+ message: 'UTF-8 byte-order marks are forbidden.',
346
+ path: '/',
347
+ location: { line: 1, column: 1 },
348
+ },
349
+ ],
350
+ };
351
+ }
352
+ const parser = new StrictJsonParser(source, {
353
+ maxDepth: options.maxDepth ?? ORACLE_CAPSULE_LIMITS.maxDepth,
354
+ maxNodes: options.maxNodes ?? ORACLE_CAPSULE_LIMITS.maxNodes,
355
+ maxScalarLength: options.maxScalarLength ?? ORACLE_CAPSULE_LIMITS.maxScalarLength,
356
+ });
357
+ try {
358
+ const value = parser.parse();
359
+ const document: StrictJsonDocument = {
360
+ value,
361
+ raw: source,
362
+ sourceName,
363
+ locations: parser.locations,
364
+ };
365
+ return { ok: true, document, findings: [] };
366
+ } catch (error) {
367
+ const failure =
368
+ error instanceof JsonFailure
369
+ ? error
370
+ : new JsonFailure('capsule-json-syntax', String(error), '/', 0);
371
+ return { ok: false, findings: [finding(failure, parser.failureLocation(failure.offset))] };
372
+ }
373
+ }