haechi 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.ko.md +97 -97
  2. package/README.md +2 -2
  3. package/SECURITY.md +19 -11
  4. package/docs/README.md +2 -0
  5. package/docs/current/api-stability.ko.md +26 -26
  6. package/docs/current/compliance-mapping.ko.md +53 -0
  7. package/docs/current/compliance-mapping.md +53 -0
  8. package/docs/current/config-version.ko.md +30 -0
  9. package/docs/current/config-version.md +51 -0
  10. package/docs/current/configuration.ko.md +242 -102
  11. package/docs/current/configuration.md +149 -9
  12. package/docs/current/operations-runbook.ko.md +121 -0
  13. package/docs/current/operations-runbook.md +204 -0
  14. package/docs/current/release-process.ko.md +19 -20
  15. package/docs/current/release-process.md +1 -2
  16. package/docs/current/reliability-hardening-track.ko.md +77 -0
  17. package/docs/current/reliability-hardening-track.md +77 -0
  18. package/docs/current/risk-register-release-gate.ko.md +26 -27
  19. package/docs/current/risk-register-release-gate.md +27 -20
  20. package/docs/current/security-whitepaper.ko.md +102 -0
  21. package/docs/current/security-whitepaper.md +102 -0
  22. package/docs/current/shared-responsibility.ko.md +33 -24
  23. package/docs/current/shared-responsibility.md +12 -3
  24. package/docs/current/threat-model.ko.md +12 -12
  25. package/docs/current/threat-model.md +3 -3
  26. package/haechi.config.example.json +19 -3
  27. package/package.json +6 -2
  28. package/packages/audit/index.mjs +26 -2
  29. package/packages/cli/bin/haechi.mjs +54 -8
  30. package/packages/cli/runtime.mjs +398 -10
  31. package/packages/core/index.mjs +189 -15
  32. package/packages/filter/index.mjs +299 -9
  33. package/packages/metrics/index.mjs +181 -0
  34. package/packages/proxy/index.mjs +535 -41
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## 1. 버전 해석
8
8
 
9
- 0.x 라인은 developer preview였다: public exports는 사용 가능했지만 stable API **아니었다**. **1.0.0이 현재 stable 릴리스**이며, 아래 계약은 **지금 발효 중**이다 — Haechi는 이 계약을 선언하고 **strict semver**를 채택한다(§2 참조). §2의 freeze 규칙(frozen `exports`/CLI 표면, audit event schema, config key shape)은 릴리스된 1.0 라인에 적용되며, 더 이상 미래의 약속이 아니다. `tests/api-contract.test.mjs` freeze guard가 frozen 표면을 핀한다; frozen export·audit 필드·config key를 제거/이름 변경하면 CI가 실패한다(이것이 breaking change임을 의식적으로 알리는 신호다).
9
+ 0.x 라인은 developer preview였습니다. public exports는 사용할 있었지만 stable API **아니었습니다**. **1.0.0이 현재 stable 릴리스**이며, 아래 계약은 **지금 발효 중**입니다 — Haechi는 이 계약을 선언하고 **strict semver**를 채택합니다(§2 참조). §2의 freeze 규칙(frozen `exports`/CLI 표면, audit event schema, config key shape)은 릴리스된 1.0 라인에 적용되며, 더 이상 미래의 약속이 아닙니다. `tests/api-contract.test.mjs` freeze guard가 frozen 표면을 핀합니다. frozen export·audit 필드·config key를 제거하거나 이름을 바꾸면 CI가 실패하는데, 이것이 breaking change임을 의식적으로 알리는 신호입니다.
10
10
 
11
11
  | 버전 범위 | 의미 |
12
12
  |---|---|
@@ -15,13 +15,13 @@
15
15
  | `0.5.x` | streaming hardening target (former preview) |
16
16
  | `0.6.x` | auth 및 운영 통제 target (former preview) |
17
17
  | `0.7.x` – `0.9.x` | dashboard / KMS / OIDC 위성 + pre-1.0 하드닝 (former preview) |
18
- | `1.0.0` | **현재 stable 릴리스.** §2의 API 계약이 strict semver 하에 frozen이며 발효 중이다. |
18
+ | `1.0.0` | **현재 stable 릴리스.** §2의 API 계약이 strict semver 하에 frozen이며 발효 중입니다. |
19
19
 
20
20
  ## 2. 1.0 안정성 계약
21
21
 
22
22
  ### 2.1 Frozen public surface (IN / OUT)
23
23
 
24
- 모든 `package.json` `exports` subpath과 CLI를 분류한다. 더 이상 "0.x는 preview"라는 암묵적 latitude는 없다.
24
+ 모든 `package.json` `exports` subpath과 CLI를 분류합니다. 더 이상 "0.x는 preview"라는 암묵적 latitude는 없습니다.
25
25
 
26
26
  | 표면 | 1.0 상태 |
27
27
  |---|---|
@@ -40,13 +40,13 @@
40
40
  | `haechi/stream-filter` — `inspectResponseStream`, `getByPath`, `setByPath`, `buildPathObject` | **FROZEN BEHAVIOR + wire/contract** |
41
41
  | `haechi/policy-bundle` — `signPolicyBundle(File)`, `verifyPolicyBundle(File)`, `loadVerifiedPolicyBundleFileSync` | **FROZEN BEHAVIOR + wire/contract** (signed-bundle 포맷 frozen) |
42
42
  | `haechi/privacy-profiles` — `listPrivacyProfiles`, `getPrivacyProfile`, `applyPrivacyProfile` | **FROZEN BEHAVIOR + wire/contract** |
43
- | **CLI** — `bin/haechi.mjs` 명령 이름, 플래그, **exit code**, 기계가 읽는(JSON) 출력 | **FROZEN BEHAVIOR + wire/contract**; 사람이 읽는 help/log/status **텍스트**는 계약이 아니며 변경 가능 |
43
+ | **CLI** — `bin/haechi.mjs` 명령 이름, 플래그, **exit code**, 기계가 읽는(JSON) 출력 | **FROZEN BEHAVIOR + wire/contract**. 사람이 읽는 help/log/status **텍스트**는 계약이 아니며 변경 가능 |
44
44
 
45
- **FROZEN** = export 이름·시그니처·동작이 major 버전 계약의 일부다. **FROZEN BEHAVIOR + wire/contract** = wire 포맷·exit code·기계가 읽는 출력·보안 동작은 frozen이지만, 사람이 읽는 CLI/log **텍스트**는 명시적으로 계약이 *아니며* minor/patch에서 변경될 수 있다.
45
+ **FROZEN** = export 이름·시그니처·동작이 major 버전 계약의 일부입니다. **FROZEN BEHAVIOR + wire/contract** = wire 포맷·exit code·기계가 읽는 출력·보안 동작은 frozen이지만, 사람이 읽는 CLI/log **텍스트**는 명시적으로 계약이 *아니며* minor/patch에서 변경될 수 있습니다.
46
46
 
