phasegate 0.229.0 → 0.264.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 (154) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/README.ja.md +31 -1
  3. package/README.md +30 -0
  4. package/docs/ADR/031-world-model-ownership-and-corpus-lifecycle.md +155 -0
  5. package/docs/ADR/032-world-node-identity.md +198 -0
  6. package/docs/ADR/033-world-snapshot-canonicalization.md +246 -0
  7. package/docs/ADR/034-world-constraint-semantics.md +218 -0
  8. package/docs/ADR/035-world-adoption-baseline-and-waiver.md +341 -0
  9. package/docs/ADR/036-world-model-and-doc-freshness.md +169 -0
  10. package/docs/ADR/037-world-cli-and-output-contract.md +398 -0
  11. package/docs/contracts/attestation-v2.schema.json +110 -0
  12. package/docs/contracts/requirement-test-matrix.schema.json +15 -0
  13. package/docs/contracts/world-baseline.schema.json +35 -0
  14. package/docs/contracts/world-constraints.schema.json +56 -0
  15. package/docs/contracts/world-debts.schema.json +32 -0
  16. package/docs/contracts/world-obligation-report.schema.json +259 -0
  17. package/docs/contracts/world-waivers.schema.json +32 -0
  18. package/docs/guide/cli-reference.md +45 -0
  19. package/docs/guide/configuration.md +56 -0
  20. package/docs/templates/ci/aidlc-gate.yml +42 -2
  21. package/package.json +1 -1
  22. package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
  23. package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
  24. package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
  25. package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
  26. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
  27. package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
  28. package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
  29. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
  30. package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
  31. package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
  32. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
  33. package/scripts/harness/attestation/composition-root.ts +19 -4
  34. package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
  35. package/scripts/harness/attestation/index.ts +13 -2
  36. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
  37. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
  38. package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
  39. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +35 -8
  40. package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
  41. package/scripts/harness/config-foundation/domain/harness-config.ts +67 -87
  42. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +77 -88
  43. package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
  44. package/scripts/harness/config-foundation/index.ts +14 -15
  45. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
  46. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
  47. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
  48. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +83 -0
  49. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +83 -0
  50. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
  51. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
  52. package/scripts/harness/integrations/pre-commit.ts +169 -27
  53. package/scripts/harness/main.ts +276 -126
  54. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
  55. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
  56. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
  57. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
  58. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
  59. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
  60. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
  61. package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
  62. package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
  63. package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
  64. package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
  65. package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
  66. package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
  67. package/scripts/harness/traceability-model/composition-root.ts +26 -5
  68. package/scripts/harness/traceability-model/index.ts +26 -6
  69. package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
  70. package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
  71. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
  72. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
  73. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +34 -0
  74. package/scripts/harness/validator-system/composition-root.ts +21 -6
  75. package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
  76. package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
  77. package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
  78. package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
  79. package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
  80. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
  81. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +38 -16
  82. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
  83. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
  84. package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
  85. package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
  86. package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
  87. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +127 -0
  88. package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
  89. package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
  90. package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
  91. package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
  92. package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
  93. package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
  94. package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
  95. package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
  96. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +146 -0
  97. package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
  98. package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
  99. package/scripts/harness/world-model/composition-root.ts +247 -0
  100. package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
  101. package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
  102. package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
  103. package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
  104. package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
  105. package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
  106. package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
  107. package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
  108. package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
  109. package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
  110. package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
  111. package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
  112. package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
  113. package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
  114. package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
  115. package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
  116. package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
  117. package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
  118. package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
  119. package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
  120. package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
  121. package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
  122. package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
  123. package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
  124. package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
  125. package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
  126. package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
  127. package/scripts/harness/world-model/index.ts +33 -0
  128. package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
  129. package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
  130. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +323 -0
  131. package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
  132. package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
  133. package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
  134. package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
  135. package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
  136. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +332 -0
  137. package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
  138. package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
  139. package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
  140. package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
  141. package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
  142. package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
  143. package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
  144. package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
  145. package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
  146. package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
  147. package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
  148. package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
  149. package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
  150. package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
  151. package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
  152. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +109 -0
  153. package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
  154. package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
