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.
- package/CHANGELOG.md +22 -0
- package/README.ja.md +6 -3
- package/README.md +6 -3
- package/docs/contracts/attestation-v2.schema.json +110 -0
- package/docs/guide/cli-reference.md +12 -1
- package/docs/guide/configuration.md +56 -0
- package/docs/templates/ci/aidlc-gate.yml +42 -2
- package/package.json +1 -1
- package/scripts/harness/agent-integration/application/dto/open-world-obligations-context-dto.ts +30 -0
- package/scripts/harness/agent-integration/application/ports/world-obligations-query-port.ts +30 -0
- package/scripts/harness/agent-integration/application/usecases/get-open-world-obligations-context-usecase.ts +66 -0
- package/scripts/harness/agent-integration/infrastructure/adapters/world-model-open-obligations-query-adapter.ts +58 -0
- package/scripts/harness/agent-integration/presentation/session-start-hook.ts +37 -1
- package/scripts/harness/agent-integration/presentation/world-obligations-session-context.ts +60 -0
- package/scripts/harness/attestation/application/dto/attestation-document.ts +17 -4
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +55 -4
- package/scripts/harness/attestation/application/ports/world-snapshot-root-provider.ts +10 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +33 -7
- package/scripts/harness/attestation/composition-root.ts +5 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +37 -2
- package/scripts/harness/attestation/index.ts +7 -1
- package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +5 -2
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +35 -8
- package/scripts/harness/config-foundation/application/mappers/world-model-config-mapper.ts +15 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +67 -87
- package/scripts/harness/config-foundation/domain/services/preset-resolution-service.ts +77 -88
- package/scripts/harness/config-foundation/domain/value-objects/world-config.ts +198 -0
- package/scripts/harness/config-foundation/index.ts +14 -15
- package/scripts/harness/config-foundation/infrastructure/presets/minimal.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/standard.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/presets/strict.json +24 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +83 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +83 -0
- package/scripts/harness/harness-api/domain/value-objects/ci-check-result.ts +18 -4
- package/scripts/harness/integrations/pre-commit.ts +169 -27
- package/scripts/harness/main.ts +174 -130
- package/scripts/harness/traceability-model/application/dto/changed-design-fragment-dto.ts +31 -0
- package/scripts/harness/traceability-model/application/facades/design-change-read-facade.ts +14 -0
- package/scripts/harness/traceability-model/application/ports/staged-design-change-source-port.ts +9 -0
- package/scripts/harness/traceability-model/composition-root.ts +5 -0
- package/scripts/harness/traceability-model/index.ts +9 -0
- package/scripts/harness/traceability-model/infrastructure/adapters/git-staged-design-change-adapter.ts +155 -0
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +31 -0
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +34 -0
- package/scripts/harness/validator-system/composition-root.ts +21 -6
- package/scripts/harness/validator-system/domain/ports/world-constraint-admission-policy-port.ts +28 -0
- package/scripts/harness/validator-system/domain/ports/world-constraint-rederivation-policy-port.ts +11 -0
- package/scripts/harness/validator-system/domain/services/design-change-declaration-policy.ts +75 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-admission-service.ts +78 -0
- package/scripts/harness/validator-system/domain/services/world-constraint-rederivation-service.ts +76 -0
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +4 -0
- package/scripts/harness/validator-system/infrastructure/adapters/harness-config-validator-config-adapter.ts +38 -16
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-admission-adapter.ts +56 -0
- package/scripts/harness/validator-system/infrastructure/adapters/world-model-constraint-rederivation-adapter.ts +56 -0
- package/scripts/harness/world-model/application/dto/pinned-design-endpoint-dto.ts +19 -0
- package/scripts/harness/world-model/application/dto/world-resolved-config-input.ts +92 -27
- package/scripts/harness/world-model/application/dto/world-snapshot-root-dto.ts +8 -0
- package/scripts/harness/world-model/application/facades/pinned-design-endpoint-facade.ts +63 -0
- package/scripts/harness/world-model/application/facades/world-snapshot-root-facade.ts +19 -0
- package/scripts/harness/world-model/application/usecases/derive-world-obligations-use-case.ts +3 -1
- package/scripts/harness/world-model/composition-root.ts +62 -33
- package/scripts/harness/world-model/index.ts +11 -0
- package/scripts/harness/world-model/infrastructure/adapters/attestation-fact-extractor.ts +48 -13
- package/scripts/harness/world-model/infrastructure/adapters/file-system-world-control-repository-adapters.ts +6 -4
- 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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 が機械的チェックとして検出するため
|
|
@@ -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 {
|
|
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
|
|
20
|
-
const
|
|
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:
|
|
137
|
-
predicateType:
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
50
|
-
|
|
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) {
|
package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|