47
47
  ### 2.2 Strict semver + deprecation 정책
48
48
 
49
- 1.0부터 "0.x minor가 깰 수 있다"는 latitude는 **끝난다**. 버저닝은 strict semver다:
49
+ 1.0부터 "0.x minor가 깰 수 있다"는 latitude는 **끝납니다**. 버저닝은 strict semver입니다.
50
50
 
51
51
  | 변경 유형 | 릴리스 |
52
52
  |---|---|
@@ -54,49 +54,49 @@
54
54
  | Additive change (새 export, 새 optional config key, 새 additive audit 필드) | **minor** |
55
55
  | Bug fix / default 값 하드닝 (shape 변경 없음) | **patch** |
56
56
 
57
- **Deprecation 정책.** deprecated된 export / audit 필드 / config 옵션은:
57
+ **Deprecation 정책.** deprecated된 export / audit 필드 / config 옵션은 다음과 같이 처리됩니다.
58
58
 
59
59
  1. deprecation 후 **최소 1 minor 동안 유지**되고,
60
60
  2. **문서화된 migration note**(`docs/current/release-*.md` 또는 README)와 함께 배포되며,
61
- 3. **안정 `code` prefix `HAECHI_DEPRECATION_*`**(예: `HAECHI_DEPRECATION_CONFIG_<key>`, `HAECHI_DEPRECATION_EXPORT_<name>`)를 가진 **일회성 런타임 `process.emitWarning`**를 발생시킨다. **경고 `code`와 그 텍스트 자체가 계약의 일부다** — 소비자가 매칭할 수 있는 안정 식별자이며, 다음 major에서만 변경된다.
61
+ 3. **안정 `code` prefix `HAECHI_DEPRECATION_*`**(예: `HAECHI_DEPRECATION_CONFIG_<key>`, `HAECHI_DEPRECATION_EXPORT_<name>`)를 가진 **일회성 런타임 `process.emitWarning`**을 발생시킵니다. **경고 `code`와 그 텍스트 자체가 계약의 일부입니다** — 소비자가 매칭할 수 있는 안정 식별자이며, 다음 major에서만 변경됩니다.
62
62
 
63
- deprecated 표면은 **다음 major에서만 제거**된다.
63
+ deprecated 표면은 **다음 major에서만 제거됩니다**.
64
64
 
65
- **보안 예외 (허용되는 단 하나의 in-minor break).** **공개된(disclosed)** 취약점을 닫기 위해 필요한 변경은 **minor 안에서** frozen 표면을 깨거나 제거할 수 있으며, **보안 권고(advisory) + migration path**와 함께 배포된다. 이는 오래된 "unsafe config 차단은 patch에서 강화 가능" latitude를 그대로 반영한다 — 보안 태세는 deprecation 창보다 빠르게 강화될 수 있다.
65
+ **보안 예외 (허용되는 단 하나의 in-minor break).** **공개된(disclosed)** 취약점을 닫기 위해 필요한 변경은 **minor 안에서** frozen 표면을 깨거나 제거할 수 있으며, **보안 권고(advisory) + migration path**와 함께 배포됩니다. 이는 오래된 "unsafe config 차단은 patch에서 강화 가능" latitude를 그대로 반영합니다 — 보안 태세는 deprecation 창보다 빠르게 강화될 수 있습니다.
66
66
 
67
67
  ### 2.3 Frozen audit event schema (중첩 sub-schema 포함)
68
68
 
69
- audit event(`packages/core/index.mjs`의 `buildAuditEvent`가 생성, `packages/audit`가 무결성 스탬프)는 top-level뿐 아니라 **중첩 sub-schema까지** frozen이다:
69
+ audit event(`packages/core/index.mjs`의 `buildAuditEvent`가 생성하고 `packages/audit`가 무결성 스탬프를 찍습니다)는 top-level뿐 아니라 **중첩 sub-schema까지** frozen입니다.
70
70
 
71
71
  - **top-level**: `{ schemaVersion, id, timestamp, protocol, operation, identity, profile, mode, enforced, blocked, payloadShapeHash, detections, summary, auditIntegrity }`
72
72
  - `detections[]`: `{ type, ruleId, path, kind, confidence, action, enforced }`
73
- - `identity` (**PII-safe** 투영): `{ id, type, subjectHash, issuerHash, provider }` — `scopes` / `labels` / raw subject은 frozen audit identity의 **일부가 아니다**(keyed-HMAC `subjectHash`/`issuerHash`가 유일한 subject/issuer 표면이다). 단, 실제 온디스크 `identity` 객체에는 `scopes`와 `labels`가 함께 포함될 수 있어 총 7개의 키를 가질 수 있으나, 해당 필드들은 frozen 계약의 **일부가 아니다** — audit 로그 소비자는 이 필드들의 존재에 의존해서는 안 된다. auth 미설정 시 `identity`는 `null`이다.
73
+ - `identity` (**PII-safe** 투영): `{ id, type, subjectHash, issuerHash, provider }` — `scopes` / `labels` / raw subject은 frozen audit identity의 **일부가 아닙니다**(keyed-HMAC `subjectHash`/`issuerHash`가 유일한 subject/issuer 표면입니다). 단, 실제 온디스크 `identity` 객체에는 `scopes`와 `labels`가 함께 포함되어 총 7개의 키를 가질 수 있으나, 해당 필드들은 frozen 계약의 **일부가 아닙니다** — audit 로그 소비자는 이 필드들의 존재에 의존해서는 안 됩니다. auth 미설정 시 `identity`는 `null`입니다.
74
74
  - `summary`: `{ byType, byAction, detectionCount }`
75
75
  - `auditIntegrity`: `{ alg, canonicalization, sequence, previousHash, eventHash }`
76
76
 
77
- 규칙:
77
+ 규칙은 다음과 같습니다.
78
78
 
