From 1a28a9a708985e7c98dcbcb87c189e7582ec288d Mon Sep 17 00:00:00 2001 From: JesseMarkowitz Date: Wed, 2 Sep 2026 06:03:10 -0400 Subject: [PATCH] Apply post-M1 corrections to the planning package --- planning/BUILD-MILESTONES.md | 39 +++++++++- planning/DECISIONS/002-ollama-only-v1.md | 30 +++++++- .../DECISIONS/004-local-only-production.md | 30 +++++++- planning/README.md | 37 +++++++-- planning/SECURITY-THREAT-MODEL.md | 24 +++++- planning/TECHNICAL-DESIGN.md | 77 ++++++++++++++++--- planning/V1-ACCEPTANCE-TESTS.md | 65 ++++++++++++++-- planning/VERSION.md | 37 ++++++++- 8 files changed, 304 insertions(+), 35 deletions(-) diff --git a/planning/BUILD-MILESTONES.md b/planning/BUILD-MILESTONES.md index f9a1a8d..3b98940 100644 --- a/planning/BUILD-MILESTONES.md +++ b/planning/BUILD-MILESTONES.md @@ -1,6 +1,6 @@ # Adventure Storyteller — Production Build Milestones -**Status:** Planning-ready; implementation not yet authorized +**Status:** In implementation. M1 complete (2026-09-02); M2 next **Base:** AI-DnD `d72f7c1bda0f34fccd84afb7a25c34eb01c901de` ## 1. Purpose @@ -63,6 +63,15 @@ Create the production fork from the pinned AI-DnD commit and make ordinary local - verify explicitly configured trusted-LAN Ollama story generation while the storyteller UI/API remains loopback-bound, - establish baseline regression/test report. +**Added during implementation:** outbound TLS trust. A trusted-LAN Ollama may be +served over HTTPS with a privately issued certificate, which the inherited HTTP +client refused because it verified against a bundled public-CA list only. M1 +made outbound HTTPS verify against the operating system's CA store as well, +with verification and hostname checking fully intact and no bypass option +(ADR 002; `backend/app/tlstrust.py`). This was not in the scope list above but +was on M1's critical path: without it the trusted-LAN Definition of Done below +is unreachable against a realistic host. + ## Explicit Non-Scope - no history rewrite yet, @@ -86,6 +95,22 @@ Must demonstrate: A clean production build can start, open the browser UI, generate and persist story turns through either same-host Ollama or an explicitly configured trusted-LAN Ollama host, restart, and resume with outbound Internet blocked. The storyteller UI/API remains loopback-bound by default. +## Status: COMPLETE + +Accepted 2026-09-02. Evidence: `planning/reports/M1-BASELINE-REPORT.md` (run +logs and packet captures) and `planning/reports/M1-IMPLEMENTATION-REPORT.md` +(review report). A01-A06, H01-H03 and H11 all pass on runtime evidence; 648 +backend tests pass, including with no route to the Internet. + +**Capabilities M1 delivered, which later milestones inherit rather than build:** + +- offline first turn — the tokenizer table is vendored and digest-checked, +- no remote runtime assets — fonts self-hosted, CSP names no remote origin, +- loopback-bound storyteller in every run path, including the published Docker port, +- **working trusted-LAN inference, plain HTTP and HTTPS with a private CA**, + demonstrated against a second physical machine, +- a reproducible environment (`backend/requirements.lock`) and an offline-capable test suite. + --- # M2 — Remove Hosted, Cloud, Scripting, and Unneeded Deployment Surface @@ -111,7 +136,15 @@ Remove or isolate as appropriate: Add: -- explicit Ollama endpoint policy: same-host loopback default plus user-configured trusted-LAN inference, +- explicit Ollama endpoint policy. **Trusted-LAN inference already works as of + M1** — same-host loopback default, user-configured LAN endpoint, HTTP or + HTTPS with a privately issued certificate, verified against the machine's CA + store. M2's job is not to invent that capability but to **formalize and + narrow** it: decide and enforce what an endpoint may be in normal v1 + configuration, reject or remove what it may not, and keep the endpoint's + configuration from affecting the storyteller's own loopback bind. Do not + regress the TLS behavior while narrowing the surface — the shared + verification context must follow any client the removal work rewrites, - clear local-model connection diagnostics, - migration/test instrumentation replacements for any tests that depended on JS hooks or removed hosted paths. @@ -126,7 +159,7 @@ Add: - local story play still works, - existing tree/retry/memory/context behavior remains intact, - application requires no cloud API keys, -- approved trusted-LAN Ollama endpoints work while arbitrary public/Internet provider endpoints are rejected or absent from normal production configuration, +- approved trusted-LAN Ollama endpoints work — including an HTTPS endpoint with a privately issued certificate, per A06 — while arbitrary public/Internet provider endpoints are rejected or absent from normal production configuration, - removed provider/auth/analytics/scripting paths are no longer reachable from normal production configuration. ## Definition of Done diff --git a/planning/DECISIONS/002-ollama-only-v1.md b/planning/DECISIONS/002-ollama-only-v1.md index bf5c009..fe8b8e5 100644 --- a/planning/DECISIONS/002-ollama-only-v1.md +++ b/planning/DECISIONS/002-ollama-only-v1.md @@ -1,6 +1,6 @@ # ADR 002 — Ollama Is the v1 Model Backend -**Status:** Accepted +**Status:** Accepted; transport/TLS consequence added after M1 ## Decision @@ -30,3 +30,31 @@ Ollama is already available locally, provides a simple local API, supports both - LAN inference does not imply LAN exposure of the storyteller UI/API; the storyteller should still bind to loopback by default, - arbitrary public Internet/cloud model endpoints remain outside normal v1 configuration, - future backend abstraction may be added, but v1 should not be delayed to support it. + +## Transport for a Trusted-LAN Endpoint + +Added after M1. Same-host Ollama speaks plain HTTP over loopback, and it was +assumed a LAN endpoint would look the same. It does not have to. + +A trusted-LAN Ollama may be served over **HTTPS with a certificate issued by a +private or local CA** rather than a public one — a self-hosted server that +terminates TLS for everything it exposes is the ordinary case, not an exotic +one, and it may offer no cleartext port at all. A v1 client that trusts only a +bundled public-CA list cannot talk to such a host, while `curl` and the user's +browser on the same machine can. + +Therefore: + +- production clients must verify against the **operating system's trusted CA + store** in addition to any bundled certificate list, so a CA the user has + installed on their own machine is honoured by this application too; +- certificate **and hostname** verification remain fully enabled; +- there must be **no "ignore TLS errors" / "insecure" option**, in the UI, + in configuration, or as an environment variable. A LAN endpoint the machine + does not trust is a configuration problem to fix at the OS level, not a check + to switch off; +- the endpoint URL must therefore accept `https://` on any port, not only + `http://…:11434`. + +M1 implemented this (`backend/app/tlstrust.py`); see +`planning/reports/M1-IMPLEMENTATION-REPORT.md` §G. diff --git a/planning/DECISIONS/004-local-only-production.md b/planning/DECISIONS/004-local-only-production.md index 0237424..3ad35ca 100644 --- a/planning/DECISIONS/004-local-only-production.md +++ b/planning/DECISIONS/004-local-only-production.md @@ -1,6 +1,6 @@ # ADR 004 — Local-Only Production Default -**Status:** Accepted; Phase 0B hardening requirements identified +**Status:** Accepted; Phase 0B hardening requirements identified; testing consequence strengthened after M1 ## Decision @@ -31,6 +31,27 @@ The selected AI-DnD base does **not** satisfy this requirement unchanged: These are bounded production-hardening tasks rather than reasons to reject the fork. +## Testing Consequence + +Strengthened after M1, which confirmed the failure mode this rule exists to catch. + +**Offline behavior must be tested with a fresh cache/data state on a machine +with no route to the Internet.** Both inherited violations above were *first-use* +downloads: `tiktoken` caches its encoding to a temp directory, and the browser +caches Google's fonts. On any machine that had been online once, both were +invisible — the application appeared to work offline while depending on an +artifact an earlier online run had left behind. Neither was findable by static +analysis; each took an actually isolated run to surface. + +Therefore an offline claim is only evidence when the test: + +- runs on a network with **no route out and no external DNS**, verified before + the test rather than assumed, +- starts from a **fresh application data directory and a fresh cache**, so + nothing warmed by a previous run is available, +- exercises the **first** story turn, which is when a first-use download fires, +- observes actual network destinations rather than only the absence of an error. + ## Consequences The production application must avoid or remove: @@ -48,4 +69,11 @@ The production application must avoid or remove: Production packaging must contain all runtime assets required for ordinary story use after the user has installed the intended local Ollama models. +Vendored runtime artifacts should be **integrity-verifiable where practical**: a +recorded source and a digest the application checks when it loads them, rather +than an opaque blob nobody can re-derive. A substituted or truncated artifact +should then fail loudly instead of silently changing behavior — a corrupted +tokenizer table, for instance, would quietly change every token count the +context budget is computed from. + The storyteller application should bind to loopback by default. Ollama should default to same-host loopback but may be explicitly configured to an approved trusted-LAN endpoint for v1. This LAN inference path does not authorize LAN exposure of the storyteller UI/API. Arbitrary public/Internet inference endpoints remain prohibited in normal v1 configuration. diff --git a/planning/README.md b/planning/README.md index f21cc1d..15be316 100644 --- a/planning/README.md +++ b/planning/README.md @@ -1,7 +1,7 @@ # Adventure Storyteller Planning Package -**Status:** Phase 0 complete; architecture selected; planning revision ready for review. -**Production coding:** Not yet authorized. Review this package before preparing the first implementation prompt. +**Status:** Phase 0 complete; architecture selected; **Milestone M1 implemented and accepted (2026-09-02)**. +**Production coding:** Underway, milestone by milestone. M1 is done; M2 is the next milestone to brief. This package contains the current product requirements, final Phase 0 architecture decisions, detailed subsystem designs, acceptance tests, research evidence, and the production milestone plan for the local-only interactive-story project. @@ -153,14 +153,17 @@ Phase 0 research and spikes COMPLETE Architecture/fork decision COMPLETE | v -Planning package revision CURRENT REVIEW +Planning package revision COMPLETE | v -Approve planning package +Approve planning package COMPLETE | v -Prepare one implementation prompt -for Production Milestone 1 +Milestone M1 COMPLETE (2026-09-02) + fork + offline baseline see planning/reports/M1-*.md + | + v +Milestone M2 NEXT — brief not yet prepared | v Implement and review milestone-by-milestone @@ -168,6 +171,24 @@ Implement and review milestone-by-milestone ## Stop Rule -**Do not begin production coding from this package yet.** +**One milestone at a time. Do not begin a milestone before its brief exists.** -The current action is to review the planning changes. No replacement Codex prompt has been prepared in this revision. +M1 is complete and accepted; its evidence is in `reports/M1-BASELINE-REPORT.md` +and `reports/M1-IMPLEMENTATION-REPORT.md`. **No M2 brief has been prepared.** +The current action is to review the post-M1 planning corrections below before +writing one. + +### Post-M1 corrections applied (2026-09-02) + +Implementation evidence contradicted or under-specified six places in this +package, and a seventh was added on review. All seven are now corrected: + +| Document | Correction | +| --- | --- | +| `DECISIONS/002-ollama-only-v1.md` | New section: a trusted-LAN Ollama may be HTTPS with a private CA; verify against the OS trust store; full certificate and hostname checking; no bypass option. | +| `DECISIONS/004-local-only-production.md` | New *Testing Consequence*: offline tests need a fresh cache and no route out. Vendored runtime artifacts should be integrity-verifiable. | +| `V1-ACCEPTANCE-TESTS.md` A05 | Pass conditions reworded around *accepted* history; explicit note that the user's submitted text is deliberately retained. | +| `V1-ACCEPTANCE-TESTS.md` A06 | Now requires a real second machine and an HTTPS endpoint with a locally issued certificate; a plain-HTTP LAN test is no longer sufficient evidence. | +| `V1-ACCEPTANCE-TESTS.md` §3 | Record CPU/GPU/RAM: cold model load on a CPU-only host exceeded the inherited 120 s timeout. | +| `BUILD-MILESTONES.md` | M1 marked COMPLETE with the capabilities it delivered; M2's endpoint-policy line reframed from inventing trusted-LAN support to narrowing it. | +| `TECHNICAL-DESIGN.md` §5 | Runtime boundary restated: the storyteller is loopback-only, inference may be same-host *or* trusted-LAN, and the two are independent. Adds the TLS/private-CA rule. | diff --git a/planning/SECURITY-THREAT-MODEL.md b/planning/SECURITY-THREAT-MODEL.md index 6130e88..0e44bb9 100644 --- a/planning/SECURITY-THREAT-MODEL.md +++ b/planning/SECURITY-THREAT-MODEL.md @@ -783,6 +783,26 @@ LAN access to the **storyteller browser UI/API** is different and remains a futu Do not accidentally inherit storyteller LAN exposure because a candidate project binds to all interfaces. +### Two different TLS questions — do not conflate them + +Clarified after M1, which implemented one of these and not the other. + +**Inbound TLS — serving the storyteller over HTTPS.** Deferred, and still +deferred. It only becomes a question if storyteller LAN access is ever added. +This is what "local certificate support" refers to in the deferred list further +down. + +**Outbound TLS — verifying the certificate of a trusted-LAN inference host.** +**Implemented in M1 and required for v1.** A LAN Ollama is often served over +HTTPS with a privately issued certificate, so the storyteller must verify +against the machine's own CA store as well as any bundled list, with +certificate and hostname checking fully enabled and no bypass option +(ADR 002; `TECHNICAL-DESIGN.md` §5). + +The storyteller remaining loopback-bound is unaffected by either. Outbound +verification is about who the storyteller is willing to *talk to*; inbound TLS +would be about who may talk to *it*. + ## 54. Tailscale / VPN Access Treat remote access to the storyteller UI/API like storyteller LAN access. @@ -1167,7 +1187,9 @@ Potential later improvements: - signed release builds, - dependency SBOM, - LAN authentication, -- local certificate support, +- local certificate support **for serving the storyteller over HTTPS** — note + that *outbound* verification of a trusted-LAN inference host's certificate is + a separate matter, is required for v1, and was implemented in M1 (see §53), - optional AppArmor/container confinement. These are not required for initial v1 unless Phase 0B reveals a specific need. diff --git a/planning/TECHNICAL-DESIGN.md b/planning/TECHNICAL-DESIGN.md index 2ad8c4a..fdbcd6d 100644 --- a/planning/TECHNICAL-DESIGN.md +++ b/planning/TECHNICAL-DESIGN.md @@ -132,19 +132,59 @@ Accepted Story / Scene State `Local-only` means user-controlled local infrastructure with no required Internet/cloud dependency; it does not require every process to share one host. -Default same-host runtime path: +The architecture has **two boundaries, and they are not the same boundary**. The +storyteller is loopback-only, always. Inference may be same-host or on a +specifically configured trusted-LAN machine. Wording that describes Ollama as +simply "loopback/local" collapses the two and understates the intended +deployment: ```text -Browser -> local FastAPI application -> 127.0.0.1 Ollama +Browser ──loopback──> storyteller (FastAPI + SPA), bound to 127.0.0.1 + │ + ├── same-host Ollama on 127.0.0.1:11434 (default) + │ + └── OR an explicitly configured trusted-LAN Ollama + on another user-controlled machine + http://:11434/v1 or https:///v1 ``` -Supported v1 trusted-LAN inference path: +Read that as three separate rules: -```text -Browser -> local FastAPI application -> explicitly approved LAN Ollama host -``` +1. **The storyteller's own listener is loopback, in every run path.** Dev + server, production server, and container alike. Nothing about the inference + choice changes it. Where a container must listen on `0.0.0.0` because a + published port cannot reach anything else, the port is published to the + host's loopback only. +2. **The inference endpoint is an outbound connection, chosen by the user.** + Same-host loopback is the default. A trusted-LAN host is a first-class, + supported v1 configuration — not a workaround and not a development-only + convenience. +3. **The two are independent.** Reaching a LAN Ollama never requires, and must + never cause, LAN exposure of the storyteller UI/API. There is no supported + v1 configuration in which the storyteller itself is reachable from the LAN. -The browser and storyteller API still remain loopback-bound by default. Supporting LAN inference does **not** expose the storyteller web application to the LAN. +### Transport to a trusted-LAN endpoint + +A LAN inference host is often reached over **HTTPS with a certificate issued by +a private or local CA**, and may offer no cleartext port at all. This is +ordinary for a self-hosted server, so v1 must handle it rather than assume the +same plain HTTP that same-host loopback uses: + +- outbound HTTPS verifies against the **operating system's trusted CA store** in + addition to any bundled certificate list, so a CA the user installed on their + own machine is honoured here as it is by `curl` and their browser; +- certificate **and hostname** verification stay fully enabled; +- there is **no "ignore TLS errors" option** anywhere — not in the UI, not in + configuration, not as an environment variable; +- the endpoint field therefore accepts `https://` on any port. + +Prompts, story text, retrieved knowledge and embedding inputs all travel to +whichever inference host is configured, which is why it must be one the user +controls on a network they trust — and why the endpoint is always explicitly +configured, never discovered. + +See ADR 002 (*Transport for a Trusted-LAN Endpoint*) and, for the demonstrated +deployment, `planning/reports/M1-IMPLEMENTATION-REPORT.md` §F. Allowed future local paths may also include explicitly configured local media services. @@ -161,17 +201,30 @@ Production defaults must not require: ### 5.1 Known AI-DnD hardening work -Phase 0B identified concrete inherited violations: +Phase 0B identified concrete inherited violations, and M1 added a fifth. Items +1, 2 and 5 are **resolved**; items 3 and 4 remain open and belong to M2. -1. `tiktoken` attempts to download the `cl100k_base` encoding on first use. - - production packaging must include/cache the required encoding or replace the dependency path so first story use is offline. -2. the SPA requests Google Fonts at runtime. - - fonts must be self-hosted or replaced with local/system fonts and CSP tightened. +1. ~~`tiktoken` attempts to download the `cl100k_base` encoding on first use.~~ + **Done in M1.** The encoding table is vendored in the tree and loaded + directly, with its SHA-256 verified against the digest `tiktoken` pins, so no + code path in the tokenizer can reach the network. +2. ~~the SPA requests Google Fonts at runtime.~~ + **Done in M1.** All three families are self-hosted, and the CSP names no + remote origin at all. 3. hosted/multi-user/auth/demo/analytics/Postgres/cloud-provider/QuickJS paths are unnecessary. - remove them rather than merely hide them where practical. + - **Open — M2.** M1 removed nothing, so this surface is unchanged from the + fork point. 4. endpoint validation must reflect this product's threat model. - same-host loopback Ollama is the default; an explicitly configured trusted-LAN Ollama endpoint is supported; arbitrary public/Internet model endpoints must be rejected or kept outside normal v1 configuration. - inference endpoint configuration must not change the storyteller's own loopback bind behavior. + - **Open — M2.** The trusted-LAN path itself works as of M1; what remains is + deciding and enforcing which endpoints normal v1 configuration may name. +5. ~~outbound TLS verified only against a bundled public-CA list, so a LAN host + with a privately issued certificate was refused.~~ + **Found and fixed in M1.** Not visible to Phase 0B: every run up to that + point used plain HTTP over loopback, where certificate verification never + happens. See *Transport to a trusted-LAN endpoint* above. ## 6. Browser UI Boundary diff --git a/planning/V1-ACCEPTANCE-TESTS.md b/planning/V1-ACCEPTANCE-TESTS.md index a90adeb..a233f20 100644 --- a/planning/V1-ACCEPTANCE-TESTS.md +++ b/planning/V1-ACCEPTANCE-TESTS.md @@ -64,10 +64,13 @@ Recommended environment: - one installed narrator model, - one installed embedding model if semantic retrieval is enabled, - outbound Internet blocked after setup, -- fresh test data directory. +- fresh test data directory, +- for A06, a second user-controlled machine on the trusted LAN serving HTTPS + with a locally issued certificate. Record: - OS, +- **CPU (core count), GPU (or explicitly none), and RAM**, - application commit/version, - Ollama version, - narrator model, @@ -75,6 +78,14 @@ Record: - browser, - test date. +Hardware is not bookkeeping. A model's **cold load** time depends on it, and +M1 measured a cold `qwen2.5:3b-instruct` load on a GPU-less four-core host +exceeding the inherited 120-second client timeout — three times — while the +same turn completed in 6 to 9 seconds once the model was resident. A timeout +result is therefore uninterpretable unless the hardware and the warm/cold state +are recorded with it, and a pass on a GPU machine does not predict a pass on a +CPU-only one. + ## 4. Standard Test Campaign Create a campaign named: @@ -284,9 +295,29 @@ Transcript and authoritative current state are restored. 5. Reopen campaign. ### Pass -- prior story remains intact, -- failed turn is not partially committed as accepted, -- user can retry. +- **previously accepted history and state are unchanged** — every earlier turn, + its text and the authoritative state remain exactly as they were, +- **no AI response is accepted for the failed turn**: no partial or truncated + narration is committed, and the count of accepted AI turns does not move, +- the failure is reported to the user rather than swallowed, +- the user can retry and continue. + +### Note on wording + +"Nothing was committed" would be misleading, and this test should not be read +that way. The user's **submitted text is deliberately retained**: it is +committed before the model is called, so a model failure never discards what +the player typed. A failed turn therefore leaves the player's input at the head +of the story with no reply, and the total row count grows by one. + +The invariant is about *accepted* history, not about row counts. What must +never happen is a partially generated AI response entering the story as though +it were accepted, or an earlier turn being altered or lost. + +A test that asserts the story is byte-identical before and after will fail for +the wrong reason. Assert instead on the accepted prefix — for example, a digest +over every action up to the pre-failure head — and on the number of accepted AI +turns. --- @@ -296,13 +327,18 @@ Transcript and authoritative current state are restored. ### Preconditions - storyteller and browser run on machine A, -- Ollama runs on a separate user-controlled machine B on the trusted LAN, +- Ollama runs on a separate user-controlled machine B on the trusted LAN — + **a genuinely separate machine**, not another container or namespace on + machine A, +- **machine B serves HTTPS with a certificate issued by a private/local CA**, + and that CA is installed in machine A's operating-system trust store, - required models are already installed, - outbound Internet access is blocked. ### Steps 1. Keep the storyteller UI/API bound to loopback on machine A. -2. Configure the storyteller's Ollama endpoint to machine B. +2. Configure the storyteller's Ollama endpoint to machine B, as an `https://` + URL using the hostname the certificate is issued for. 3. Verify model discovery/connection diagnostics. 4. Generate at least three story turns. 5. Trigger state extraction and embeddings/memory retrieval if enabled. @@ -311,11 +347,28 @@ Transcript and authoritative current state are restored. ### Pass - story operation succeeds through the explicitly configured LAN Ollama host, +- **TLS is verified, not bypassed**: the certificate chains to the CA installed + on machine A and the hostname is checked; no "insecure" option was used, + because none exists, - no cloud API key or Internet access is required, - inference/model traffic goes only to the approved LAN host, - storyteller UI/API remains loopback-bound, - prompts, state, retrieved knowledge, and embedding inputs do not go to any unapproved destination. +### Note on sufficient evidence + +Added after M1. **A plain-HTTP LAN test is no longer sufficient evidence for +this test.** Certificate verification never happens over cleartext, so an +HTTP-only run cannot exercise the path that actually broke: against a real +HTTPS LAN host, the application refused an endpoint that `curl` and the browser +on the same machine accepted, because it verified against a bundled public-CA +list instead of the machine's own trust store (ADR 002, *Transport for a +Trusted-LAN Endpoint*). + +A container or network namespace standing in for machine B is likewise not +sufficient on its own. It exercises the non-loopback address but typically +speaks plain HTTP, and it will hide exactly this class of defect. + --- # B. Basic Story Interaction diff --git a/planning/VERSION.md b/planning/VERSION.md index b3486ac..2fbddb9 100644 --- a/planning/VERSION.md +++ b/planning/VERSION.md @@ -1,8 +1,39 @@ # Planning Package Version -**Package:** Adventure Storyteller Planning Package v2 -**Revision date:** 2026-09-01 -**Status:** Phase 0 complete; architecture selected; production milestone plan approved for milestone-by-milestone implementation. +**Package:** Adventure Storyteller Planning Package v2.1 +**Revision date:** 2026-09-02 +**Status:** Phase 0 complete; architecture selected; **Milestone M1 implemented and accepted**; M2 not yet briefed. + +## v2.1 — Post-M1 Corrections (2026-09-02) + +M1 implementation evidence contradicted or under-specified parts of v2. The +corrections are recorded in the documents themselves and listed in +`README.md` § *Post-M1 corrections applied*; the evidence behind them is in +`reports/M1-BASELINE-REPORT.md` and `reports/M1-IMPLEMENTATION-REPORT.md`. + +In summary: + +- a trusted-LAN Ollama may be **HTTPS with a privately issued certificate**; + clients verify against the operating system's CA store, with full certificate + and hostname verification and no bypass option (ADR 002, + `TECHNICAL-DESIGN.md` §5, A06), +- offline claims require a **fresh cache and no route out** to be evidence at + all, and vendored runtime artifacts should be integrity-verifiable + (ADR 004), +- A05's invariant is about **accepted** history; the user's submitted text is + deliberately retained on a failed turn, +- A06 requires a **real second machine and an HTTPS endpoint**; a plain-HTTP + LAN test is no longer sufficient evidence, +- the standard test environment records **CPU/GPU/RAM**, because cold model + load on a CPU-only host exceeded the inherited 120 s client timeout, +- `BUILD-MILESTONES.md` records M1 as complete and reframes M2's endpoint work + as **narrowing** an existing capability rather than inventing it, +- `SECURITY-THREAT-MODEL.md` §53 distinguishes **inbound** TLS (still deferred) + from **outbound** certificate verification (required, done in M1). + +Nothing in the architecture selected in v2 was reversed. + +## v2 — Post Phase 0B Revision (2026-09-01) This v2 package supersedes the earlier planning package produced before the final Phase 0B review and the trusted-LAN Ollama deployment clarification.