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,8 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-306
4
+
5
+ export interface WorldSnapshotRootDto {
6
+ readonly schemaVersion: "phasegate-world-snapshot-root/v1";
7
+ readonly worldSnapshotRoot: string;
8
+ }
@@ -0,0 +1,63 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-305
4
+
5
+ import type { PinnedDesignEndpointDto, PinnedDesignEndpointReadResultDto } from "../dto/pinned-design-endpoint-dto.js";
6
+ import type { ConstraintDeclarationRepositoryPort } from "../ports/world-control-declaration-repository-port.js";
7
+
8
+ const EXPLICIT_FRAGMENT_ID = /^pgw:v1:fragment:(product|inception):(.+)$/;
9
+ const compare = (left: string, right: string): number => (left < right ? -1 : left > right ? 1 : 0);
10
+
11
+ export class PinnedDesignEndpointFacade {
12
+ constructor(private readonly repository: ConstraintDeclarationRepositoryPort) {}
13
+
14
+ async read(): Promise<PinnedDesignEndpointReadResultDto> {
15
+ const result = await this.repository.load();
16
+ if (result.state === "invalid") {
17
+ return Object.freeze({
18
+ state: "unavailable",
19
+ diagnosticCodes: Object.freeze([...new Set(result.diagnostics.map((item) => item.code))].sort(compare)),
20
+ });
21
+ }
22
+ if (result.value.malformedDeclarations.length > 0 || result.value.diagnostics.length > 0) {
23
+ return Object.freeze({
24
+ state: "unavailable",
25
+ diagnosticCodes: Object.freeze(
26
+ [
27
+ ...(result.value.malformedDeclarations.length > 0 ? ["malformed-constraint-declaration"] : []),
28
+ ...result.value.diagnostics.map((item) => item.code),
29
+ ].sort(compare),
30
+ ),
31
+ });
32
+ }
33
+ const endpoints: PinnedDesignEndpointDto[] = [];
34
+ for (const record of result.value.records) {
35
+ for (const endpoint of ["claimant", "premise"] as const) {
36
+ const nodeId = record[endpoint].nodeId.toString();
37
+ const match = EXPLICIT_FRAGMENT_ID.exec(nodeId);
38
+ if (!match) continue;
39
+ endpoints.push(
40
+ Object.freeze({
41
+ constraintId: record.constraintId.toString(),
42
+ endpoint,
43
+ nodeId,
44
+ corpusRole: match[1] as "product" | "inception",
45
+ declaredKey: match[2],
46
+ }),
47
+ );
48
+ }
49
+ }
50
+ endpoints.sort(
51
+ (left, right) =>
52
+ compare(left.corpusRole, right.corpusRole) ||
53
+ compare(left.declaredKey, right.declaredKey) ||
54
+ compare(left.constraintId, right.constraintId) ||
55
+ compare(left.endpoint, right.endpoint),
56
+ );
57
+ return Object.freeze({
58
+ state: "available",
59
+ endpoints: Object.freeze(endpoints),
60
+ diagnosticCodes: Object.freeze([]),
61
+ });
62
+ }
63
+ }
@@ -0,0 +1,19 @@
1
+ // @unit world-model
2
+ // @layer application
3
+ // @work-item-id WI-306
4
+
5
+ import type { WorldSnapshotRootDto } from "../dto/world-snapshot-root-dto.js";
6
+ import type { BuildSnapshotContract } from "../usecases/build-snapshot-use-case.js";
7
+
8
+ /** World domain型を公開せずcurrent corpusRootだけを返すpublic read facade。 */
9
+ export class WorldSnapshotRootFacade {
10
+ constructor(private readonly buildSnapshot: BuildSnapshotContract) {}
11
+
12
+ async read(): Promise<WorldSnapshotRootDto> {
13
+ const snapshot = await this.buildSnapshot.execute();
14
+ return {
15
+ schemaVersion: "phasegate-world-snapshot-root/v1",
16
+ worldSnapshotRoot: snapshot.corpusRoot.toString(),
17
+ };
18
+ }
19
+ }
@@ -1,6 +1,7 @@
1
1
  // @unit world-model