79
- - **`schemaVersion`**은 명시적 top-level reader-facing 필드(1.0 라인에서 값 `"1"`)로, 소비자가 `auditIntegrity`를 파싱하지 않고도 분기할 수 있게 한다. **additive**이며 canonicalize 대상 객체의 일부다.
80
- - **새 필드는 additive-only이고 기존 필드의 canonicalization을 절대 바꾸지 않는다.** `canonicalize`는 리터럴 객체를 해싱하고 `verifyAuditChain`은 *동일한* 저장 객체로 `eventHash`를 재계산하므로, future-additive 필드를 담은 1.x event도 그 레코드를 읽는 1.0 `verifyAuditChain` 하에서 여전히 검증된다 — 보장은 "future-additive 필드가 새 레코드를 읽는 옛 verifier를 깨뜨리지 않는다"이다.
81
- - **canonicalization 변경**은 **major** event-schema bump다: **새 `canonicalization` 태그**(현재 값 `json-stable-v1`)와 **reader-migration path**를 함께 배포한다. 기존 필드의 해시 기반이 바뀔 수 있는 유일한 방법이다.
79
+ - **`schemaVersion`**은 명시적 top-level reader-facing 필드(1.0 라인에서 값 `"1"`)로, 소비자가 `auditIntegrity`를 파싱하지 않고도 분기할 수 있게 합니다. **additive**이며 canonicalize 대상 객체의 일부입니다.
80
+ - **새 필드는 additive-only이고 기존 필드의 canonicalization을 절대 바꾸지 않습니다.** `canonicalize`는 리터럴 객체를 해싱하고 `verifyAuditChain`은 *동일한* 저장 객체로 `eventHash`를 재계산하므로, future-additive 필드를 담은 1.x event도 그 레코드를 읽는 1.0 `verifyAuditChain` 하에서 여전히 검증됩니다 — 보장은 "future-additive 필드가 새 레코드를 읽는 옛 verifier를 깨뜨리지 않는다"입니다.
81
+ - **canonicalization 변경**은 **major** event-schema bump입니다. **새 `canonicalization` 태그**(현재 값 `json-stable-v1`)와 **reader-migration path**를 함께 배포합니다. 기존 필드의 해시 기반이 바뀔 수 있는 유일한 방법입니다.
82
82
 
83
83
  ### 2.4 Config schema freeze 단위
84
84
 
85
- **config key 존재 + shape**가 frozen이다(top-level key `mode`, `target`, `proxy`, `responseProtection`, `streaming`, `limits`, `policy`, `filters`, `keys`, `audit`, `tokenVault`, `privacy`, `auth`, `mcp` 및 그 중첩 shape). **default *값*은 여전히 하드닝될 수 있다** — 더 안전한 default(예: 더 엄격한 `failureMode`)는 breaking change가 **아니다**. **알 수 없는 key는 여전히 throw한다**(fail-closed): `normalizeConfig`는 엄격한 enumerated 검증을 수행하며, 그 fail-closed 태세가 계약의 일부다.
85
+ **config key 존재 + shape**가 frozen입니다(top-level key `mode`, `target`, `proxy`, `responseProtection`, `streaming`, `limits`, `policy`, `filters`, `keys`, `audit`, `tokenVault`, `privacy`, `auth`, `mcp` 및 그 중첩 shape). **default *값*은 여전히 하드닝될 수 있습니다** — 더 안전한 default(예: 더 엄격한 `failureMode`)는 breaking change가 **아닙니다**. **알 수 없는 key는 여전히 throw합니다**(fail-closed). `normalizeConfig`는 엄격한 enumerated 검증을 수행하며, 그 fail-closed 태세가 계약의 일부입니다.
86
86
 
87
87
  ## 3. Graduated / 잔존 preview exports
88
88
 
89
- 0.x "experimental exports" 목록은 1.0에서 **해소**된다 — 모든 항목은 **graduated**(이제 §2.1 FROZEN / FROZEN-BEHAVIOR 표면의 일부)되거나 명시적 이유와 함께 **1.0 이후에도 preview로 유지**된다. 암묵적 모호함은 없다.
89
+ 0.x "experimental exports" 목록은 1.0에서 **해소됩니다** — 모든 항목은 **graduated**(이제 §2.1 FROZEN / FROZEN-BEHAVIOR 표면의 일부)되거나 명시적 이유와 함께 **1.0 이후에도 preview로 유지**됩니다. 암묵적 모호함은 없습니다.
90
90
 
91
- **Graduated (이제 §2.1에 따라 FROZEN):** `haechi/runtime`, `haechi/proxy`, `haechi/protocol-adapters`, `haechi/privacy-profiles`, `haechi/plugin`, `haechi/mcp-stdio` (`wrapMcpChild`), `haechi/token-vault` (`detokenize` / deterministic tokenization 옵션), `identity` audit 필드와 `authProvider` 계약, `haechi/stream-filter`와 `createStreamProtector`, `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`, `buildExternalIdentity`), `assertCryptoProviderConformance`와 강화된 `cryptoProvider` 계약, `audit.anchor` + `verifyAuditChain(path, { anchorPath })`, `scripts/release-checksums.mjs`, `policy.profiles` / `policy.profileBinding` / `modelAllowlist` / `rate`와 `identity` / `profile` audit 필드. `status` / `audit-verify` CLI 출력의 기계가 읽는 형태는 frozen이다(FROZEN BEHAVIOR — 사람이 읽는 텍스트는 아님).
91
+ **Graduated (이제 §2.1에 따라 FROZEN):** `haechi/runtime`, `haechi/proxy`, `haechi/protocol-adapters`, `haechi/privacy-profiles`, `haechi/plugin`, `haechi/mcp-stdio` (`wrapMcpChild`), `haechi/token-vault` (`detokenize` / deterministic tokenization 옵션), `identity` audit 필드와 `authProvider` 계약, `haechi/stream-filter`와 `createStreamProtector`, `haechi/auth` (`createBearerAuthProvider`, token store, `buildIdentity`, `buildExternalIdentity`), `assertCryptoProviderConformance`와 강화된 `cryptoProvider` 계약, `audit.anchor` + `verifyAuditChain(path, { anchorPath })`, `scripts/release-checksums.mjs`, `policy.profiles` / `policy.profileBinding` / `modelAllowlist` / `rate`와 `identity` / `profile` audit 필드. `status` / `audit-verify` CLI 출력의 기계가 읽는 형태는 frozen입니다(FROZEN BEHAVIOR — 사람이 읽는 텍스트는 아닙니다).
92
92
 
93
93
  **1.0 이후에도 preview로 유지 (이유 명시):**
94
94
 
95
- - **`injection` detection type과 그 휴리스틱 룰** — 휴리스틱 집합은 계속 진화할 것으로 예상되며 **기본 report-only**다(명시적 escalate 없이는 response 방향에서 action `allow`로 고정). 따라서 그 *룰 멤버십 / confidence*는 frozen이 아니지만, 그것이 생성하는 detection *shape*는 frozen `detections[]` shape다. `injection` 추가/변경은 breaking change가 아니다.
95
+ - **`injection` detection type과 그 휴리스틱 룰** — 휴리스틱 집합은 계속 진화할 것으로 예상되며 **기본 report-only**입니다(명시적 escalate 없이는 response 방향에서 action `allow`로 고정). 따라서 그 *룰 멤버십 / confidence*는 frozen이 아니지만, 그것이 생성하는 detection *shape*는 frozen `detections[]` shape입니다. `injection` 룰을 추가하거나 변경하는 것은 breaking change가 아닙니다.
96
96
 
