phasegate 0.254.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 (65) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.ja.md +6 -3
  3. package/README.md +6 -3
  4. package/docs/contracts/attestation-v2.schema.json +110 -0
  5. package/docs/guide/cli-reference.md +12 -1
  6. package/docs/guide/configuration.md +56 -0
  7. package/docs/templates/ci/aidlc-gate.yml +42 -2
  8. package/package.json +1 -1
  9. package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
  10. package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
  11. package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
  12. package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
  13. package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
  14. package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
  15. package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
  16. package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
  17. package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
  18. package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
  19. package/scripts/harness/attestation/composition-root.ts +5 -0
  20. package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
  21. package/scripts/harness/attestation/index.ts +7 -1
  22. package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
  23. package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +35 -8
  24. package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
  25. package/scripts/harness/config-foundation/domain/harness-config.ts +67 -87
  26. package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +77 -88
  27. package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
  28. package/scripts/harness/config-foundation/index.ts +14 -15
  29. package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
  30. package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
  31. package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
  32. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +83 -0
  33. package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +83 -0
  34. package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
  35. package/scripts/harness/integrations/pre-commit.ts +169 -27
  36. package/scripts/harness/main.ts +174 -130
  37. package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
  38. package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
  39. package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
  40. package/scripts/harness/traceability-model/composition-root.ts +5 -0
  41. package/scripts/harness/traceability-model/index.ts +9 -0
  42. package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
  43. package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
  44. package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +34 -0
  45. package/scripts/harness/validator-system/composition-root.ts +21 -6
  46. package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
  47. package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
  48. package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
  49. package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
  50. package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
  51. package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
  52. package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +38 -16
  53. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
  54. package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
  55. package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
  56. package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
  57. package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
  58. package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
  59. package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
  60. package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
  61. package/scripts/harness/world-model/composition-root.ts +62 -33
  62. package/scripts/harness/world-model/index.ts +11 -0
  63. package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
  64. package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
  65. package/scripts/harness/world-model/presentation/cli/world-derive-command-handler.ts +6 -2
