phasegate 0.229.0 → 0.254.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 (106) hide show
  1. package/CHANGELOG.md +51 -1
  2. package/README.ja.md +28 -1
  3. package/README.md +27 -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/requirement-test-matrix.schema.json +15 -0
  12. package/docs/contracts/world-baseline.schema.json +35 -0
  13. package/docs/contracts/world-constraints.schema.json +56 -0
  14. package/docs/contracts/world-debts.schema.json +32 -0
  15. package/docs/contracts/world-obligation-report.schema.json +259 -0
  16. package/docs/contracts/world-waivers.schema.json +32 -0
  17. package/docs/guide/cli-reference.md +34 -0
  18. package/package.json +1 -1
  19. package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
  20. package/scripts/harness/attestation/composition-root.ts +14 -4
  21. package/scripts/harness/attestation/index.ts +6 -1
  22. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
  23. package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
  24. package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
  25. package/scripts/harness/main.ts +107 -1
  26. package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
  27. package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
  28. package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
  29. package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
  30. package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
  31. package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
  32. package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
  33. package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
  34. package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
  35. package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
  36. package/scripts/harness/traceability-model/composition-root.ts +21 -5
  37. package/scripts/harness/traceability-model/index.ts +17 -6
  38. package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
  39. package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
  40. package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
  41. package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
  42. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +62 -0
  43. package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
  44. package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
  45. package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
  46. package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
  47. package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
  48. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +144 -0
  49. package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
  50. package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
  51. package/scripts/harness/world-model/composition-root.ts +218 -0
  52. package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
  53. package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
  54. package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
  55. package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
  56. package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
  57. package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
  58. package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
  59. package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
  60. package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
  61. package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
  62. package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
  63. package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
  64. package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
  65. package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
  66. package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
  67. package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
  68. package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
  69. package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
  70. package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
  71. package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
  72. package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
  73. package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
  74. package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
  75. package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
  76. package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
  77. package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
  78. package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
  79. package/scripts/harness/world-model/index.ts +22 -0
  80. package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
  81. package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
  82. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +288 -0
  83. package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
  84. package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
  85. package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
  86. package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
  87. package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
  88. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +330 -0
  89. package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
  90. package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
  91. package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
  92. package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
  93. package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
  94. package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
  95. package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
  96. package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
  97. package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
  98. package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
  99. package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
  100. package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
  101. package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
  102. package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
  103. package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
  104. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +105 -0
  105. package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
  106. package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