97
97
  ## 4. Migration note 기준
98
98
 
99
- 다음 변경이 있으면 `docs/current/release-*.md` 또는 README에 migration note를 남긴다. 1.0부터 이 목록의 변경이 **FROZEN** 표면에 가해지면 **major** 이벤트(또는 §2.2의 보안 예외 minor)이며, deprecation 창이 적용되는 경우 `HAECHI_DEPRECATION_*` 런타임 경고를 동반하고 `tests/api-contract.test.mjs`를 갱신한다.
99
+ 다음 변경이 있으면 `docs/current/release-*.md` 또는 README에 migration note를 남깁니다. 1.0부터 이 목록의 변경이 **FROZEN** 표면에 가해지면 **major** 이벤트(또는 §2.2의 보안 예외 minor)이며, deprecation 창이 적용되는 경우 `HAECHI_DEPRECATION_*` 런타임 경고를 동반하고 `tests/api-contract.test.mjs`를 갱신합니다.
100
100
 
101
101
  - config key 추가/삭제
102
102
  - default enforcement 변경
@@ -107,12 +107,12 @@ audit event(`packages/core/index.mjs`의 `buildAuditEvent`가 생성, `packages/
107
107
 
108
108
  ## 5. Satellite 패키지 (`haechi-*`)
109
109
 
110
- 위성(예: `haechi-crypto-kms`, `haechi-auth-jwt`, `haechi-dashboard`, `haechi-auth-oidc`)은 core와 **독립적으로** 버저닝한다 — 위성 릴리스가 `haechi`를 bump하지 않고, 그 반대도 마찬가지다.
110
+ 위성(예: `haechi-crypto-kms`, `haechi-auth-jwt`, `haechi-dashboard`, `haechi-auth-oidc`)은 core와 **독립적으로** 버저닝합니다 — 위성 릴리스가 `haechi`를 bump하지 않고, 그 반대도 마찬가지입니다.
111
111
 
112
- - **pre-1.0:** 위성은 npm semver를 따르며 `0.x` **minor** bump가 breaking change를 담을 수 있다; `major.minor`로 핀한다(예: `haechi-crypto-kms@~0.2`). 각자 자체 `1.0.0`까지 pre-stable.
113
- - **core 호환성**은 `peerDependencies` 범위(`"haechi": ">=0.8.0 <2.0.0"`)로 표현한다 — 위성은 소비자가 설치한 단일 `haechi`를 재사용하므로 crypto/identity 표면이 하나다. `haechi-auth-oidc`는 추가로 `haechi-auth-jwt`(`">=0.2.0 <2.0.0"`)에 peer-depend하여 둘이 audit되는 단일 JWS/JWKS 검증 경로를 공유한다. 위성의 `haechi` peer-dependency **상한은 반드시 core MAJOR를 추적해야 하며**(`<2.0.0`), 다음 minor 미만으로 고정해서는 안 된다 — core의 minor/major 호환 범프가 위성 설치를 깨뜨리지 않도록 하기 위함이다; `release:preflight` 게이트(`scripts/check-satellite-peer-ranges.mjs`)가 이를 자동으로 강제한다.
114
- - **무거운 백엔드는 optional peer다.** `haechi-crypto-kms`는 SDK 백엔드(`@aws-sdk/client-kms`, 그리고 0.2.0의 `@google-cloud/kms`, `@azure/keyvault-keys`, `@azure/identity`)를 `peerDependencies` + `peerDependenciesMeta.optional`로 선언하고 lazy import하므로, 해당 경로를 쓰지 않는 소비자는 설치하지 않고 core는 zero-dependency를 유지한다. `./vault` 백엔드는 `node:` `fetch`만 사용한다(optional peer 없음). 위성의 배포 tarball은 항상 **runtime `dependencies` 0**을 선언한다(CI `check-satellite-packaging`로 강제).
115
- - **pre-1.0 위성 export**는 preview이며 각 위성의 자체 `1.0.0` 전에 변경될 수 있다:
112
+ - **pre-1.0:** 위성은 npm semver를 따르며 `0.x` **minor** bump가 breaking change를 담을 수 있습니다. `major.minor`로 핀합니다(예: `haechi-crypto-kms@~0.2`). 각자 자체 `1.0.0`까지 pre-stable입니다.
113
+ - **core 호환성**은 `peerDependencies` 범위(`"haechi": ">=0.8.0 <2.0.0"`)로 표현합니다 — 위성은 소비자가 설치한 단일 `haechi`를 재사용하므로 crypto/identity 표면이 하나입니다. `haechi-auth-oidc`는 추가로 `haechi-auth-jwt`(`">=0.2.0 <2.0.0"`)에 peer-depend하여 둘이 audit되는 단일 JWS/JWKS 검증 경로를 공유합니다. 위성의 `haechi` peer-dependency **상한은 반드시 core MAJOR를 추적해야 하며**(`<2.0.0`), 다음 minor 미만으로 고정해서는 안 됩니다 — core의 minor/major 호환 범프가 위성 설치를 깨뜨리지 않도록 하기 위함입니다. `release:preflight` 게이트(`scripts/check-satellite-peer-ranges.mjs`)가 이를 자동으로 강제합니다.
114
+ - **무거운 백엔드는 optional peer입니다.** `haechi-crypto-kms`는 SDK 백엔드(`@aws-sdk/client-kms`, 그리고 0.2.0의 `@google-cloud/kms`, `@azure/keyvault-keys`, `@azure/identity`)를 `peerDependencies` + `peerDependenciesMeta.optional`로 선언하고 lazy import하므로, 해당 경로를 쓰지 않는 소비자는 설치하지 않고 core는 zero-dependency를 유지합니다. `./vault` 백엔드는 `node:` `fetch`만 사용합니다(optional peer 없음). 위성의 배포 tarball은 항상 **runtime `dependencies` 0**을 선언합니다(CI `check-satellite-packaging`로 강제).
115
+ - **pre-1.0 위성 export**는 preview이며 각 위성의 자체 `1.0.0` 전에 변경될 수 있습니다.
116
116
  - `haechi-crypto-kms` (0.8 → 0.2.0): `createKmsCryptoProvider`, `createInMemoryKms`, `./aws`의 `createAwsKmsClient`, 그리고 0.2.0의 새 subpath `./gcp`(`createGcpKmsClient`), `./azure`(`createAzureKmsClient`), `./vault`(`createVaultKmsClient`).
117
117
  - `haechi-auth-jwt` (0.2.0): `createJwtAuthProvider`(0.8, behavior-preserving)와 추가된 `createJwtVerifier`(재사용 가능한 JWS 검증 primitive), `isBlockedAddress`(SSRF 범위 술어, `haechi-auth-oidc`가 재사용).
118
118
  - `haechi-dashboard` (0.1.0, 신규): `createDashboardServer`, `normalizeDashboardConfig`.
@@ -0,0 +1,53 @@
1
+ # 컴플라이언스 통제 매핑 & DSAR/Retention 워크플로
2
+
3
+ - 문서 상태: Living document (WS6 — reliability-hardening-track §WS6)
4
+ - 이 문서의 성격: **통제 매핑**이며 컴플라이언스 **인증**이 아닙니다. reliability-hardening-track §5는 인증을 명시적 비목표로 두며, `SECURITY.md` Scope는 이 저장소가 컴플라이언스 인증·법률 의견·보증 보고서가 아님을 밝힙니다. 이 문서는 Haechi 통제를, 운영자가 충족하도록 돕는 *의무 범주*에 매핑합니다 — Haechi를 배포하면 어떤 규제를 준수하게 된다고 주장하지 않습니다.
5
+
6
+ ## 0. 읽는 법
7
+
8
+ 규제 의무(예: "데이터 최소화", "접근 로깅", "정보주체 권리")는 *프로그램* — 사람·프로세스·기술 — 으로 충족됩니다. Haechi는 운영자가 그 프로그램의 LLM/MCP 게이트웨이 경계에 배선하는 하나의 **기술 통제**입니다. 아래에서는 각 의무 **범주**를 이를 지원하는 Haechi 통제에 매핑하며, Haechi가 하는 일과 하지 않는 일의 경계를 기록합니다. 권위 있는 통제 정의는 코드와 `docs/current/threat-model.md`에 있으며, 이 문서는 그것을 매핑할 뿐 재서술하지 않습니다.
9
+
10
+ ## 1. 통제 → 의무 범주 매핑
11
+
12
+ | 의무 범주 | Haechi 통제 | Haechi의 기여 | 운영자가 여전히 소유 |
13
+ |---|---|---|---|
14
+ | **데이터 최소화** | 탐지 + redact/mask/tokenize/encrypt/block 파이프라인(`packages/core`, `packages/filter`, `packages/policy`); privacy profile(`kr-pipa`/`eu-gdpr`/`us-general`) | PII/비밀이 모델·도구·로그에 도달하기 전에 제거하거나 가명화하여 최소 필요 데이터만 하류로 흐르게 합니다. tokenization은 값을 vault에만 보관되는 가역 참조로 대체합니다. | 적법 근거 정책, 지역 profile, 어떤 필드가 실제로 필요한지의 선택. |
15
+ | **접근 로깅 / 감사성** | SHA-256 hash chain + head anchoring을 가진 audit JSONL(`packages/audit`); request별 `correlationId`; PII 없는 이벤트(`FORBIDDEN_KEYS`) | *어떤 범주*가 탐지되었고, *어떤 action*이 집행되었으며, *어떤*(keyed-hashed) identity인지, *언제*인지를 — 민감 값 자체는 저장하지 않고 — 변조 증거와 함께 기록합니다. | append-only/불변 저장 매체, 로그 전송, retention 일정(§3). |
16
+ | **목적 제한 / 접근 통제** | body 읽기 전 auth gate; named policy profile; model allowlist; identity별 rate limit(`packages/proxy`, `packages/auth`) | 누가 게이트웨이를 사용할 수 있고 각 identity가 어떤 모델/연산/쿼터를 받는지를, payload를 읽기 전에 제약합니다. | identity 수명주기, token 발급/폐기 정책, 게이트웨이 너머의 인가 모델. |
17
+ | **보관 제한 / retention** | token-vault retention(`tokenVault.retentionDays`, mutation 시 만료 정리); chain-aware audit rotation/retention 절차(`operations-runbook.md` §6) | bounded token 수명과, hash-chain을 보존하는 문서화된 audit rotation/retention 절차. | 법적 요구에 따른 retention 윈도 설정과 rotation 일정 운영. |
18
+ | **정보주체 권리(열람 / 삭제)** | token-vault reveal 거버넌스(`revealPolicy`) + purge, 둘 다 token id 기준 audit; §2의 DSAR 워크플로 | reveal/purge 프리미티브와 그 거버넌스/audit이 DSAR 대응의 기술적 빌딩 블록입니다(§2 참고). | 각 요청의 법적 접수, 신원 확인, 결정, 기록 보존. |
19
+ | **전송 중 기밀성** | proxy TLS / remote-bind 강화(`proxy.tls` / `proxy.trustForwardedProto`); 기본 loopback(`packages/proxy`) | remote bind는 bearer token + payload를 평문으로 제공할 수 없습니다 — TLS를 종단하거나 검증된 `X-Forwarded-Proto: https` hop 뒤에 있어야 하며, 아니면 기동 시 fail-closed. | 인증서 발급/회전과 네트워크 경계. |
20
+ | **무결성 & 변조 증거** | audit hash chain + anchoring; canonical-AAD 결합 암호화(`packages/crypto`); 강화-전용 정책(`ACTION_STRENGTH`) | 변조 증거 audit, AEAD 결합 ciphertext, 조용히 약화될 수 없는 정책 격자. | 키 보관(프로덕션 KMS/HSM은 주입된 `cryptoProvider`이며 절대 코어 아님)과 사고 대응. |
21
+ | **처리의 안전성 / 복원력** | fail-closed 집행; depth/byte/encoding 가드; readiness(`/__haechi/ready`) + backpressure(`packages/proxy`, `packages/core`) | 인라인 집행과 fail-closed 가용성 통제가 미보호 payload나 unbounded-consumption 사건의 가능성을 줄입니다. | 용량 계획, 모니터링, 더 넓은 보안 프로그램. |
22
+
23
+ ## 2. DSAR / retention 운영 워크플로
24
+
25
+ **정보주체 열람/삭제 요청(DSAR)**은 법적/프로세스 워크플로이며, Haechi는 그것이 귀결되는 기술 연산을 제공합니다. 아래 흐름은 요청을 구체적 Haechi 프리미티브에 매핑합니다. **모든 reveal/purge 연산은 token id 기준으로 audit되며(평문 아님)**, `tokenVault.revealPolicy`로 거버넌스됩니다.
26
+
27
+ ### 2.1 열람 요청 (정보주체가 "내 데이터를 무엇을 보유/처리하는가?"라고 물을 때)
28
+ 1. **위치 파악.** audit 로그로 정보주체에 관련된 이벤트를 찾습니다 — keyed-HMAC `subjectHash`(audit는 원문 subject를 저장하지 않음), `correlationId`, 시간 윈도, 탐지 summary로 매칭합니다. audit는 *어떤 범주*가 처리되고 *어떤 action*이 취해졌는지를 값 없이 알려줍니다.
29
+ 2. **거버넌스될 때에 한해 token 해석.** 값이 **tokenize**되었다면 가역 참조가 token vault에 있습니다. reveal은 `tokenVault.revealPolicy`로 게이트됩니다:
30
+ - `disabled`(기본): reveal 거부. 이것이 프로덕션 안전 자세입니다 — DSAR 열람 응답은 live reveal이 아니라 audit 메타데이터 + 운영자의 upstream 기록으로 구성합니다.
31
+ - `local-dev`: 명시적 로컬 개발 워크플로에서만 reveal 허용(`haechi token-reveal <token> --allow-dev-reveal`). `--allow-dev-reveal`을 프로덕션 DSAR 절차로 **사용하지 마십시오**(`shared-responsibility.md` §2 참고).
32
+ 모든 reveal 결정은 token id 기준으로 audit 로그에 기록됩니다.
33
+ 3. **응답.** 법적/프로세스 채널을 통해 응답합니다. Haechi는 기술 증거를 제공하고, 운영자가 신원 확인과 응답을 소유합니다.
34
+
35
+ ### 2.2 삭제 요청 (정보주체가 "내 데이터를 삭제하라"고 할 때)
36
+ 1. **token 매핑 purge.** `haechi token-purge`가 vault 매핑을 제거해 tokenize된 값을 더 이상 reveal할 수 없게 합니다; 만료된 token도 vault mutation 시 자동 정리됩니다. purge 결정은 token id 기준으로 audit됩니다.
37
+ 2. **retention 윈도 밖의 audit 세그먼트 만료.** `operations-runbook.md` §6에 따라 audit 로그는 세그먼트로 rotation되며, retention은 **세그먼트 전체를 만료**합니다(부분 라인은 hash chain을 깨므로 절대 아님). audit는 의도적으로 **평문 PII를 보유하지 않으므로** — *내용*에 대한 삭제 의무는 대체로 upstream/운영자 저장소에서 충족되며, audit는 keyed-hashed 식별자와 범주 메타데이터만 보유합니다.
38
+ 3. **upstream 복사본 삭제.** 모델 제공자의 로그, 애플리케이션 DB, 백업은 **Haechi 밖**입니다 — 운영자가 자신의 데이터 맵에 따라 삭제해야 합니다.
39
+
40
+ ### 2.3 retention 운영 (상시)
41
+ - **token vault:** `tokenVault.retentionDays` 설정; 만료는 vault mutation 시 정리됩니다.
42
+ - **audit 로그:** `operations-runbook.md` §6의 chain-aware rotation 운영 — 유지보수 경계에서 rotation하고, 각 rotation된 세그먼트 **와 그 anchor**를 retention 윈도 동안 보관해 이력이 여전히 검증되게 한 뒤, 세그먼트 전체를 만료합니다. token-vault retention과 audit retention은 독립이며, audit rotation이 token을 purge하지 않습니다.
43
+
44
+ ## 3. 경계 & 비목표 (정직하게)
45
+ - 이것은 **매핑**이며, 인증이나 법률 자문이 아닙니다. Haechi 배포가 시스템을 "GDPR/PIPA 등 준수"로 만들지 않습니다.
46
+ - Haechi는 **게이트웨이 경계**만 통제합니다. 모델 제공자의 retention, 애플리케이션 저장소, 백업은 운영자 책임입니다(`shared-responsibility.md`).
47
+ - 탐지는 regex + validator(ML 없음)이며 문서화된 제외는 유효합니다(`threat-model.md` §4). DSAR/삭제 프로그램은 Haechi가 어떤 값의 *모든* 인스턴스를 잡았다고 가정해서는 안 됩니다.
48
+
49
+ ## 4. 상호 참조
50
+ - `docs/current/shared-responsibility.md` — Haechi 대 운영자 책임 매트릭스(DSAR/retention 구분이 거기에 명시됨).
51
+ - `docs/current/operations-runbook.md` — §6 chain-aware audit rotation & retention.
52
+ - `docs/current/security-whitepaper.md` — OWASP-LLM / NIST-AI-RMF 통제 매핑 + self-pentest.
53
+ - `docs/current/threat-model.md` — 제외 항목과 수용된 잔여 위험.
@@ -0,0 +1,53 @@
1
+ # Compliance Control Mapping & DSAR/Retention Workflow
2
+
3
+ - Status: Living document (WS6 — reliability-hardening-track §WS6)
4
+ - Nature of this document: a **control mapping**, NOT a compliance **certification**. The reliability-hardening-track §5 lists a certification as an explicit non-goal, and `SECURITY.md` Scope states this repository is not a compliance certification, legal opinion, or assurance report. This document maps Haechi controls to the *obligation categories* they help an operator satisfy — it does not assert that deploying Haechi makes a system compliant with any regulation.
5
+
6
+ ## 0. How to read this
7
+
8
+ A regulatory obligation (e.g. "data minimization", "access logging", "subject rights") is satisfied by a *program* — people, process, and technology. Haechi is one **technical control** an operator wires into that program at the LLM/MCP gateway boundary. Below, each obligation **category** is mapped to the Haechi control(s) that support it, with the boundary of what Haechi does and does not do. The authoritative control definitions are in the code and `docs/current/threat-model.md`; this maps them, it does not restate them.
9
+
10
+ ## 1. Control → obligation-category mapping
11
+
12
+ | Obligation category | Haechi control(s) | What Haechi contributes | Operator still owns |
13
+ |---|---|---|---|
14
+ | **Data minimization** | Detection + redact/mask/tokenize/encrypt/block pipeline (`packages/core`, `packages/filter`, `packages/policy`); privacy profiles (`kr-pipa`/`eu-gdpr`/`us-general`) | Strips or pseudonymizes PII/secrets before they reach the model, tools, or logs — so the minimum necessary data flows downstream. Tokenization replaces a value with a reversible reference held only in the vault. | Choosing the lawful-basis policy, the regional profile, and which fields are truly necessary. |
15
+ | **Access logging / auditability** | Audit JSONL with SHA-256 hash chain + head anchoring (`packages/audit`); per-request `correlationId`; PII-free events (`FORBIDDEN_KEYS`) | A tamper-evident record of *what category* was detected, *what action* was enforced, *which* (keyed-hashed) identity, and *when* — without storing the sensitive value itself. | Append-only/immutable storage media, log shipping, and the retention schedule (see §3). |
16
+ | **Purpose limitation / access control** | Auth gate before body-read; named policy profiles; model allowlist; per-identity rate limit (`packages/proxy`, `packages/auth`) | Constrains who may use the gateway and which models/operations/quotas each identity gets, enforced before any payload is read. | Identity lifecycle, token issuance/revocation policy, and the authorization model beyond the gateway. |
17
+ | **Storage limitation / retention** | Token-vault retention (`tokenVault.retentionDays`, expiry pruned on mutation); chain-aware audit rotation/retention procedure (`operations-runbook.md` §6) | Bounded token lifetime and a documented, hash-chain-preserving rotation/retention procedure for the audit log. | Setting the retention window per legal requirement and operating the rotation schedule. |
18
+ | **Subject rights (access / erasure)** | Token-vault reveal governance (`revealPolicy`) + purge, both audited by token id; the DSAR workflow in §2 | The reveal/purge primitives and their governance/audit are the technical building blocks of a DSAR response (see §2). | The legal intake, identity verification, decision, and recordkeeping of each request. |
19
+ | **Confidentiality in transit** | Proxy TLS / remote-bind hardening (`proxy.tls` / `proxy.trustForwardedProto`); loopback-by-default (`packages/proxy`) | A remote bind cannot serve bearer tokens + payloads in plaintext — it must terminate TLS or sit behind a verified `X-Forwarded-Proto: https` hop, else it fails closed at startup. | Certificate issuance/rotation and the network perimeter. |
20
+ | **Integrity & tamper evidence** | Audit hash chain + anchoring; canonical-AAD-bound encryption (`packages/crypto`); policies-only-get-stronger (`ACTION_STRENGTH`) | Tamper-evident audit, AEAD-bound ciphertext, and a policy lattice that cannot silently weaken. | Key custody (a production KMS/HSM is an injected `cryptoProvider`, never core), and incident response. |
21
+ | **Security of processing / resilience** | Fail-closed enforcement; depth/byte/encoding guards; readiness (`/__haechi/ready`) + backpressure (`packages/proxy`, `packages/core`) | Inline enforcement and fail-closed availability controls reduce the chance of an unprotected payload or an unbounded-consumption event. | Capacity planning, monitoring, and the broader security program. |
22
+
23
+ ## 2. DSAR / retention operational workflow
24
+
25
+ A **Data Subject Access/erasure Request (DSAR)** is a legal/process workflow; Haechi provides the technical operations it bottoms out on. The flow below maps a request to concrete Haechi primitives. **All reveal/purge operations are audited by token id (never plaintext)** and are governed by `tokenVault.revealPolicy`.
26
+
27
+ ### 2.1 Access request (the subject asks "what data of mine do you hold / process?")
28
+ 1. **Locate.** Use the audit log to find the events touching the subject — match on the keyed-HMAC `subjectHash` (the audit never stores a raw subject), the `correlationId`, the time window, and the detection summary. The audit tells you *that* a category was processed and *which action* was taken, without the value.
29
+ 2. **Resolve tokens, if and only if governed.** If a value was **tokenized**, the reversible reference lives in the token vault. Revealing it is gated by `tokenVault.revealPolicy`:
30
+ - `disabled` (the default): reveal is refused. This is the production-safe posture — a DSAR access response is assembled from the audit metadata + the operator's upstream records, not from a live reveal.
31
+ - `local-dev`: reveal is permitted only for explicit local-development workflows (`haechi token-reveal <token> --allow-dev-reveal`). **Do not** use `--allow-dev-reveal` as a production DSAR procedure (see `shared-responsibility.md` §2).
32
+ Every reveal decision is written to the audit log by token id.
33
+ 3. **Respond** through your legal/process channel. Haechi supplies the technical evidence; the operator owns identity verification and the response.
34
+
35
+ ### 2.2 Erasure request (the subject asks "delete my data")
36
+ 1. **Purge the token mapping.** `haechi token-purge` removes the vault mapping so the tokenized value can no longer be revealed; expired tokens are also pruned automatically on vault mutations. The purge decision is audited by token id.
37
+ 2. **Expire the audit segments** that fall outside the retention window. Per `operations-runbook.md` §6, the audit log rotates into segments; retention **expires whole segments** (never partial lines, which would break the hash chain). The audit deliberately holds **no plaintext PII** — so an erasure obligation against the *content* is largely satisfied by the upstream/operator store, while the audit holds only keyed-hashed identifiers and category metadata.
38
+ 3. **Erase upstream copies.** The model provider's logs, your application database, and any backups are **outside Haechi** — the operator must erase those per their own data map.
39
+
40
+ ### 2.3 Retention operation (ongoing)
41
+ - **Token vault:** set `tokenVault.retentionDays`; expiry is pruned on vault mutations.
42
+ - **Audit log:** operate the chain-aware rotation in `operations-runbook.md` §6 — rotate at a maintenance boundary, keep each rotated segment **and its anchor** for the retention window so the history still verifies, then expire whole segments. Token-vault retention and audit retention are independent; rotating the audit does not purge tokens.
43
+
44
+ ## 3. Boundaries & non-goals (honest)
45
+ - This is a **mapping**, not a certification or legal advice. Deploying Haechi does not make a system "GDPR/PIPA/etc. compliant."
46
+ - Haechi controls the **gateway boundary** only. The model provider's retention, your application store, and backups are the operator's responsibility (`shared-responsibility.md`).
47
+ - Detection is regex + validators (no ML); documented exclusions stand (`threat-model.md` §4). A DSAR/erasure program must not assume Haechi caught *every* instance of a value.
48
+
49
+ ## 4. Cross-references
50
+ - `docs/current/shared-responsibility.md` — the Haechi-vs-operator responsibility matrix (the DSAR/retention split is called out there).
51
+ - `docs/current/operations-runbook.md` — §6 chain-aware audit rotation & retention.
52
+ - `docs/current/security-whitepaper.md` — the OWASP-LLM / NIST-AI-RMF control mapping + self-pentest.
53
+ - `docs/current/threat-model.md` — exclusions and accepted residuals.
@@ -0,0 +1,30 @@
1
+ # Haechi `configVersion` & 업그레이드 노트
2
+
3
+ - 상태: Living document (코어 1.2.x 추적)
4
+
5
+ `configVersion`는 `haechi.config.json`(및 `haechi.config.example.json`) 최상위에 찍히는 단일 정수입니다. 향후 호환성을 깨는 설정 스키마 변경이 구체적으로 게이트할 수 있는 **버전 앵커**로서, 다른 Haechi 빌드가 쓴 설정을 조용히 잘못 읽는 일을 막습니다.
6
+
7
+ ## 동작
8
+
9
+ - **기본값 / 없음:** `configVersion`를 생략한 설정(예: 스탬프가 생기기 전의 1.1 파일)은 **현재** 버전으로 간주합니다. 필드 추가는 기존 설정에 아무 영향이 없었습니다.
10
+ - **현재 버전:** `1`.
11
+ - **더 높은/알 수 없는 값은 fail-closed:** 빌드가 이해하는 값보다 **큰** `configVersion`는 로드 시 throw합니다 — *더 새로운* Haechi가 쓴 설정은 이 빌드가 구현하지 않은 의미에 의존할 수 있으므로, 추측 대신 거부합니다. Haechi를 업그레이드하거나, 호환성을 확인한 뒤 스탬프를 낮추십시오.
12
+ - **형식이 잘못되면 fail-closed:** 양수 정수가 아닌 `configVersion`는 throw합니다(`configVersion must be a positive integer`).
13
+
14
+ 이는 `normalizeConfig`의 나머지와 동일한 fail-closed 자세입니다: 모호하거나 미래 시점의 설정은 게이트웨이를 약화시키는 대신 멈춥니다.
15
+
16
+ ## 더 높은 버전에 fail-closed인 이유
17
+
18
+ 알 수 없는 설정을 조용히 실행하는 보안 게이트웨이는, 예를 들어 인식하지 못한 미래의 집행 키를 무시하고 운영자 의도보다 약하게 동작할 수 있습니다. 기동을 거부하면 불일치가 즉시 드러나며 "정책은 더 강해질 뿐 / fail closed" 불변식이 유지됩니다.
19
+
20
+ ## 버전 맵
21
+
22
+ | `configVersion` | 코어 라인 | 노트 |
23
+ |---|---|---|
24
+ | `1` | 1.0 – 1.2.x | 최초 스탬프. 모든 키는 1.0 frozen 설정 표면(`api-stability.md` §2.4)에 대해 additive입니다. 1.1.x의 additive 키(`logging`, `metrics`, WS4-B의 `limits.maxInFlight` / `limits.shutdownGraceMs` / `limits.requestTimeoutMs` / `limits.headersTimeoutMs`, 그리고 `configVersion` 자체)와 1.2.0 신뢰성 강화 키(`filters.minConfidence` / `filters.allowlist`, `proxy.tls` / `proxy.trustForwardedProto`)는 모두 이전 동작을 기본값으로 합니다. 마이그레이션 불필요. |
25
+
26
+ ## 업그레이드
27
+
28
+ 향후 마이너가 설정 키를 추가할 때, 그 키들은 **additive**(이전 동작 기본값)로 유지되고 `configVersion`는 `1`에 머뭅니다 — 조치 불필요. `configVersion`는 의도적인 호환성 파괴 스키마 변경과 함께서만 **올라가며**, 그 변경은 `api-stability.md` §2.2에 따라 메이저 버전 상승과 deprecation 노트도 동반합니다. 그 시점에 이 표에 마이그레이션을 설명하는 행이 추가되며, 더 낮은 버전으로 찍힌 설정은 조용히가 아니라 명시적으로 마이그레이션(또는 호환 규칙으로 읽기)됩니다.
29
+
30
+ 핀 고정: 설정 최상위에 `"configVersion": 1`을 설정하십시오(예제 설정은 이미 그렇게 합니다). 향후 스키마 상승을 넘어 Haechi를 업그레이드하려면, 스탬프를 올리기 전에 대상 버전의 마이그레이션 행을 따르십시오.
@@ -0,0 +1,51 @@
1
+ # Haechi `configVersion` & Upgrade Notes
2
+
3
+ - Status: Living document (tracks core 1.2.x)
4
+
5
+ `configVersion` is a single integer stamped at the top of `haechi.config.json`
6
+ (and `haechi.config.example.json`). It is a **versioned anchor** so a future
7
+ breaking config-schema change has something concrete to gate on, rather than
8
+ silently mis-reading a config written by a different Haechi build.
9
+
10
+ ## Behavior
11
+
12
+ - **Default / absent:** a config that omits `configVersion` (e.g. a 1.1 file
13
+ written before the stamp existed) is treated as the **current** version. Adding
14
+ the field changed nothing for existing configs.
15
+ - **Current version:** `1`.
16
+ - **Fail-closed on newer/unknown:** a `configVersion` **greater** than the build
17
+ understands throws at load — a config a *newer* Haechi wrote may rely on
18
+ semantics this build does not implement, so Haechi refuses rather than guessing.
19
+ Upgrade Haechi, or lower the stamp once you have confirmed compatibility.
20
+ - **Fail-closed on malformed:** a non-positive or non-integer `configVersion`
21
+ throws (`configVersion must be a positive integer`).
22
+
23
+ This is the same fail-closed posture as the rest of `normalizeConfig`: an
24
+ ambiguous or forward-dated config stops the gateway rather than degrading it.
25
+
26
+ ## Why fail-closed on a newer version
27
+
28
+ A security gateway that silently runs an unfamiliar config could, for example,
29
+ ignore a future enforcement key it does not recognize and run weaker than the
30
+ operator intended. Refusing to start surfaces the mismatch immediately and keeps
31
+ the "policies only get stronger / fail closed" invariant intact.
32
+
33
+ ## Version map
34
+
35
+ | `configVersion` | Core line | Notes |
36
+ |---|---|---|
37
+ | `1` | 1.0 – 1.2.x | Initial stamp. All keys are additive over the 1.0 frozen config surface (`api-stability.md` §2.4). The 1.1.x additive keys (`logging`, `metrics`, the WS4-B `limits.maxInFlight` / `limits.shutdownGraceMs` / `limits.requestTimeoutMs` / `limits.headersTimeoutMs`, `configVersion` itself) and the 1.2.0 Reliability-Hardening keys (`filters.minConfidence` / `filters.allowlist`, `proxy.tls` / `proxy.trustForwardedProto`) all default to prior behavior. No migration needed. |
38
+
39
+ ## Upgrading
40
+
41
+ When a future minor adds config keys, they remain **additive** (default to prior
42
+ behavior) and `configVersion` stays `1` — no action required. `configVersion`
43
+ will only be **bumped** alongside a deliberate breaking schema change, which would
44
+ also carry a major version bump and a deprecation note per `api-stability.md`
45
+ §2.2. At that point this table gains a row describing the migration, and a config
46
+ stamped with the older version is migrated (or read under compatibility rules)
47
+ explicitly — never silently.
48
+
49
+ To pin: set `"configVersion": 1` at the top of your config (the example config
50
+ already does). To upgrade Haechi past a future schema bump, follow the migration
51
+ row for the target version before raising the stamp.