2
2
  // @layer application
3
3
  // @work-item-id WI-296, WI-297
4
+ // @work-item-id WI-300
4
5
 
5
6
  import { ExtractionDiagnostic } from "../../domain/entities/extraction-diagnostic.js";
6
7
  import type { Snapshot } from "../../domain/entities/snapshot.js";
@@ -38,6 +39,7 @@ interface Dependencies {
38
39
  readonly policyDate: PolicyDatePort;
39
40
  readonly constraintConfigDigest: Sha256Digest;
40
41
  readonly evaluationConfigDigest: Sha256Digest;
42
+ readonly constraintPath?: string;
41
43
  }
42
44
 
43
45
  const RULESET_VERSION = "phasegate-world-wcr/v1";
@@ -64,7 +66,7 @@ export class DeriveWorldObligationsUseCase {
64
66
  ...constraints.value.malformedDeclarations.map((declaration) =>
65
67
  ExtractionDiagnostic.create({
66
68
  code: "malformed-constraint-declaration",
67
- path: PathKey.create("phasegate.world-constraints.json"),
69
+ path: PathKey.create(this.dependencies.constraintPath ?? "phasegate.world-constraints.json"),
68
70
  payload: declaration.toCanonicalValue(),
69
71
  }),
70
72
  ),
@@ -4,6 +4,9 @@
4
4
  // @work-item-id WI-292
5
5
  // @work-item-id WI-295
6
6
  // @work-item-id WI-296, WI-297
7
+ // @work-item-id WI-300
8
+ // @work-item-id WI-305
9
+ // @work-item-id WI-306
7
10
 
8
11
  import { createAttestationModule, createSha256Capability } from "../attestation/index.js";
9
12
  import { createTraceabilityModelModule } from "../traceability-model/index.js";
@@ -11,6 +14,8 @@ import {
11
14
  resolveWorldCorpusConfig,
12
15
  type WorldResolvedConfigInput,
13
16
  } from "./application/dto/world-resolved-config-input.js";
17
+ import { PinnedDesignEndpointFacade } from "./application/facades/pinned-design-endpoint-facade.js";
18
+ import { WorldSnapshotRootFacade } from "./application/facades/world-snapshot-root-facade.js";
14
19
  import { BuildSnapshotUseCase } from "./application/usecases/build-snapshot-use-case.js";
15
20
  import { DeriveObligationsUseCase } from "./application/usecases/derive-obligations-use-case.js";
16
21
  import {
@@ -68,15 +73,17 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
68
73
  const hashingPort = new AttestationSha256WorldHashingAdapter(createSha256Capability());
69
74
  const serializer = new CanonicalJsonSerializer();
70
75
  const markdownExtractor = new MarkdownDesignFactExtractor({ hashingPort });
71
- const traceabilityFacades = config.productScopes.map(
72
- (scope) =>
73
- createTraceabilityModelModule(options.rootDir, {
74
- pathRoots: {
75
- designDocsRoot: scope.designDocsRoot,
76
- inceptionRoot: config.inceptionRoot,
77
- testRoots: [`${config.sourceRoot}/__tests__`],
78
- },
79
- }).worldReadFacade,
76
+ const traceabilityFacades = config.productScopes.flatMap((scope) =>
77
+ config.inceptionRoots.map(
78
+ (inceptionRoot) =>
79
+ createTraceabilityModelModule(options.rootDir, {
80
+ pathRoots: {
81
+ designDocsRoot: scope.designDocsRoot,
82
+ inceptionRoot,
83
+ testRoots: config.sourceRoots.map((sourceRoot) => `${sourceRoot}/__tests__`),
84
+ },
85
+ }).worldReadFacade,
86
+ ),
80
87
  );
81
88
  const designCorpus = new DesignCorpusFactExtractor({
82
89
  traceabilityAdapter: new TraceabilityDesignFactAdapter({
@@ -94,16 +101,14 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
94
101
  }),
95
102
  ),
96
103
  ),
97
- proposalExtractor: new ProposalFactExtractor({
98
- rootDir: options.rootDir,
99
- markdownExtractor,
100
- inceptionRoot: config.inceptionRoot,
101
- }),
102
- adrExtractor: new AdrFactExtractor({
103
- rootDir: options.rootDir,
104
- markdownExtractor,
105
- adrRoot: config.adrRoot,
106
- }),
104
+ proposalExtractor: new CompositeDesignFactSource(
105
+ config.inceptionRoots.map(
106
+ (inceptionRoot) => new ProposalFactExtractor({ rootDir: options.rootDir, markdownExtractor, inceptionRoot }),
107
+ ),
108
+ ),
109
+ adrExtractor: new CompositeDesignFactSource(
110
+ config.adrRoots.map((adrRoot) => new AdrFactExtractor({ rootDir: options.rootDir, markdownExtractor, adrRoot })),
111
+ ),
107
112
  unitExtractor: new CompositeDesignFactSource(
108
113
  config.productScopes.map(
109
114
  (scope) =>
@@ -118,16 +123,10 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
118
123
  const attestationModule = createAttestationModule(options.rootDir);
119
124
  const factSource = new AssembledWorldFactSource([
120
125
  designCorpus,
121
- new SourceMetadataFactExtractor({
122
- rootDir: options.rootDir,
123
- hashingPort,
124
- sourceRoot: config.sourceRoot,
125
- }),
126
- new TestReferenceSourceFactExtractor({
127
- rootDir: options.rootDir,
128
- hashingPort,
129
- sourceRoot: config.sourceRoot,
130
- }),
126
+ ...config.sourceRoots.flatMap((sourceRoot) => [
127
+ new SourceMetadataFactExtractor({ rootDir: options.rootDir, hashingPort, sourceRoot }),
128
+ new TestReferenceSourceFactExtractor({ rootDir: options.rootDir, hashingPort, sourceRoot }),
129
+ ]),
131
130
  new MatrixFactExtractor({
132
131
  rootDir: options.rootDir,
133
132
  hashingPort,
@@ -148,7 +147,22 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
148
147
  serializer,
149
148
  }),
150
149
  ]);
151
- const corpusConfigDigest = hashingPort.sha256(serializer.serialize(config));
150
+ const corpusConfigDigest = hashingPort.sha256(
151
+ serializer.serialize({
152
+ schemaVersion: "phasegate-world-corpus-config/v1",
153
+ productScopes: config.productScopes,
154
+ inceptionRoots: config.inceptionRoots,
155
+ adrRoots: config.adrRoots,
156
+ sourceRoots: config.sourceRoots,
157
+ include: config.include,
158
+ exclude: config.exclude,
159
+ inputs: {
160
+ matrixPath: config.matrixPath,
161
+ attestationPath: config.attestationPath,
162
+ integrityManifestPath: config.integrityManifestPath,
163
+ },
164
+ }),
165
+ );
152
166
  const buildSnapshotUseCase = new BuildSnapshotUseCase({
153
167
  factSource,
154
168
  rootDeriver: new SnapshotRootDeriver(serializer, hashingPort),
@@ -158,26 +172,34 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
158
172
  corpusConfigDigest,
159
173
  });
160
174
  const inspectWorldUseCase = new InspectWorldUseCase({ buildSnapshot: buildSnapshotUseCase });
175
+ const worldSnapshotRootFacade = new WorldSnapshotRootFacade(buildSnapshotUseCase);
161
176
  const fingerprintDeriver = new ViolationFingerprintDeriver(serializer, hashingPort);
162
177
  const constraintRepository = new FileSystemConstraintDeclarationRepositoryAdapter({
163
178
  rootDir: options.rootDir,
179
+ fileName: config.constraintsPath,
164
180
  });
165
181
  const rootDeriver = new SnapshotRootDeriver(serializer, hashingPort);
166
182
  const deriveObligationsUseCase = new DeriveObligationsUseCase({
167
183
  baselineRepository: new FileSystemAdoptionBaselineRepositoryAdapter({
168
184
  rootDir: options.rootDir,
185
+ fileName: config.baselinePath,
169
186
  }),
170
187
  waiverRepository: new FileSystemWaiverDeclarationRepositoryAdapter({
171
188
  rootDir: options.rootDir,
189
+ fileName: config.waiversPath,
172
190
  }),
173
191
  semanticDebtRepository: new FileSystemSemanticDebtRepositoryAdapter({
174
192
  rootDir: options.rootDir,
193
+ fileName: config.debtsPath,
175
194
  }),
176
195
  policyInputsDigestDeriver: new PolicyInputsDigestDeriver(serializer, hashingPort),
177
196
  evaluationIdDeriver: new SnapshotRootDeriver(serializer, hashingPort),
178
197
  obligationDerivationService: new ObligationDerivationService(fingerprintDeriver),
179
198
  serializer,
180
- writer: new FileSystemObligationReportWriterAdapter({ rootDir: options.rootDir }),
199
+ writer: new FileSystemObligationReportWriterAdapter({
200
+ rootDir: options.rootDir,
201
+ reportPath: config.obligationReportPath,
202
+ }),
181
203
  });
182
204
  const deriveWorldObligationsUseCase = new DeriveWorldObligationsUseCase({
183
205
  buildSnapshot: buildSnapshotUseCase,
@@ -190,7 +212,7 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
190
212
  constraintConfigDigest: hashingPort.sha256(
191
213
  serializer.serialize({
192
214
  schemaVersion: "phasegate-world-constraint-config/v1",
193
- constraintsPath: "phasegate.world-constraints.json",
215
+ constraintsPath: config.constraintsPath,
194
216
  }),
195
217
  ),
196
218
  evaluationConfigDigest: hashingPort.sha256(
@@ -199,11 +221,16 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
199
221
  rulesetVersion: "phasegate-world-wcr/v1",
200
222
  }),
201
223
  ),
224
+ constraintPath: config.constraintsPath,
202
225
  });
203
226
  const pinConstraintEndpointUseCase = new PinConstraintEndpointUseCase(buildSnapshotUseCase, constraintRepository);
227
+ const pinnedDesignEndpointFacade = new PinnedDesignEndpointFacade(constraintRepository);
204
228
  const worldInspectCommandHandler = new WorldInspectCommandHandler({ inspectWorld: inspectWorldUseCase });
205
229
  const worldPinCommandHandler = new WorldPinCommandHandler(pinConstraintEndpointUseCase);
206
- const worldDeriveCommandHandler = new WorldDeriveCommandHandler(deriveWorldObligationsUseCase);
230
+ const worldDeriveCommandHandler = new WorldDeriveCommandHandler(
231
+ deriveWorldObligationsUseCase,
232
+ config.obligationReportPath,
233
+ );
207
234
 
208
235
  return {
209
236
  buildSnapshotUseCase,
@@ -211,8 +238,10 @@ export function createWorldModelModule(options: WorldModelModuleOptions) {
211
238
  deriveWorldObligationsUseCase,
212
239
  inspectWorldUseCase,
213
240
  pinConstraintEndpointUseCase,
241
+ pinnedDesignEndpointFacade,
214
242
  worldDeriveCommandHandler,
215
243
  worldInspectCommandHandler,
244
+ worldSnapshotRootFacade,
216
245
  worldPinCommandHandler,
217
246
  } as const;
218
247
  }
@@ -1,7 +1,14 @@
1
1
  // @unit world-model
2
2
  // @layer public-api
3
3
  // @work-item-id WI-291, WI-296
4
+ // @work-item-id WI-304
5
+ // @work-item-id WI-305
6
+ // @work-item-id WI-306
4
7
 
8
+ export type {
9
+ PinnedDesignEndpointDto,
10
+ PinnedDesignEndpointReadResultDto,
11
+ } from "./application/dto/pinned-design-endpoint-dto.js";
5
12
  export type {
6
13
  WorldExtractionDiagnosticDto,
7
14
  WorldInspectionDto,
@@ -9,10 +16,14 @@ export type {
9
16
  WorldJsonObject,
10
17
  WorldJsonValue,
11
18
  } from "./application/dto/world-inspection-dto.js";
19
+ export type { WorldObligationReportDto } from "./application/dto/world-obligation-report-dto.js";
12
20
  export type {
13
21
  WorldCorpusConfig,
14
22
  WorldResolvedConfigInput,
15
23
  } from "./application/dto/world-resolved-config-input.js";
24
+ export type { WorldSnapshotRootDto } from "./application/dto/world-snapshot-root-dto.js";
25
+ export { PinnedDesignEndpointFacade } from "./application/facades/pinned-design-endpoint-facade.js";
26
+ export { WorldSnapshotRootFacade } from "./application/facades/world-snapshot-root-facade.js";
16
27
  export type { WorldModelModuleOptions } from "./composition-root.js";
17
28
  export { createWorldModelModule } from "./composition-root.js";
18
29
  export type { WorldCommandResult } from "./presentation/cli/world-command-support.js";
@@ -1,6 +1,7 @@
1
1
  // @unit world-model
2
2
  // @layer infrastructure
3
3
  // @work-item-id WI-290
4
+ // @work-item-id WI-306
4
5
 
5
6
  import type {
6
7
  AttestationDocument,
@@ -114,13 +115,24 @@ export class AttestationFactExtractor {
114
115
 
115
116
  private parseDocument(value: unknown): AttestationDocument {
116
117
  const root = requireObject(value, "attestation");
118
+ const schemaVersion = requireString(root.schemaVersion, "attestation.schemaVersion");
119
+ const isV2 = schemaVersion === "phasegate-attestation/v2";
117
120
  assertExactKeys(
118
121
  root,
119
- ["schemaVersion", "predicateType", "subject", "inputs", "granularity", "acBoundScope", "metadata", "signature"],
122
+ [
123
+ "schemaVersion",
124
+ "predicateType",
125
+ "subject",
126
+ "inputs",
127
+ "granularity",
128
+ "acBoundScope",
129
+ "metadata",
130
+ "signature",
131
+ ...(isV2 ? ["worldSnapshotRoot"] : []),
132
+ ],
120
133
  "attestation",
121
134
  );
122
- const schemaVersion = requireString(root.schemaVersion, "attestation.schemaVersion");
123
- if (schemaVersion !== "phasegate-attestation/v1") {
135
+ if (schemaVersion !== "phasegate-attestation/v1" && !isV2) {
124
136
  throw new OwnerProjectionError(
125
137
  "unsupported-provider-schema",
126
138
  `unsupported attestation schema: ${schemaVersion}`,
@@ -128,14 +140,21 @@ export class AttestationFactExtractor {
128
140
  );
129
141
  }
130
142
  const predicateType = requireString(root.predicateType, "attestation.predicateType");
143
+ const expectedPredicate = `https://phasegate.dev/attestation/gate-run/${isV2 ? "v2" : "v1"}`;
144
+ if (predicateType !== expectedPredicate) {
145
+ throw new OwnerProjectionError("malformed-provider-document", "attestation predicateType is inconsistent");
146
+ }
147
+ const worldSnapshotRoot = isV2 ? requireString(root.worldSnapshotRoot, "attestation.worldSnapshotRoot") : undefined;
148
+ if (worldSnapshotRoot !== undefined) this.assertDigest(worldSnapshotRoot, "attestation.worldSnapshotRoot");
131
149
 
132
150
  const subject = requireObject(root.subject, "attestation.subject");
133
151
  assertExactKeys(subject, ["command", "gateResult", "validatorSet"], "attestation.subject");
134
152
  const command = requireString(subject.command, "attestation.subject.command");
135
- const gateResult = requireString(subject.gateResult, "attestation.subject.gateResult");
136
- if (gateResult !== "pass" && gateResult !== "fail") {
153
+ const gateResultRaw = requireString(subject.gateResult, "attestation.subject.gateResult");
154
+ if (gateResultRaw !== "pass" && gateResultRaw !== "fail") {
137
155
  throw new OwnerProjectionError("malformed-provider-document", "attestation gateResult is invalid");
138
156
  }
157
+ const gateResult: "pass" | "fail" = gateResultRaw;
139
158
  const validatorSet = requireArray(subject.validatorSet, "attestation.subject.validatorSet").map((raw, index) => {
140
159
  const field = `attestation.subject.validatorSet[${index}]`;
141
160
  const outcome = requireObject(raw, field);
@@ -174,10 +193,11 @@ export class AttestationFactExtractor {
174
193
  ["validator", "level", "claim", "knownLimitations"],
175
194
  "attestation.granularity.traceability",
176
195
  );
177
- const level = requireString(traceability.level, "attestation.granularity.traceability.level");
178
- if (level !== "file" && level !== "ac") {
196
+ const levelRaw = requireString(traceability.level, "attestation.granularity.traceability.level");
197
+ if (levelRaw !== "file" && levelRaw !== "ac") {
179
198
  throw new OwnerProjectionError("malformed-provider-document", "attestation granularity level is invalid");
180
199
  }
200
+ const level: "file" | "ac" = levelRaw;
181
201
  const knownLimitations = requireArray(
182
202
  traceability.knownLimitations,
183
203
  "attestation.granularity.traceability.knownLimitations",
@@ -193,21 +213,20 @@ export class AttestationFactExtractor {
193
213
 
194
214
  const signature = requireObject(root.signature, "attestation.signature");
195
215
  assertExactKeys(signature, ["mode", "attestationDigest", "algorithm", "keyId", "value"], "attestation.signature");
196
- const mode = requireString(signature.mode, "attestation.signature.mode");
197
- if (mode !== "unsigned-poc" && mode !== "signed") {
216
+ const modeRaw = requireString(signature.mode, "attestation.signature.mode");
217
+ if (modeRaw !== "unsigned-poc" && modeRaw !== "signed") {
198
218
  throw new OwnerProjectionError("malformed-provider-document", "attestation signature mode is invalid");
199
219
  }
220
+ const mode: "unsigned-poc" | "signed" = modeRaw;
200
221
  const attestationDigest = requireString(signature.attestationDigest, "attestation.signature.attestationDigest");
201
222
  this.assertDigest(attestationDigest, "attestation.signature.attestationDigest");
202
223
  const nullableString = (candidate: unknown, field: string): string | null =>
203
224
  candidate === null ? null : requireString(candidate, field);
204
225
 
205
- return {
206
- schemaVersion,
207
- predicateType,
226
+ const common = {
208
227
  subject: { command, gateResult, validatorSet },
209
228
  inputs: {
210
- digestAlgorithm: "sha256",
229
+ digestAlgorithm: "sha256" as const,
211
230
  sources,
212
231
  inputDigest,
213
232
  },
@@ -233,6 +252,22 @@ export class AttestationFactExtractor {
233
252
  value: nullableString(signature.value, "attestation.signature.value"),
234
253
  },
235
254
  };
255
+ if (isV2) {
256
+ if (worldSnapshotRoot === undefined) {
257
+ throw new OwnerProjectionError("malformed-provider-document", "attestation v2 root is missing");
258
+ }
259
+ return {
260
+ ...common,
261
+ schemaVersion: "phasegate-attestation/v2",
262
+ predicateType: "https://phasegate.dev/attestation/gate-run/v2",
263
+ worldSnapshotRoot,
264
+ };
265
+ }
266
+ return {
267
+ ...common,
268
+ schemaVersion: "phasegate-attestation/v1",
269
+ predicateType: "https://phasegate.dev/attestation/gate-run/v1",
270
+ };
236
271
  }
237
272
 
238
273
  private project(document: AttestationDocument, verification: VerifyAttestationOutput): CanonicalJsonObject {
@@ -1,6 +1,7 @@
1
1
  // @unit world-model
2
2
  // @layer infrastructure
3
3
  // @work-item-id WI-294
4
+ // @work-item-id WI-300
4
5
 
5
6
  import { readFile, rename, rm, writeFile } from "node:fs/promises";
6
7
  import path from "node:path";
@@ -40,6 +41,7 @@ let temporarySequence = 0;
40
41
 
41
42
  interface RepositoryOptions {
42
43
  readonly rootDir: string;
44
+ readonly fileName?: string;
43
45
  }
44
46
 
45
47
  interface DeclarationSpec<T> {
@@ -238,7 +240,7 @@ export class FileSystemConstraintDeclarationRepositoryAdapter implements Constra
238
240
  private readonly repository: FileSystemWorldControlRepository<ConstraintDeclarationSet>;
239
241
 
240
242
  constructor(options: RepositoryOptions) {
241
- const fileName = "phasegate.world-constraints.json";
243
+ const fileName = options.fileName ?? "phasegate.world-constraints.json";
242
244
  const artifactId = WorldNodeId.artifact(
243
245
  ArtifactKind.externalDeclaration(),
244
246
  CorpusRole.external(),
@@ -268,7 +270,7 @@ export class FileSystemAdoptionBaselineRepositoryAdapter implements AdoptionBase
268
270
 
269
271
  constructor(options: RepositoryOptions) {
270
272
  this.repository = new FileSystemWorldControlRepository(options, {
271
- fileName: "phasegate.world-baseline.json",
273
+ fileName: options.fileName ?? "phasegate.world-baseline.json",
272
274
  schemaFileName: "world-baseline.schema.json",
273
275
  schemaVersion: "phasegate-world-adoption-baseline/v1",
274
276
  emptyValue: null,
@@ -290,7 +292,7 @@ export class FileSystemWaiverDeclarationRepositoryAdapter implements WaiverDecla
290
292
 
291
293
  constructor(options: RepositoryOptions) {
292
294
  this.repository = new FileSystemWorldControlRepository(options, {
293
- fileName: "phasegate.world-waivers.json",
295
+ fileName: options.fileName ?? "phasegate.world-waivers.json",
294
296
  schemaFileName: "world-waivers.schema.json",
295
297
  schemaVersion: "phasegate-world-waivers/v1",
296
298
  emptyValue: Object.freeze([]),
@@ -312,7 +314,7 @@ export class FileSystemSemanticDebtRepositoryAdapter implements SemanticDebtRepo
312
314
 
313
315
  constructor(options: RepositoryOptions) {
314
316
  this.repository = new FileSystemWorldControlRepository(options, {
315
- fileName: "phasegate.world-debts.json",
317
+ fileName: options.fileName ?? "phasegate.world-debts.json",
316
318
  schemaFileName: "world-debts.schema.json",
317
319
  schemaVersion: "phasegate-world-debts/v1",
318
320
  emptyValue: Object.freeze([]),
@@ -1,13 +1,17 @@
1
1
  // @unit world-model
2
2
  // @layer presentation
3
3
  // @work-item-id WI-296
4
+ // @work-item-id WI-300
4
5
 
5
6
  import type { DeriveWorldObligationsUseCase } from "../../application/usecases/derive-world-obligations-use-case.js";
6
7
  import { PathKey } from "../../domain/value-objects/path-key.js";
7
8
  import { envelope, parseFormat, type WorldCommandResult, type WorldOutputFormat } from "./world-command-support.js";
8
9
 
9
10
  export class WorldDeriveCommandHandler {
10
- constructor(private readonly useCase: Pick<DeriveWorldObligationsUseCase, "execute">) {}
11
+ constructor(
12
+ private readonly useCase: Pick<DeriveWorldObligationsUseCase, "execute">,
13
+ private readonly defaultReportPath = ".harness/world-obligations.json",
14
+ ) {}
11
15
 
12
16
  static fromFailure(error: unknown): WorldDeriveCommandHandler {
13
17
  return new WorldDeriveCommandHandler({
@@ -57,7 +61,7 @@ export class WorldDeriveCommandHandler {
57
61
  const data = {
58
62
  report,
59
63
  persistence: result.result.persistence,
60
- writtenPath: writeReport ? (reportPath ?? ".harness/world-obligations.json") : null,
64
+ writtenPath: writeReport ? (reportPath ?? this.defaultReportPath) : null,
61
65
  };
62
66
  if (formatted.format === "json") {
63
67
  return {