@@ -0,0 +1,218 @@
1
+ // @unit world-model
2
+ // @layer composition
3
+ // @work-item-id WI-291
4
+ // @work-item-id WI-292
5
+ // @work-item-id WI-295
6
+ // @work-item-id WI-296, WI-297
7
+
8
+ import { createAttestationModule, createSha256Capability } from "../attestation/index.js";
9
+ import { createTraceabilityModelModule } from "../traceability-model/index.js";
10
+ import {
11
+ resolveWorldCorpusConfig,
12
+ type WorldResolvedConfigInput,
13
+ } from "./application/dto/world-resolved-config-input.js";
14
+ import { BuildSnapshotUseCase } from "./application/usecases/build-snapshot-use-case.js";
15
+ import { DeriveObligationsUseCase } from "./application/usecases/derive-obligations-use-case.js";
16
+ import {
17
+ DeriveWorldObligationsUseCase,
18
+ type PolicyDatePort,
19
+ } from "./application/usecases/derive-world-obligations-use-case.js";
20
+ import { InspectWorldUseCase } from "./application/usecases/inspect-world-use-case.js";
21
+ import { PinConstraintEndpointUseCase } from "./application/usecases/pin-constraint-endpoint-use-case.js";
22
+ import { CanonicalJsonSerializer } from "./domain/services/canonical-json-serializer.js";
23
+ import { ObligationDerivationService } from "./domain/services/obligation-derivation-service.js";
24
+ import { PolicyInputsDigestDeriver } from "./domain/services/policy-inputs-digest-deriver.js";
25
+ import { SnapshotRootDeriver } from "./domain/services/snapshot-root-deriver.js";
26
+ import { ViolationFingerprintDeriver } from "./domain/services/violation-fingerprint-deriver.js";
27
+ import { AdrFactExtractor } from "./infrastructure/adapters/adr-fact-extractor.js";
28
+ import { AssembledWorldFactSource } from "./infrastructure/adapters/assembled-world-fact-source.js";
29
+ import {
30
+ AttestationFactExtractor,
31
+ AttestationVerificationHandlerAdapter,
32
+ } from "./infrastructure/adapters/attestation-fact-extractor.js";
33
+ import { AttestationSha256WorldHashingAdapter } from "./infrastructure/adapters/attestation-sha256-world-hashing-adapter.js";
34
+ import { CompositeDesignFactSource } from "./infrastructure/adapters/composite-design-fact-source.js";
35
+ import { DesignCorpusFactExtractor } from "./infrastructure/adapters/design-corpus-fact-extractor.js";
36
+ import { FileSystemObligationReportWriterAdapter } from "./infrastructure/adapters/file-system-obligation-report-writer-adapter.js";
37
+ import {
38
+ FileSystemAdoptionBaselineRepositoryAdapter,
39
+ FileSystemConstraintDeclarationRepositoryAdapter,
40
+ FileSystemSemanticDebtRepositoryAdapter,
41
+ FileSystemWaiverDeclarationRepositoryAdapter,
42
+ } from "./infrastructure/adapters/file-system-world-control-repository-adapters.js";
43
+ import { IntegrityManifestFactExtractor } from "./infrastructure/adapters/integrity-manifest-fact-extractor.js";
44
+ import { MarkdownDesignFactExtractor } from "./infrastructure/adapters/markdown-design-fact-extractor.js";
45
+ import { MatrixFactExtractor } from "./infrastructure/adapters/matrix-fact-extractor.js";
46
+ import { ProductFactExtractor } from "./infrastructure/adapters/product-fact-extractor.js";
47
+ import { ProposalFactExtractor } from "./infrastructure/adapters/proposal-fact-extractor.js";
48
+ import { SourceMetadataFactExtractor } from "./infrastructure/adapters/source-metadata-fact-extractor.js";
49
+ import { TestReferenceSourceFactExtractor } from "./infrastructure/adapters/test-reference-source-fact-extractor.js";
50
+ import { TraceabilityDesignFactAdapter } from "./infrastructure/adapters/traceability-design-fact-adapter.js";
51
+ import { TraceabilityWorldReadFacadeMerger } from "./infrastructure/adapters/traceability-world-read-facade-merger.js";
52
+ import { UnitFactExtractor } from "./infrastructure/adapters/unit-fact-extractor.js";
53
+ import { WorldDeriveCommandHandler } from "./presentation/cli/world-derive-command-handler.js";
54
+ import { WorldInspectCommandHandler } from "./presentation/cli/world-inspect-command-handler.js";
55
+ import { WorldPinCommandHandler } from "./presentation/cli/world-pin-command-handler.js";
56
+
57
+ const WORLD_SNAPSHOT_SCHEMA_VERSION = "phasegate-world-snapshot/v1";
58
+ const WORLD_EXTRACTOR_VERSION = "phasegate-world-extractor/v2";
59
+
60
+ export interface WorldModelModuleOptions {
61
+ readonly rootDir: string;
62
+ readonly resolvedConfig?: WorldResolvedConfigInput;
63
+ readonly policyDate?: PolicyDatePort;
64
+ }
65
+
66
+ export function createWorldModelModule(options: WorldModelModuleOptions) {
67
+ const config = resolveWorldCorpusConfig(options.resolvedConfig);
68
+ const hashingPort = new AttestationSha256WorldHashingAdapter(createSha256Capability());
69
+ const serializer = new CanonicalJsonSerializer();
70
+ const markdownExtractor = new MarkdownDesignFactExtractor({ hashingPort });
71
+ const traceabilityFacades = config.productScopes.map(
72
+ (scope) =>
73
+ createTraceabilityModelModule(options.rootDir, {
74
+ pathRoots: {
75
+ designDocsRoot: scope.designDocsRoot,
76
+ inceptionRoot: config.inceptionRoot,
77
+ testRoots: [`${config.sourceRoot}/__tests__`],
78
+ },
79
+ }).worldReadFacade,
80
+ );
81
+ const designCorpus = new DesignCorpusFactExtractor({
82
+ traceabilityAdapter: new TraceabilityDesignFactAdapter({
83
+ facade: new TraceabilityWorldReadFacadeMerger(traceabilityFacades),
84
+ hashingPort,
85
+ serializer,
86
+ }),
87
+ productExtractor: new CompositeDesignFactSource(
88
+ config.productScopes.map(
89
+ (scope) =>
90
+ new ProductFactExtractor({
91
+ rootDir: options.rootDir,
92
+ markdownExtractor,
93
+ productRoot: scope.productRoot,
94
+ }),
95
+ ),
96
+ ),
97
+ proposalExtractor: new ProposalFactExtractor({
98
+ rootDir: options.rootDir,
99
+ markdownExtractor,
100
+ inceptionRoot: config.inceptionRoot,
101
+ }),
102
+ adrExtractor: new AdrFactExtractor({
103
+ rootDir: options.rootDir,
104
+ markdownExtractor,
105
+ adrRoot: config.adrRoot,
106
+ }),
107
+ unitExtractor: new CompositeDesignFactSource(
108
+ config.productScopes.map(
109
+ (scope) =>
110
+ new UnitFactExtractor({
111
+ rootDir: options.rootDir,
112
+ markdownExtractor,
113
+ unitRoot: scope.unitRoot,
114
+ }),
115
+ ),
116
+ ),
117
+ });
118
+ const attestationModule = createAttestationModule(options.rootDir);
119
+ const factSource = new AssembledWorldFactSource([
120
+ designCorpus,
121
+ new SourceMetadataFactExtractor({
122
+ rootDir: options.rootDir,
123
+ hashingPort,
124
+ sourceRoot: config.sourceRoot,
125
+ }),
126
+ new TestReferenceSourceFactExtractor({
127
+ rootDir: options.rootDir,
128
+ hashingPort,
129
+ sourceRoot: config.sourceRoot,
130
+ }),
131
+ new MatrixFactExtractor({
132
+ rootDir: options.rootDir,
133
+ hashingPort,
134
+ matrixPath: config.matrixPath,
135
+ serializer,
136
+ }),
137
+ new AttestationFactExtractor({
138
+ rootDir: options.rootDir,
139
+ hashingPort,
140
+ attestationPath: config.attestationPath,
141
+ verificationFacade: new AttestationVerificationHandlerAdapter(attestationModule.verifyAttestationHandler),
142
+ serializer,
143
+ }),
144
+ new IntegrityManifestFactExtractor({
145
+ rootDir: options.rootDir,
146
+ hashingPort,
147
+ manifestPath: config.integrityManifestPath,
148
+ serializer,
149
+ }),
150
+ ]);
151
+ const corpusConfigDigest = hashingPort.sha256(serializer.serialize(config));
152
+ const buildSnapshotUseCase = new BuildSnapshotUseCase({
153
+ factSource,
154
+ rootDeriver: new SnapshotRootDeriver(serializer, hashingPort),
155
+ serializer,
156
+ schemaVersion: WORLD_SNAPSHOT_SCHEMA_VERSION,
157
+ extractorVersion: WORLD_EXTRACTOR_VERSION,
158
+ corpusConfigDigest,
159
+ });
160
+ const inspectWorldUseCase = new InspectWorldUseCase({ buildSnapshot: buildSnapshotUseCase });
161
+ const fingerprintDeriver = new ViolationFingerprintDeriver(serializer, hashingPort);
162
+ const constraintRepository = new FileSystemConstraintDeclarationRepositoryAdapter({
163
+ rootDir: options.rootDir,
164
+ });
165
+ const rootDeriver = new SnapshotRootDeriver(serializer, hashingPort);
166
+ const deriveObligationsUseCase = new DeriveObligationsUseCase({
167
+ baselineRepository: new FileSystemAdoptionBaselineRepositoryAdapter({
168
+ rootDir: options.rootDir,
169
+ }),
170
+ waiverRepository: new FileSystemWaiverDeclarationRepositoryAdapter({
171
+ rootDir: options.rootDir,
172
+ }),
173
+ semanticDebtRepository: new FileSystemSemanticDebtRepositoryAdapter({
174
+ rootDir: options.rootDir,
175
+ }),
176
+ policyInputsDigestDeriver: new PolicyInputsDigestDeriver(serializer, hashingPort),
177
+ evaluationIdDeriver: new SnapshotRootDeriver(serializer, hashingPort),
178
+ obligationDerivationService: new ObligationDerivationService(fingerprintDeriver),
179
+ serializer,
180
+ writer: new FileSystemObligationReportWriterAdapter({ rootDir: options.rootDir }),
181
+ });
182
+ const deriveWorldObligationsUseCase = new DeriveWorldObligationsUseCase({
183
+ buildSnapshot: buildSnapshotUseCase,
184
+ constraintRepository,
185
+ rootDeriver,
186
+ deriveObligations: deriveObligationsUseCase,
187
+ policyDate: options.policyDate ?? {
188
+ currentUtcDate: () => new Date().toISOString().slice(0, 10),
189
+ },
190
+ constraintConfigDigest: hashingPort.sha256(
191
+ serializer.serialize({
192
+ schemaVersion: "phasegate-world-constraint-config/v1",
193
+ constraintsPath: "phasegate.world-constraints.json",
194
+ }),
195
+ ),
196
+ evaluationConfigDigest: hashingPort.sha256(
197
+ serializer.serialize({
198
+ schemaVersion: "phasegate-world-evaluation-config/v1",
199
+ rulesetVersion: "phasegate-world-wcr/v1",
200
+ }),
201
+ ),
202
+ });
203
+ const pinConstraintEndpointUseCase = new PinConstraintEndpointUseCase(buildSnapshotUseCase, constraintRepository);
204
+ const worldInspectCommandHandler = new WorldInspectCommandHandler({ inspectWorld: inspectWorldUseCase });
205
+ const worldPinCommandHandler = new WorldPinCommandHandler(pinConstraintEndpointUseCase);
206
+ const worldDeriveCommandHandler = new WorldDeriveCommandHandler(deriveWorldObligationsUseCase);
207
+
208
+ return {
209
+ buildSnapshotUseCase,
210
+ deriveObligationsUseCase,
211
+ deriveWorldObligationsUseCase,
212
+ inspectWorldUseCase,
213
+ pinConstraintEndpointUseCase,
214
+ worldDeriveCommandHandler,
215
+ worldInspectCommandHandler,
216
+ worldPinCommandHandler,
217
+ } as const;
218
+ }
@@ -0,0 +1,148 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-293
4
+
5
+ import type { CanonicalJsonObject } from "../services/canonical-json-serializer.js";
6
+ import type { NodePin } from "../value-objects/node-pin.js";
7
+ import type { WcrRuleId } from "../value-objects/wcr-rule-id.js";
8
+ import type { WorldNodeId } from "../value-objects/world-node-id.js";
9
+
10
+ export type ConstraintFactType = "references" | "depends-on" | "refines" | "content-equals";
11
+
12
+ const FACT_TYPES: readonly ConstraintFactType[] = ["references", "depends-on", "refines", "content-equals"];
13
+ const SCHEMA_VERSION = "phasegate-world-constraints/v1";
14
+ const compareStrings = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
15
+
16
+ export interface ConstraintRecordProps {
17
+ readonly constraintId: WorldNodeId;
18
+ readonly schemaVersion: string;
19
+ readonly factType: ConstraintFactType;
20
+ readonly claimant: NodePin;
21
+ readonly premise: NodePin;
22
+ readonly applicableRuleIds: readonly WcrRuleId[];
23
+ readonly declarationArtifactId: WorldNodeId;
24
+ readonly declarationLocator: string;
25
+ }
26
+
27
+ export class InvalidConstraintRecordError extends Error {
28
+ constructor(message: string) {
29
+ super(`Invalid World constraint record: ${message}`);
30
+ this.name = "InvalidConstraintRecordError";
31
+ }
32
+ }
33
+
34
+ export class ConstraintRecord {
35
+ readonly constraintId: WorldNodeId;
36
+ readonly schemaVersion: string;
37
+ readonly factType: ConstraintFactType;
38
+ readonly claimant: NodePin;
39
+ readonly premise: NodePin;
40
+ readonly applicableRuleIds: readonly WcrRuleId[];
41
+ readonly declarationArtifactId: WorldNodeId;
42
+ readonly declarationLocator: string;
43
+
44
+ private constructor(props: ConstraintRecordProps, ruleIds: readonly WcrRuleId[]) {
45
+ this.constraintId = props.constraintId;
46
+ this.schemaVersion = props.schemaVersion;
47
+ this.factType = props.factType;
48
+ this.claimant = props.claimant;
49
+ this.premise = props.premise;
50
+ this.applicableRuleIds = ruleIds;
51
+ this.declarationArtifactId = props.declarationArtifactId;
52
+ this.declarationLocator = props.declarationLocator;
53
+ Object.freeze(this);
54
+ }
55
+
56
+ static create(props: ConstraintRecordProps): ConstraintRecord {
57
+ if (props.constraintId.nodeType !== "constraint") {
58
+ throw new InvalidConstraintRecordError("constraintId must be a constraint node ID");
59
+ }
60
+ if (props.schemaVersion !== SCHEMA_VERSION) {
61
+ throw new InvalidConstraintRecordError(`unsupported schemaVersion "${props.schemaVersion}"`);
62
+ }
63
+ if (!FACT_TYPES.includes(props.factType)) {
64
+ throw new InvalidConstraintRecordError(`unsupported factType "${props.factType}"`);
65
+ }
66
+ if (props.applicableRuleIds.length === 0) {
67
+ throw new InvalidConstraintRecordError("applicableRuleIds must be non-empty");
68
+ }
69
+ const sortedRuleIds = [...props.applicableRuleIds].sort((left, right) =>
70
+ compareStrings(left.toString(), right.toString()),
71
+ );
72
+ const uniqueRuleIds = new Set(sortedRuleIds.map((ruleId) => ruleId.toString()));
73
+ if (uniqueRuleIds.size !== sortedRuleIds.length) {
74
+ throw new InvalidConstraintRecordError("applicableRuleIds must be unique");
75
+ }
76
+ if (
77
+ props.declarationArtifactId.nodeType !== "artifact" ||
78
+ !props.declarationArtifactId.toString().startsWith("pgw:v1:artifact:external-declaration:external:")
79
+ ) {
80
+ throw new InvalidConstraintRecordError("declarationArtifactId must identify an external declaration artifact");
81
+ }
82
+ if (props.declarationLocator.trim().length === 0) {
83
+ throw new InvalidConstraintRecordError("declarationLocator must be non-empty");
84
+ }
85
+ return new ConstraintRecord(props, Object.freeze(sortedRuleIds));
86
+ }
87
+
88
+ applies(ruleId: string): boolean {
89
+ return this.applicableRuleIds.some((candidate) => candidate.toString() === ruleId);
90
+ }
91
+
92
+ toCanonicalValue(): CanonicalJsonObject {
93
+ return {
94
+ applicableRuleIds: this.applicableRuleIds.map((ruleId) => ruleId.toString()),
95
+ claimant: this.claimant.toCanonicalValue(),
96
+ constraintId: this.constraintId.toString(),
97
+ declarationArtifactId: this.declarationArtifactId.toString(),
98
+ declarationLocator: this.declarationLocator,
99
+ factType: this.factType,
100
+ premise: this.premise.toCanonicalValue(),
101
+ schemaVersion: this.schemaVersion,
102
+ };
103
+ }
104
+ }
105
+
106
+ export interface MalformedConstraintDeclarationProps {
107
+ readonly declaredConstraintId?: string;
108
+ readonly declarationArtifactId: WorldNodeId;
109
+ readonly declarationLocator: string;
110
+ readonly reasons: readonly string[];
111
+ }
112
+
113
+ export class MalformedConstraintDeclaration {
114
+ readonly declaredConstraintId: string | null;
115
+ readonly declarationArtifactId: WorldNodeId;
116
+ readonly declarationLocator: string;
117
+ readonly reasons: readonly string[];
118
+
119
+ private constructor(props: MalformedConstraintDeclarationProps) {
120
+ this.declaredConstraintId = props.declaredConstraintId ?? null;
121
+ this.declarationArtifactId = props.declarationArtifactId;
122
+ this.declarationLocator = props.declarationLocator;
123
+ this.reasons = Object.freeze([...props.reasons].sort());
124
+ Object.freeze(this);
125
+ }
126
+
127
+ static create(props: MalformedConstraintDeclarationProps): MalformedConstraintDeclaration {
128
+ if (
129
+ props.declarationArtifactId.nodeType !== "artifact" ||
130
+ !props.declarationArtifactId.toString().startsWith("pgw:v1:artifact:external-declaration:external:")
131
+ ) {
132
+ throw new InvalidConstraintRecordError("malformed declaration provenance must be an external declaration");
133
+ }
134
+ if (props.declarationLocator.trim().length === 0 || props.reasons.length === 0) {
135
+ throw new InvalidConstraintRecordError("malformed declaration requires locator and reasons");
136
+ }
137
+ return new MalformedConstraintDeclaration(props);
138
+ }
139
+
140
+ toCanonicalValue(): CanonicalJsonObject {
141
+ return {
142
+ declarationArtifactId: this.declarationArtifactId.toString(),
143
+ declarationLocator: this.declarationLocator,
144
+ declaredConstraintId: this.declaredConstraintId,
145
+ reasons: this.reasons,
146
+ };
147
+ }
148
+ }
@@ -0,0 +1,274 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-294
4
+
5
+ import type { CanonicalJsonObject } from "../services/canonical-json-serializer.js";
6
+ import type { EvaluationId } from "../value-objects/evaluation-id.js";
7
+ import type { Sha256Digest } from "../value-objects/sha256-digest.js";
8
+ import type { ViolationFingerprint } from "../value-objects/violation-fingerprint.js";
9
+ import type { WcrRuleId } from "../value-objects/wcr-rule-id.js";
10
+ import type { WorldNodeId } from "../value-objects/world-node-id.js";
11
+
12
+ const compareStrings = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
13
+ const WORK_ITEM_PATTERN = /^WI-\d+$/;
14
+ const WAIVER_ID_PATTERN = /^pgw:v1:waiver:[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
15
+ const DEBT_ID_PATTERN = /^pgw:v1:semantic-debt:[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
16
+ const UNIT_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
17
+
18
+ export class InvalidControlDeclarationDomainError extends Error {
19
+ constructor(message: string) {
20
+ super(`Invalid World control declaration: ${message}`);
21
+ this.name = "InvalidControlDeclarationDomainError";
22
+ }
23
+ }
24
+
25
+ const requireNonEmpty = (value: string, field: string): string => {
26
+ if (value.trim().length === 0) throw new InvalidControlDeclarationDomainError(`${field} must be non-empty`);
27
+ return value;
28
+ };
29
+
30
+ const requireWorkItemId = (value: string, field: string): string => {
31
+ if (!WORK_ITEM_PATTERN.test(value)) throw new InvalidControlDeclarationDomainError(`${field} must be WI-<digits>`);
32
+ return value;
33
+ };
34
+
35
+ const requireCalendarDate = (value: string): string => {
36
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
37
+ if (!match) throw new InvalidControlDeclarationDomainError("expiresOn must be YYYY-MM-DD");
38
+ const year = Number(match[1]);
39
+ const month = Number(match[2]);
40
+ const day = Number(match[3]);
41
+ const date = new Date(Date.UTC(year, month - 1, day));
42
+ if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
43
+ throw new InvalidControlDeclarationDomainError("expiresOn must be a valid calendar date");
44
+ }
45
+ return value;
46
+ };
47
+
48
+ export interface AdoptionBaselineEntryProps {
49
+ readonly violationFingerprint: ViolationFingerprint;
50
+ readonly ruleId: WcrRuleId;
51
+ readonly constraintId: WorldNodeId | null;
52
+ }
53
+
54
+ export class AdoptionBaselineEntry {
55
+ readonly violationFingerprint: ViolationFingerprint;
56
+ readonly ruleId: WcrRuleId;
57
+ readonly constraintId: WorldNodeId | null;
58
+
59
+ private constructor(props: AdoptionBaselineEntryProps) {
60
+ this.violationFingerprint = props.violationFingerprint;
61
+ this.ruleId = props.ruleId;
62
+ this.constraintId = props.constraintId;
63
+ Object.freeze(this);
64
+ }
65
+
66
+ static create(props: AdoptionBaselineEntryProps): AdoptionBaselineEntry {
67
+ if (props.ruleId.toString() === "WCR-001") {
68
+ throw new InvalidControlDeclarationDomainError("WCR-001 is not adoptable");
69
+ }
70
+ if (props.constraintId !== null && props.constraintId.nodeType !== "constraint") {
71
+ throw new InvalidControlDeclarationDomainError("baseline constraintId must identify a constraint");
72
+ }
73
+ return new AdoptionBaselineEntry(props);
74
+ }
75
+
76
+ toCanonicalValue(): CanonicalJsonObject {
77
+ return {
78
+ constraintId: this.constraintId?.toString() ?? null,
79
+ ruleId: this.ruleId.toString(),
80
+ violationFingerprint: this.violationFingerprint.toString(),
81
+ };
82
+ }
83
+ }
84
+
85
+ export interface AdoptionBaselineProps {
86
+ readonly schemaVersion: string;
87
+ readonly rulesetVersion: string;
88
+ readonly sourceEvaluationId: EvaluationId;
89
+ readonly sourceCorpusRoot: Sha256Digest;
90
+ readonly sourceConstraintRoot: Sha256Digest;
91
+ readonly adoptedByWorkItemId: string;
92
+ readonly adoptionReason: string;
93
+ readonly entries: readonly AdoptionBaselineEntry[];
94
+ }
95
+
96
+ export class AdoptionBaseline {
97
+ readonly schemaVersion: string;
98
+ readonly rulesetVersion: string;
99
+ readonly sourceEvaluationId: EvaluationId;
100
+ readonly sourceCorpusRoot: Sha256Digest;
101
+ readonly sourceConstraintRoot: Sha256Digest;
102
+ readonly adoptedByWorkItemId: string;
103
+ readonly adoptionReason: string;
104
+ readonly entries: readonly AdoptionBaselineEntry[];
105
+
106
+ private constructor(props: AdoptionBaselineProps, entries: readonly AdoptionBaselineEntry[]) {
107
+ this.schemaVersion = props.schemaVersion;
108
+ this.rulesetVersion = props.rulesetVersion;
109
+ this.sourceEvaluationId = props.sourceEvaluationId;
110
+ this.sourceCorpusRoot = props.sourceCorpusRoot;
111
+ this.sourceConstraintRoot = props.sourceConstraintRoot;
112
+ this.adoptedByWorkItemId = props.adoptedByWorkItemId;
113
+ this.adoptionReason = props.adoptionReason;
114
+ this.entries = entries;
115
+ Object.freeze(this);
116
+ }
117
+
118
+ static create(props: AdoptionBaselineProps): AdoptionBaseline {
119
+ if (props.schemaVersion !== "phasegate-world-adoption-baseline/v1") {
120
+ throw new InvalidControlDeclarationDomainError("unsupported adoption baseline schemaVersion");
121
+ }
122
+ requireNonEmpty(props.rulesetVersion, "rulesetVersion");
123
+ requireWorkItemId(props.adoptedByWorkItemId, "adoptedByWorkItemId");
124
+ requireNonEmpty(props.adoptionReason, "adoptionReason");
125
+ const entries = [...props.entries].sort((left, right) =>
126
+ compareStrings(left.violationFingerprint.toString(), right.violationFingerprint.toString()),
127
+ );
128
+ const fingerprints = entries.map((entry) => entry.violationFingerprint.toString());
129
+ if (new Set(fingerprints).size !== fingerprints.length) {
130
+ throw new InvalidControlDeclarationDomainError("baseline entries contain duplicate violationFingerprint");
131
+ }
132
+ return new AdoptionBaseline(props, Object.freeze(entries));
133
+ }
134
+
135
+ toCanonicalValue(): CanonicalJsonObject {
136
+ return {
137
+ adoptedByWorkItemId: this.adoptedByWorkItemId,
138
+ adoptionReason: this.adoptionReason,
139
+ entries: this.entries.map((entry) => entry.toCanonicalValue()),
140
+ rulesetVersion: this.rulesetVersion,
141
+ schemaVersion: this.schemaVersion,
142
+ sourceConstraintRoot: this.sourceConstraintRoot.toString(),
143
+ sourceCorpusRoot: this.sourceCorpusRoot.toString(),
144
+ sourceEvaluationId: this.sourceEvaluationId.toString(),
145
+ };
146
+ }
147
+ }
148
+
149
+ export interface WorldWaiverProps {
150
+ readonly schemaVersion: string;
151
+ readonly waiverId: string;
152
+ readonly violationFingerprint: ViolationFingerprint;
153
+ readonly reason: string;
154
+ readonly expiresOn: string;
155
+ readonly workItemId: string;
156
+ readonly renewalOf: string | null;
157
+ }
158
+
159
+ export class WorldWaiver {
160
+ readonly schemaVersion: string;
161
+ readonly waiverId: string;
162
+ readonly violationFingerprint: ViolationFingerprint;
163
+ readonly reason: string;
164
+ readonly expiresOn: string;
165
+ readonly workItemId: string;
166
+ readonly renewalOf: string | null;
167
+
168
+ private constructor(props: WorldWaiverProps) {
169
+ this.schemaVersion = props.schemaVersion;
170
+ this.waiverId = props.waiverId;
171
+ this.violationFingerprint = props.violationFingerprint;
172
+ this.reason = props.reason;
173
+ this.expiresOn = props.expiresOn;
174
+ this.workItemId = props.workItemId;
175
+ this.renewalOf = props.renewalOf;
176
+ Object.freeze(this);
177
+ }
178
+
179
+ static create(props: WorldWaiverProps): WorldWaiver {
180
+ if (props.schemaVersion !== "phasegate-world-waivers/v1") {
181
+ throw new InvalidControlDeclarationDomainError("unsupported waiver schemaVersion");
182
+ }
183
+ if (!WAIVER_ID_PATTERN.test(props.waiverId)) {
184
+ throw new InvalidControlDeclarationDomainError("waiverId is invalid");
185
+ }
186
+ if (props.renewalOf !== null && !WAIVER_ID_PATTERN.test(props.renewalOf)) {
187
+ throw new InvalidControlDeclarationDomainError("renewalOf is invalid");
188
+ }
189
+ requireNonEmpty(props.reason, "reason");
190
+ requireWorkItemId(props.workItemId, "workItemId");
191
+ requireCalendarDate(props.expiresOn);
192
+ return new WorldWaiver(props);
193
+ }
194
+
195
+ toCanonicalValue(): CanonicalJsonObject {
196
+ return {
197
+ expiresOn: this.expiresOn,
198
+ reason: this.reason,
199
+ renewalOf: this.renewalOf,
200
+ schemaVersion: this.schemaVersion,
201
+ violationFingerprint: this.violationFingerprint.toString(),
202
+ waiverId: this.waiverId,
203
+ workItemId: this.workItemId,
204
+ };
205
+ }
206
+ }
207
+
208
+ export interface SemanticDebtDeclarationProps {
209
+ readonly schemaVersion: string;
210
+ readonly debtId: string;
211
+ readonly kind: "semantic";
212
+ readonly title: string;
213
+ readonly reason: string;
214
+ readonly ownerUnit: string;
215
+ readonly introducedByWorkItemId: string;
216
+ readonly references: readonly WorldNodeId[];
217
+ }
218
+
219
+ export class SemanticDebtDeclaration {
220
+ readonly schemaVersion: string;
221
+ readonly debtId: string;
222
+ readonly kind = "semantic" as const;
223
+ readonly title: string;
224
+ readonly reason: string;
225
+ readonly ownerUnit: string;
226
+ readonly introducedByWorkItemId: string;
227
+ readonly references: readonly WorldNodeId[];
228
+
229
+ private constructor(props: SemanticDebtDeclarationProps, references: readonly WorldNodeId[]) {
230
+ this.schemaVersion = props.schemaVersion;
231
+ this.debtId = props.debtId;
232
+ this.title = props.title;
233
+ this.reason = props.reason;
234
+ this.ownerUnit = props.ownerUnit;
235
+ this.introducedByWorkItemId = props.introducedByWorkItemId;
236
+ this.references = references;
237
+ Object.freeze(this);
238
+ }
239
+
240
+ static create(props: SemanticDebtDeclarationProps): SemanticDebtDeclaration {
241
+ if (props.schemaVersion !== "phasegate-world-debts/v1") {
242
+ throw new InvalidControlDeclarationDomainError("unsupported semantic debt schemaVersion");
243
+ }
244
+ if (!DEBT_ID_PATTERN.test(props.debtId)) {
245
+ throw new InvalidControlDeclarationDomainError("debtId is invalid");
246
+ }
247
+ if (props.kind !== "semantic") throw new InvalidControlDeclarationDomainError("kind must be semantic");
248
+ if (!UNIT_ID_PATTERN.test(props.ownerUnit)) {
249
+ throw new InvalidControlDeclarationDomainError("ownerUnit is invalid");
250
+ }
251
+ requireNonEmpty(props.title, "title");
252
+ requireNonEmpty(props.reason, "reason");
253
+ requireWorkItemId(props.introducedByWorkItemId, "introducedByWorkItemId");
254
+ const references = [...props.references].sort((left, right) => compareStrings(left.toString(), right.toString()));
255
+ const values = references.map((reference) => reference.toString());
256
+ if (new Set(values).size !== values.length) {
257
+ throw new InvalidControlDeclarationDomainError("references must be unique");
258
+ }
259
+ return new SemanticDebtDeclaration(props, Object.freeze(references));
260
+ }
261
+
262
+ toCanonicalValue(): CanonicalJsonObject {
263
+ return {
264
+ debtId: this.debtId,
265
+ introducedByWorkItemId: this.introducedByWorkItemId,
266
+ kind: this.kind,
267
+ ownerUnit: this.ownerUnit,
268
+ reason: this.reason,
269
+ references: this.references.map((reference) => reference.toString()),
270
+ schemaVersion: this.schemaVersion,
271
+ title: this.title,
272
+ };
273
+ }
274
+ }
@@ -0,0 +1,41 @@
1
+ // @unit world-model
2
+ // @layer domain
3
+ // @work-item-id WI-287
4
+ import type { CanonicalJsonObject } from "../services/canonical-json-serializer.js";
5
+ import type { DeclaredKey } from "../value-objects/declared-key.js";
6
+ import type { WorldNodeId } from "../value-objects/world-node-id.js";
7
+
8
+ export interface EdgeProps {
9
+ readonly edgeType: DeclaredKey;
10
+ readonly from: WorldNodeId;
11
+ readonly to: WorldNodeId;
12
+ readonly qualifier?: CanonicalJsonObject;
13
+ }
14
+
15
+ export class Edge {
16
+ readonly edgeType: DeclaredKey;
17
+ readonly from: WorldNodeId;
18
+ readonly to: WorldNodeId;
19
+ readonly qualifier: CanonicalJsonObject;
20
+
21
+ private constructor(props: EdgeProps) {
22
+ this.edgeType = props.edgeType;
23
+ this.from = props.from;
24
+ this.to = props.to;
25
+ this.qualifier = Object.freeze({ ...(props.qualifier ?? {}) });
26
+ Object.freeze(this);
27
+ }
28
+
29
+ static create(props: EdgeProps): Edge {
30
+ return new Edge(props);
31
+ }
32
+
33
+ toCanonicalValue(): CanonicalJsonObject {
34
+ return {
35
+ edgeType: this.edgeType.toString(),
36
+ from: this.from.toString(),
37
+ qualifier: this.qualifier,
38
+ to: this.to.toString(),
39
+ };
40
+ }
41
+ }