/ docs / plans / roadmap.md
roadmap.md
  1  # Auths v0.1.0 Engineering Roadmap
  2  
  3  Zero users. No backward compatibility constraints. Break anything, delete anything.
  4  
  5  ## Priority Order
  6  
  7  | # | Epic | What | Blocked by |
  8  |---|------|------|------------|
  9  | 1 | Epic 1 | Wire Rust commit verification into Python | — | DONE
 10  | 2 | Epic 2 | Replace all string-matching error dispatch | — |
 11  | 3 | Epic 3 | Fix known bugs and code violations | — |
 12  | 4 | Epic 4 | Lock down public API surface | — |
 13  | 5 | Epic 5 | Typestate PassphraseProvider | — |
 14  | 6 | Epic 6 | Python wheels for all platforms | — |
 15  | 7 | Epic 7 | Decouple widget from local filesystem | — |
 16  | 8 | Epic 8 | GitHub Action cleanup | — |
 17  | 9 | Epic 9 | Python SDK types | Epics 1, 2 |
 18  | 10 | Epic 10 | UMD bundle for CDN | Epic 7 |
 19  | 11 | Epic 11 | Getting started guide | All above |
 20  
 21  
 22  ## Epic 2 — Structured Error Codes Everywhere
 23  
 24  Three places use string matching to classify errors. Replace all of them with `AuthsErrorInfo::error_code()` dispatch.
 25  
 26  **Task 2.1** — Add `error_code: Option<String>` to `VerificationResult` in `packages/auths-python/src/types.rs` (currently only has `valid` and `error`). Propagate `e.error_code()` from all FFI error paths in `verify.rs` and `sign.rs`.
 27  
 28  ```rust
 29  // packages/auths-python/src/types.rs
 30  
 31  #[pyclass]
 32  #[derive(Clone)]
 33  pub struct VerificationResult {
 34      #[pyo3(get)]
 35      pub valid: bool,
 36      #[pyo3(get)]
 37      pub error: Option<String>,
 38      #[pyo3(get)]
 39      pub error_code: Option<String>,
 40  }
 41  ```
 42  
 43  **Task 2.2** — Delete `_map_verify_error()`, `_map_sign_error()`, and `_map_network_error()` in `_client.py:30-56`. Replace with code-based dispatch. No fallback logic needed — there are no callers relying on the old behavior.
 44  
 45  ```python
 46  # packages/auths-python/python/auths/_client.py
 47  
 48  _ERROR_CODE_MAP = {
 49      "AUTHS_VERIFICATION_ERROR": ("invalid_signature", VerificationError),
 50      "AUTHS_MISSING_CAPABILITY": ("missing_capability", VerificationError),
 51      "AUTHS_CRYPTO_ERROR": ("invalid_key", CryptoError),
 52      "AUTHS_DID_RESOLUTION_ERROR": ("invalid_key", CryptoError),
 53      "AUTHS_INVALID_INPUT": ("invalid_signature", VerificationError),
 54      "AUTHS_SERIALIZATION_ERROR": ("invalid_signature", VerificationError),
 55      "AUTHS_BUNDLE_EXPIRED": ("expired_attestation", VerificationError),
 56      "AUTHS_KEY_NOT_FOUND": ("key_not_found", CryptoError),
 57      "AUTHS_INCORRECT_PASSPHRASE": ("signing_failed", CryptoError),
 58      "AUTHS_SIGNING_FAILED": ("signing_failed", CryptoError),
 59  }
 60  
 61  def _map_error(exc: Exception) -> Exception:
 62      code = getattr(exc, "error_code", None)
 63      if code and code in _ERROR_CODE_MAP:
 64          py_code, cls = _ERROR_CODE_MAP[code]
 65          return cls(str(exc), code=py_code)
 66      return VerificationError(str(exc), code="unknown")
 67  ```
 68  
 69  **Task 2.3** — Fix C FFI string matching in `crates/auths-verifier/src/ffi.rs:206-224` (marked `TECH-DEBT(fn-33)`). Replace substring checks with `e.error_code()` match.
 70  
 71  ```rust
 72  // crates/auths-verifier/src/ffi.rs
 73  
 74  fn attestation_error_to_code(e: &AttestationError) -> i32 {
 75      match e.error_code() {
 76          "AUTHS_VERIFICATION_ERROR" => 2,
 77          "AUTHS_CRYPTO_ERROR" => 3,
 78          "AUTHS_MISSING_CAPABILITY" => 4,
 79          "AUTHS_INVALID_INPUT" => 5,
 80          "AUTHS_SERIALIZATION_ERROR" => 6,
 81          "AUTHS_BUNDLE_EXPIRED" => 7,
 82          _ => -1,
 83      }
 84  }
 85  ```
 86  
 87  ---
 88  
 89  ## Epic 3 — Bug Fixes and Code Violations
 90  
 91  Known bugs that must be fixed before v0.1.0. No epic overlap — these fell through the cracks.
 92  
 93  **Task 3.1** — Fix `Utc::now()` violation in `crates/auths-core/src/witness/server.rs:492`. The `submit_event` handler calls `chrono::Utc::now()` directly. Change the function signature to accept `now: DateTime<Utc>` and have the caller inject it.
 94  
 95  **Task 3.2** — Replace 7 `anyhow::anyhow!()` calls in `crates/auths-storage/src/git/identity_adapter.rs` (lines 127, 129, 132, 134, 172, 177, 184) with a `StorageError` thiserror enum. Layer 4 crate, `anyhow` is banned.
 96  
 97  **Task 3.3** — Fix `__init__.py` import bug. `verify_chain_with_witnesses` is in `__all__` at `packages/auths-python/python/auths/__init__.py:70` but never imported from `_native` (lines 13-29). Add the import if the function exists in the native module, otherwise remove it from `__all__`.
 98  
 99  ---
