# Story 4.14: fapilog-tamper Package Bootstrap

## Status

Ready

## Story

**As a** compliance-focused enterprise developer,  
**I want** a separate `fapilog-tamper` package that provides tamper-evident logging capabilities,  
**So that** I can add cryptographic integrity verification to my logs without bloating the core fapilog library with heavy crypto dependencies.

## Relationship to Other Stories

- **Prerequisite for 4.15–4.18**: Establishes package structure and core types
- **Integrates with core**: Uses the `IntegrityPlugin` protocol from `fapilog.plugins.integrity`
- **Supersedes 4.13**: All tamper-evident functionality is in the plugin, not core

## Background

The core fapilog library provides a plugin hook for integrity add-ons via the `fapilog.integrity` entry point group. This story creates the separate `fapilog-tamper` package that:

1. Isolates crypto dependencies (`cryptography`, optionally `pynacl`) from core
2. Registers as an integrity plugin discoverable by core
3. Provides the foundational types and configuration used by subsequent stories

## Acceptance Criteria

### AC1: Package Structure

- [ ] New package `fapilog-tamper` in `packages/fapilog-tamper/`
- [ ] `pyproject.toml` with dependencies: `fapilog>=3.0.0`, `cryptography>=41.0.0`
- [ ] Optional dependency group `[signatures]` for `pynacl>=1.5.0` (Ed25519)
- [ ] Entry point: `[project.entry-points."fapilog.integrity"]` → `tamper-sealed = "fapilog_tamper:TamperSealedPlugin"`

### AC2: Core Types and Protocols

- [ ] `IntegrityFields` dataclass for MAC/chain metadata injected into events:
  ```python
  @dataclass
  class IntegrityFields:
      seq: int
      mac: str  # base64url encoded
      algo: str  # e.g., "HMAC-SHA256"
      key_id: str
      chain_hash: str  # base64url encoded
      prev_chain_hash: str  # base64url encoded
  ```
- [ ] `ChainState` dataclass for tracking chain continuity:
  ```python
  @dataclass
  class ChainState:
      seq: int
      prev_chain_hash: bytes
      key_id: str
  ```
- [ ] `TamperConfig` Pydantic model for configuration:
  ```python
  class TamperConfig(BaseModel):
      enabled: bool = False
      algorithm: Literal["HMAC-SHA256", "Ed25519"] = "HMAC-SHA256"
      key_id: str = ""
      key_source: Literal["env", "file", "kms", "vault"] = "env"
      key_env_var: str = "FAPILOG_TAMPER_KEY"
      key_file_path: str | None = None
      state_dir: str = ".fapilog-chainstate"
      fsync_on_write: bool = False
      fsync_on_rotate: bool = True
      rotate_chain: bool = False  # Reset chain per file vs continuous
      verify_on_close: bool = False
      alert_on_failure: bool = True
  ```

### AC3: Plugin Entry Point

- [ ] `TamperSealedPlugin` class implementing `IntegrityPlugin` protocol:
  ```python
  class TamperSealedPlugin:
      def get_enricher(self, config: dict | None = None) -> IntegrityEnricher:
          ...
      def wrap_sink(self, sink: Any, config: dict | None = None) -> SealedSink:
          ...
  ```
- [ ] Plugin discoverable via `load_integrity_plugin("tamper-sealed")`
- [ ] Graceful error handling when crypto dependencies unavailable

### AC4: Canonical Serialization

- [ ] `canonicalize(event: dict) -> bytes` function:
  - Sorted keys
  - UTF-8 encoding
  - Deterministic JSON (no whitespace, consistent float repr)
  - Excludes `integrity` field from input before serialization
- [ ] Unit tests for deterministic output across Python versions

### AC5: Base64url Encoding

- [ ] `b64url_encode(data: bytes) -> str` and `b64url_decode(s: str) -> bytes`
- [ ] No padding characters (RFC 4648 §5 compliant)

## Technical Design

### Package Layout

```
packages/fapilog-tamper/
├── pyproject.toml
├── README.md
├── src/
│   └── fapilog_tamper/
│       ├── __init__.py          # TamperSealedPlugin, public API
│       ├── config.py            # TamperConfig
│       ├── types.py             # IntegrityFields, ChainState
│       ├── canonical.py         # canonicalize(), b64url helpers
│       ├── enricher.py          # IntegrityEnricher (Story 4.15)
│       ├── chain_state.py       # ChainStatePersistence (Story 4.15)
│       ├── sealed_sink.py       # SealedSink wrapper (Story 4.16)
│       ├── manifest.py          # Manifest generation (Story 4.16)
│       ├── verify.py            # Verification API (Story 4.17)
│       └── cli.py               # CLI entry point (Story 4.17)
└── tests/
    └── ...
```

### pyproject.toml

```toml
[project]
name = "fapilog-tamper"
version = "0.1.0"
description = "Tamper-evident logging add-on for fapilog"
requires-python = ">=3.10"
dependencies = [
    "fapilog>=3.0.0",
    "cryptography>=41.0.0",
]

[project.optional-dependencies]
signatures = ["pynacl>=1.5.0"]

[project.entry-points."fapilog.integrity"]
tamper-sealed = "fapilog_tamper:TamperSealedPlugin"

[project.scripts]
fapilog-tamper = "fapilog_tamper.cli:main"
```

## Test Cases

| Test                                   | Description                                             |
| -------------------------------------- | ------------------------------------------------------- |
| `test_plugin_discoverable`             | `load_integrity_plugin("tamper-sealed")` returns plugin |
| `test_plugin_implements_protocol`      | Plugin satisfies `IntegrityPlugin` protocol             |
| `test_canonicalize_deterministic`      | Same dict → same bytes across calls                     |
| `test_canonicalize_sorted_keys`        | Keys sorted alphabetically                              |
| `test_canonicalize_excludes_integrity` | `integrity` field stripped before hash                  |
| `test_b64url_roundtrip`                | Encode then decode returns original                     |
| `test_b64url_no_padding`               | Output has no `=` padding characters                    |
| `test_config_defaults`                 | `TamperConfig()` has sane defaults                      |
| `test_config_validation`               | Invalid algorithm/key_source rejected                   |

## Out of Scope

- Enricher implementation (Story 4.15)
- Sink wrapper implementation (Story 4.16)
- Verification tools (Story 4.17)
- KMS/Vault key loading (Story 4.18)

## Definition of Done

- [ ] Package structure created with all boilerplate
- [ ] Core types implemented with full type hints
- [ ] `TamperSealedPlugin` stub returns placeholder enricher/sink
- [ ] Entry point registered and discoverable from core
- [ ] `canonicalize()` and b64url helpers implemented
- [ ] Unit tests pass with >90% coverage
- [ ] `pip install ./packages/fapilog-tamper` works
- [ ] README documents installation and basic usage

## Estimation

**Story Points: 3**

## Labels

`enhancement`, `epic:enterprise-compliance`, `priority:high`, `status:ready`, `story`, `compliance`, `new-package`

## Change Log

| Date       | Version | Description                              | Author   |
| ---------- | ------- | ---------------------------------------- | -------- |
| 2025-12-29 | 0.1     | Story created from addon design document | AI Agent |