@@ -0,0 +1,75 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-305
4
+
5
+ export interface DesignChangedFragmentInput {
6
+ readonly corpusRole: "product" | "inception";
7
+ readonly declaredKey: string;
8
+ readonly path: string;
9
+ readonly workItemIds: readonly string[];
10
+ }
11
+
12
+ export interface DesignPinnedEndpointInput {
13
+ readonly constraintId: string;
14
+ readonly endpoint: "claimant" | "premise";
15
+ readonly corpusRole: "product" | "inception";
16
+ readonly declaredKey: string;
17
+ }
18
+
19
+ export interface DesignChangeDeclarationFinding {
20
+ readonly code: "design-change-declaration-missing";
21
+ readonly corpusRole: "product" | "inception";
22
+ readonly declaredKey: string;
23
+ readonly path: string;
24
+ readonly expectedWorkItemIds: readonly string[];
25
+ readonly constraintIds: readonly string[];
26
+ }
27
+
28
+ export interface DesignChangeDeclarationEvaluation {
29
+ readonly checkedFragmentCount: number;
30
+ readonly findings: readonly DesignChangeDeclarationFinding[];
31
+ }
32
+
33
+ export interface DesignChangeDeclarationInput {
34
+ readonly changedFragments: readonly DesignChangedFragmentInput[];
35
+ readonly pinnedEndpoints: readonly DesignPinnedEndpointInput[];
36
+ readonly trailerWorkItemIds: readonly string[];
37
+ }
38
+
39
+ const compare = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
40
+ const keyOf = (value: { readonly corpusRole: string; readonly declaredKey: string }): string =>
41
+ `${value.corpusRole}\u0000${value.declaredKey}`;
42
+ const uniqueSorted = (values: readonly string[]): readonly string[] =>
43
+ Object.freeze([...new Set(values)].sort(compare));
44
+
45
+ export class DesignChangeDeclarationPolicy {
46
+ evaluate(input: DesignChangeDeclarationInput): DesignChangeDeclarationEvaluation {
47
+ const endpointsByFragment = new Map<string, DesignPinnedEndpointInput[]>();
48
+ for (const endpoint of input.pinnedEndpoints) {
49
+ const key = keyOf(endpoint);
50
+ const current = endpointsByFragment.get(key) ?? [];
51
+ current.push(endpoint);
52
+ endpointsByFragment.set(key, current);
53
+ }
54
+ const trailerIds = new Set(input.trailerWorkItemIds);
55
+ const pinnedChanges = input.changedFragments
56
+ .filter((fragment) => endpointsByFragment.has(keyOf(fragment)))
57
+ .sort((left, right) => compare(keyOf(left), keyOf(right)) || compare(left.path, right.path));
58
+ const findings = pinnedChanges.flatMap((fragment): readonly DesignChangeDeclarationFinding[] => {
59
+ if (fragment.workItemIds.some((workItemId) => trailerIds.has(workItemId))) return [];
60
+ return [
61
+ Object.freeze({
62
+ code: "design-change-declaration-missing" as const,
63
+ corpusRole: fragment.corpusRole,
64
+ declaredKey: fragment.declaredKey,
65
+ path: fragment.path,
66
+ expectedWorkItemIds: uniqueSorted(fragment.workItemIds),
67
+ constraintIds: uniqueSorted(
68
+ (endpointsByFragment.get(keyOf(fragment)) ?? []).map((endpoint) => endpoint.constraintId),
69
+ ),
70
+ }),
71
+ ];
72
+ });
73
+ return Object.freeze({ checkedFragmentCount: pinnedChanges.length, findings: Object.freeze(findings) });
74
+ }
75
+ }
@@ -0,0 +1,78 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-301
4
+
5
+ import type {
6
+ WorldAdmissionClassification,
7
+ WorldConstraintAdmissionObservation,
8
+ } from "../ports/world-constraint-admission-policy-port.js";
9
+
10
+ export interface WorldConstraintAdmissionFinding {
11
+ readonly severity: "error" | "warning";
12
+ readonly ruleId: string | null;
13
+ readonly violationFingerprint: string | null;
14
+ readonly constraintId: string | null;
15
+ readonly classification: WorldAdmissionClassification | "constraint-input-invalid" | "local-evaluation-incomplete";
16
+ readonly message: string;
17
+ readonly suggestion: string;
18
+ readonly sourcePath: string | null;
19
+ }
20
+
21
+ const LOCAL_TRUST_NOTICE = "Local L2 evidence can be forged; authoritative L3 re-derivation is required.";
22
+ const AUTHORITATIVE_SUGGESTION =
23
+ "Repair the declaration or structural finding, then rely on authoritative L3 clean-corpus re-derivation.";
24
+
25
+ const compareNullable = (left: string | null, right: string | null): number =>
26
+ (left ?? "") < (right ?? "") ? -1 : (left ?? "") > (right ?? "") ? 1 : 0;
27
+
28
+ export class WorldConstraintAdmissionService {
29
+ evaluate(observation: WorldConstraintAdmissionObservation): readonly WorldConstraintAdmissionFinding[] {
30
+ const obligationFindings = observation.obligations.map((obligation): WorldConstraintAdmissionFinding => {
31
+ const blocking =
32
+ obligation.classification === "new-structural" || obligation.classification === "invalid-declaration";
33
+ const label =
34
+ obligation.classification === "invalid-declaration"
35
+ ? "non-adoptable invalid World constraint declaration"
36
+ : obligation.classification === "new-structural"
37
+ ? "new World structural violation"
38
+ : obligation.classification === "adopted-legacy"
39
+ ? "adopted legacy World violation"
40
+ : "actively waived World violation";
41
+ return Object.freeze({
42
+ severity: blocking ? "error" : "warning",
43
+ ruleId: obligation.ruleId,
44
+ violationFingerprint: obligation.violationFingerprint,
45
+ constraintId: obligation.constraintId,
46
+ classification: obligation.classification,
47
+ message: `L2 local fast-path reports ${label}: ${obligation.ruleId} ${obligation.violationFingerprint}. ${LOCAL_TRUST_NOTICE}`,
48
+ suggestion: AUTHORITATIVE_SUGGESTION,
49
+ sourcePath: null,
50
+ });
51
+ });
52
+ const diagnosticFindings = observation.diagnostics.map((item): WorldConstraintAdmissionFinding => {
53
+ const constraintInput = item.scope === "constraint";
54
+ return Object.freeze({
55
+ severity: constraintInput ? "error" : "warning",
56
+ ruleId: constraintInput ? "WCR-001" : null,
57
+ violationFingerprint: null,
58
+ constraintId: null,
59
+ classification: constraintInput ? "constraint-input-invalid" : "local-evaluation-incomplete",
60
+ message: constraintInput
61
+ ? `L2 local fast-path cannot admit constraint declaration input (${item.code}): ${item.message}. ${LOCAL_TRUST_NOTICE}`
62
+ : `L2 local fast-path could not complete non-constraint policy observation (${item.code}): ${item.message}. ${LOCAL_TRUST_NOTICE}`,
63
+ suggestion: AUTHORITATIVE_SUGGESTION,
64
+ sourcePath: item.path,
65
+ });
66
+ });
67
+
68
+ return Object.freeze(
69
+ [...obligationFindings, ...diagnosticFindings].sort(
70
+ (left, right) =>
71
+ compareNullable(left.ruleId, right.ruleId) ||
72
+ compareNullable(left.violationFingerprint, right.violationFingerprint) ||
73
+ compareNullable(left.sourcePath, right.sourcePath) ||
74
+ left.classification.localeCompare(right.classification),
75
+ ),
76
+ );
77
+ }
78
+ }
@@ -0,0 +1,76 @@
1
+ // @unit validator-system
2
+ // @layer domain
3
+ // @work-item-id WI-302
4
+
5
+ import type { WorldAdmissionClassification } from "../ports/world-constraint-admission-policy-port.js";
6
+ import type { WorldConstraintRederivationObservation } from "../ports/world-constraint-rederivation-policy-port.js";
7
+
8
+ export interface WorldConstraintRederivationFinding {
9
+ readonly severity: "error" | "warning";
10
+ readonly ruleId: string | null;
11
+ readonly violationFingerprint: string | null;
12
+ readonly constraintId: string | null;
13
+ readonly classification:
14
+ | WorldAdmissionClassification
15
+ | "constraint-input-invalid"
16
+ | "authoritative-evaluation-failed";
17
+ readonly message: string;
18
+ readonly suggestion: string;
19
+ readonly sourcePath: string | null;
20
+ }
21
+
22
+ const REPAIR_SUGGESTION =
23
+ "Repair the versioned declaration or current corpus finding, then rerun authoritative L3 clean-corpus re-derivation.";
24
+
25
+ const compareNullable = (left: string | null, right: string | null): number =>
26
+ (left ?? "") < (right ?? "") ? -1 : (left ?? "") > (right ?? "") ? 1 : 0;
27
+
28
+ export class WorldConstraintRederivationService {
29
+ evaluate(observation: WorldConstraintRederivationObservation): readonly WorldConstraintRederivationFinding[] {
30
+ const obligationFindings = observation.obligations.map((obligation): WorldConstraintRederivationFinding => {
31
+ const blocking =
32
+ obligation.classification === "new-structural" || obligation.classification === "invalid-declaration";
33
+ const label =
34
+ obligation.classification === "invalid-declaration"
35
+ ? "non-adoptable invalid World constraint declaration"
36
+ : obligation.classification === "new-structural"
37
+ ? "new World structural violation"
38
+ : obligation.classification === "adopted-legacy"
39
+ ? "adopted legacy World violation"
40
+ : "actively waived World violation";
41
+ return Object.freeze({
42
+ severity: blocking ? "error" : "warning",
43
+ ruleId: obligation.ruleId,
44
+ violationFingerprint: obligation.violationFingerprint,
45
+ constraintId: obligation.constraintId,
46
+ classification: obligation.classification,
47
+ message: `L3 authoritative clean-corpus re-derivation reports ${label}: ${obligation.ruleId} ${obligation.violationFingerprint}.`,
48
+ suggestion: REPAIR_SUGGESTION,
49
+ sourcePath: null,
50
+ });
51
+ });
52
+ const diagnosticFindings = observation.diagnostics.map((item): WorldConstraintRederivationFinding => {
53
+ const constraintInput = item.scope === "constraint";
54
+ return Object.freeze({
55
+ severity: "error",
56
+ ruleId: constraintInput ? "WCR-001" : null,
57
+ violationFingerprint: null,
58
+ constraintId: null,
59
+ classification: constraintInput ? "constraint-input-invalid" : "authoritative-evaluation-failed",
60
+ message: `L3 authoritative clean-corpus re-derivation cannot produce a trustworthy result (${item.code}): ${item.message}.`,
61
+ suggestion: REPAIR_SUGGESTION,
62
+ sourcePath: item.path,
63
+ });
64
+ });
65
+
66
+ return Object.freeze(
67
+ [...obligationFindings, ...diagnosticFindings].sort(
68
+ (left, right) =>
69
+ compareNullable(left.ruleId, right.ruleId) ||
70
+ compareNullable(left.violationFingerprint, right.violationFingerprint) ||
71
+ compareNullable(left.sourcePath, right.sourcePath) ||
72
+ left.classification.localeCompare(right.classification),
73
+ ),
74
+ );
75
+ }
76
+ }
@@ -14,6 +14,8 @@
14
14
  * WI-258 (ADR-030 §Decision.3.②) で L2-016(coverage-attestation-gating, fail-closed)を追加
