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
@@ -1,13 +1,18 @@
1
1
  // @unit agent-integration
2
2
  // @layer presentation
3
+ // @work-item-id WI-304
3
4
 
4
5
  import { buildCiGovernance } from "../../ci-governance/composition-root.js";
6
+ import { createConfigFoundationModule, toWorldModelConfig } from "../../config-foundation/index.js";
7
+ import { GetOpenWorldObligationsContextUseCase } from "../application/usecases/get-open-world-obligations-context-usecase.js";
8
+ import { WorldModelOpenObligationsQueryAdapter } from "../infrastructure/adapters/world-model-open-obligations-query-adapter.js";
5
9
  import {
6
10
  buildIntegrityUnverifiableWarning,
7
11
  buildIntegrityWarning,
8
12
  buildSessionStartContext,
9
13
  collectPhasegateStatus,
10
14
  } from "./phasegate-status-context.js";
15
+ import { buildWorldObligationsSessionContext } from "./world-obligations-session-context.js";
11
16
 
12
17
  async function readStdin(): Promise<string> {
13
18
  const chunks: Buffer[] = [];
@@ -36,6 +41,36 @@ async function buildIntegrityContext(cwd: string): Promise<string | null> {
36
41
  }
37
42
  }
38
43
 
44
+ /**
45
+ * World の保存済み report は読まず、公開 facade の pure derive から現在の open obligation を要約する。
46
+ * config/derive の失敗は固定 warning へ縮退し、repository 由来のエラー文字列は中継しない。
47
+ */
48
+ async function buildWorldContext(cwd: string): Promise<string | null> {
49
+ try {
50
+ const configModule = createConfigFoundationModule();
51
+ const resolved = await configModule.usecases.loadResolvedConfigUseCase.execute();
52
+ const worldConfig = toWorldModelConfig(resolved.config);
53
+ if (worldConfig === undefined) return null;
54
+
55
+ const contextUseCase = new GetOpenWorldObligationsContextUseCase({
56
+ worldObligationsQueryPort: new WorldModelOpenObligationsQueryAdapter({
57
+ rootDir: cwd,
58
+ resolvedConfig: worldConfig,
59
+ }),
60
+ });
61
+ const context = await contextUseCase.execute({
62
+ enabled: worldConfig.enabled && worldConfig.sessionStart.enabled,
63
+ });
64
+ return buildWorldObligationsSessionContext(context, {
65
+ maxItems: worldConfig.sessionStart.maxItems,
66
+ maxChars: worldConfig.sessionStart.maxChars,
67
+ });
68
+ } catch (error) {
69
+ if (error instanceof Error && error.name === "ConfigNotFoundError") return null;
70
+ return buildWorldObligationsSessionContext({ status: "unavailable" }, { maxItems: 5, maxChars: 2000 });
71
+ }
72
+ }
73
+
39
74
  async function main(): Promise<void> {
40
75
  try {
41
76
  await readStdin();
@@ -46,7 +81,8 @@ async function main(): Promise<void> {
46
81
  const status = await collectPhasegateStatus(process.cwd());
47
82
  const baseContext = buildSessionStartContext(status);
48
83
  const integrityContext = await buildIntegrityContext(process.cwd());
49
- const additionalContext = integrityContext === null ? baseContext : `${integrityContext}\n\n${baseContext}`;
84
+ const worldContext = await buildWorldContext(process.cwd());
85
+ const additionalContext = [integrityContext, worldContext, baseContext].filter((item) => item !== null).join("\n\n");
50
86
 
51
87
  const output = {
52
88
  hookSpecificOutput: {
@@ -0,0 +1,60 @@
1
+ // @unit agent-integration
2
+ // @layer presentation
3
+ // @work-item-id WI-304
4
+
5
+ import type {
6
+ OpenWorldObligationContextItemDto,
7
+ OpenWorldObligationsContextDto,
8
+ } from "../application/dto/open-world-obligations-context-dto.js";
9
+
10
+ const UNAVAILABLE_WARNING =
11
+ "⚠ World obligations unavailable at SessionStart; continuing fail-open. Run phasegate world:derive.";
12
+ const HEADER = "## World open obligations (SessionStart)";
13
+ const HARD_MAX_ITEMS = 5;
14
+ const HARD_MAX_CHARS = 2000;
15
+
16
+ export const countUnicodeScalars = (value: string): number => [...value].length;
17
+
18
+ const renderEntry = (entry: OpenWorldObligationContextItemDto): string => {
19
+ if (entry.kind === "policy-diagnostic") {
20
+ return `- [BLOCKING ${entry.classification}] subject=${entry.subjectId ?? "unknown"}`;
21
+ }
22
+ const label =
23
+ entry.classification === "cleanup-required"
24
+ ? "CLEANUP"
25
+ : entry.classification === "waived"
26
+ ? "WAIVED"
27
+ : `BLOCKING ${entry.classification}`;
28
+ return `- [${label}] ${entry.ruleId ?? "unknown-rule"} constraint=${entry.constraintId ?? "implicit"} fingerprint=${entry.violationFingerprint ?? "none"}`;
29
+ };
30
+
31
+ const renderAvailable = (
32
+ input: Extract<OpenWorldObligationsContextDto, { status: "available" }>,
33
+ shown: number,
34
+ ): string => {
35
+ const lines = [HEADER, ...input.entries.slice(0, shown).map(renderEntry)];
36
+ if (input.adoptedLegacyCount > 0) lines.push(`- Adopted legacy: ${input.adoptedLegacyCount} (summary only)`);
37
+ if (input.entries.length === 0 && input.adoptedLegacyCount === 0) lines.push("- None.");
38
+ const omitted = input.entries.length - shown;
39
+ if (omitted > 0) lines.push(`... ${omitted} more; run phasegate world:derive`);
40
+ return lines.join("\n");
41
+ };
42
+
43
+ export function buildWorldObligationsSessionContext(
44
+ input: OpenWorldObligationsContextDto,
45
+ limits: { readonly maxItems: number; readonly maxChars: number },
46
+ ): string | null {
47
+ const maxItems = Math.min(limits.maxItems, HARD_MAX_ITEMS);
48
+ const maxChars = Math.min(limits.maxChars, HARD_MAX_CHARS);
49
+ if (input.status === "disabled") return null;
50
+ if (input.status === "unavailable") {
51
+ return countUnicodeScalars(UNAVAILABLE_WARNING) <= maxChars ? UNAVAILABLE_WARNING : null;
52
+ }
53
+
54
+ const maximumShown = Math.min(input.entries.length, maxItems);
55
+ for (let shown = maximumShown; shown >= 0; shown -= 1) {
56
+ const candidate = renderAvailable(input, shown);
57
+ if (countUnicodeScalars(candidate) <= maxChars) return candidate;
58
+ }
59
+ return null;
60
+ }
@@ -1,8 +1,9 @@
1
1
  // @unit attestation
2
2
  // @layer application
3
+ // @work-item-id WI-306
3
4
 
4
5
  /**
5
- * record format `phasegate-attestation/v1` plain object 表現(logical_design §1.4.3)。
6
+ * versioned record formatのplain object表現(logical_design §1.4.3 / WI-306)。
6
7
  * ファイル入出力・stdout エコー・mapper の境界で用いる契約型。VO はプリミティブ展開する。
7
8
  */
8
9
  export interface AttestationDocumentValidatorOutcome {
@@ -23,9 +24,7 @@ export interface AttestationDocumentGranularityClaim {
23
24
  readonly knownLimitations: string[];
24
25
  }
25
26
 
26
- export interface AttestationDocument {
27
- readonly schemaVersion: "phasegate-attestation/v1";
28
- readonly predicateType: string;
27
+ interface AttestationDocumentBase {
29
28
  readonly subject: {
30
29
  readonly command: string;
31
30
  readonly gateResult: "pass" | "fail";
@@ -54,3 +53,17 @@ export interface AttestationDocument {
54
53
  readonly value: string | null;
55
54
  };
56
55
  }
56
+
57
+ export interface AttestationDocumentV1 extends AttestationDocumentBase {
58
+ readonly schemaVersion: "phasegate-attestation/v1";
59
+ readonly predicateType: "https://phasegate.dev/attestation/gate-run/v1";
60
+ }
61
+
62
+ export interface AttestationDocumentV2 extends AttestationDocumentBase {
63
+ readonly schemaVersion: "phasegate-attestation/v2";
64
+ readonly predicateType: "https://phasegate.dev/attestation/gate-run/v2";
65
+ /** 実行時のWorld canonical corpusRoot。個別fragment digestは保持しない。 */
66
+ readonly worldSnapshotRoot: string;
67
+ }
68
+
69
+ export type AttestationDocument = AttestationDocumentV1 | AttestationDocumentV2;
@@ -1,5 +1,6 @@
1
1
  // @unit attestation
2
2
  // @layer application
3
+ // @work-item-id WI-306
3
4
 
4
5
  import {
5
6
  AttestationRecord,
@@ -44,15 +45,20 @@ function requireBoolean(v: unknown, field: string): boolean {
44
45
  return v;
45
46
  }
46
47
 
48
+ function rejectUnknownFields(value: Record<string, unknown>, allowed: readonly string[], field: string): void {
49
+ const unsupported = Object.keys(value).filter((key) => !allowed.includes(key));
50
+ if (unsupported.length > 0) {
51
+ throw new MalformedAttestationError(`${field} contains unsupported fields: ${unsupported.sort().join(", ")}`);
52
+ }
53
+ }
54
+
47
55
  /**
48
56
  * AttestationRecord(domain)↔ AttestationDocument(application DTO)の双方向変換。
49
57
  */
50
58
  export class AttestationRecordMapper {
51
59
  /** 集約を plain object へ射影(VO をプリミティブ展開)。 */
52
60
  toDocument(record: AttestationRecord): AttestationDocument {
53
- return {
54
- schemaVersion: record.schemaVersion as "phasegate-attestation/v1",
55
- predicateType: record.predicateType,
61
+ const common = {
56
62
  subject: {
57
63
  command: record.subject.command,
58
64
  gateResult: record.subject.gateResult,
@@ -63,7 +69,7 @@ export class AttestationRecordMapper {
63
69
  })),
64
70
  },
65
71
  inputs: {
66
- digestAlgorithm: "sha256",
72
+ digestAlgorithm: "sha256" as const,
67
73
  sources: record.inputs.sources.map((s) => ({ path: s.path, digest: s.digest.value })),
68
74
  inputDigest: record.inputs.inputDigest.value,
69
75
  },
@@ -89,6 +95,19 @@ export class AttestationRecordMapper {
89
95
  value: record.signature.value,
90
96
  },
91
97
  };
98
+ if (record.worldSnapshotRoot !== null) {
99
+ return {
100
+ ...common,
101
+ schemaVersion: "phasegate-attestation/v2",
102
+ predicateType: "https://phasegate.dev/attestation/gate-run/v2",
103
+ worldSnapshotRoot: record.worldSnapshotRoot.value,
104
+ };
105
+ }
106
+ return {
107
+ ...common,
108
+ schemaVersion: "phasegate-attestation/v1",
109
+ predicateType: "https://phasegate.dev/attestation/gate-run/v1",
110
+ };
92
111
  }
93
112
 
94
113
  /**
@@ -102,6 +121,37 @@ export class AttestationRecordMapper {
102
121
 
103
122
  const schemaVersion = requireString(doc.schemaVersion, "schemaVersion");
104
123
  const predicateType = requireString(doc.predicateType, "predicateType");
124
+ let worldSnapshotRoot: Digest | undefined;
125
+ if (schemaVersion === "phasegate-attestation/v1") {
126
+ if (predicateType !== "https://phasegate.dev/attestation/gate-run/v1") {
127
+ throw new MalformedAttestationError("v1 predicateType does not match schemaVersion");
128
+ }
129
+ if (doc.worldSnapshotRoot !== undefined) {
130
+ throw new MalformedAttestationError("v1 must not contain worldSnapshotRoot");
131
+ }
132
+ } else if (schemaVersion === "phasegate-attestation/v2") {
133
+ if (predicateType !== "https://phasegate.dev/attestation/gate-run/v2") {
134
+ throw new MalformedAttestationError("v2 predicateType does not match schemaVersion");
135
+ }
136
+ worldSnapshotRoot = this.toDigest(doc.worldSnapshotRoot, "worldSnapshotRoot");
137
+ rejectUnknownFields(
138
+ doc,
139
+ [
140
+ "schemaVersion",
141
+ "predicateType",
142
+ "subject",
143
+ "inputs",
144
+ "granularity",
145
+ "acBoundScope",
146
+ "worldSnapshotRoot",
147
+ "metadata",
148
+ "signature",
149
+ ],
150
+ "v2 attestation",
151
+ );
152
+ } else {
153
+ throw new MalformedAttestationError(`unsupported schemaVersion: ${schemaVersion}`);
154
+ }
105
155
 
106
156
  const subject = doc.subject;
107
157
  if (!isObject(subject)) {
@@ -185,6 +235,7 @@ export class AttestationRecordMapper {
185
235
  },
186
236
  signature: this.toSignatureBlock(mode, attestationDigest, signature),
187
237
  acBoundScope,
238
+ ...(worldSnapshotRoot === undefined ? {} : { worldSnapshotRoot }),
188
239
  };
189
240
 
190
241
  // verify 用の再構築: INV-1/INV-3 は verify usecase が機械的チェックとして検出するため
@@ -0,0 +1,10 @@
1
+ // @unit attestation
2
+ // @layer application
3
+ // @work-item-id WI-306
4
+
5
+ /**
6
+ * attestationが所有するconsumer port。providerのWorld domain型を公開しない。
7
+ */
8
+ export interface WorldSnapshotRootProvider {
9
+ getWorldSnapshotRoot(): Promise<string>;
10
+ }
@@ -1,23 +1,28 @@
1
1
  // @unit attestation
2
2
  // @layer application
3
+ // @work-item-id WI-306
3
4
 
4
5
  import { AttestationRecord, type GateResult, type SourceEntry } from "../../domain/entities/attestation-record.js";
5
6
  import type { ContentHasherPort } from "../../domain/ports/content-hasher-port.js";
6
- import type { GranularityDerivationService } from "../../domain/services/granularity-derivation-service.js";
7
7
  import type { AcBoundScopeService } from "../../domain/services/ac-bound-scope-service.js";
8
+ import type { GranularityDerivationService } from "../../domain/services/granularity-derivation-service.js";
9
+ import { Digest } from "../../domain/value-objects/digest.js";
8
10
  import { SignatureBlock } from "../../domain/value-objects/signature-block.js";
9
11
  import { ValidatorOutcome } from "../../domain/value-objects/validator-outcome.js";
10
12
  import type { AttestationDocument } from "../dto/attestation-document.js";
11
13
  import type { ProduceAttestationInput } from "../dto/produce-attestation-input.js";
12
14
  import type { AttestationRecordMapper } from "../mappers/attestation-record-mapper.js";
15
+ import type { AcBoundAllowlistPort } from "../ports/ac-bound-allowlist-port.js";
13
16
  import type { AttestationRepositoryPort } from "../ports/attestation-repository-port.js";
14
17
  import type { GateResultSourcePort } from "../ports/gate-result-source-port.js";
15
- import type { SourceDigesterPort } from "../ports/source-digester-port.js";
16
18
  import type { MatrixSourcePort } from "../ports/matrix-source-port.js";
17
- import type { AcBoundAllowlistPort } from "../ports/ac-bound-allowlist-port.js";
19
+ import type { SourceDigesterPort } from "../ports/source-digester-port.js";
20
+ import type { WorldSnapshotRootProvider } from "../ports/world-snapshot-root-provider.js";
18
21
 
19
- const SCHEMA_VERSION = "phasegate-attestation/v1";
20
- const PREDICATE_TYPE = "https://phasegate.dev/attestation/gate-run/v1";
22
+ const V1_SCHEMA_VERSION = "phasegate-attestation/v1";
23
+ const V1_PREDICATE_TYPE = "https://phasegate.dev/attestation/gate-run/v1";
24
+ const V2_SCHEMA_VERSION = "phasegate-attestation/v2";
25
+ const V2_PREDICATE_TYPE = "https://phasegate.dev/attestation/gate-run/v2";
21
26
  const DEFAULT_INPUT_SOURCES: readonly string[] = Object.freeze([
22
27
  "phasegate.config.json",
23
28
  ".harness/requirement-test-matrix.json",
@@ -46,11 +51,14 @@ export interface ProduceAttestationDeps {
46
51
  readonly acBoundScopeService?: AcBoundScopeService;
47
52
  /** H16-03: matrix 供給元パス(inputSourcePaths のうち matrix にあたるもの。既定は 2 番目の source)。 */
48
53
  readonly matrixFilePath?: string;
54
+ /** WI-306: 配線時はv2、未配線時は既存v1を生成する。 */
55
+ readonly worldSnapshotRootProvider?: WorldSnapshotRootProvider;
49
56
  }
50
57
 
51
58
  export interface ProduceAttestationResult {
52
59
  readonly document: AttestationDocument | null;
53
60
  readonly exitCode: 0 | 1 | 2;
61
+ readonly error?: string;
54
62
  }
55
63
 
56
64
  /**
@@ -77,6 +85,20 @@ export class ProduceAttestationUseCase {
77
85
  return { document: null, exitCode: 1 };
78
86
  }
79
87
 
88
+ // 3b. World root providerはv2 opt-in。失敗や不正digestをv1へdowngradeしない。
89
+ let worldSnapshotRoot: Digest | undefined;
90
+ if (this.deps.worldSnapshotRootProvider) {
91
+ try {
92
+ worldSnapshotRoot = Digest.create(await this.deps.worldSnapshotRootProvider.getWorldSnapshotRoot());
93
+ } catch (error) {
94
+ return {
95
+ document: null,
96
+ exitCode: 2,
97
+ error: `cannot obtain worldSnapshotRoot: ${error instanceof Error ? error.message : String(error)}`,
98
+ };
99
+ }
100
+ }
101
+
80
102
  // 4. inputs.sources 構築(config / matrix の sha256 + git commit SHA)と inputDigest 算出
81
103
  const sources = await this.buildSources();
82
104
  const preInputRecord = this.assembleRecord({
@@ -88,6 +110,7 @@ export class ProduceAttestationUseCase {
88
110
  producer: `phasegate-attestation/${this.deps.pkgVersion}`,
89
111
  gitCommit: null,
90
112
  acBoundScope: [],
113
+ worldSnapshotRoot,
91
114
  });
92
115
  const inputDigest = preInputRecord.computeInputDigest(this.deps.hasher);
93
116
 
@@ -110,6 +133,7 @@ export class ProduceAttestationUseCase {
110
133
  producer: `phasegate-attestation/${this.deps.pkgVersion}`,
111
134
  gitCommit,
112
135
  acBoundScope,
136
+ worldSnapshotRoot,
113
137
  });
114
138
  const sealed = record.seal(this.deps.hasher);
115
139
 
@@ -130,11 +154,12 @@ export class ProduceAttestationUseCase {
130
154
  producer: string;
131
155
  gitCommit: string | null;
132
156
  acBoundScope: readonly string[];
157
+ worldSnapshotRoot?: Digest;
133
158
  }): AttestationRecord {
134
159
  const granularity = this.deps.granularityService.derive(args.validatorSet);
135
160
  return AttestationRecord.create({
136
- schemaVersion: SCHEMA_VERSION,
137
- predicateType: PREDICATE_TYPE,
161
+ schemaVersion: args.worldSnapshotRoot ? V2_SCHEMA_VERSION : V1_SCHEMA_VERSION,
162
+ predicateType: args.worldSnapshotRoot ? V2_PREDICATE_TYPE : V1_PREDICATE_TYPE,
138
163
  subject: {
139
164
  command: "phasegate:ci-check",
140
165
  gateResult: args.gateResult,
@@ -154,6 +179,7 @@ export class ProduceAttestationUseCase {
154
179
  // 仮の unsigned-poc block(seal 前)。seal が正しい digest で置換する。
155
180
  signature: SignatureBlock.unsignedPoc(args.inputDigest),
156
181
  acBoundScope: args.acBoundScope,
182
+ ...(args.worldSnapshotRoot ? { worldSnapshotRoot: args.worldSnapshotRoot } : {}),
157
183
  });
158
184
  }
159
185
 
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * @layer infrastructure
3
3
  * @unit attestation
4
+ * @work-item-id WI-306
4
5
  *
5
6
  * Composition Root — attestation Unit の依存性組み立て。
6
7
  * createAttestationModule() は adapters + usecases + handlers を組み立て、
@@ -11,6 +12,7 @@ import { execFile } from "node:child_process";
11
12
  import { promisify } from "node:util";
12
13
  import { AttestationRecordMapper } from "./application/mappers/attestation-record-mapper.js";
13
14
  import type { Sha256Capability } from "./application/ports/sha256-capability.js";
15
+ import type { WorldSnapshotRootProvider } from "./application/ports/world-snapshot-root-provider.js";
14
16
  import { ProduceAttestationUseCase } from "./application/usecases/produce-attestation-usecase.js";
15
17
  import { VerifyAttestationUseCase } from "./application/usecases/verify-attestation-usecase.js";
16
18
  import { AcBoundScopeService } from "./domain/services/ac-bound-scope-service.js";
@@ -41,6 +43,8 @@ export interface AttestationModuleOptions {
41
43
  readonly mainTsPath?: string;
42
44
  /** metadata.producedAt 用クロックのオーバーライド(テスト用)。 */
43
45
  readonly clock?: () => Date;
46
+ /** WI-306: 配線時はv2 recordへplain World corpus rootを注入する。 */
47
+ readonly worldSnapshotRootProvider?: WorldSnapshotRootProvider;
44
48
  }
45
49
 
46
50
  /**
@@ -87,6 +91,7 @@ export function createAttestationModule(rootDir: string, options?: AttestationMo
87
91
  matrixSource,
88
92
  allowlist,
89
93
  acBoundScopeService,
94
+ worldSnapshotRootProvider: options?.worldSnapshotRootProvider,
90
95
  });
91
96
  const verifyUseCase = new VerifyAttestationUseCase({
92
97
  repository,
@@ -1,5 +1,6 @@
1
1
  // @unit attestation
2
2
  // @layer domain
3
+ // @work-item-id WI-306
3
4
 
4
5
  import type { ContentHasherPort } from "../ports/content-hasher-port.js";
5
6
  import { GranularityDerivationService } from "../services/granularity-derivation-service.js";
@@ -47,6 +48,8 @@ export interface AttestationRecordProps {
47
48
  * 省略時は [] として扱う(additive-safe)。
48
49
  */
49
50
  readonly acBoundScope?: readonly string[];
51
+ /** v2だけが持つ実行時World corpus root。 */
52
+ readonly worldSnapshotRoot?: Digest;
50
53
  }
51
54
 
52
55
  /**
@@ -94,6 +97,7 @@ export class AttestationRecord {
94
97
  readonly metadata: MetadataSection;
95
98
  readonly signature: SignatureBlock;
96
99
  readonly acBoundScope: readonly string[];
100
+ readonly worldSnapshotRoot: Digest | null;
97
101
 
98
102
  private constructor(props: AttestationRecordProps) {
99
103
  this.schemaVersion = props.schemaVersion;
@@ -104,10 +108,12 @@ export class AttestationRecord {
104
108
  this.metadata = props.metadata;
105
109
  this.signature = props.signature;
106
110
  this.acBoundScope = Object.freeze([...(props.acBoundScope ?? [])]);
111
+ this.worldSnapshotRoot = props.worldSnapshotRoot ?? null;
107
112
  Object.freeze(this);
108
113
  }
109
114
 
110
115
  static create(props: AttestationRecordProps): AttestationRecord {
116
+ AttestationRecord.assertVersionContract(props);
111
117
  // INV-1: gateResult == "pass" iff validatorSet.every(passed || skipped)
112
118
  const allGreen = props.subject.validatorSet.every((o) => o.isGreen());
113
119
  const expectedGateResult: GateResult = allGreen ? "pass" : "fail";
@@ -149,6 +155,22 @@ export class AttestationRecord {
149
155
  return new AttestationRecord(props);
150
156
  }
151
157
 
158
+ private static assertVersionContract(props: AttestationRecordProps): void {
159
+ const v1 =
160
+ props.schemaVersion === "phasegate-attestation/v1" &&
161
+ props.predicateType === "https://phasegate.dev/attestation/gate-run/v1" &&
162
+ props.worldSnapshotRoot === undefined;
163
+ const v2 =
164
+ props.schemaVersion === "phasegate-attestation/v2" &&
165
+ props.predicateType === "https://phasegate.dev/attestation/gate-run/v2" &&
166
+ props.worldSnapshotRoot !== undefined;
167
+ if (!v1 && !v2) {
168
+ throw new AttestationInvariantError(
169
+ "version contract violated: schema, predicate, and worldSnapshotRoot disagree",
170
+ );
171
+ }
172
+ }
173
+
152
174
  gateResult(): GateResult {
153
175
  return this.subject.gateResult;
154
176
  }
@@ -165,10 +187,9 @@ export class AttestationRecord {
165
187
 
166
188
  /**
167
189
  * §1.4.1 step1-3: signature ブロック全体と volatile metadata(producedAt/gitCommit)を
168
- * 除去した plain object を返す(producer は決定論的なので残す)。
169
190
  */
170
191
  toCanonicalPayload(): Record<string, unknown> {
171
- return {
192
+ const payload: Record<string, unknown> = {
172
193
  schemaVersion: this.schemaVersion,
173
194
  predicateType: this.predicateType,
174
195
  subject: {
@@ -199,6 +220,10 @@ export class AttestationRecord {
199
220
  producer: this.metadata.producer,
200
221
  },
201
222
  };
223
+ if (this.worldSnapshotRoot !== null) {
224
+ payload.worldSnapshotRoot = this.worldSnapshotRoot.value;
225
+ }
226
+ return payload;
202
227
  }
203
228
 
204
229
  /**
@@ -222,6 +247,7 @@ export class AttestationRecord {
222
247
  metadata: this.metadata,
223
248
  signature: SignatureBlock.unsignedPoc(digest),
224
249
  acBoundScope: this.acBoundScope,
250
+ ...(this.worldSnapshotRoot === null ? {} : { worldSnapshotRoot: this.worldSnapshotRoot }),
225
251
  });
226
252
  }
227
253
 
@@ -247,6 +273,15 @@ export class AttestationRecord {
247
273
  if (!this.granularity.traceability.equals(other.granularity.traceability)) return false;
248
274
  if (this.acBoundScope.length !== other.acBoundScope.length) return false;
249
275
  if (!this.acBoundScope.every((s, i) => s === other.acBoundScope[i])) return false;
276
+ if (this.worldSnapshotRoot === null && other.worldSnapshotRoot !== null) return false;
277
+ if (this.worldSnapshotRoot !== null && other.worldSnapshotRoot === null) return false;
278
+ if (
279
+ this.worldSnapshotRoot !== null &&
280
+ other.worldSnapshotRoot !== null &&
281
+ !this.worldSnapshotRoot.equals(other.worldSnapshotRoot)
282
+ ) {
283
+ return false;
284
+ }
250
285
  if (!this.signature.equals(other.signature)) return false;
251
286
  return true;
252
287
  }
@@ -1,15 +1,21 @@
1
1
  // @layer infrastructure
2
2
  // @unit attestation
3
+ // @work-item-id WI-306
3
4
  // index.ts — attestation Unit の公開バレルエクスポート
4
5
 
5
6
  // Public DTO types
6
- export type { AttestationDocument } from "./application/dto/attestation-document.js";
7
+ export type {
8
+ AttestationDocument,
9
+ AttestationDocumentV1,
10
+ AttestationDocumentV2,
11
+ } from "./application/dto/attestation-document.js";
7
12
  export type { VerifyAttestationOutput } from "./application/dto/verify-attestation-output.js";
8
13
  export {
9
14
  hashUtf8,
10
15
  type Sha256Capability,
11
16
  type Sha256DigestString,
12
17
  } from "./application/ports/sha256-capability.js";
18
+ export type { WorldSnapshotRootProvider } from "./application/ports/world-snapshot-root-provider.js";
13
19
  export type { AttestationModule, AttestationModuleOptions } from "./composition-root.js";
14
20
  // Composition Root
15
21
  export { createAttestationModule, createSha256Capability } from "./composition-root.js";
@@ -1,5 +1,6 @@
1
1
  // @unit attestation
2
2
  // @layer presentation
3
+ // @work-item-id WI-306
3
4
 
4
5
  import type { ProduceAttestationInput } from "../../application/dto/produce-attestation-input.js";
5
6
  import type { ProduceAttestationUseCase } from "../../application/usecases/produce-attestation-usecase.js";
@@ -46,8 +47,10 @@ export class AttestHandler {
46
47
  const result = await this.useCase.execute(input);
47
48
 
48
49
  if (result.exitCode === 2) {
49
- // 唯一の usecase 由来 2 は mode === "signed"。
50
- return { output: 'Error: --mode "signed" is not yet implemented (only unsigned-poc is supported)', exitCode: 2 };
50
+ const output = result.error
51
+ ? `Error: ${result.error}`
52
+ : 'Error: --mode "signed" is not yet implemented (only unsigned-poc is supported)';
53
+ return { output, exitCode: 2 };
51
54
  }
52
55
 
53
56
  if (result.exitCode === 1) {
@@ -7,6 +7,9 @@
7
7
  * @work-item-id WI-258
8
8
  * @work-item-id WI-259
9
9
  * @work-item-id WI-268
10
+ * @work-item-id WI-300
11
+ * @work-item-id WI-301
12
+ * @work-item-id WI-302
10
13
  */
11
14
  import type { HarnessConfigV2 } from "../../domain/harness-config.js";
12
15
 
@@ -23,6 +26,7 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
23
26
  "ac-bound-coverage": "L3-005",
24
27
  "injection-scan": "L3-006",
25
28
  "coverage-attestation-verification": "L3-007",
29
+ "world-constraint-rederivation": "L3-008",
26
30
  },
27
31
  /^L3-\d{3}$/,
28
32
  );
@@ -60,7 +64,10 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
60
64
  // WI-258 / ADR-030 §Decision.3.②: L2-016 (coverage-attestation-gating) を default-ON でリストに含める。
61
65
  L2: {
62
66
  enabled: resolvedConfig.layers.L2.enabled,
63
- validators: ["L2-001", "L2-002", "L2-003", "L2-013", "L2-014", "L2-015", "L2-016"],
67
+ validators:
68
+ resolvedConfig.world?.enabled === true
69
+ ? ["L2-001", "L2-002", "L2-003", "L2-013", "L2-014", "L2-015", "L2-016", "L2-017"]
70
+ : ["L2-001", "L2-002", "L2-003", "L2-013", "L2-014", "L2-015", "L2-016"],
64
71
  },
65
72
  L3: {
66
73
  enabled: resolvedConfig.layers.L3.enabled,
@@ -68,13 +75,28 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
68
75
  // WI-268 / ADR-030 §Decision.1・§Decision.3.② 第2段: L3-007 (coverage-attestation-verification)
69
76
  // は fail-closed default-ON。いずれも fallback 判定の後に force-include し、normalize 結果でも
70
77
  // fallback でも常に含める(L3-007 は fail-closed だが現 corpus は実参照 0 件ゆえ緑)。
71
- validators: includeValidator(
72
- includeValidator(
73
- l3Validators.length > 0 ? l3Validators : ["L3-001", "L3-002", "L3-003", "L3-004"],
74
- "L3-006",
75
- ),
76
- "L3-007",
77
- ),
78
+ validators:
79
+ resolvedConfig.world?.enabled === true
80
+ ? includeValidator(
81
+ includeValidator(
82
+ includeValidator(
83
+ l3Validators.length > 0 ? l3Validators : ["L3-001", "L3-002", "L3-003", "L3-004"],
84
+ "L3-006",
85
+ ),
86
+ "L3-007",
87
+ ),
88
+ "L3-008",
89
+ )
90
+ : excludeValidator(
91
+ includeValidator(
92
+ includeValidator(
93
+ l3Validators.length > 0 ? l3Validators : ["L3-001", "L3-002", "L3-003", "L3-004"],
94
+ "L3-006",
95
+ ),
96
+ "L3-007",
97
+ ),
98
+ "L3-008",
99
+ ),
78
100
  coverageThreshold: resolvedConfig.layers.L3.coverageThreshold,
79
101
  requirementMatrixPath: resolvedConfig.layers.L3.requirementMatrixPath,
80
102
  // WI-227 / H16-03: L3-005 のスコープ(additive-safe。未設定は [])
@@ -96,6 +118,7 @@ export function toValidatorSystemConfig(resolvedConfig: HarnessConfigV2 | undefi
96
118
  validate: {
97
119
  failOnWarning: resolvedConfig.validate.failOnWarning,
98
120
  },
121
+ ...(resolvedConfig.world === undefined ? {} : { world: structuredClone(resolvedConfig.world) }),
99
122
  };
100
123
  }
101
124
 
@@ -110,6 +133,10 @@ function includeValidator(validators: readonly string[], validatorId: string): r
110
133
  return validators.includes(validatorId) ? validators : [...validators, validatorId];
111
134
  }
112
135
 
136
+ function excludeValidator(validators: readonly string[], validatorId: string): readonly string[] {
137
+ return validators.filter((candidate) => candidate !== validatorId);
138
+ }
139
+
113
140
  function normalizeValidators(
114
141
  validators: readonly string[],
115
142
  aliases: Readonly<Record<string, string>>,
@@ -0,0 +1,15 @@
1
+ // @unit config-foundation
2
+ // @layer application
3
+ // @work-item-id WI-300
4
+
5
+ import type { HarnessConfigV2 } from "../../domain/harness-config.js";
6
+ import {
7
+ WORLD_CONFIG_DEFAULTS,
8
+ WorldConfig,
9
+ type WorldConfigDocument,
10
+ } from "../../domain/value-objects/world-config.js";
11
+
12
+ export function toWorldModelConfig(resolvedConfig: HarnessConfigV2 | undefined): WorldConfigDocument | undefined {
13
+ if (!resolvedConfig) return undefined;
14
+ return WorldConfig.create(resolvedConfig.world ?? WORLD_CONFIG_DEFAULTS).toDocument();
15
+ }