phasegate 0.165.0 → 0.171.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 +63 -0
- package/docs/ADR/021-severity-contract.md +46 -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 +2 -0
- package/scripts/harness/attestation/application/dto/verify-attestation-output.ts +2 -0
- package/scripts/harness/attestation/application/mappers/attestation-record-mapper.ts +13 -0
- package/scripts/harness/attestation/application/ports/ac-bound-allowlist-port.ts +12 -0
- package/scripts/harness/attestation/application/ports/matrix-source-port.ts +16 -0
- package/scripts/harness/attestation/application/usecases/produce-attestation-usecase.ts +39 -0
- package/scripts/harness/attestation/application/usecases/verify-attestation-usecase.ts +60 -1
- package/scripts/harness/attestation/composition-root.ts +14 -0
- package/scripts/harness/attestation/domain/entities/attestation-record.ts +13 -0
- package/scripts/harness/attestation/domain/services/ac-bound-scope-service.ts +80 -0
- package/scripts/harness/attestation/infrastructure/adapters/config-ac-bound-allowlist-adapter.ts +23 -0
- package/scripts/harness/attestation/infrastructure/adapters/file-system-matrix-source-adapter.ts +25 -0
- package/scripts/harness/attestation/presentation/handlers/verify-attestation-handler.ts +1 -0
- package/scripts/harness/ci-governance/infrastructure/adapters/validator-id-registry-adapter.ts +8 -0
- package/scripts/harness/config-foundation/application/mappers/validator-system-config-mapper.ts +3 -0
- package/scripts/harness/config-foundation/domain/harness-config.ts +1 -0
- package/scripts/harness/config-foundation/domain/value-objects/l3-config.ts +4 -0
- package/scripts/harness/config-foundation/domain/value-objects/layers-config.ts +1 -1
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v2.schema.json +7 -0
- package/scripts/harness/config-foundation/infrastructure/schemas/harness-config-v3.schema.json +7 -0
- package/scripts/harness/harness-error/domain/errors/severity-downgrade-violation-error.ts +1 -1
- package/scripts/harness/main.ts +2 -2
- package/scripts/harness/nyquist-validation/application/dto/generate-matrix-output.ts +42 -0
- package/scripts/harness/nyquist-validation/application/mappers/coverage-result-mapper.ts +13 -15
- package/scripts/harness/nyquist-validation/application/mappers/impact-analysis-result-mapper.ts +12 -12
- package/scripts/harness/nyquist-validation/application/usecases/analyze-impact-usecase.ts +11 -11
- package/scripts/harness/nyquist-validation/application/usecases/calculate-coverage-usecase.ts +11 -11
- package/scripts/harness/nyquist-validation/application/usecases/check-ac-coverage-gate-usecase.ts +9 -9
- package/scripts/harness/nyquist-validation/application/usecases/generate-requirement-test-matrix-usecase.ts +62 -7
- package/scripts/harness/nyquist-validation/domain/aggregates/requirement-test-matrix.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/entities/story-mapping.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/ac-coverage-gate-policy.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/coverage-calculation-service.ts +1 -1
- package/scripts/harness/nyquist-validation/domain/services/impact-analysis-service.ts +2 -2
- package/scripts/harness/nyquist-validation/domain/services/matrix-validation-service.ts +5 -5
- package/scripts/harness/nyquist-validation/domain/value-objects/impact-analysis-result.ts +1 -1
- package/scripts/harness/nyquist-validation/infrastructure/adapters/ajv-json-schema-validator-adapter.ts +3 -3
- package/scripts/harness/nyquist-validation/infrastructure/adapters/config-foundation-coverage-threshold-adapter.ts +3 -3
- 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/dto/run-l3-validators-input.ts +2 -0
- package/scripts/harness/validator-system/application/use-cases/run-l1-validators-usecase.ts +1 -1
- package/scripts/harness/validator-system/application/use-cases/run-l2-validators-usecase.ts +3 -3
- package/scripts/harness/validator-system/application/use-cases/run-l3-validators-usecase.ts +33 -3
- package/scripts/harness/validator-system/application/use-cases/run-l4-validators-usecase.ts +26 -3
- package/scripts/harness/validator-system/application/use-cases/run-quick-mode-usecase.ts +3 -3
- package/scripts/harness/validator-system/composition-root.ts +22 -0
- package/scripts/harness/validator-system/domain/ports/ac-bound-coverage-policy-port.ts +22 -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/services/validator-execution-service.ts +1 -1
- package/scripts/harness/validator-system/domain/services/validator-registry.ts +2 -2
- package/scripts/harness/validator-system/domain/value-objects/layer-config.ts +1 -1
- package/scripts/harness/validator-system/domain/value-objects/validation-result.ts +1 -1
- package/scripts/harness/validator-system/domain/value-objects/validator-definition.ts +2 -2
- package/scripts/harness/validator-system/domain/value-objects/validator-id.ts +5 -0
- package/scripts/harness/validator-system/infrastructure/adapters/json-coverage-report-adapter.ts +1 -1
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-bound-coverage-policy-adapter.ts +118 -0
- package/scripts/harness/validator-system/infrastructure/adapters/nyquist-ac-level-traceability-adapter.ts +104 -0
- package/scripts/harness/validator-system/presentation/handlers/report-validation-results-handler.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,69 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.171.0] - 2026-07-05
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **WI-227 / H16-03 — L3-005 "AC-bound coverage" gate (fail-closed, default-OFF)** — new opt-in L3 validator that, for each story in `layers.L3.acBoundStories`, fail-closed verifies that **every** linked AC has ≥1 `binding:"ac"` test reference (`fileFallbackOnly===0`). Unlike L3-004 (story-level) this is per-AC binding. `ac-bound-coverage-policy-port.ts` (domain) + `nyquist-ac-bound-coverage-policy-adapter.ts` (infrastructure, reads the matrix and fail-closes on absent/parse-fail); `ValidatorId` gains `L3-005 → ac-bound-coverage`; `RunL3ValidatorsUseCase` gains an L3-005 override block mirroring L3-004 (only when config enables L3-005). **NOT** added to `DEFAULT_CONFIG` nor to standard/strict presets (default-OFF opt-in). config alias `ac-bound-coverage → L3-005`; new `layers.L3.acBoundStories?: string[]` (additive-safe; existing configs still load).
|
|
15
|
+
- **WI-227 / H16-03 — attestation `acBoundScope`** — attestation records now carry a machine-readable `acBoundScope: string[]` (sorted; stories that are genuinely ac-bound AND in scope AND passing). New pure domain `AcBoundScopeService.derive(matrix, allowlist)`; new application ports `MatrixSourcePort` / `AcBoundAllowlistPort` with `FileSystemMatrixSourceAdapter` / `ConfigAcBoundAllowlistAdapter`. `acBoundScope` is included in the canonical payload (covered by `attestationDigest`) and in `equals()`/`create/reconstruct/seal`. **verify RE-DERIVES** `acBoundScope` from the stored (hash-verified) matrix + config allowlist and compares to stored (anti-laundering); unreadable/unparsable re-derivation inputs → fail-closed (exit 1). `GranularityDerivationService` is UNTOUCHED — `granularity.traceability.level` stays `"file"` (acBoundScope is an independent per-story dimension, no global flip).
|
|
16
|
+
- **Attestation readiness docs (debt repayment)** — authored the three readiness docs that had been missing for the attestation unit since 手1/v0.165.0: `docs/product/construction/attestation/unit_test_design.md`, `it_test_design.md`, `coverage_report.md`. Coverage is measured (vitest v8: 83.53% statements/lines, 78.08% branches, 97.75% functions on the pre-slice baseline) with the exact reproduction command recorded; numbers are real, not fabricated.
|
|
17
|
+
- **`l3-005-ac-bound-ratchet.md`** — new honesty/ratchet doc mirroring the l3-004 R-table; first scope `["HF2-05"]`, records H05-02 excluded (legacy) and H06-03 partial (only AC-4).
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Self-repo `phasegate.config.json` only** — `layers.L3.validators` gains `L3-005` and `layers.L3.acBoundStories: ["HF2-05"]`. Product presets (`standard.json`/`strict.json`) and `DEFAULT_CONFIG` are untouched — no impact on other users. HF2-05 is genuinely ac-bound (all 6 ACs have ≥1 `binding:"ac"`), so enabling L3-005 keeps self-repo CI green.
|
|
22
|
+
- Design docs updated design-first: validator-system `logical_design.md` (§1.3, §2.1.1 L3-005 row, §3.4.2 note, §4.8b adapter), attestation `logical_design.md` (§1.3, §1.4.1 acBoundScope-in-canonical note, §1.4.3 record format, §4.4/§4.5/§4.6) and `domain_model.md` (INV-8), `user_stories.md` (H16-03 + summary tables), config schemas v2/v3 (`acBoundStories`).
|
|
23
|
+
|
|
24
|
+
## [0.170.0] - 2026-07-05
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **WI-226 / H05-02 — real-corpus ADR integration test (honest-partial)** — closes the honesty gap recorded in `l3-004-traceability-ratchet.md` §6b ("no test verifies the real `docs/ADR/` corpus exists / conforms / has valid status"). New `scripts/harness/__tests__/integration/adr-foundation/real-adr-corpus.it.test.ts` wires the **real** `createAdrFoundationModule(<repoRoot>/docs/ADR)` pipeline against the **committed** corpus (no `mkdtemp`, repoRoot resolved from the test file location) and **fail-closed verifies** four SCOPED properties: (1) canonical (`NNN-slug.md`) ADRs are discovered with 019/020/021 present (membership + floor, property-based so ADR-022 won't break it); (2) every discovered ADR conforms via `validate-all` (zero violations); (3) each discovered ADR's status ∈ {Accepted, Proposed}; (4) the **≥18 legacy `ADR-NNN-*.md` files exist on disk but are provably excluded from discovery** — pinning the known gate-invisibility limitation as a tested invariant.
|
|
29
|
+
- **Honest-partial framing — H05-02 NOT over-claimed** — the new file carries **only** a file-level `// @story H05-02` tag and **deliberately no `// @ac H05-02-N` tags**. The §12 Key Decisions that H05-02 AC-1/2/3 enumerate live mostly in the legacy (gate-invisible) corpus, so the test genuinely verifies only the discoverable canonical corpus + the legacy-exclusion invariant — it does not fully assert AC-1/2/3 as written. Adding `@ac` tags would flip those ACs to `binding:"ac"` and over-claim per-AC verification of an unverified corpus. **H05-02 AC-1/2/3 remain `fileFallbackOnly`** (confirmed via `phasegate:generate-matrix`: all H05-02 references stay `binding:"file"`). Per-AC binding is deferred until the legacy corpus is normalized to canonical format (adr-gate-normalization-followup.md); H05-02 stays out of any future L3-005 "AC-bound coverage" scope until then. `l3-004-traceability-ratchet.md` §6b updated to record the now-existing real-corpus test and the retained fileFallbackOnly status. No gate behavior, matrix byte-equality, or attestation granularity (`level:"file"`) changed.
|
|
30
|
+
|
|
31
|
+
## [0.169.0] - 2026-07-05
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **WI-225 / H06-03 AC-4 — severity contract violation message now carries its ADR basis (correct ADR-021)** — `SeverityDowngradeViolationError` previously threw a message stating only the violation content (`default=…` / `requested=…`) but **omitted the ADR reference** that H06-03 AC-4 requires ("契約違反時のエラーメッセージに違反内容と根拠(ADR参照)が含まれる"). The message now appends `根拠: ADR-021`. A new **ADR-021 "severity 契約 — 格下げ禁止"** (`docs/ADR/021-severity-contract.md`, status Accepted) forward-documents the existing downgrade-prohibition rule (implemented in `SeverityContractEnforcer`, specified in `harness-error/logical_design.md` §2.3.3 / §4.6) and is the ADR that actually decides the prohibition. This corrects a prior draft that referenced **ADR-017 by mistake** — ADR-017 governs warning-severity aggregation (`failOnWarning`), **not** the downgrade prohibition; citing it was a plausible-but-wrong reference of exactly the kind phasegate exists to prevent. The domain unit test (`severity-contract-enforcer.test.ts`) asserts the message contains the violation content **and** `ADR-021`. Constructor signature is unchanged (single call site is the enforcer); no API contract change.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **Honest `@ac` backfill — 3 ACs promoted to `binding:"ac"` (no new gate, no attestation change)** — prerequisite work for a future fail-closed L3-005 gate. Global `acLevelCoverage.acBound` moves **4 → 7**:
|
|
40
|
+
- **H06-03 AC-4** — tagged (`// @ac AC-4`) on the new severity-contract-enforcer assertion above.
|
|
41
|
+
- **HF2-05 AC-1 / AC-2** — tagged on the tracer's own genuine assertions in `type-script-test-reference-source-adapter-ac.it.test.ts` using the **absolute** form (`// @ac HF2-05-1` / `// @ac HF2-05-2`). Assessed as SAFE: the file's `storyCount` is 9 (real `@story HF2-05` plus fixture `@story` string-literals), so the **relative** form (`AC-N`) would become a `relative-multi-story` orphan; the absolute form binds against the file's real storyId and was empirically confirmed to add exactly 2 correct `binding:"ac"` references with **zero** new spurious/orphan matrix entries (total refs and orphan-bearing refs unchanged). **HF2-05 is now `fileFallbackOnly === 0`** (all 6 ACs `binding:"ac"`).
|
|
42
|
+
- **H05-02 left untouched** (AC-1/2/3 remain `fileFallbackOnly`); its genuine test is planned separately.
|
|
43
|
+
- No `binding` was faked; L3-004 verdict and attestation granularity (`level:"file"`) are unchanged.
|
|
44
|
+
|
|
45
|
+
## [0.168.0] - 2026-07-05
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
49
|
+
- **WI-223 — biome dogfooding cleanup (`nyquist-validation` / `validator-system` units)** — cleared pre-existing repo-wide biome lint debt in files not touched by recent work. All fixes are mechanical and behavior-preserving:
|
|
50
|
+
- `lint/complexity/useLiteralKeys` (58 diagnostics) — converted string-literal computed member access (`obj['stories']`) to dot access (`obj.stories`) where the key is a valid identifier, across the coverage/impact use-cases, `matrix-validation-service`, the AJV adapter, the coverage-threshold adapter, the JSON coverage-report adapter, and the validation-result contract handler.
|
|
51
|
+
- `lint/style/useImportType` (27 diagnostics) — marked type-only imports with `import type` (or `type` specifiers) across `nyquist-validation` domain entities/aggregates/services/value-objects and `validator-system` L1–L4 use-cases and domain services/value-objects.
|
|
52
|
+
- `lint/complexity/noStaticOnlyClass` (2 diagnostics) — converted the static-only `CoverageResultMapper` and `ImpactAnalysisResultMapper` classes into plain exported functions (`toCalculateCoverageOutput`, `toAnalyzeImpactOutput`) and updated their single call sites in `calculate-coverage-usecase` / `analyze-impact-usecase`. No public behavior change.
|
|
53
|
+
|
|
54
|
+
## [0.167.0] - 2026-07-05
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- **WI-222 / HF2-05 — AC-level traceability (`@ac` contract, additive)** — adds a mechanical (AST-free, ADR-019-compliant) `@ac` annotation contract so individual test cases can record which acceptance criterion they assert. The `TypeScriptTestReferenceSourceAdapter` parses absolute (`// @ac H05-02-1`), story-relative (`// @ac AC-2`, resolved only when the file has exactly one `@story`), and multi-AC (`// @ac H05-02-1 H05-02-2`) forms via a positional scan that binds the nearest preceding `@ac` since the last `it(`/`test(`. New DTO fields: `TestReferenceSourceDto.acIds`, `MatrixTestReferenceDto.binding` (`"ac" | "file"`), report `acLevelCoverage` (`{ total, acBound, fileFallbackOnly }`) and `orphanAcTags`.
|
|
59
|
+
- **WI-222 / HF2-05 — L4-007 `ac-level-traceability` advisory validator (`validator-system` unit)** — a new L4 validator that surfaces file-fallback-only ACs and orphan `@ac` tags as **warning-severity findings only (never errors)**. It is **default-OFF** (registered in the registry but not in the default/standard/strict enabled validator sets, so it always `skipped=true` in `ci-check`), **advisory/non-blocking**, and **attestation-trust-excluded** (the attestation `GranularityDerivationService` re-derives granularity solely from the static `KNOWN_LIMITATIONS_REGISTRY`, which whitelists only L3-004; L4-007 is ignored by construction). New domain service `AcLevelTraceabilityService`, port `AcLevelTraceabilityPort`, and infrastructure adapter `NyquistAcLevelTraceabilityAdapter`.
|
|
60
|
+
- **requirement-test-matrix schema 1.1** — adds an optional `binding` enum (`["ac","file"]`) on `testReferences`, keeping `additionalProperties:false` and full backward compatibility so existing 1.0 matrices (without `binding`) still validate.
|
|
61
|
+
|
|
62
|
+
### Changed
|
|
63
|
+
|
|
64
|
+
- **L3-004 verdict preserved (byte-identical)** — references without `@ac` continue to fan out to all ACs with `binding:"file"` exactly as before; the reference dedup key now incorporates `binding` and normalizes `undefined → "file"` so 1.0 matrices without `binding` produce no spurious duplicates. Proven invariant by golden-matrix comparison (all 358 pre-existing ACs: identical coverage, 0 missing before/after). Generated matrix `version` bumped `1.0 → 1.1`.
|
|
65
|
+
- **Attestation granularity unchanged** — `granularity.traceability.level` remains `"file"`; `level:"ac"` is design-gated on a future fail-closed **L3-005 "AC-bound coverage"** validator flipping the static registry binary/global (documented in `nyquist-validation/logical_design.md` and the L3-004 ratchet R5 row).
|
|
66
|
+
|
|
67
|
+
## [0.166.0] - 2026-07-05
|
|
68
|
+
|
|
69
|
+
### Fixed
|
|
70
|
+
|
|
71
|
+
- **WI-220 — noUselessTernary dogfooding fix (`harness-api` unit, `scripts/harness/main.ts`)** — replaces two `lint/complexity/noUselessTernary` violations in the `setup` install path (`includeHusky: personal ? false : true` / `includeCi: personal ? false : true`) with the behavior-preserving `!personal`. Clears self-repo biome lint debt; `npx biome lint scripts/harness/main.ts` now reports 0 errors. No behavior change — `!personal` is semantically identical to `personal ? false : true`.
|
|
72
|
+
|
|
10
73
|
## [0.165.0] - 2026-07-05
|
|
11
74
|
|
|
12
75
|
### Added
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
adr_id: "021"
|
|
3
|
+
title: "severity 契約 — 格下げ禁止"
|
|
4
|
+
status: Accepted
|
|
5
|
+
date: 2026-07-05
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# severity 契約 — 格下げ禁止
|
|
9
|
+
|
|
10
|
+
## Context
|
|
11
|
+
|
|
12
|
+
phasegate は品質防御ツールであり、その検査結果 severity(`error` / `warning`)はゲートの通過可否を左右する。各 error code は canonical カタログ(`ErrorDefinition`)に `defaultSeverity` を持ち、これがその code の最小限の重大度を規定する。
|
|
13
|
+
|
|
14
|
+
呼び出し側(orchestration 層 / CLI)は effective severity を明示的に要求できるが、ここに **severity を静かに弱めてゲートをすり抜ける** という抜け道のリスクがある。例えば `defaultSeverity=error` の違反を `warning` に格下げすれば、`--fail-on-error` 系の終了コード判定を回避できてしまう。これは「チェッカーは常に機械であり、trust は機械的・再現可能な検査のみが付与する」という ADR-019 の trust 境界、および「単方向の強制は交渉不可」という ADR-020 の単方向規律の精神に反する — severity の格下げは、検査が既に検出した違反を事後的に無力化する行為だからである。
|
|
15
|
+
|
|
16
|
+
この格下げ禁止ルールの実体は既に `scripts/harness/harness-error/domain/services/severity-contract-enforcer.ts`(`SeverityContractEnforcer`)に実装され、`docs/product/construction/harness-error/logical_design.md` §2.3.3 / §4.6 に規定されている。しかし ADR としては未文書化であり、`SeverityDowngradeViolationError` のメッセージは根拠として ADR-017(warning severity の集計 / `failOnWarning`)を誤って参照していた。ADR-017 は集計側の決定であって格下げ禁止を定めるものではない。本 ADR-021 は、この既存ルールを前方文書化し、格下げ禁止を実際に決定する ADR として位置づける。
|
|
17
|
+
|
|
18
|
+
## Decision
|
|
19
|
+
|
|
20
|
+
error の effective severity を、その canonical `defaultSeverity` より **下位へ格下げすることを禁止する。**
|
|
21
|
+
|
|
22
|
+
- `SeverityContractEnforcer.resolveEffectiveSeverity(requested, defaultSeverity)` は次のように振る舞う。
|
|
23
|
+
1. `requested` 未指定なら `defaultSeverity` を返す。
|
|
24
|
+
2. `requested` が `defaultSeverity` より高位(格上げ)なら `requested` を返す — 格上げは許容する。
|
|
25
|
+
3. `requested` が `defaultSeverity` と同一なら `requested` を返す。
|
|
26
|
+
4. それ以外(格下げ)は `SeverityDowngradeViolationError` を throw する。
|
|
27
|
+
- 不変条件として `error -> warning` の格下げは常に拒否される。
|
|
28
|
+
- 格下げ違反時のメッセージは **違反内容(default / requested の両 severity)と根拠(本 ADR-021)** を必ず含む。これにより違反の再現・監査が機械的に可能になる。
|
|
29
|
+
|
|
30
|
+
trust 境界の性質上(ADR-019)、この判定は LLM を一切用いず、値の比較のみで機械的に完結する。
|
|
31
|
+
|
|
32
|
+
## Consequences
|
|
33
|
+
|
|
34
|
+
- severity が黙って弱められてゲートをすり抜けることが構造的に不可能になる。attestation の「green」が severity の観点でも信頼できる。
|
|
35
|
+
- 呼び出し側は severity を格上げ(より厳格化)することはできるが、格下げはできない。厳格化の一方向のみが許容され、ADR-020 の単方向規律と整合する。
|
|
36
|
+
- `defaultSeverity` を運用上どうしても下げたい正当なケースがある場合、それは呼び出し側での格下げではなく **canonical カタログ(`ErrorDefinition`)の `defaultSeverity` そのものを前方フローで変更する** ことでしか実現できない。これは意図的な設計判断を前方ゲート経由で通すことを強制する。
|
|
37
|
+
|
|
38
|
+
## Alternatives
|
|
39
|
+
|
|
40
|
+
1. **格下げを警告のみとし throw しない** — 事実上の格下げを許すため抜け道が残る。品質防御の目的に反するため不採用。
|
|
41
|
+
|
|
42
|
+
2. **格下げ禁止を throw で強制(本決定で採用)** — 格下げは `SeverityDowngradeViolationError` を throw し、メッセージに違反内容と根拠 ADR-021 を含める。
|
|
43
|
+
|
|
44
|
+
3. **呼び出し側で任意の severity 上書きを許容し、カタログを advisory 扱いにする** — canonical カタログの規範性が失われ、severity 契約が有名無実化するため不採用。
|
|
45
|
+
|
|
46
|
+
関連: ADR-019(「AI 非依存」の境界 — trust 境界は機械的)、ADR-020(単方向規律と逆流学習)、ADR-006(エージェント非依存設計原則)、ADR-017(warning severity の集計 / `failOnWarning` — 集計側の別決定)、`docs/product/construction/harness-error/logical_design.md` §2.3.3 / §4.6。
|
package/package.json
CHANGED
|
@@ -39,6 +39,8 @@ export interface AttestationDocument {
|
|
|
39
39
|
readonly granularity: {
|
|
40
40
|
readonly traceability: AttestationDocumentGranularityClaim;
|
|
41
41
|
};
|
|
42
|
+
/** H16-03: 実際に ac-bound かつ L3-005 スコープ内で pass した story-id(昇順)。 */
|
|
43
|
+
readonly acBoundScope: string[];
|
|
42
44
|
readonly metadata: {
|
|
43
45
|
readonly producedAt: string;
|
|
44
46
|
readonly producer: string;
|
|
@@ -15,6 +15,8 @@ export interface VerifyAttestationChecks {
|
|
|
15
15
|
readonly inputHashes: boolean;
|
|
16
16
|
/** granularity を validatorSet から再導出 == 格納値(anti-laundering) */
|
|
17
17
|
readonly granularity: boolean;
|
|
18
|
+
/** acBoundScope を stored matrix + allowlist から再導出 == 格納値(anti-laundering, H16-03) */
|
|
19
|
+
readonly acBoundScope: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
/**
|
|
@@ -75,6 +75,7 @@ export class AttestationRecordMapper {
|
|
|
75
75
|
knownLimitations: [...record.granularity.traceability.knownLimitations],
|
|
76
76
|
},
|
|
77
77
|
},
|
|
78
|
+
acBoundScope: [...record.acBoundScope],
|
|
78
79
|
metadata: {
|
|
79
80
|
producedAt: record.metadata.producedAt,
|
|
80
81
|
producer: record.metadata.producer,
|
|
@@ -153,6 +154,8 @@ export class AttestationRecordMapper {
|
|
|
153
154
|
}
|
|
154
155
|
const traceability = this.toGranularityClaim(granularity.traceability);
|
|
155
156
|
|
|
157
|
+
const acBoundScope = this.toAcBoundScope(doc.acBoundScope);
|
|
158
|
+
|
|
156
159
|
const metadata = doc.metadata;
|
|
157
160
|
if (!isObject(metadata)) {
|
|
158
161
|
throw new MalformedAttestationError("metadata must be an object");
|
|
@@ -181,6 +184,7 @@ export class AttestationRecordMapper {
|
|
|
181
184
|
gitCommit,
|
|
182
185
|
},
|
|
183
186
|
signature: this.toSignatureBlock(mode, attestationDigest, signature),
|
|
187
|
+
acBoundScope,
|
|
184
188
|
};
|
|
185
189
|
|
|
186
190
|
// verify 用の再構築: INV-1/INV-3 は verify usecase が機械的チェックとして検出するため
|
|
@@ -188,6 +192,15 @@ export class AttestationRecordMapper {
|
|
|
188
192
|
return AttestationRecord.reconstruct(props);
|
|
189
193
|
}
|
|
190
194
|
|
|
195
|
+
private toAcBoundScope(v: unknown): string[] {
|
|
196
|
+
// H16-03: 後方互換のため未設定は [] とみなす(additive-safe)。
|
|
197
|
+
if (v === undefined || v === null) return [];
|
|
198
|
+
if (!Array.isArray(v)) {
|
|
199
|
+
throw new MalformedAttestationError("acBoundScope must be an array of strings");
|
|
200
|
+
}
|
|
201
|
+
return v.map((item, i) => requireString(item, `acBoundScope[${i}]`));
|
|
202
|
+
}
|
|
203
|
+
|
|
191
204
|
private toDigest(v: unknown, field: string): Digest {
|
|
192
205
|
const raw = requireString(v, field);
|
|
193
206
|
try {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// @unit attestation
|
|
2
|
+
// @layer application
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AcBoundAllowlistPort(H16-03 / WI-227)
|
|
6
|
+
*
|
|
7
|
+
* acBoundScope 導出のスコープ(`layers.L3.acBoundStories`)を供給する application ポート。
|
|
8
|
+
*/
|
|
9
|
+
export interface AcBoundAllowlistPort {
|
|
10
|
+
/** スコープ対象 story-id を返す(未設定は [])。 */
|
|
11
|
+
getAcBoundStories(): Promise<readonly string[]>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// @unit attestation
|
|
2
|
+
// @layer application
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MatrixSourcePort(H16-03 / WI-227)
|
|
6
|
+
*
|
|
7
|
+
* acBoundScope 導出用の requirement-test-matrix を供給する application ポート。
|
|
8
|
+
* 読み込み/parse に失敗した場合は throw する(呼び出し側 usecase が fail-closed に変換する)。
|
|
9
|
+
*/
|
|
10
|
+
export interface MatrixSourcePort {
|
|
11
|
+
/**
|
|
12
|
+
* @param matrixFilePath matrix のパス(省略時は adapter の既定パス)
|
|
13
|
+
* @returns parse 済みの matrix plain object
|
|
14
|
+
*/
|
|
15
|
+
load(matrixFilePath?: string): Promise<unknown>;
|
|
16
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { AttestationRecord, type GateResult, type SourceEntry } from "../../domain/entities/attestation-record.js";
|
|
5
5
|
import type { ContentHasherPort } from "../../domain/ports/content-hasher-port.js";
|
|
6
6
|
import type { GranularityDerivationService } from "../../domain/services/granularity-derivation-service.js";
|
|
7
|
+
import type { AcBoundScopeService } from "../../domain/services/ac-bound-scope-service.js";
|
|
7
8
|
import { SignatureBlock } from "../../domain/value-objects/signature-block.js";
|
|
8
9
|
import { ValidatorOutcome } from "../../domain/value-objects/validator-outcome.js";
|
|
9
10
|
import type { AttestationDocument } from "../dto/attestation-document.js";
|
|
@@ -12,6 +13,8 @@ import type { AttestationRecordMapper } from "../mappers/attestation-record-mapp
|
|
|
12
13
|
import type { AttestationRepositoryPort } from "../ports/attestation-repository-port.js";
|
|
13
14
|
import type { GateResultSourcePort } from "../ports/gate-result-source-port.js";
|
|
14
15
|
import type { SourceDigesterPort } from "../ports/source-digester-port.js";
|
|
16
|
+
import type { MatrixSourcePort } from "../ports/matrix-source-port.js";
|
|
17
|
+
import type { AcBoundAllowlistPort } from "../ports/ac-bound-allowlist-port.js";
|
|
15
18
|
|
|
16
19
|
const SCHEMA_VERSION = "phasegate-attestation/v1";
|
|
17
20
|
const PREDICATE_TYPE = "https://phasegate.dev/attestation/gate-run/v1";
|
|
@@ -35,6 +38,14 @@ export interface ProduceAttestationDeps {
|
|
|
35
38
|
readonly clock?: () => Date;
|
|
36
39
|
/** 入力 source パス群(既定は config + matrix)。 */
|
|
37
40
|
readonly inputSourcePaths?: readonly string[];
|
|
41
|
+
/** H16-03: acBoundScope 導出用 matrix 供給(省略時は acBoundScope=[])。 */
|
|
42
|
+
readonly matrixSource?: MatrixSourcePort;
|
|
43
|
+
/** H16-03: acBoundScope 導出用 allowlist 供給(省略時は acBoundScope=[])。 */
|
|
44
|
+
readonly allowlist?: AcBoundAllowlistPort;
|
|
45
|
+
/** H16-03: acBoundScope 導出サービス(省略時は acBoundScope=[])。 */
|
|
46
|
+
readonly acBoundScopeService?: AcBoundScopeService;
|
|
47
|
+
/** H16-03: matrix 供給元パス(inputSourcePaths のうち matrix にあたるもの。既定は 2 番目の source)。 */
|
|
48
|
+
readonly matrixFilePath?: string;
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
export interface ProduceAttestationResult {
|
|
@@ -76,10 +87,15 @@ export class ProduceAttestationUseCase {
|
|
|
76
87
|
producedAt: "1970-01-01T00:00:00Z",
|
|
77
88
|
producer: `phasegate-attestation/${this.deps.pkgVersion}`,
|
|
78
89
|
gitCommit: null,
|
|
90
|
+
acBoundScope: [],
|
|
79
91
|
});
|
|
80
92
|
const inputDigest = preInputRecord.computeInputDigest(this.deps.hasher);
|
|
81
93
|
|
|
82
94
|
// 5. granularity 導出(domain service)
|
|
95
|
+
// 5b. acBoundScope 導出(H16-03): matrixSource + allowlist + AcBoundScopeService。
|
|
96
|
+
// いずれか未配線なら acBoundScope=[]。
|
|
97
|
+
const acBoundScope = await this.deriveAcBoundScope();
|
|
98
|
+
|
|
83
99
|
// 6. metadata 構築
|
|
84
100
|
const now = (this.deps.clock ?? (() => new Date()))().toISOString();
|
|
85
101
|
const gitCommit = await this.deps.gitCommitProvider();
|
|
@@ -93,6 +109,7 @@ export class ProduceAttestationUseCase {
|
|
|
93
109
|
producedAt: now,
|
|
94
110
|
producer: `phasegate-attestation/${this.deps.pkgVersion}`,
|
|
95
111
|
gitCommit,
|
|
112
|
+
acBoundScope,
|
|
96
113
|
});
|
|
97
114
|
const sealed = record.seal(this.deps.hasher);
|
|
98
115
|
|
|
@@ -112,6 +129,7 @@ export class ProduceAttestationUseCase {
|
|
|
112
129
|
producedAt: string;
|
|
113
130
|
producer: string;
|
|
114
131
|
gitCommit: string | null;
|
|
132
|
+
acBoundScope: readonly string[];
|
|
115
133
|
}): AttestationRecord {
|
|
116
134
|
const granularity = this.deps.granularityService.derive(args.validatorSet);
|
|
117
135
|
return AttestationRecord.create({
|
|
@@ -135,9 +153,30 @@ export class ProduceAttestationUseCase {
|
|
|
135
153
|
},
|
|
136
154
|
// 仮の unsigned-poc block(seal 前)。seal が正しい digest で置換する。
|
|
137
155
|
signature: SignatureBlock.unsignedPoc(args.inputDigest),
|
|
156
|
+
acBoundScope: args.acBoundScope,
|
|
138
157
|
});
|
|
139
158
|
}
|
|
140
159
|
|
|
160
|
+
/**
|
|
161
|
+
* H16-03: matrixSource + allowlist + AcBoundScopeService から acBoundScope を導出する。
|
|
162
|
+
* いずれかのポート/サービスが未配線なら [] を返す(additive-safe)。
|
|
163
|
+
* matrix の読み込みに失敗した場合も [] を返す(produce は attest の記録が主目的であり、
|
|
164
|
+
* 過大主張の防止は verify の再導出比較が担保する)。
|
|
165
|
+
*/
|
|
166
|
+
private async deriveAcBoundScope(): Promise<string[]> {
|
|
167
|
+
if (!this.deps.matrixSource || !this.deps.allowlist || !this.deps.acBoundScopeService) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
const allowlist = await this.deps.allowlist.getAcBoundStories();
|
|
172
|
+
if (allowlist.length === 0) return [];
|
|
173
|
+
const matrix = await this.deps.matrixSource.load(this.deps.matrixFilePath);
|
|
174
|
+
return this.deps.acBoundScopeService.derive(matrix, allowlist);
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
141
180
|
private async buildSources(): Promise<SourceEntry[]> {
|
|
142
181
|
const paths = this.deps.inputSourcePaths ?? DEFAULT_INPUT_SOURCES;
|
|
143
182
|
const sources: SourceEntry[] = [];
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
import type { AttestationRecord } from "../../domain/entities/attestation-record.js";
|
|
5
5
|
import type { ContentHasherPort } from "../../domain/ports/content-hasher-port.js";
|
|
6
6
|
import type { GranularityDerivationService } from "../../domain/services/granularity-derivation-service.js";
|
|
7
|
+
import type { AcBoundScopeService } from "../../domain/services/ac-bound-scope-service.js";
|
|
7
8
|
import type { VerifyAttestationInput } from "../dto/verify-attestation-input.js";
|
|
8
9
|
import type { VerifyAttestationChecks, VerifyAttestationOutput } from "../dto/verify-attestation-output.js";
|
|
9
10
|
import type { AttestationRecordMapper } from "../mappers/attestation-record-mapper.js";
|
|
10
11
|
import type { AttestationRepositoryPort } from "../ports/attestation-repository-port.js";
|
|
11
12
|
import type { SourceDigesterPort } from "../ports/source-digester-port.js";
|
|
13
|
+
import type { MatrixSourcePort } from "../ports/matrix-source-port.js";
|
|
14
|
+
import type { AcBoundAllowlistPort } from "../ports/ac-bound-allowlist-port.js";
|
|
12
15
|
|
|
13
16
|
export interface VerifyAttestationDeps {
|
|
14
17
|
readonly repository: AttestationRepositoryPort;
|
|
@@ -16,6 +19,12 @@ export interface VerifyAttestationDeps {
|
|
|
16
19
|
readonly hasher: ContentHasherPort;
|
|
17
20
|
readonly granularityService: GranularityDerivationService;
|
|
18
21
|
readonly mapper: AttestationRecordMapper;
|
|
22
|
+
/** H16-03: acBoundScope 再導出用(省略時は空 allowlist として扱い、格納が [] のときのみ合格)。 */
|
|
23
|
+
readonly matrixSource?: MatrixSourcePort;
|
|
24
|
+
readonly allowlist?: AcBoundAllowlistPort;
|
|
25
|
+
readonly acBoundScopeService?: AcBoundScopeService;
|
|
26
|
+
/** H16-03: matrix パスの明示指定(省略時は inputs.sources から解決)。 */
|
|
27
|
+
readonly matrixFilePath?: string;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export interface VerifyAttestationResult {
|
|
@@ -96,14 +105,18 @@ export class VerifyAttestationUseCase {
|
|
|
96
105
|
mismatches.push("granularity mismatch: stored granularity does not match re-derived value");
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
// 6b. acBoundScope を stored matrix + allowlist から再導出 == 格納値(anti-laundering, H16-03)
|
|
109
|
+
const acBoundScopeOk = await this.checkAcBoundScope(record, mismatches);
|
|
110
|
+
|
|
99
111
|
const checks: VerifyAttestationChecks = {
|
|
100
112
|
schema: true,
|
|
101
113
|
mode: true,
|
|
102
114
|
attestationDigest: attestationDigestOk,
|
|
103
115
|
inputHashes: inputHashesOk,
|
|
104
116
|
granularity: granularityOk,
|
|
117
|
+
acBoundScope: acBoundScopeOk,
|
|
105
118
|
};
|
|
106
|
-
const ok = attestationDigestOk && inputHashesOk && granularityOk;
|
|
119
|
+
const ok = attestationDigestOk && inputHashesOk && granularityOk && acBoundScopeOk;
|
|
107
120
|
|
|
108
121
|
return {
|
|
109
122
|
output: { ok, checks, mismatches },
|
|
@@ -118,10 +131,56 @@ export class VerifyAttestationUseCase {
|
|
|
118
131
|
attestationDigest: false,
|
|
119
132
|
inputHashes: false,
|
|
120
133
|
granularity: false,
|
|
134
|
+
acBoundScope: false,
|
|
121
135
|
...partialChecks,
|
|
122
136
|
};
|
|
123
137
|
return { output: { ok: false, checks, mismatches }, exitCode: 2 };
|
|
124
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* H16-03: acBoundScope を stored matrix + config allowlist から再導出し格納値と比較する(anti-laundering)。
|
|
142
|
+
*
|
|
143
|
+
* - ポート/サービスが未配線: 格納 acBoundScope が [] のときのみ合格(後方互換。空 allowlist 相当)。
|
|
144
|
+
* - matrix/allowlist が読めない・parse 不能: fail-closed(false, Q2)。
|
|
145
|
+
* - matrix パスは inputs.sources(ハッシュ検証済み入力)から解決する(Q3)。
|
|
146
|
+
*/
|
|
147
|
+
private async checkAcBoundScope(record: AttestationRecord, mismatches: string[]): Promise<boolean> {
|
|
148
|
+
const stored = [...record.acBoundScope].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
149
|
+
|
|
150
|
+
if (!this.deps.matrixSource || !this.deps.allowlist || !this.deps.acBoundScopeService) {
|
|
151
|
+
// 未配線: 格納が空のときのみ合格(空 allowlist で再導出すれば [] になるため)。
|
|
152
|
+
if (stored.length === 0) return true;
|
|
153
|
+
mismatches.push("acBoundScope check unavailable: matrix source not wired but stored acBoundScope is non-empty");
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let rederived: string[];
|
|
158
|
+
try {
|
|
159
|
+
const allowlist = await this.deps.allowlist.getAcBoundStories();
|
|
160
|
+
const matrixPath = this.deps.matrixFilePath ?? this.resolveMatrixPath(record);
|
|
161
|
+
const matrix = await this.deps.matrixSource.load(matrixPath);
|
|
162
|
+
rederived = this.deps.acBoundScopeService.derive(matrix, allowlist);
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// FAIL-CLOSED(Q2): 再導出入力が読めない/parse 不能は不一致として扱う。
|
|
165
|
+
mismatches.push(`acBoundScope re-derivation failed (fail-closed): ${errMsg(e)}`);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const rederivedSorted = [...rederived].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
170
|
+
const equal = stored.length === rederivedSorted.length && stored.every((s, i) => s === rederivedSorted[i]);
|
|
171
|
+
if (!equal) {
|
|
172
|
+
mismatches.push(
|
|
173
|
+
`acBoundScope mismatch: stored [${stored.join(",")}], re-derived [${rederivedSorted.join(",")}]`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return equal;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** inputs.sources から matrix にあたるパスを解決する(Q3)。 */
|
|
180
|
+
private resolveMatrixPath(record: AttestationRecord): string | undefined {
|
|
181
|
+
const matrixEntry = record.inputs.sources.find((s) => s.path.includes("requirement-test-matrix"));
|
|
182
|
+
return matrixEntry?.path;
|
|
183
|
+
}
|
|
125
184
|
}
|
|
126
185
|
|
|
127
186
|
function errMsg(e: unknown): string {
|
|
@@ -13,9 +13,12 @@ import { AttestationRecordMapper } from "./application/mappers/attestation-recor
|
|
|
13
13
|
import { ProduceAttestationUseCase } from "./application/usecases/produce-attestation-usecase.js";
|
|
14
14
|
import { VerifyAttestationUseCase } from "./application/usecases/verify-attestation-usecase.js";
|
|
15
15
|
import { GranularityDerivationService } from "./domain/services/granularity-derivation-service.js";
|
|
16
|
+
import { AcBoundScopeService } from "./domain/services/ac-bound-scope-service.js";
|
|
16
17
|
import { CiCheckGateResultAdapter } from "./infrastructure/adapters/ci-check-gate-result-adapter.js";
|
|
17
18
|
import { FileSystemAttestationRepositoryAdapter } from "./infrastructure/adapters/file-system-attestation-repository-adapter.js";
|
|
18
19
|
import { FileSystemSourceDigesterAdapter } from "./infrastructure/adapters/file-system-source-digester-adapter.js";
|
|
20
|
+
import { FileSystemMatrixSourceAdapter } from "./infrastructure/adapters/file-system-matrix-source-adapter.js";
|
|
21
|
+
import { ConfigAcBoundAllowlistAdapter } from "./infrastructure/adapters/config-ac-bound-allowlist-adapter.js";
|
|
19
22
|
import { NodeCryptoContentHasherAdapter } from "./infrastructure/adapters/node-crypto-content-hasher-adapter.js";
|
|
20
23
|
import { AttestHandler } from "./presentation/handlers/attest-handler.js";
|
|
21
24
|
import { VerifyAttestationHandler } from "./presentation/handlers/verify-attestation-handler.js";
|
|
@@ -51,8 +54,13 @@ export function createAttestationModule(rootDir: string, options?: AttestationMo
|
|
|
51
54
|
|
|
52
55
|
// Domain services / mappers
|
|
53
56
|
const granularityService = new GranularityDerivationService();
|
|
57
|
+
const acBoundScopeService = new AcBoundScopeService();
|
|
54
58
|
const mapper = new AttestationRecordMapper();
|
|
55
59
|
|
|
60
|
+
// H16-03: acBoundScope 導出用 adapter(matrix 供給 + config allowlist)。
|
|
61
|
+
const matrixSource = new FileSystemMatrixSourceAdapter(rootDir);
|
|
62
|
+
const allowlist = new ConfigAcBoundAllowlistAdapter();
|
|
63
|
+
|
|
56
64
|
const gitCommitProvider = options?.gitCommitProvider ?? (() => defaultGitCommitProvider(rootDir));
|
|
57
65
|
|
|
58
66
|
// Use cases
|
|
@@ -66,6 +74,9 @@ export function createAttestationModule(rootDir: string, options?: AttestationMo
|
|
|
66
74
|
gitCommitProvider,
|
|
67
75
|
pkgVersion: options?.pkgVersion ?? "0.0.0",
|
|
68
76
|
clock: options?.clock,
|
|
77
|
+
matrixSource,
|
|
78
|
+
allowlist,
|
|
79
|
+
acBoundScopeService,
|
|
69
80
|
});
|
|
70
81
|
const verifyUseCase = new VerifyAttestationUseCase({
|
|
71
82
|
repository,
|
|
@@ -73,6 +84,9 @@ export function createAttestationModule(rootDir: string, options?: AttestationMo
|
|
|
73
84
|
hasher,
|
|
74
85
|
granularityService,
|
|
75
86
|
mapper,
|
|
87
|
+
matrixSource,
|
|
88
|
+
allowlist,
|
|
89
|
+
acBoundScopeService,
|
|
76
90
|
});
|
|
77
91
|
|
|
78
92
|
// Handlers
|
|
@@ -41,6 +41,12 @@ export interface AttestationRecordProps {
|
|
|
41
41
|
readonly granularity: { readonly traceability: GranularityClaim };
|
|
42
42
|
readonly metadata: MetadataSection;
|
|
43
43
|
readonly signature: SignatureBlock;
|
|
44
|
+
/**
|
|
45
|
+
* H16-03 / WI-227: 実際に ac-bound かつ L3-005 スコープ内で pass した story-id(昇順)。
|
|
46
|
+
* canonical payload に含まれ attestationDigest でカバーされる。granularity.level とは独立。
|
|
47
|
+
* 省略時は [] として扱う(additive-safe)。
|
|
48
|
+
*/
|
|
49
|
+
readonly acBoundScope?: readonly string[];
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
/**
|
|
@@ -87,6 +93,7 @@ export class AttestationRecord {
|
|
|
87
93
|
readonly granularity: { readonly traceability: GranularityClaim };
|
|
88
94
|
readonly metadata: MetadataSection;
|
|
89
95
|
readonly signature: SignatureBlock;
|
|
96
|
+
readonly acBoundScope: readonly string[];
|
|
90
97
|
|
|
91
98
|
private constructor(props: AttestationRecordProps) {
|
|
92
99
|
this.schemaVersion = props.schemaVersion;
|
|
@@ -96,6 +103,7 @@ export class AttestationRecord {
|
|
|
96
103
|
this.granularity = props.granularity;
|
|
97
104
|
this.metadata = props.metadata;
|
|
98
105
|
this.signature = props.signature;
|
|
106
|
+
this.acBoundScope = Object.freeze([...(props.acBoundScope ?? [])]);
|
|
99
107
|
Object.freeze(this);
|
|
100
108
|
}
|
|
101
109
|
|
|
@@ -185,6 +193,8 @@ export class AttestationRecord {
|
|
|
185
193
|
knownLimitations: [...this.granularity.traceability.knownLimitations],
|
|
186
194
|
},
|
|
187
195
|
},
|
|
196
|
+
// H16-03: acBoundScope は canonical payload に含める(attestationDigest でカバー)。
|
|
197
|
+
acBoundScope: [...this.acBoundScope],
|
|
188
198
|
metadata: {
|
|
189
199
|
producer: this.metadata.producer,
|
|
190
200
|
},
|
|
@@ -211,6 +221,7 @@ export class AttestationRecord {
|
|
|
211
221
|
granularity: this.granularity,
|
|
212
222
|
metadata: this.metadata,
|
|
213
223
|
signature: SignatureBlock.unsignedPoc(digest),
|
|
224
|
+
acBoundScope: this.acBoundScope,
|
|
214
225
|
});
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -234,6 +245,8 @@ export class AttestationRecord {
|
|
|
234
245
|
return false;
|
|
235
246
|
}
|
|
236
247
|
if (!this.granularity.traceability.equals(other.granularity.traceability)) return false;
|
|
248
|
+
if (this.acBoundScope.length !== other.acBoundScope.length) return false;
|
|
249
|
+
if (!this.acBoundScope.every((s, i) => s === other.acBoundScope[i])) return false;
|
|
237
250
|
if (!this.signature.equals(other.signature)) return false;
|
|
238
251
|
return true;
|
|
239
252
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @unit attestation
|
|
2
|
+
// @layer domain
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AcBoundScopeService(H16-03 / WI-227)
|
|
6
|
+
*
|
|
7
|
+
* requirement-test-matrix + allowlist から acBoundScope を機械導出する純粋・決定論サービス。
|
|
8
|
+
* 生成(produce)と検証(verify anti-laundering 再導出)で完全に同一の結果を返す(option-a determinism)。
|
|
9
|
+
*
|
|
10
|
+
* 資格条件(domain_model INV-8): story が返り値に含まれる ⟺
|
|
11
|
+
* (1) allowlist に含まれ、かつ
|
|
12
|
+
* (2) その story の全 linked AC が ≥1 の `binding:"ac"` テスト参照を持つ(fileFallbackOnly===0)。
|
|
13
|
+
*
|
|
14
|
+
* anti-laundering: 導出は stored matrix + config allowlist のみを真実の源とする。格納 acBoundScope を
|
|
15
|
+
* 過大主張へ改竄しても、再導出は matrix/allowlist の honest な結果を返すため mismatch が検出される。
|
|
16
|
+
*
|
|
17
|
+
* `granularity.traceability.level`("file")とは完全に独立した別次元の主張である。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
interface MatrixTestReference {
|
|
21
|
+
readonly binding?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface MatrixAcMapping {
|
|
25
|
+
readonly testReferences?: readonly MatrixTestReference[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface MatrixStory {
|
|
29
|
+
readonly storyId?: string;
|
|
30
|
+
readonly storyMappings?: readonly MatrixAcMapping[];
|
|
31
|
+
readonly acMappings?: readonly MatrixAcMapping[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class AcBoundScopeService {
|
|
35
|
+
/**
|
|
36
|
+
* @param matrix requirement-test-matrix(parse 済み plain object)
|
|
37
|
+
* @param allowlist スコープ対象 story-id
|
|
38
|
+
* @returns 資格を満たす story-id の昇順ソート配列
|
|
39
|
+
*/
|
|
40
|
+
derive(matrix: unknown, allowlist: readonly string[]): string[] {
|
|
41
|
+
const scope = new Set(allowlist);
|
|
42
|
+
if (scope.size === 0) return [];
|
|
43
|
+
|
|
44
|
+
const stories = this.extractStories(matrix);
|
|
45
|
+
const qualified: string[] = [];
|
|
46
|
+
|
|
47
|
+
for (const story of stories) {
|
|
48
|
+
const storyId = story.storyId ?? "";
|
|
49
|
+
if (!scope.has(storyId)) continue;
|
|
50
|
+
|
|
51
|
+
const acMappings = story.storyMappings ?? story.acMappings ?? [];
|
|
52
|
+
// linked AC が 1 件も無い場合は資格対象外(検証すべき AC が存在しない)。
|
|
53
|
+
if (acMappings.length === 0) continue;
|
|
54
|
+
|
|
55
|
+
const everyAcBound = acMappings.every((ac) => {
|
|
56
|
+
const refs = ac.testReferences ?? [];
|
|
57
|
+
// 未リンク AC(refs 空)があれば ac-bound 主張は成立しない。
|
|
58
|
+
if (refs.length === 0) return false;
|
|
59
|
+
return refs.some((ref) => ref.binding === "ac");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (everyAcBound) {
|
|
63
|
+
qualified.push(storyId);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return qualified.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private extractStories(matrix: unknown): readonly MatrixStory[] {
|
|
71
|
+
if (typeof matrix !== "object" || matrix === null) return [];
|
|
72
|
+
const obj = matrix as { stories?: unknown; storyMappings?: unknown };
|
|
73
|
+
const raw = Array.isArray(obj.stories)
|
|
74
|
+
? obj.stories
|
|
75
|
+
: Array.isArray(obj.storyMappings)
|
|
76
|
+
? obj.storyMappings
|
|
77
|
+
: [];
|
|
78
|
+
return raw as readonly MatrixStory[];
|
|
79
|
+
}
|
|
80
|
+
}
|
package/scripts/harness/attestation/infrastructure/adapters/config-ac-bound-allowlist-adapter.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// @unit attestation
|
|
2
|
+
// @layer infrastructure
|
|
3
|
+
|
|
4
|
+
import type { AcBoundAllowlistPort } from "../../application/ports/ac-bound-allowlist-port.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* AcBoundAllowlistPort の config-foundation 実装(H16-03 / WI-227)。
|
|
8
|
+
* resolved config の `layers.L3.acBoundStories`(既定 [])を供給する。
|
|
9
|
+
* config 不在・取得失敗時は [] を返す(acBoundScope は空になる)。
|
|
10
|
+
*/
|
|
11
|
+
export class ConfigAcBoundAllowlistAdapter implements AcBoundAllowlistPort {
|
|
12
|
+
async getAcBoundStories(): Promise<readonly string[]> {
|
|
13
|
+
try {
|
|
14
|
+
const { createConfigFoundationModule } = await import("../../../config-foundation/composition-root.js");
|
|
15
|
+
const configModule = createConfigFoundationModule();
|
|
16
|
+
const resolvedConfig = await configModule.usecases.loadResolvedConfigUseCase.execute();
|
|
17
|
+
const l3 = resolvedConfig.config.layers.L3 as { acBoundStories?: readonly string[] };
|
|
18
|
+
return l3.acBoundStories ?? [];
|
|
19
|
+
} catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|