@@ -0,0 +1,210 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-287, WI-290
4
+ import type { CanonicalJsonObject } from "../services/canonical-json-serializer.js";
5
+ import type { ArtifactKind } from "../value-objects/artifact-kind.js";
6
+ import type { CorpusRole } from "../value-objects/corpus-role.js";
7
+ import type { DeclaredKey } from "../value-objects/declared-key.js";
8
+ import type { PathKey } from "../value-objects/path-key.js";
9
+ import type { Sha256Digest } from "../value-objects/sha256-digest.js";
10
+ import { type TestReferenceBinding, type TestReferenceType, WorldNodeId } from "../value-objects/world-node-id.js";
11
+
12
+ export type WorldNodeProjection =
13
+ | {
14
+ readonly type: "artifact";
15
+ readonly artifactKind: string;
16
+ readonly corpusRole: string;
17
+ readonly pathKey: string;
18
+ }
19
+ | { readonly type: "source-file"; readonly pathKey: string }
20
+ | {
21
+ readonly type: "fragment";
22
+ readonly identityMode: "explicit" | "legacy-whole-file";
23
+ readonly corpusRole: string;
24
+ readonly artifactId: string;
25
+ }
26
+ | { readonly type: "work-item"; readonly providerId: string }
27
+ | {
28
+ readonly type: "test-reference";
29
+ readonly storyId: string;
30
+ readonly acId: string;
31
+ readonly binding: TestReferenceBinding;
32
+ readonly testType: TestReferenceType;
33
+ readonly filePath: string;
34
+ readonly testName: string | null;
35
+ };
36
+
37
+ interface CommonNodeProps {
38
+ readonly digest: Sha256Digest;
39
+ readonly attributes?: CanonicalJsonObject;
40
+ }
41
+
42
+ export interface ArtifactNodeProps extends CommonNodeProps {
43
+ readonly artifactKind: ArtifactKind;
44
+ readonly corpusRole: CorpusRole;
45
+ readonly path: PathKey;
46
+ }
47
+
48
+ export interface SourceFileNodeProps extends CommonNodeProps {
49
+ readonly path: PathKey;
50
+ }
51
+
52
+ export interface ExplicitFragmentNodeProps extends CommonNodeProps {
53
+ readonly corpusRole: CorpusRole;
54
+ readonly declaredKey: DeclaredKey;
55
+ readonly artifactId: WorldNodeId;
56
+ }
57
+
58
+ export interface LegacyFragmentNodeProps extends CommonNodeProps {
59
+ readonly artifactKind: ArtifactKind;
60
+ readonly corpusRole: CorpusRole;
61
+ readonly path: PathKey;
62
+ readonly artifactId: WorldNodeId;
63
+ }
64
+
65
+ export interface WorkItemNodeProps extends CommonNodeProps {
66
+ readonly workItemId: string;
67
+ }
68
+
69
+ export interface TestReferenceNodeProps extends CommonNodeProps {
70
+ readonly storyId: string;
71
+ readonly acId: string;
72
+ readonly binding?: TestReferenceBinding;
73
+ readonly testType: TestReferenceType;
74
+ readonly filePath: PathKey;
75
+ readonly testName?: string;
76
+ }
77
+
78
+ export class InvalidWorldNodeError extends Error {
79
+ constructor(message: string) {
80
+ super(`Invalid World node: ${message}`);
81
+ this.name = "InvalidWorldNodeError";
82
+ }
83
+ }
84
+
85
+ export class WorldNode {
86
+ readonly id: WorldNodeId;
87
+ readonly contentDigest: Sha256Digest;
88
+ readonly projection: WorldNodeProjection;
89
+ readonly attributes: CanonicalJsonObject;
90
+
91
+ private constructor(
92
+ id: WorldNodeId,
93
+ contentDigest: Sha256Digest,
94
+ projection: WorldNodeProjection,
95
+ attributes?: CanonicalJsonObject,
96
+ ) {
97
+ this.id = id;
98
+ this.contentDigest = contentDigest;
99
+ this.projection = Object.freeze({ ...projection });
100
+ this.attributes = Object.freeze({ ...(attributes ?? {}) });
101
+ Object.freeze(this);
102
+ }
103
+
104
+ static artifact(props: ArtifactNodeProps): WorldNode {
105
+ const id = WorldNodeId.artifact(props.artifactKind, props.corpusRole, props.path);
106
+ return new WorldNode(
107
+ id,
108
+ props.digest,
109
+ {
110
+ type: "artifact",
111
+ artifactKind: props.artifactKind.toString(),
112
+ corpusRole: props.corpusRole.toString(),
113
+ pathKey: props.path.toString(),
114
+ },
115
+ props.attributes,
116
+ );
117
+ }
118
+
119
+ static sourceFile(props: SourceFileNodeProps): WorldNode {
120
+ return new WorldNode(
121
+ WorldNodeId.sourceFile(props.path),
122
+ props.digest,
123
+ { type: "source-file", pathKey: props.path.toString() },
124
+ props.attributes,
125
+ );
126
+ }
127
+
128
+ static explicitFragment(props: ExplicitFragmentNodeProps): WorldNode {
129
+ if (props.artifactId.nodeType !== "artifact") {
130
+ throw new InvalidWorldNodeError("explicit Fragment parent must be an Artifact");
131
+ }
132
+ const parentRole = /^pgw:v1:artifact:[^:]+:([^:]+):/.exec(props.artifactId.toString())?.[1];
133
+ if (parentRole !== props.corpusRole.toString()) {
134
+ throw new InvalidWorldNodeError("explicit Fragment corpus role must match its parent Artifact");
135
+ }
136
+ return new WorldNode(
137
+ WorldNodeId.fragment(props.corpusRole, props.declaredKey),
138
+ props.digest,
139
+ {
140
+ type: "fragment",
141
+ identityMode: "explicit",
142
+ corpusRole: props.corpusRole.toString(),
143
+ artifactId: props.artifactId.toString(),
144
+ },
145
+ props.attributes,
146
+ );
147
+ }
148
+
149
+ static legacyFragment(props: LegacyFragmentNodeProps): WorldNode {
150
+ const expectedArtifactId = WorldNodeId.artifact(props.artifactKind, props.corpusRole, props.path);
151
+ if (!props.artifactId.equals(expectedArtifactId)) {
152
+ throw new InvalidWorldNodeError("legacy Fragment parent does not match its artifact tuple");
153
+ }
154
+ return new WorldNode(
155
+ WorldNodeId.legacyFragment(props.artifactKind, props.corpusRole, props.path),
156
+ props.digest,
157
+ {
158
+ type: "fragment",
159
+ identityMode: "legacy-whole-file",
160
+ corpusRole: props.corpusRole.toString(),
161
+ artifactId: props.artifactId.toString(),
162
+ },
163
+ props.attributes,
164
+ );
165
+ }
166
+
167
+ static workItem(props: WorkItemNodeProps): WorldNode {
168
+ return new WorldNode(
169
+ WorldNodeId.workItem(props.workItemId),
170
+ props.digest,
171
+ { type: "work-item", providerId: props.workItemId },
172
+ props.attributes,
173
+ );
174
+ }
175
+
176
+ static testReference(props: TestReferenceNodeProps): WorldNode {
177
+ const binding = props.binding ?? "file";
178
+ return new WorldNode(
179
+ WorldNodeId.testReference({
180
+ storyId: props.storyId,
181
+ acId: props.acId,
182
+ binding,
183
+ testType: props.testType,
184
+ filePath: props.filePath,
185
+ testName: props.testName,
186
+ }),
187
+ props.digest,
188
+ {
189
+ type: "test-reference",
190
+ storyId: props.storyId,
191
+ acId: props.acId,
192
+ binding,
193
+ testType: props.testType,
194
+ filePath: props.filePath.toString(),
195
+ testName: props.testName ?? null,
196
+ },
197
+ props.attributes,
198
+ );
199
+ }
200
+
201
+ toCanonicalValue(): CanonicalJsonObject {
202
+ return {
203
+ attributes: this.attributes,
204
+ contentDigest: this.contentDigest.toString(),
205
+ id: this.id.toString(),
206
+ nodeType: this.id.nodeType,
207
+ projection: this.projection as CanonicalJsonObject,
208
+ };
209
+ }
210
+ }
@@ -0,0 +1,8 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-287
4
+ import type { Sha256Digest } from "../value-objects/sha256-digest.js";
5
+
6
+ export interface WorldHashingPort {
7
+ sha256(bytes: Uint8Array): Sha256Digest;
8
+ }
@@ -0,0 +1,113 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-287
4
+ export type CanonicalJsonPrimitive = null | boolean | number | string;
5
+ export type CanonicalJsonArray = readonly CanonicalJsonValue[];
6
+ export interface CanonicalJsonObject {
7
+ readonly [key: string]: CanonicalJsonValue;
8
+ }
9
+ export type CanonicalJsonValue = CanonicalJsonPrimitive | CanonicalJsonArray | CanonicalJsonObject;
10
+
11
+ export class CanonicalizationError extends Error {
12
+ constructor(message: string) {
13
+ super(`World canonicalization failed: ${message}`);
14
+ this.name = "CanonicalizationError";
15
+ }
16
+ }
17
+
18
+ const isPlainObject = (value: object): boolean => {
19
+ const prototype = Object.getPrototypeOf(value);
20
+ return prototype === Object.prototype || prototype === null;
21
+ };
22
+
23
+ const stringifyValue = (value: unknown, ancestors: WeakSet<object>, path: string): string => {
24
+ if (value === null) {
25
+ return "null";
26
+ }
27
+
28
+ switch (typeof value) {
29
+ case "boolean":
30
+ case "string":
31
+ return JSON.stringify(value);
32
+ case "number":
33
+ if (!Number.isFinite(value)) {
34
+ throw new CanonicalizationError(`${path} contains a non-finite number`);
35
+ }
36
+ return JSON.stringify(value);
37
+ case "undefined":
38
+ case "bigint":
39
+ case "function":
40
+ case "symbol":
41
+ throw new CanonicalizationError(`${path} contains unsupported ${typeof value}`);
42
+ case "object":
43
+ break;
44
+ default:
45
+ throw new CanonicalizationError(`${path} contains an unsupported value`);
46
+ }
47
+
48
+ if (ancestors.has(value)) {
49
+ throw new CanonicalizationError(`${path} contains a cycle`);
50
+ }
51
+ ancestors.add(value);
52
+
53
+ try {
54
+ if (Array.isArray(value)) {
55
+ if (Object.getOwnPropertySymbols(value).length > 0) {
56
+ throw new CanonicalizationError(`${path} contains a symbol-keyed array property`);
57
+ }
58
+ const propertyNames = Object.getOwnPropertyNames(value);
59
+ const allowedNames = new Set(["length", ...value.map((_, index) => String(index))]);
60
+ if (propertyNames.some((name) => !allowedNames.has(name))) {
61
+ throw new CanonicalizationError(`${path} contains a non-index array property`);
62
+ }
63
+
64
+ const entries: string[] = [];
65
+ for (let index = 0; index < value.length; index += 1) {
66
+ if (!(index in value)) {
67
+ throw new CanonicalizationError(`${path}[${index}] is sparse`);
68
+ }
69
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
70
+ if (!descriptor || !("value" in descriptor)) {
71
+ throw new CanonicalizationError(`${path}[${index}] is an accessor property`);
72
+ }
73
+ entries.push(stringifyValue(value[index], ancestors, `${path}[${index}]`));
74
+ }
75
+ return `[${entries.join(",")}]`;
76
+ }
77
+
78
+ if (!isPlainObject(value)) {
79
+ throw new CanonicalizationError(`${path} is not a plain object`);
80
+ }
81
+ if (Object.getOwnPropertySymbols(value).length > 0) {
82
+ throw new CanonicalizationError(`${path} contains a symbol-keyed property`);
83
+ }
84
+
85
+ const propertyNames = Object.getOwnPropertyNames(value);
86
+ const enumerableKeys = Object.keys(value);
87
+ if (propertyNames.length !== enumerableKeys.length) {
88
+ throw new CanonicalizationError(`${path} contains a non-enumerable property`);
89
+ }
90
+
91
+ const record = value as Record<string, unknown>;
92
+ const entries = enumerableKeys.sort().map((key) => {
93
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
94
+ if (!descriptor || !("value" in descriptor)) {
95
+ throw new CanonicalizationError(`${path}.${key} is an accessor property`);
96
+ }
97
+ return `${JSON.stringify(key)}:${stringifyValue(record[key], ancestors, `${path}.${key}`)}`;
98
+ });
99
+ return `{${entries.join(",")}}`;
100
+ } finally {
101
+ ancestors.delete(value);
102
+ }
103
+ };
104
+
105
+ export class CanonicalJsonSerializer {
106
+ serialize(value: unknown): Uint8Array {
107
+ return new TextEncoder().encode(this.stringify(value));
108
+ }
109
+
110
+ stringify(value: unknown): string {
111
+ return stringifyValue(value, new WeakSet<object>(), "$");
112
+ }
113
+ }