phasegate 0.163.0 → 0.167.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 +30 -0
- package/docs/ADR/019-ai-independence-boundary.md +63 -0
- package/docs/ADR/020-reverse-learning-forward-proposal.md +52 -0
- package/docs/contracts/requirement-test-matrix.schema.json +5 -0
- package/package.json +1 -1
- package/scripts/harness/attestation/application/dto/attestation-document.ts +54 -0
- package/scripts/harness/attestation/application/dto/produce-attestation-input.ts +18 -0
- package/scripts/harness/attestation/application/dto/verify-attestation-input.ts +12 -0
- package/scripts/harness/attestation/application/dto/verify-attestation-output.ts +30 -0
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +240 -0
- package/scripts/harness/attestation/application/ports/attestation-repository-port.ts +14 -0
- package/scripts/harness/attestation/application/ports/gate-result-source-port.ts +23 -0
- package/scripts/harness/attestation/application/ports/source-digester-port.ts +15 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +155 -0
- package/scripts/harness/attestation/application/usecases/verify-attestation-usecase.ts +137 -0
- package/scripts/harness/attestation/composition-root.ts +93 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +240 -0
- package/scripts/harness/attestation/domain/ports/content-hasher-port.ts +13 -0
- package/scripts/harness/attestation/domain/services/granularity-derivation-service.ts +51 -0
- package/scripts/harness/attestation/domain/value-objects/digest.ts +51 -0
- package/scripts/harness/attestation/domain/value-objects/granularity-claim.ts +89 -0
- package/scripts/harness/attestation/domain/value-objects/signature-block.ts +87 -0
- package/scripts/harness/attestation/domain/value-objects/validator-outcome.ts +44 -0
- package/scripts/harness/attestation/index.ts +18 -0
- package/scripts/harness/attestation/infrastructure/adapters/ci-check-gate-result-adapter.ts +164 -0
- package/scripts/harness/attestation/infrastructure/adapters/file-system-attestation-repository-adapter.ts +32 -0
- package/scripts/harness/attestation/infrastructure/adapters/file-system-source-digester-adapter.ts +27 -0
- package/scripts/harness/attestation/infrastructure/adapters/node-crypto-content-hasher-adapter.ts +18 -0
- package/scripts/harness/attestation/presentation/handlers/attest-handler.ts +71 -0
- package/scripts/harness/attestation/presentation/handlers/verify-attestation-handler.ts +74 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +8 -0
- package/scripts/harness/main.ts +53 -4
- package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +42 -0
- package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +62 -7
- package/scripts/harness/nyquist-validation/infrastructure/adapters/type-script-test-reference-source-adapter.ts +110 -8
- package/scripts/harness/nyquist-validation/infrastructure/schema/matrix-schema-loader.ts +5 -0
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +23 -0
- package/scripts/harness/validator-system/composition-root.ts +10 -0
- package/scripts/harness/validator-system/domain/ports/ac-level-traceability-port.ts +14 -0
- package/scripts/harness/validator-system/domain/services/l4/ac-level-traceability-service.ts +102 -0
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +3 -0
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-level-traceability-adapter.ts +104 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer domain
|
|
3
|
+
* @unit validator-system
|
|
4
|
+
* @work-item-id WI-222
|
|
5
|
+
*
|
|
6
|
+
* HF2-05 / L4-007: AC 単位トレーサビリティ advisory サービス。
|
|
7
|
+
*
|
|
8
|
+
* このサービスは fileFallbackOnly な AC(story-level では linked だが、個別 AC を検証する
|
|
9
|
+
* @ac 注釈付きテストが 1 件も無い AC)と orphanAcTags(解決に失敗した @ac)を
|
|
10
|
+
* warning severity の finding へ変換する。**error は一切出さない**(ADR-019 §5: advisory tier は
|
|
11
|
+
* non-blocking でなければならない)。fileFallbackOnly が 0 かつ orphanAcTags が空なら PASS。
|
|
12
|
+
*
|
|
13
|
+
* L3-004 の二値 pass/fail 判定には一切関与しない(verdict 不変)。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface AcLevelCoverageSummary {
|
|
17
|
+
readonly total: number;
|
|
18
|
+
readonly acBound: number;
|
|
19
|
+
readonly fileFallbackOnly: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FileFallbackOnlyAc {
|
|
23
|
+
readonly storyId: string;
|
|
24
|
+
readonly acId: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface OrphanAcTag {
|
|
28
|
+
readonly storyId: string;
|
|
29
|
+
readonly filePath: string;
|
|
30
|
+
readonly testName?: string;
|
|
31
|
+
readonly rawTag: string;
|
|
32
|
+
readonly reason: 'ac-not-in-story' | 'relative-multi-story';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AcLevelTraceabilitySnapshot {
|
|
36
|
+
readonly acLevelCoverage: AcLevelCoverageSummary;
|
|
37
|
+
readonly fileFallbackOnlyAcs: readonly FileFallbackOnlyAc[];
|
|
38
|
+
readonly orphanAcTags: readonly OrphanAcTag[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AcLevelTraceabilityFinding {
|
|
42
|
+
readonly kind: 'file-fallback-only-ac' | 'orphan-ac-tag';
|
|
43
|
+
readonly message: string;
|
|
44
|
+
readonly suggestion: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class AcLevelTraceabilityReport {
|
|
48
|
+
readonly findings: readonly AcLevelTraceabilityFinding[];
|
|
49
|
+
|
|
50
|
+
constructor(findings: readonly AcLevelTraceabilityFinding[]) {
|
|
51
|
+
this.findings = Object.freeze([...findings]);
|
|
52
|
+
Object.freeze(this);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
hasFindings(): boolean {
|
|
56
|
+
return this.findings.length > 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
toHarnessErrors(): readonly {
|
|
60
|
+
readonly code: { readonly value: string; toString(): string };
|
|
61
|
+
readonly severity: { readonly value: string; toString(): string };
|
|
62
|
+
readonly message: string;
|
|
63
|
+
readonly suggestion: string;
|
|
64
|
+
readonly kind: string;
|
|
65
|
+
}[] {
|
|
66
|
+
return this.findings.map((finding) => ({
|
|
67
|
+
code: { value: 'L4-007', toString: () => 'L4-007' },
|
|
68
|
+
severity: { value: 'warning', toString: () => 'warning' },
|
|
69
|
+
message: finding.message,
|
|
70
|
+
suggestion: finding.suggestion,
|
|
71
|
+
kind: finding.kind,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class AcLevelTraceabilityService {
|
|
77
|
+
check(snapshot: AcLevelTraceabilitySnapshot): AcLevelTraceabilityReport {
|
|
78
|
+
const findings: AcLevelTraceabilityFinding[] = [];
|
|
79
|
+
|
|
80
|
+
for (const ac of snapshot.fileFallbackOnlyAcs) {
|
|
81
|
+
findings.push({
|
|
82
|
+
kind: 'file-fallback-only-ac',
|
|
83
|
+
message: `${ac.storyId} ${ac.acId} is linked only via file-fallback (no @ac-bound test asserts it individually).`,
|
|
84
|
+
suggestion: `Add an "// @ac ${ac.storyId}-${ac.acId.replace(/^AC-/, '')}" annotation to the test case that specifically asserts ${ac.acId}, or accept this as a known file-level gap.`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const orphan of snapshot.orphanAcTags) {
|
|
89
|
+
const location = orphan.testName ? `${orphan.filePath} ("${orphan.testName}")` : orphan.filePath;
|
|
90
|
+
const reasonText = orphan.reason === 'relative-multi-story'
|
|
91
|
+
? 'relative @ac cannot be resolved because the file declares multiple @story tags'
|
|
92
|
+
: 'the referenced AC does not belong to the file\'s story';
|
|
93
|
+
findings.push({
|
|
94
|
+
kind: 'orphan-ac-tag',
|
|
95
|
+
message: `${location}: @ac "${orphan.rawTag}" is unresolved — ${reasonText}.`,
|
|
96
|
+
suggestion: 'Use an absolute @ac (HXX-YY-N) that matches the file\'s @story, or split multi-story test files so relative AC-N resolves unambiguously.',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new AcLevelTraceabilityReport(findings);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* WI-140 で L2-014 を追加
|
|
10
10
|
* WI-132/WI-133/WI-136/WI-137/WI-138 で L2-015 を追加
|
|
11
11
|
* WI-156 で L4-006 を追加
|
|
12
|
+
* WI-222 (HF2-05) で L4-007(ac-level-traceability, default-OFF advisory)を追加
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
export class InvalidValidatorIdError extends Error {
|
|
@@ -42,6 +43,7 @@ const VALIDATOR_NAME_MAP: Record<string, string> = {
|
|
|
42
43
|
'L4-004': 'doc-freshness',
|
|
43
44
|
'L4-005': 'pointer-validation',
|
|
44
45
|
'L4-006': 'skill-catalog-drift',
|
|
46
|
+
'L4-007': 'ac-level-traceability',
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
/** バリデータ名 -> バリデータID の逆引きマップ */
|
|
@@ -53,6 +55,7 @@ const NAME_TO_ID_MAP: Record<string, string> = {
|
|
|
53
55
|
'doc-freshness-checker': 'L4-004',
|
|
54
56
|
'pointer-validator': 'L4-005',
|
|
55
57
|
'skill-catalog-drift': 'L4-006',
|
|
58
|
+
'ac-level-traceability': 'L4-007',
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
/** 有効なValidatorID集合 */
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @layer infrastructure
|
|
3
|
+
* @unit validator-system
|
|
4
|
+
* @work-item-id WI-222
|
|
5
|
+
*
|
|
6
|
+
* HF2-05 / L4-007: nyquist-validation の generate-matrix usecase を実行し、
|
|
7
|
+
* AC 単位トレーサビリティの snapshot(acLevelCoverage / fileFallbackOnlyAcs / orphanAcTags)を
|
|
8
|
+
* 収集して AcLevelTraceabilityPort として提供する adapter。
|
|
9
|
+
*
|
|
10
|
+
* advisory 用途のため、収集に失敗しても throw せず「所見なし」snapshot を返す(fail-open)。
|
|
11
|
+
* これは L3-004(fail-closed な AC 網羅ゲート)とは独立した advisory tier であり、
|
|
12
|
+
* 収集失敗が CI を落とすことは意図しない(ADR-019 §5: advisory は non-blocking)。
|
|
13
|
+
*/
|
|
14
|
+
import type { AcLevelTraceabilityPort } from '../../domain/ports/ac-level-traceability-port.js';
|
|
15
|
+
import type {
|
|
16
|
+
AcLevelTraceabilitySnapshot,
|
|
17
|
+
FileFallbackOnlyAc,
|
|
18
|
+
OrphanAcTag,
|
|
19
|
+
} from '../../domain/services/l4/ac-level-traceability-service.js';
|
|
20
|
+
|
|
21
|
+
const EMPTY_SNAPSHOT: AcLevelTraceabilitySnapshot = Object.freeze({
|
|
22
|
+
acLevelCoverage: { total: 0, acBound: 0, fileFallbackOnly: 0 },
|
|
23
|
+
fileFallbackOnlyAcs: Object.freeze([]),
|
|
24
|
+
orphanAcTags: Object.freeze([]),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export interface NyquistAcLevelTraceabilityAdapterOptions {
|
|
28
|
+
readonly requirementsPath?: string;
|
|
29
|
+
readonly testRoot?: string;
|
|
30
|
+
readonly matrixFilePath?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class NyquistAcLevelTraceabilityAdapter implements AcLevelTraceabilityPort {
|
|
34
|
+
private readonly requirementsPath: string;
|
|
35
|
+
private readonly testRoot: string;
|
|
36
|
+
private readonly matrixFilePath: string;
|
|
37
|
+
|
|
38
|
+
constructor(options: NyquistAcLevelTraceabilityAdapterOptions = {}) {
|
|
39
|
+
this.requirementsPath = options.requirementsPath ?? 'docs/product/user_stories.md';
|
|
40
|
+
this.testRoot = options.testRoot ?? 'scripts/harness/__tests__';
|
|
41
|
+
this.matrixFilePath = options.matrixFilePath ?? '.harness/requirement-test-matrix.json';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async collect(): Promise<AcLevelTraceabilitySnapshot> {
|
|
45
|
+
try {
|
|
46
|
+
const storyIds = await this.loadValidStoryIds();
|
|
47
|
+
const { createNyquistValidationModule } = await import('../../../nyquist-validation/composition-root.js');
|
|
48
|
+
const mod = createNyquistValidationModule({ getStoryIds: async () => storyIds });
|
|
49
|
+
const output = await mod.generateMatrixUseCase.execute({
|
|
50
|
+
requirementsPath: this.requirementsPath,
|
|
51
|
+
testRoot: this.testRoot,
|
|
52
|
+
matrixFilePath: this.matrixFilePath,
|
|
53
|
+
write: false,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const fileFallbackOnlyAcs: FileFallbackOnlyAc[] = [];
|
|
57
|
+
for (const story of output.matrix.stories) {
|
|
58
|
+
for (const mapping of story.storyMappings) {
|
|
59
|
+
if (mapping.testReferences.length === 0) continue;
|
|
60
|
+
const hasAcBound = mapping.testReferences.some((ref) => ref.binding === 'ac');
|
|
61
|
+
if (!hasAcBound) {
|
|
62
|
+
fileFallbackOnlyAcs.push({ storyId: story.storyId, acId: mapping.acId });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const orphanAcTags: OrphanAcTag[] = output.report.orphanAcTags.map((tag) => ({
|
|
68
|
+
storyId: tag.storyId,
|
|
69
|
+
filePath: tag.filePath,
|
|
70
|
+
testName: tag.testName,
|
|
71
|
+
rawTag: tag.rawTag,
|
|
72
|
+
reason: tag.reason,
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
acLevelCoverage: {
|
|
77
|
+
total: output.report.acLevelCoverage.total,
|
|
78
|
+
acBound: output.report.acLevelCoverage.acBound,
|
|
79
|
+
fileFallbackOnly: output.report.acLevelCoverage.fileFallbackOnly,
|
|
80
|
+
},
|
|
81
|
+
fileFallbackOnlyAcs: Object.freeze(fileFallbackOnlyAcs),
|
|
82
|
+
orphanAcTags: Object.freeze(orphanAcTags),
|
|
83
|
+
};
|
|
84
|
+
} catch {
|
|
85
|
+
return EMPTY_SNAPSHOT;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private async loadValidStoryIds(): Promise<readonly string[]> {
|
|
90
|
+
try {
|
|
91
|
+
const { createConfigFoundationModule } = await import('../../../config-foundation/composition-root.js');
|
|
92
|
+
const configModule = createConfigFoundationModule();
|
|
93
|
+
const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
|
|
94
|
+
const { createTraceabilityModelModule } = await import('../../../traceability-model/composition-root.js');
|
|
95
|
+
const traceModule = createTraceabilityModelModule(process.cwd(), {
|
|
96
|
+
pathRoots: { designDocsRoot: resolvedConfig.config.paths.designDocs },
|
|
97
|
+
});
|
|
98
|
+
const storyIds = await traceModule.storyCatalog.getAllStoryIds();
|
|
99
|
+
return storyIds.map((s) => s.value);
|
|
100
|
+
} catch {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|