15
15
  * WI-259 (ADR-030 §Decision.3.④) で L3-006(injection-scan, advisory warning-only)を追加
16
16
  * WI-268 (ADR-030 §Decision.1・§Decision.3.② 第2段) で L3-007(coverage-attestation-verification, fail-closed)を追加
17
+ * WI-301 で L2-017(world-constraint-admission, local fast-path)を追加
18
+ * WI-302 で L3-008(world-constraint-rederivation, authoritative)を追加
17
19
  */
18
20
 
19
21
  export class InvalidValidatorIdError extends Error {
@@ -38,6 +40,7 @@ const VALIDATOR_NAME_MAP: Record<string, string> = {
38
40
  "L2-014": "work-item-status-staleness",
39
41
  "L2-015": "contract-traceability-coverage",
40
42
  "L2-016": "coverage-attestation-gating",
43
+ "L2-017": "world-constraint-admission",
41
44
  "L3-001": "security",
42
45
  "L3-002": "performance",
43
46
  "L3-003": "coverage",
@@ -45,6 +48,7 @@ const VALIDATOR_NAME_MAP: Record<string, string> = {
45
48
  "L3-005": "ac-bound-coverage",
46
49
  "L3-006": "injection-scan",
47
50
  "L3-007": "coverage-attestation-verification",
51
+ "L3-008": "world-constraint-rederivation",
48
52
  "L4-001": "drift-detect",
49
53
  "L4-002": "consistency-check",
50
54
  "L4-003": "dead-code",
@@ -3,28 +3,39 @@
3
3
  * @unit validator-system
4
4
  * @work-item-id WI-156
5
5
  * @work-item-id WI-212
6
+ * @work-item-id WI-301
7
+ * @work-item-id WI-302
6
8
  *
7
9
  * HarnessConfigValidatorConfigAdapter — ValidatorConfigPort実装
8
10
  * HarnessConfigV2からLayerConfig VOを構築する
9
11
  */
10
- import { LayerConfig } from '../../domain/value-objects/layer-config.js';
11
- import { ValidatorId } from '../../domain/value-objects/validator-id.js';
12
- import type { ValidatorConfigPort } from '../../domain/ports/validator-config-port.js';
12
+
13
+ import type { ValidatorConfigPort } from "../../domain/ports/validator-config-port.js";
14
+ import { LayerConfig } from "../../domain/value-objects/layer-config.js";
15
+ import { ValidatorId } from "../../domain/value-objects/validator-id.js";
13
16
 
14
17
  export interface HarnessConfigLayers {
15
18
  L2?: { enabled?: boolean; validators?: string[]; strictOnly?: boolean; preset?: string };
16
- L3?: { enabled?: boolean; validators?: string[]; strictOnly?: boolean; preset?: string; coverageThreshold?: number; bundleSizeLimit?: number };
19
+ L3?: {
20
+ enabled?: boolean;
21
+ validators?: string[];
22
+ strictOnly?: boolean;
23
+ preset?: string;
24
+ coverageThreshold?: number;
25
+ bundleSizeLimit?: number;
26
+ };
17
27
  L4?: { enabled?: boolean; validators?: string[]; strictOnly?: boolean; preset?: string; deadCodeGC?: boolean };
18
28
  }
19
29
 
20
30
  export interface HarnessConfigV2Like {
21
- preset?: 'minimal' | 'standard' | 'strict';
22
- project?: { preset?: 'minimal' | 'standard' | 'strict'; languages?: readonly string[] };
31
+ preset?: "minimal" | "standard" | "strict";
32
+ project?: { preset?: "minimal" | "standard" | "strict"; languages?: readonly string[] };
23
33
  layers?: HarnessConfigLayers;
24
34
  harnesses?: {
25
35
  bundleSizeLimit?: number;
26
36
  deadCodeGC?: boolean;
27
37
  };
38
+ world?: { enabled?: boolean };
28
39
  }
29
40
 
30
41
  export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort {
@@ -34,20 +45,23 @@ export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort
34
45
  this.config = config;
35
46
  }
36
47
 
37
- async getLayerConfig(layer: 'L2' | 'L3' | 'L4'): Promise<LayerConfig> {
38
- const preset = (this.config.project?.preset ?? this.config.preset ?? 'standard') as 'minimal' | 'standard' | 'strict';
39
- const strictOnly = preset === 'strict';
48
+ async getLayerConfig(layer: "L2" | "L3" | "L4"): Promise<LayerConfig> {
49
+ const preset = (this.config.project?.preset ?? this.config.preset ?? "standard") as
50
+ | "minimal"
51
+ | "standard"
52
+ | "strict";
53
+ const strictOnly = preset === "strict";
40
54
  const layerData = this.config.layers?.[layer] ?? {};
41
55
 
42
56
  const defaultValidators: Record<string, string[]> = {
43
- L2: ['L2-001', 'L2-002', 'L2-003', 'L2-013', 'L2-014', 'L2-015'],
44
- L3: ['L3-001', 'L3-002', 'L3-003', 'L3-004'],
45
- L4: ['L4-001', 'L4-002', 'L4-003', 'L4-004', 'L4-005', 'L4-006'],
57
+ L2: ["L2-001", "L2-002", "L2-003", "L2-013", "L2-014", "L2-015"],
58
+ L3: ["L3-001", "L3-002", "L3-003", "L3-004"],
59
+ L4: ["L4-001", "L4-002", "L4-003", "L4-004", "L4-005", "L4-006"],
46
60
  };
47
61
 
48
62
  const thresholds: Record<string, number> = {};
49
63
 
50
- if (layer === 'L3') {
64
+ if (layer === "L3") {
51
65
  const l3 = this.config.layers?.L3;
52
66
  if (l3?.coverageThreshold !== undefined) thresholds.coverageThreshold = l3.coverageThreshold;
53
67
  if (l3?.bundleSizeLimit !== undefined) thresholds.bundleSizeLimit = l3.bundleSizeLimit;
@@ -56,16 +70,24 @@ export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort
56
70
  }
57
71
  }
58
72
 
59
- if (layer === 'L4') {
73
+ if (layer === "L4") {
60
74
  if (this.config.harnesses?.deadCodeGC !== undefined) {
61
75
  thresholds.deadCodeGC = this.config.harnesses.deadCodeGC ? 1 : 0;
62
76
  }
63
77
  }
64
78
 
79
+ const configuredValidatorIds = layerData.validators ?? defaultValidators[layer];
80
+ const automaticWorldValidatorId = layer === "L2" ? "L2-017" : layer === "L3" ? "L3-008" : null;
81
+ const selectedValidatorIds =
82
+ automaticWorldValidatorId === null
83
+ ? configuredValidatorIds
84
+ : this.config.world?.enabled === true
85
+ ? [...configuredValidatorIds, automaticWorldValidatorId]
86
+ : configuredValidatorIds.filter((validatorId) => validatorId !== automaticWorldValidatorId);
65
87
  return LayerConfig.create({
66
88
  layer,
67
89
  enabled: layerData.enabled !== false,
68
- validatorIds: (layerData.validators ?? defaultValidators[layer]).map((idOrName) => this.normalizeValidatorId(idOrName)),
90
+ validatorIds: [...new Set(selectedValidatorIds.map((idOrName) => this.normalizeValidatorId(idOrName)))],
69
91
  thresholds,
70
92
  strictOnly: layerData.strictOnly ?? strictOnly,
71
93
  preset,
@@ -74,7 +96,7 @@ export class HarnessConfigValidatorConfigAdapter implements ValidatorConfigPort
74
96
 
75
97
  async getProjectLanguages(): Promise<readonly string[]> {
76
98
  const languages = this.config.project?.languages;
77
- return languages && languages.length > 0 ? [...languages] : ['typescript'];
99
+ return languages && languages.length > 0 ? [...languages] : ["typescript"];
78
100
  }
79
101
 
80
102
  private normalizeValidatorId(idOrName: string): string {
@@ -0,0 +1,56 @@
1
+ // @unit validator-system
2
+ // @layer infrastructure
3
+ // @work-item-id WI-301
4
+
5
+ import { createWorldModelModule, type WorldResolvedConfigInput } from "../../../world-model/index.js";
6
+ import type {
7
+ WorldConstraintAdmissionObservation,
8
+ WorldConstraintAdmissionPolicyPort,
9
+ } from "../../domain/ports/world-constraint-admission-policy-port.js";
10
+
11
+ interface Options {
12
+ readonly rootDir: string;
13
+ readonly resolvedConfig?: WorldResolvedConfigInput;
14
+ }
15
+
16
+ export class WorldModelConstraintAdmissionAdapter implements WorldConstraintAdmissionPolicyPort {
17
+ constructor(private readonly options: Options) {}
18
+
19
+ async collect(): Promise<WorldConstraintAdmissionObservation> {
20
+ const module = createWorldModelModule({
21
+ rootDir: this.options.rootDir,
22
+ resolvedConfig: this.options.resolvedConfig,
23
+ });
24
+ const actual = await module.deriveWorldObligationsUseCase.execute({ writeReport: false });
25
+ const constraintsPath =
26
+ this.options.resolvedConfig?.declarations?.constraintsPath ?? "phasegate.world-constraints.json";
27
+ if (actual.status === "execution-failure") {
28
+ return Object.freeze({
29
+ obligations: Object.freeze([]),
30
+ diagnostics: Object.freeze(
31
+ actual.diagnostics.map((item) =>
32
+ Object.freeze({
33
+ code: item.code,
34
+ path: item.path,
35
+ message: item.message,
36
+ scope: item.path === constraintsPath ? ("constraint" as const) : ("other" as const),
37
+ }),
38
+ ),
39
+ ),
40
+ });
41
+ }
42
+ return Object.freeze({
43
+ obligations: Object.freeze(
44
+ actual.result.report.structuralObligations.map((item) =>
45
+ Object.freeze({
46
+ ruleId: item.ruleId,
47
+ violationFingerprint: item.violationFingerprint,
48
+ constraintId: item.constraintId,
49
+ classification: item.classification,
50
+ }),
51
+ ),
52
+ ),
53
+ diagnostics: Object.freeze([]),
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,56 @@
1
+ // @unit validator-system
2
+ // @layer infrastructure
3
+ // @work-item-id WI-302
4
+
5
+ import { createWorldModelModule, type WorldResolvedConfigInput } from "../../../world-model/index.js";
6
+ import type {
7
+ WorldConstraintRederivationObservation,
8
+ WorldConstraintRederivationPolicyPort,
9
+ } from "../../domain/ports/world-constraint-rederivation-policy-port.js";
10
+
11
+ interface Options {
12
+ readonly rootDir: string;
13
+ readonly resolvedConfig?: WorldResolvedConfigInput;
14
+ }
15
+
16
+ export class WorldModelConstraintRederivationAdapter implements WorldConstraintRederivationPolicyPort {
17
+ constructor(private readonly options: Options) {}
18
+
19
+ async collect(): Promise<WorldConstraintRederivationObservation> {
20
+ const module = createWorldModelModule({
21
+ rootDir: this.options.rootDir,
22
+ resolvedConfig: this.options.resolvedConfig,
23
+ });
24
+ const actual = await module.deriveWorldObligationsUseCase.execute({ writeReport: false });
25
+ const constraintsPath =
26
+ this.options.resolvedConfig?.declarations?.constraintsPath ?? "phasegate.world-constraints.json";
27
+ if (actual.status === "execution-failure") {
28
+ return Object.freeze({
29
+ obligations: Object.freeze([]),
30
+ diagnostics: Object.freeze(
31
+ actual.diagnostics.map((item) =>
32
+ Object.freeze({
33
+ code: item.code,
34
+ path: item.path,
35
+ message: item.message,
36
+ scope: item.path === constraintsPath ? ("constraint" as const) : ("other" as const),
37
+ }),
38
+ ),
39
+ ),
40
+ });
41
+ }
42
+ return Object.freeze({
43
+ obligations: Object.freeze(
44
+ actual.result.report.structuralObligations.map((item) =>
45
+ Object.freeze({
46
+ ruleId: item.ruleId,
47
+ violationFingerprint: item.violationFingerprint,
48
+ constraintId: item.constraintId,
49
+ classification: item.classification,
50
+ }),
51
+ ),
52
+ ),
53
+ diagnostics: Object.freeze([]),
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,19 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-305
4
+
5
+ export interface PinnedDesignEndpointDto {
6
+ readonly constraintId: string;
7
+ readonly endpoint: "claimant" | "premise";
8
+ readonly nodeId: string;
9
+ readonly corpusRole: "product" | "inception";
10
+ readonly declaredKey: string;
11
+ }
12
+
13
+ export type PinnedDesignEndpointReadResultDto =
14
+ | {
15
+ readonly state: "available";
16
+ readonly endpoints: readonly PinnedDesignEndpointDto[];
17
+ readonly diagnosticCodes: readonly string[];
18
+ }
19
+ | { readonly state: "unavailable"; readonly diagnosticCodes: readonly string[] };
@@ -1,62 +1,127 @@
1
1
  // @unit world-model
2
2
  // @layer application
3
- // @work-item-id WI-291
3
+ // @work-item-id WI-291, WI-300
4
4
 
5
5
  import { PathKey } from "../../domain/value-objects/path-key.js";
6
6
 
7
7
  export interface WorldResolvedConfigInput {
8
+ readonly enabled?: boolean;
9
+ readonly corpus?: {
10
+ readonly productRoots?: readonly string[];
11
+ readonly inceptionRoots?: readonly string[];
12
+ readonly adrRoots?: readonly string[];
13
+ readonly sourceRoots?: readonly string[];
14
+ readonly include?: readonly string[];
15
+ readonly exclude?: readonly string[];
16
+ };
17
+ readonly inputs?: {
18
+ readonly matrixPath?: string;
19
+ readonly attestationPath?: string;
20
+ readonly integrityManifestPath?: string;
21
+ };
22
+ readonly declarations?: {
23
+ readonly constraintsPath?: string;
24
+ readonly baselinePath?: string;
25
+ readonly waiversPath?: string;
26
+ readonly debtsPath?: string;
27
+ };
28
+ readonly output?: { readonly obligationReportPath?: string };
29
+ readonly sessionStart?: {
30
+ readonly enabled?: boolean;
31
+ readonly maxItems?: number;
32
+ readonly maxChars?: number;
33
+ };
8
34
  readonly designDocsRoot?: string;
9
35
  readonly inceptionRoot?: string;
10
36
  readonly requirementMatrixPath?: string;
11
37
  }
12
38
 
13
39
  export interface WorldCorpusConfig {
40
+ readonly enabled: boolean;
14
41
  readonly productScopes: readonly {
15
42
  readonly productRoot: string;
16
43
  readonly designDocsRoot: string;
17
44
  readonly unitRoot: string;
18
45
  }[];
19
- readonly inceptionRoot: string;
20
- readonly adrRoot: string;
21
- readonly sourceRoot: string;
46
+ readonly inceptionRoots: readonly string[];
47
+ readonly adrRoots: readonly string[];
48
+ readonly sourceRoots: readonly string[];
22
49
  readonly include: readonly string[];
23
50
  readonly exclude: readonly string[];
24
51
  readonly matrixPath: string;
25
52
  readonly attestationPath: string;
26
53
  readonly integrityManifestPath: string;
54
+ readonly constraintsPath: string;
55
+ readonly baselinePath: string;
56
+ readonly waiversPath: string;
57
+ readonly debtsPath: string;
58
+ readonly obligationReportPath: string;
59
+ readonly sessionStart: { readonly enabled: boolean; readonly maxItems: number; readonly maxChars: number };
27
60
  }
28
61
 
29
- const DEFAULT_DESIGN_DOCS_ROOT = "docs/product/construction";
30
-
31
62
  const validatePath = (value: string): string => PathKey.create(value).toString();
63
+ const validatePaths = (values: readonly string[]): readonly string[] => Object.freeze(values.map(validatePath));
32
64
 
33
65
  const deriveProductRoot = (designDocsRoot: string): string => {
34
66
  const normalized = designDocsRoot.replace(/\/+$/, "");
35
67
  return normalized.endsWith("/construction") ? normalized.slice(0, -"/construction".length) : normalized;
36
68
  };
37
69
 
70
+ const resolveProductRoots = (input?: WorldResolvedConfigInput): readonly string[] => {
71
+ if (input?.corpus?.productRoots !== undefined) return validatePaths(input.corpus.productRoots);
72
+ if (input?.designDocsRoot === undefined) return Object.freeze(["docs/product"]);
73
+ const productRoot = validatePath(deriveProductRoot(input.designDocsRoot));
74
+ return Object.freeze(productRoot === "docs/product" ? [productRoot] : ["docs/product", productRoot]);
75
+ };
76
+
77
+ const assertLimit = (name: string, value: number, maximum: number): number => {
78
+ if (!Number.isInteger(value) || value < 1 || value > maximum) {
79
+ throw new Error(`${name} must be an integer between 1 and ${maximum}`);
80
+ }
81
+ return value;
82
+ };
83
+
38
84
  export const resolveWorldCorpusConfig = (input?: WorldResolvedConfigInput): WorldCorpusConfig => {
39
- const designDocsRoot = validatePath(input?.designDocsRoot ?? DEFAULT_DESIGN_DOCS_ROOT);
40
- const productRoot = validatePath(deriveProductRoot(designDocsRoot));
41
- const productScopes = [
42
- {
43
- productRoot: "docs/product",
44
- designDocsRoot: DEFAULT_DESIGN_DOCS_ROOT,
45
- unitRoot: "docs/product/units",
46
- },
47
- ...(productRoot === "docs/product"
48
- ? []
49
- : [{ productRoot, designDocsRoot, unitRoot: validatePath(`${productRoot}/units`) }]),
50
- ];
85
+ const productRoots = resolveProductRoots(input);
86
+ const inceptionRoots = validatePaths(input?.corpus?.inceptionRoots ?? [input?.inceptionRoot ?? "docs/inception"]);
87
+ const adrRoots = validatePaths(input?.corpus?.adrRoots ?? ["docs/ADR"]);
88
+ const sourceRoots = validatePaths(input?.corpus?.sourceRoots ?? ["scripts/harness"]);
89
+ if ([productRoots, inceptionRoots, adrRoots, sourceRoots].some((roots) => roots.length === 0)) {
90
+ throw new Error("World corpus roots must not be empty");
91
+ }
92
+ const include = Object.freeze([...(input?.corpus?.include ?? ["**/*"])]);
93
+ const exclude = Object.freeze([...(input?.corpus?.exclude ?? [])]);
94
+ if (include.length === 0 || [...include, ...exclude].some((pattern) => pattern.length === 0)) {
95
+ throw new Error("World corpus selection patterns must not be empty");
96
+ }
51
97
  return Object.freeze({
52
- productScopes: Object.freeze(productScopes),
53
- inceptionRoot: validatePath(input?.inceptionRoot ?? "docs/inception"),
54
- adrRoot: "docs/ADR",
55
- sourceRoot: "scripts/harness",
56
- include: Object.freeze(["**/*"]),
57
- exclude: Object.freeze([]),
58
- matrixPath: validatePath(input?.requirementMatrixPath ?? ".harness/requirement-test-matrix.json"),
59
- attestationPath: ".harness/attestation.json",
60
- integrityManifestPath: "phasegate.integrity.json",
98
+ enabled: input?.enabled ?? false,
99
+ productScopes: Object.freeze(
100
+ productRoots.map((productRoot) => ({
101
+ productRoot,
102
+ designDocsRoot: validatePath(`${productRoot}/construction`),
103
+ unitRoot: validatePath(`${productRoot}/units`),
104
+ })),
105
+ ),
106
+ inceptionRoots,
107
+ adrRoots,
108
+ sourceRoots,
109
+ include,
110
+ exclude,
111
+ matrixPath: validatePath(
112
+ input?.inputs?.matrixPath ?? input?.requirementMatrixPath ?? ".harness/requirement-test-matrix.json",
113
+ ),
114
+ attestationPath: validatePath(input?.inputs?.attestationPath ?? ".harness/attestation.json"),
115
+ integrityManifestPath: validatePath(input?.inputs?.integrityManifestPath ?? "phasegate.integrity.json"),
116
+ constraintsPath: validatePath(input?.declarations?.constraintsPath ?? "phasegate.world-constraints.json"),
117
+ baselinePath: validatePath(input?.declarations?.baselinePath ?? "phasegate.world-baseline.json"),
118
+ waiversPath: validatePath(input?.declarations?.waiversPath ?? "phasegate.world-waivers.json"),
119
+ debtsPath: validatePath(input?.declarations?.debtsPath ?? "phasegate.world-debts.json"),
120
+ obligationReportPath: validatePath(input?.output?.obligationReportPath ?? ".harness/world-obligations.json"),
121
+ sessionStart: Object.freeze({
122
+ enabled: input?.sessionStart?.enabled ?? true,
123
+ maxItems: assertLimit("world.sessionStart.maxItems", input?.sessionStart?.maxItems ?? 5, 20),
124
+ maxChars: assertLimit("world.sessionStart.maxChars", input?.sessionStart?.maxChars ?? 2000, 8000),
125
+ }),
61
126
  });
62
127
  };