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.
- package/CHANGELOG.md +51 -1
- package/README.ja.md +28 -1
- package/README.md +27 -0
- package/docs/ADR/031-world-model-ownership-and-corpus-lifecycle.md +155 -0
- package/docs/ADR/032-world-node-identity.md +198 -0
- package/docs/ADR/033-world-snapshot-canonicalization.md +246 -0
- package/docs/ADR/034-world-constraint-semantics.md +218 -0
- package/docs/ADR/035-world-adoption-baseline-and-waiver.md +341 -0
- package/docs/ADR/036-world-model-and-doc-freshness.md +169 -0
- package/docs/ADR/037-world-cli-and-output-contract.md +398 -0
- package/docs/contracts/requirement-test-matrix.schema.json +15 -0
- package/docs/contracts/world-baseline.schema.json +35 -0
- package/docs/contracts/world-constraints.schema.json +56 -0
- package/docs/contracts/world-debts.schema.json +32 -0
- package/docs/contracts/world-obligation-report.schema.json +259 -0
- package/docs/contracts/world-waivers.schema.json +32 -0
- package/docs/guide/cli-reference.md +34 -0
- package/package.json +1 -1
- package/scripts/harness/attestation/application/ports/sha256-capability.ts +25 -0
- package/scripts/harness/attestation/composition-root.ts +14 -4
- package/scripts/harness/attestation/index.ts +6 -1
- package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +6 -6
- package/scripts/harness/attestation/infrastructure/adapters/node-crypto-sha256-capability.ts +19 -0
- package/scripts/harness/harness-api/domain/value-objects/known-harness-commands.ts +4 -1
- package/scripts/harness/main.ts +107 -1
- package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +11 -0
- package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +6 -0
- package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +8 -4
- package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +29 -2
- package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +28 -0
- package/scripts/harness/nyquist-validation/infrastructure/adapters/markdown-requirement-source-adapter.ts +71 -4
- package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +4 -0
- package/scripts/harness/traceability-model/application/dto/traceability-world-read-dto.ts +67 -0
- package/scripts/harness/traceability-model/application/facades/traceability-world-read-facade.ts +372 -0
- package/scripts/harness/traceability-model/application/ports/traceability-world-read-source-port.ts +61 -0
- package/scripts/harness/traceability-model/composition-root.ts +21 -5
- package/scripts/harness/traceability-model/index.ts +17 -6
- package/scripts/harness/traceability-model/infrastructure/adapters/file-system-traceability-world-read-adapter.ts +223 -0
- package/scripts/harness/traceability-model/infrastructure/parsers/story-catalog-parser.ts +86 -8
- package/scripts/harness/world-model/application/dto/world-inspection-dto.ts +43 -0
- package/scripts/harness/world-model/application/dto/world-obligation-report-dto.ts +54 -0
- package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +62 -0
- package/scripts/harness/world-model/application/ports/obligation-report-writer-port.ts +7 -0
- package/scripts/harness/world-model/application/ports/world-control-declaration-repository-port.ts +57 -0
- package/scripts/harness/world-model/application/ports/world-fact-source-port.ts +17 -0
- package/scripts/harness/world-model/application/usecases/build-snapshot-use-case.ts +111 -0
- package/scripts/harness/world-model/application/usecases/derive-obligations-use-case.ts +155 -0
- package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +144 -0
- package/scripts/harness/world-model/application/usecases/inspect-world-use-case.ts +74 -0
- package/scripts/harness/world-model/application/usecases/pin-constraint-endpoint-use-case.ts +139 -0
- package/scripts/harness/world-model/composition-root.ts +218 -0
- package/scripts/harness/world-model/domain/entities/constraint-record.ts +148 -0
- package/scripts/harness/world-model/domain/entities/control-declarations.ts +274 -0
- package/scripts/harness/world-model/domain/entities/edge.ts +41 -0
- package/scripts/harness/world-model/domain/entities/extraction-diagnostic.ts +60 -0
- package/scripts/harness/world-model/domain/entities/snapshot.ts +75 -0
- package/scripts/harness/world-model/domain/entities/world-node.ts +210 -0
- package/scripts/harness/world-model/domain/ports/world-hashing-port.ts +8 -0
- package/scripts/harness/world-model/domain/services/canonical-json-serializer.ts +113 -0
- package/scripts/harness/world-model/domain/services/constraint-evaluator.ts +486 -0
- package/scripts/harness/world-model/domain/services/obligation-derivation-service.ts +199 -0
- package/scripts/harness/world-model/domain/services/policy-inputs-digest-deriver.ts +66 -0
- package/scripts/harness/world-model/domain/services/snapshot-root-deriver.ts +218 -0
- package/scripts/harness/world-model/domain/services/text-content-normalizer.ts +30 -0
- package/scripts/harness/world-model/domain/services/violation-fingerprint-deriver.ts +183 -0
- package/scripts/harness/world-model/domain/value-objects/artifact-kind.ts +58 -0
- package/scripts/harness/world-model/domain/value-objects/change-provenance.ts +137 -0
- package/scripts/harness/world-model/domain/value-objects/corpus-role.ts +57 -0
- package/scripts/harness/world-model/domain/value-objects/declared-key.ts +35 -0
- package/scripts/harness/world-model/domain/value-objects/evaluation-id.ts +41 -0
- package/scripts/harness/world-model/domain/value-objects/explicit-constraint-relation.ts +53 -0
- package/scripts/harness/world-model/domain/value-objects/explicit-node-alias.ts +30 -0
- package/scripts/harness/world-model/domain/value-objects/node-pin.ts +34 -0
- package/scripts/harness/world-model/domain/value-objects/path-key.ts +103 -0
- package/scripts/harness/world-model/domain/value-objects/sha256-digest.ts +39 -0
- package/scripts/harness/world-model/domain/value-objects/violation-fingerprint.ts +36 -0
- package/scripts/harness/world-model/domain/value-objects/wcr-rule-id.ts +59 -0
- package/scripts/harness/world-model/domain/value-objects/world-node-id.ts +222 -0
- package/scripts/harness/world-model/index.ts +22 -0
- package/scripts/harness/world-model/infrastructure/adapters/adr-fact-extractor.ts +27 -0
- package/scripts/harness/world-model/infrastructure/adapters/assembled-world-fact-source.ts +17 -0
- package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +288 -0
- package/scripts/harness/world-model/infrastructure/adapters/attestation-sha256-world-hashing-adapter.ts +15 -0
- package/scripts/harness/world-model/infrastructure/adapters/composite-design-fact-source.ts +23 -0
- package/scripts/harness/world-model/infrastructure/adapters/design-corpus-fact-extractor.ts +285 -0
- package/scripts/harness/world-model/infrastructure/adapters/design-fact-extraction.ts +63 -0
- package/scripts/harness/world-model/infrastructure/adapters/file-system-obligation-report-writer-adapter.ts +37 -0
- package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +330 -0
- package/scripts/harness/world-model/infrastructure/adapters/integrity-manifest-fact-extractor.ts +101 -0
- package/scripts/harness/world-model/infrastructure/adapters/json-fact-extractor-support.ts +151 -0
- package/scripts/harness/world-model/infrastructure/adapters/markdown-design-fact-extractor.ts +493 -0
- package/scripts/harness/world-model/infrastructure/adapters/matrix-fact-extractor.ts +283 -0
- package/scripts/harness/world-model/infrastructure/adapters/product-fact-extractor.ts +29 -0
- package/scripts/harness/world-model/infrastructure/adapters/proposal-fact-extractor.ts +29 -0
- package/scripts/harness/world-model/infrastructure/adapters/runtime-fact-extraction.ts +19 -0
- package/scripts/harness/world-model/infrastructure/adapters/source-metadata-fact-extractor.ts +22 -0
- package/scripts/harness/world-model/infrastructure/adapters/test-reference-source-fact-extractor.ts +22 -0
- package/scripts/harness/world-model/infrastructure/adapters/traceability-design-fact-adapter.ts +112 -0
- package/scripts/harness/world-model/infrastructure/adapters/traceability-world-read-facade-merger.ts +38 -0
- package/scripts/harness/world-model/infrastructure/adapters/type-script-source-fact-extractor.ts +236 -0
- package/scripts/harness/world-model/infrastructure/adapters/unit-fact-extractor.ts +71 -0
- package/scripts/harness/world-model/infrastructure/adapters/world-control-declaration-mapper.ts +383 -0
- package/scripts/harness/world-model/presentation/cli/world-command-support.ts +60 -0
- package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +105 -0
- package/scripts/harness/world-model/presentation/cli/world-inspect-command-handler.ts +194 -0
- package/scripts/harness/world-model/presentation/cli/world-pin-command-handler.ts +100 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-287
|
|
4
|
+
export type CorpusRoleValue = "product" | "inception" | "adr" | "generated" | "external";
|
|
5
|
+
|
|
6
|
+
const VALUES: readonly CorpusRoleValue[] = ["product", "inception", "adr", "generated", "external"];
|
|
7
|
+
|
|
8
|
+
export class InvalidCorpusRoleError extends Error {
|
|
9
|
+
constructor(value: string) {
|
|
10
|
+
super(`Invalid World corpus role: "${value}"`);
|
|
11
|
+
this.name = "InvalidCorpusRoleError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class CorpusRole {
|
|
16
|
+
readonly value: CorpusRoleValue;
|
|
17
|
+
|
|
18
|
+
private constructor(value: CorpusRoleValue) {
|
|
19
|
+
this.value = value;
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static parse(value: string): CorpusRole {
|
|
24
|
+
if (!VALUES.includes(value as CorpusRoleValue)) {
|
|
25
|
+
throw new InvalidCorpusRoleError(value);
|
|
26
|
+
}
|
|
27
|
+
return new CorpusRole(value as CorpusRoleValue);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static product(): CorpusRole {
|
|
31
|
+
return new CorpusRole("product");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static inception(): CorpusRole {
|
|
35
|
+
return new CorpusRole("inception");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
static adr(): CorpusRole {
|
|
39
|
+
return new CorpusRole("adr");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static generated(): CorpusRole {
|
|
43
|
+
return new CorpusRole("generated");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static external(): CorpusRole {
|
|
47
|
+
return new CorpusRole("external");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
equals(other: CorpusRole): boolean {
|
|
51
|
+
return this.value === other.value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
toString(): string {
|
|
55
|
+
return this.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-287
|
|
4
|
+
const DECLARED_KEY_PATTERN = /^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)*$/;
|
|
5
|
+
|
|
6
|
+
export class InvalidDeclaredKeyError extends Error {
|
|
7
|
+
constructor(value: string) {
|
|
8
|
+
super(`Invalid World declared key: "${value}"`);
|
|
9
|
+
this.name = "InvalidDeclaredKeyError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class DeclaredKey {
|
|
14
|
+
readonly value: string;
|
|
15
|
+
|
|
16
|
+
private constructor(value: string) {
|
|
17
|
+
this.value = value;
|
|
18
|
+
Object.freeze(this);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static create(value: string): DeclaredKey {
|
|
22
|
+
if (!DECLARED_KEY_PATTERN.test(value)) {
|
|
23
|
+
throw new InvalidDeclaredKeyError(value);
|
|
24
|
+
}
|
|
25
|
+
return new DeclaredKey(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
equals(other: DeclaredKey): boolean {
|
|
29
|
+
return this.value === other.value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
toString(): string {
|
|
33
|
+
return this.value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-287
|
|
4
|
+
import type { Sha256Digest } from "./sha256-digest.js";
|
|
5
|
+
|
|
6
|
+
const EVALUATION_ID_PATTERN = /^pgw:v1:evaluation:sha256:[0-9a-f]{64}$/;
|
|
7
|
+
|
|
8
|
+
export class InvalidEvaluationIdError extends Error {
|
|
9
|
+
constructor(value: string) {
|
|
10
|
+
super(`Invalid World evaluation ID: "${value}"`);
|
|
11
|
+
this.name = "InvalidEvaluationIdError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class EvaluationId {
|
|
16
|
+
readonly value: string;
|
|
17
|
+
|
|
18
|
+
private constructor(value: string) {
|
|
19
|
+
this.value = value;
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static fromDigest(digest: Sha256Digest): EvaluationId {
|
|
24
|
+
return new EvaluationId(`pgw:v1:evaluation:${digest.toString()}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static parse(value: string): EvaluationId {
|
|
28
|
+
if (!EVALUATION_ID_PATTERN.test(value)) {
|
|
29
|
+
throw new InvalidEvaluationIdError(value);
|
|
30
|
+
}
|
|
31
|
+
return new EvaluationId(value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
equals(other: EvaluationId): boolean {
|
|
35
|
+
return this.value === other.value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
toString(): string {
|
|
39
|
+
return this.value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-293
|
|
4
|
+
|
|
5
|
+
import type { ConstraintFactType } from "../entities/constraint-record.js";
|
|
6
|
+
import type { CanonicalJsonObject } from "../services/canonical-json-serializer.js";
|
|
7
|
+
import type { WorldNodeId } from "./world-node-id.js";
|
|
8
|
+
|
|
9
|
+
export interface ExplicitConstraintRelationProps {
|
|
10
|
+
readonly constraintId: WorldNodeId;
|
|
11
|
+
readonly factType: ConstraintFactType;
|
|
12
|
+
readonly claimantId: WorldNodeId;
|
|
13
|
+
readonly premiseId: WorldNodeId;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class InvalidExplicitConstraintRelationError extends Error {
|
|
17
|
+
constructor(message: string) {
|
|
18
|
+
super(`Invalid explicit World constraint relation: ${message}`);
|
|
19
|
+
this.name = "InvalidExplicitConstraintRelationError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class ExplicitConstraintRelation {
|
|
24
|
+
readonly constraintId: WorldNodeId;
|
|
25
|
+
readonly factType: ConstraintFactType;
|
|
26
|
+
readonly claimantId: WorldNodeId;
|
|
27
|
+
readonly premiseId: WorldNodeId;
|
|
28
|
+
|
|
29
|
+
private constructor(props: ExplicitConstraintRelationProps) {
|
|
30
|
+
this.constraintId = props.constraintId;
|
|
31
|
+
this.factType = props.factType;
|
|
32
|
+
this.claimantId = props.claimantId;
|
|
33
|
+
this.premiseId = props.premiseId;
|
|
34
|
+
Object.freeze(this);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static create(props: ExplicitConstraintRelationProps): ExplicitConstraintRelation {
|
|
38
|
+
if (props.constraintId.nodeType !== "constraint") {
|
|
39
|
+
throw new InvalidExplicitConstraintRelationError("constraintId must be a constraint node ID");
|
|
40
|
+
}
|
|
41
|
+
return new ExplicitConstraintRelation(props);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
toCanonicalValue(): CanonicalJsonObject {
|
|
45
|
+
return {
|
|
46
|
+
claimantId: this.claimantId.toString(),
|
|
47
|
+
constraintId: this.constraintId.toString(),
|
|
48
|
+
factType: this.factType,
|
|
49
|
+
premiseId: this.premiseId.toString(),
|
|
50
|
+
source: "constraint-declaration",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 { WorldNodeId } from "./world-node-id.js";
|
|
7
|
+
|
|
8
|
+
export interface ExplicitNodeAliasProps {
|
|
9
|
+
readonly from: WorldNodeId;
|
|
10
|
+
readonly to: WorldNodeId;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ExplicitNodeAlias {
|
|
14
|
+
readonly from: WorldNodeId;
|
|
15
|
+
readonly to: WorldNodeId;
|
|
16
|
+
|
|
17
|
+
private constructor(props: ExplicitNodeAliasProps) {
|
|
18
|
+
this.from = props.from;
|
|
19
|
+
this.to = props.to;
|
|
20
|
+
Object.freeze(this);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static create(props: ExplicitNodeAliasProps): ExplicitNodeAlias {
|
|
24
|
+
return new ExplicitNodeAlias(props);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
toCanonicalValue(): CanonicalJsonObject {
|
|
28
|
+
return { from: this.from.toString(), to: this.to.toString() };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { Sha256Digest } from "./sha256-digest.js";
|
|
7
|
+
import type { WorldNodeId } from "./world-node-id.js";
|
|
8
|
+
|
|
9
|
+
export interface NodePinProps {
|
|
10
|
+
readonly nodeId: WorldNodeId;
|
|
11
|
+
readonly contentDigest: Sha256Digest;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class NodePin {
|
|
15
|
+
readonly nodeId: WorldNodeId;
|
|
16
|
+
readonly contentDigest: Sha256Digest;
|
|
17
|
+
|
|
18
|
+
private constructor(props: NodePinProps) {
|
|
19
|
+
this.nodeId = props.nodeId;
|
|
20
|
+
this.contentDigest = props.contentDigest;
|
|
21
|
+
Object.freeze(this);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static create(props: NodePinProps): NodePin {
|
|
25
|
+
return new NodePin(props);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toCanonicalValue(): CanonicalJsonObject {
|
|
29
|
+
return {
|
|
30
|
+
contentDigest: this.contentDigest.toString(),
|
|
31
|
+
nodeId: this.nodeId.toString(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-287
|
|
4
|
+
export class InvalidPathKeyError extends Error {
|
|
5
|
+
constructor(value: string) {
|
|
6
|
+
super(`Invalid World PathKey: "${value}"`);
|
|
7
|
+
this.name = "InvalidPathKeyError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const encodeRfc3986Component = (value: string): string =>
|
|
12
|
+
encodeURIComponent(value).replace(
|
|
13
|
+
/[!'()*]/g,
|
|
14
|
+
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const encodeWorldIdComponent = (value: string): string => {
|
|
18
|
+
if (value.length === 0) {
|
|
19
|
+
throw new InvalidPathKeyError(value);
|
|
20
|
+
}
|
|
21
|
+
return encodeRfc3986Component(value);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const decodeWorldIdComponent = (encoded: string): string => {
|
|
25
|
+
if (encoded.length === 0) {
|
|
26
|
+
throw new InvalidPathKeyError(encoded);
|
|
27
|
+
}
|
|
28
|
+
let decoded: string;
|
|
29
|
+
try {
|
|
30
|
+
decoded = decodeURIComponent(encoded);
|
|
31
|
+
} catch {
|
|
32
|
+
throw new InvalidPathKeyError(encoded);
|
|
33
|
+
}
|
|
34
|
+
if (encodeWorldIdComponent(decoded) !== encoded) {
|
|
35
|
+
throw new InvalidPathKeyError(encoded);
|
|
36
|
+
}
|
|
37
|
+
return decoded;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const normalizeSegments = (value: string): readonly string[] => {
|
|
41
|
+
const trimmed = value.trim();
|
|
42
|
+
if (trimmed.length === 0 || trimmed.includes("\\") || trimmed.startsWith("/") || /^[A-Za-z]:\//.test(trimmed)) {
|
|
43
|
+
throw new InvalidPathKeyError(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const segments = trimmed
|
|
47
|
+
.split("/")
|
|
48
|
+
.filter((segment) => segment.length > 0 && segment !== ".")
|
|
49
|
+
.map((segment) => {
|
|
50
|
+
if (segment === "..") {
|
|
51
|
+
throw new InvalidPathKeyError(value);
|
|
52
|
+
}
|
|
53
|
+
return segment;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (segments.length === 0) {
|
|
57
|
+
throw new InvalidPathKeyError(value);
|
|
58
|
+
}
|
|
59
|
+
return Object.freeze(segments);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export class PathKey {
|
|
63
|
+
readonly value: string;
|
|
64
|
+
|
|
65
|
+
private constructor(value: string) {
|
|
66
|
+
this.value = value;
|
|
67
|
+
Object.freeze(this);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static create(value: string): PathKey {
|
|
71
|
+
return new PathKey(normalizeSegments(value).join("/"));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
static fromEncodedString(value: string): PathKey {
|
|
75
|
+
if (value.includes("\\") || value.startsWith("/") || value.split("/").some((part) => part === "")) {
|
|
76
|
+
throw new InvalidPathKeyError(value);
|
|
77
|
+
}
|
|
78
|
+
const decoded = value
|
|
79
|
+
.split("/")
|
|
80
|
+
.map((segment) => decodeWorldIdComponent(segment))
|
|
81
|
+
.join("/");
|
|
82
|
+
const path = PathKey.create(decoded);
|
|
83
|
+
if (path.toEncodedString() !== value) {
|
|
84
|
+
throw new InvalidPathKeyError(value);
|
|
85
|
+
}
|
|
86
|
+
return path;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
toEncodedString(): string {
|
|
90
|
+
return this.value
|
|
91
|
+
.split("/")
|
|
92
|
+
.map((segment) => encodeWorldIdComponent(segment))
|
|
93
|
+
.join("/");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
equals(other: PathKey): boolean {
|
|
97
|
+
return this.value === other.value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
toString(): string {
|
|
101
|
+
return this.value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-287
|
|
4
|
+
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
5
|
+
|
|
6
|
+
export class InvalidSha256DigestError extends Error {
|
|
7
|
+
constructor(value: string) {
|
|
8
|
+
super(`Invalid World SHA-256 digest: "${value}"`);
|
|
9
|
+
this.name = "InvalidSha256DigestError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class Sha256Digest {
|
|
14
|
+
readonly value: string;
|
|
15
|
+
|
|
16
|
+
private constructor(value: string) {
|
|
17
|
+
this.value = value;
|
|
18
|
+
Object.freeze(this);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static create(value: string): Sha256Digest {
|
|
22
|
+
if (!SHA256_PATTERN.test(value)) {
|
|
23
|
+
throw new InvalidSha256DigestError(value);
|
|
24
|
+
}
|
|
25
|
+
return new Sha256Digest(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static fromHex(hex: string): Sha256Digest {
|
|
29
|
+
return Sha256Digest.create(`sha256:${hex}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
equals(other: Sha256Digest): boolean {
|
|
33
|
+
return this.value === other.value;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
toString(): string {
|
|
37
|
+
return this.value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-294
|
|
4
|
+
|
|
5
|
+
const VIOLATION_FINGERPRINT_PATTERN = /^pgw:v1:violation-fingerprint:sha256:[0-9a-f]{64}$/;
|
|
6
|
+
|
|
7
|
+
export class InvalidViolationFingerprintError extends Error {
|
|
8
|
+
constructor(value: string) {
|
|
9
|
+
super(`Invalid World violation fingerprint: "${value}"`);
|
|
10
|
+
this.name = "InvalidViolationFingerprintError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ViolationFingerprint {
|
|
15
|
+
readonly value: string;
|
|
16
|
+
|
|
17
|
+
private constructor(value: string) {
|
|
18
|
+
this.value = value;
|
|
19
|
+
Object.freeze(this);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static create(value: string): ViolationFingerprint {
|
|
23
|
+
if (!VIOLATION_FINGERPRINT_PATTERN.test(value)) {
|
|
24
|
+
throw new InvalidViolationFingerprintError(value);
|
|
25
|
+
}
|
|
26
|
+
return new ViolationFingerprint(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
equals(other: ViolationFingerprint): boolean {
|
|
30
|
+
return this.value === other.value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
toString(): string {
|
|
34
|
+
return this.value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @unit world-model
|
|
2
|
+
// @layer domain
|
|
3
|
+
// @work-item-id WI-293
|
|
4
|
+
|
|
5
|
+
export type WcrRuleIdValue =
|
|
6
|
+
| "WCR-001"
|
|
7
|
+
| "WCR-002"
|
|
8
|
+
| "WCR-003"
|
|
9
|
+
| "WCR-004"
|
|
10
|
+
| "WCR-005"
|
|
11
|
+
| "WCR-006"
|
|
12
|
+
| "WCR-007"
|
|
13
|
+
| "WCR-008";
|
|
14
|
+
|
|
15
|
+
const VALUES: readonly WcrRuleIdValue[] = [
|
|
16
|
+
"WCR-001",
|
|
17
|
+
"WCR-002",
|
|
18
|
+
"WCR-003",
|
|
19
|
+
"WCR-004",
|
|
20
|
+
"WCR-005",
|
|
21
|
+
"WCR-006",
|
|
22
|
+
"WCR-007",
|
|
23
|
+
"WCR-008",
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export class InvalidWcrRuleIdError extends Error {
|
|
27
|
+
constructor(value: string) {
|
|
28
|
+
super(`Invalid World constraint rule ID: "${value}"`);
|
|
29
|
+
this.name = "InvalidWcrRuleIdError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class WcrRuleId {
|
|
34
|
+
readonly value: WcrRuleIdValue;
|
|
35
|
+
|
|
36
|
+
private constructor(value: WcrRuleIdValue) {
|
|
37
|
+
this.value = value;
|
|
38
|
+
Object.freeze(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static create(value: string): WcrRuleId {
|
|
42
|
+
if (!VALUES.includes(value as WcrRuleIdValue)) {
|
|
43
|
+
throw new InvalidWcrRuleIdError(value);
|
|
44
|
+
}
|
|
45
|
+
return new WcrRuleId(value as WcrRuleIdValue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static all(): readonly WcrRuleId[] {
|
|
49
|
+
return Object.freeze(VALUES.map((value) => new WcrRuleId(value)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
equals(other: WcrRuleId): boolean {
|
|
53
|
+
return this.value === other.value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
toString(): WcrRuleIdValue {
|
|
57
|
+
return this.value;
|
|
58
|
+
}
|
|
59
|
+
}
|