100  
101  ## Epic 4 — Seal Public API Surface
102  
103  Every `pub` symbol in a published crate is a commitment. Clean it up now while there's zero cost to breaking changes.
104  
105  **Task 4.1** — Make `ports`, `presentation`, and `workflows` modules `pub(crate)` in `crates/auths-sdk/src/lib.rs` (currently `pub` at lines 36, 38, 50). No `#[doc(hidden)]` half-measure — just make them private. The `testing` module is already feature-gated, leave it.
106  
107  ```rust
108  // crates/auths-sdk/src/lib.rs
109  
110  pub mod setup;
111  pub mod device;
112  pub mod signing;
113  pub mod keys;
114  pub mod pairing;
115  pub mod audit;
116  pub mod context;
117  pub mod error;
118  pub mod result;
119  pub mod types;
120  
121  pub(crate) mod ports;
122  pub(crate) mod presentation;
123  pub(crate) mod workflows;
124  
125  #[cfg(any(test, feature = "test-utils"))]
126  pub mod testing;
127  ```
128  
129  **Task 4.2** — Delete `submit_registration()` and `bind_platform_claim()` stubs in `crates/auths-sdk/src/setup.rs:326-382`. Both always return `None`. They're dead code with no callers outside the SDK. Remove them and their call sites in `setup()`.
130  
131  **Task 4.3** — Add `cargo-public-api` CI step for `auths-sdk`, `auths-verifier`, and `auths-core`. Check the baseline into `docs/public-api/` so API drift shows up in diffs.
132  
133  ---
134  
135  ## Epic 5 — Typestate PassphraseProvider
136  
137  `NoopPassphraseProvider` at `crates/auths-sdk/src/context.rs:30` fails at runtime. Make it a compile-time error instead.
138  
139  **Task 5.1** — Add a 7th typestate slot `PP` to `AuthsContextBuilder` (currently has 6 at `context.rs:129-133`). Split into `AuthsSigningContext` and `AuthsReadContext`. Delete `NoopPassphraseProvider` entirely.
140  
141  ```rust
142  pub type AuthsSigningContext = AuthsContext<HasPassphrase>;
143  pub type AuthsReadContext = AuthsContext<NoPassphrase>;
144  
145  pub struct HasPassphrase(Arc<dyn PassphraseProvider + Send + Sync>);
146  pub struct NoPassphrase;
147  ```
148  
149  **Task 5.2** — Update CLI factories in `crates/auths-cli/src/factories/` to build the right context type per command. Signing commands get `AuthsSigningContext`, read-only commands get `AuthsReadContext`.
150  
151  **Task 5.3** — Extract shared `build_ffi_context()` in Python FFI. The same builder block is copy-pasted 6 times across `identity.rs`, `rotation.rs`, `device_ext.rs`, `artifact_sign.rs`, `commit_sign.rs`. One function, 6 call sites.
152  
153  ```rust
154  // packages/auths-python/src/context_helper.rs
155  
156  pub fn build_ffi_context(
157      repo_path: &str,
158      passphrase: Option<&str>,
159  ) -> Result<AuthsContext, anyhow::Error> {
160      let (backend, keychain, clock, id_storage, att_storage) = resolve_ffi_deps(repo_path)?;
161      let provider = resolve_passphrase(passphrase);
162      Ok(AuthsContext::builder()
163          .registry(backend)
164          .key_storage(keychain)
165          .clock(clock)
166          .identity_storage(id_storage)
167          .attestation_sink(att_storage.clone())
168          .attestation_source(att_storage)
169          .passphrase_provider(provider)
170          .build())
171  }
172  ```
173  
174  ---
175  
176  ## Epic 6 — Cross-Platform Python Wheels
177  
178  `pip install auths` must work without a Rust toolchain on macOS (x86_64/arm64), Linux (manylinux), and Windows (amd64).
179  
180  **Task 6.1** — Add `publish-python.yml` workflow using `maturin-action` to build wheels for all platforms on tagged releases.
181  
182  ```yaml
183  jobs:
184    build:
185      strategy:
186        matrix:
187          os: [ubuntu-latest, macos-latest, windows-latest]
188          target: [x86_64, aarch64]
189          exclude:
190            - os: windows-latest
191              target: aarch64
192      steps:
193        - uses: PyO3/maturin-action@v1
194          with:
195            command: build
196            args: --release -m packages/auths-python/Cargo.toml --out dist
197            target: ${{ matrix.target }}
198  ```
199  
200  **Task 6.2** — Add smoke test: install the wheel in a clean venv (no Rust), run `from auths import Auths`, and test the `[jwt]` extra since `PyJWT`/`cryptography` have their own platform build issues.
201  
202  ---
203  
204  ## Epic 7 — Decouple Widget from Local Filesystem
205  
206  The widget can't be built or published without the exact sibling directory layout. Fix that, plus the build bugs found during review.
207  
208  **Task 7.1** — Publish `@auths/verifier-wasm` as a standalone npm package from `crates/auths-verifier` WASM build. Add `publish-wasm.yml` CI workflow on tagged releases.
209  
210  **Task 7.2** — Replace `file:../auths/packages/auths-verifier-ts` devDependency and `build:wasm` script in `auths-verify-widget/package.json` with `"@auths/verifier-wasm": "^0.1.0"`. Delete the `prepublishOnly` hook that references the parent repo.
211  
212  **Task 7.3** — Fix filename mismatch: `vite.config.ts:44` emits `auths-verify` (no extension), `package.json` expects `auths-verify.mjs`. Change Vite `fileName` to include `.mjs`.
213  
214  **Task 7.4** — Fix WASM type mismatch: `verifier-bridge.ts:16-20` and `wasm.d.ts:9-10` declare WASM functions as returning `string` but they return `Promise<string>`. Fix the types and add `await` at call sites (lines 69, 92).
215  
216  ---
217  
218  ## Epic 8 — GitHub Action Cleanup
219  
220  The action works but has several bugs and quality gaps.
221  
222  **Task 8.1** — Pin `auths-version` default to `'0.1.0'` in `action.yml` (currently `''`). Resolve `github-token` in `main.ts` with `core.getInput('github-token') || process.env.GITHUB_TOKEN` since `action.yml` defaults don't evaluate expressions.
223  
224  **Task 8.2** — Add PR comment deduplication. Currently `main.ts:132-144` creates a new comment on every run. Search for existing comments with a marker and update instead of creating.
225  
226  ```typescript
227  const COMMENT_MARKER = '<!-- auths-verify -->';
228  
229  async function upsertPrComment(octokit, prNumber: number, body: string) {
230    const { data: comments } = await octokit.rest.issues.listComments({
231      ...github.context.repo,
232      issue_number: prNumber,
233    });
234    const existing = comments.find(c => c.body?.includes(COMMENT_MARKER));
235    const markedBody = `${COMMENT_MARKER}\n${body}`;
236    if (existing) {
237      await octokit.rest.issues.updateComment({ ...github.context.repo, comment_id: existing.id, body: markedBody });
238    } else {
239      await octokit.rest.issues.createComment({ ...github.context.repo, issue_number: prNumber, body: markedBody });
240    }
241  }
242  ```
243  
244  **Task 8.3** — Fix license mismatch: `package.json` says MIT, `LICENSE` file is Apache-2.0. Pick one, update both.
245  
246  **Task 8.4** — Add tests for `classifyError()` in `src/verifier.ts:28-37`. Zero test coverage on the most fragile function in the action.
247  
248  **Task 8.5** — Add README workflow examples for identity-bundle mode, allowed-signers mode, and PR comment mode.
249  
250  ---
251  
252  ## Epic 9 — Python SDK Types
253  
254  Polish the Python-facing types. Low risk, do after the FFI plumbing is stable.
255  
256  **Task 9.1** — Change `verify()`'s `at` parameter from `str | None` to `datetime | None`. Convert to RFC 3339 internally. No deprecation needed.
257  
258  **Task 9.2** — Fix `AgentIdentityBundle.attestation_json` — it's always `""` for standalone agents (set in FFI `identity.rs:207`). Change to `Optional[str]`, set to `None` instead of empty string.
259  
260  ---
261  
262  ## Epic 10 — UMD Bundle for CDN
263  
264  Ship a `<script>` tag drop-in. Depends on Epic 7 (widget decoupled).
265  
266  **Task 10.1** — Add `vite.config.umd.ts` that builds a UMD bundle with inlined WASM. Register `<auths-verify>` custom element on load.
267  
268  **Task 10.2** — Add `unpkg` and `jsdelivr` fields to `package.json`, add `./umd` export, add `build:umd` script.
269  
270  ---
271  
272  ## Epic 11 — Getting Started Guide
273  
274  Write last when APIs are stable.
275  
276  **Task 11.1** — Create `docs/getting-started.md`: install CLI, create identity, sign commit, verify in Python, verify in browser (widget), verify in CI (GitHub Action). Exact commands and expected output. Under 3 minutes end-to-end.