# Python Port Idea Evolution

Updated: 2026-07-01

Record hypothesis changes here. Do not rewrite history; append a new entry when
evidence changes the shape.

## 2026-07-01 - Wiki Dispatch Should Split By Command Family

Old hypothesis:
`cli/dispatch/wiki.py` could own wiki command routing, workspace list/drop,
topic subcommands, serve startup, and search/show/health/tagging request
construction because all of those commands are local wiki commands.

New hypothesis:
`dispatch/wiki.py` should be the wiki-command facade, while command families
with their own subcommands or startup mechanics get separate modules.
`dispatch/topics.py` owns topic subcommand request construction,
`dispatch/workspaces.py` owns registry list/drop behavior, and
`dispatch/serve.py` owns local viewer startup.

Evidence that forced the change:
`cli/dispatch/wiki.py` was 225 lines and mixed request construction for
unrelated service families with uvicorn startup. The `../almanac` CLI keeps
root and hosted command-family dispatchers small, and Cosmic Python chapter 4
keeps interfacing code separate from application use cases.

Code or product assumption affected:
Slice 121 keeps CLI behavior unchanged. `codealmanac list`, search/show,
topics, health/reindex, tag/untag, and serve still route through the same
services and renderers. Only the dispatch module ownership changed.

Follow-up test:
Future wiki CLI commands should land in the command-family dispatcher that owns
the service request construction. `dispatch/wiki.py` should only route top-level
wiki commands or handle direct one-step wiki verbs.

## 2026-07-01 - RunStore Should Not Own Factory Or Query Mechanics

Old hypothesis:
`RunStore` could own run-id generation, initial `RunRecord` construction,
record listing sort order, spec-backed queued-run selection, worker lock
delegation, JSON IO delegation, and lifecycle transition methods because all of
those behaviors support the run ledger.

New hypothesis:
`RunStore` should remain the service-facing repository facade, but factory and
read-query mechanics should be named. `factory.py` owns run-id and initial
record construction. `queries.py` owns sorted record listing and oldest
spec-backed queued-run selection. `store.py` coordinates those helpers with
`io.py`, `transitions.py`, and `locks.py`.

Evidence that forced the change:
`services/runs/store.py` was the largest production file after slice 119 at 271
lines. It already had dedicated modules for paths, IO, worker locks, and
transition writes, but still mixed repository verbs with `uuid4`, `strftime`,
log-path construction, record sorting, and queue membership selection. Cosmic
Python chapter 6 frames repositories/unit-of-work objects as the boundary over
persistent state; that boundary is clearer when low-level construction and
query mechanics have names.

Code or product assumption affected:
Slice 120 keeps run behavior unchanged. Foreground starts still create queued
records without specs; background queueing still writes a durable spec and
selects the oldest spec-backed queued run; cancellation, attach, logs, and
terminal transitions keep the same event sequence.

Follow-up test:
Future run-ledger changes should add behavior tests through `RunsService` or
`RunQueueWorkflow`. Run-id/log-path creation belongs in `factory.py`; queue and
list ordering belongs in `queries.py`; record-plus-event atomicity belongs in
`transitions.py`; file mechanics belong in `io.py`.

## 2026-07-01 - Topic YAML Has Separate Read And Mutation Paths

Old hypothesis:
`services/wiki/topics.py` could own the topic Pydantic model, forgiving
`topics.yaml` reads for index refresh, ruamel round-trip file loading,
mutation helpers, temporary-file writes, parent normalization, and title
defaults because all of those concerns touch `topics.yaml`.

New hypothesis:
Topic data models, read/index tolerance, and round-trip mutation mechanics are
separate reasons to change. `topic_models.py` owns `TopicDefinition`,
`TopicsYaml`, and `title_for_slug`; `topic_read.py` owns the forgiving PyYAML
read path; `topic_file.py` owns strict ruamel round-trip mutation and writes;
`topics.py` remains only an import facade.

Evidence that forced the change:
`services/wiki/topics.py` reached 266 lines and mixed Pydantic validation,
PyYAML tolerant reads, ruamel comment-preserving mutation, atomic writes, and
topic graph parent-list helpers. Cosmic Python chapter 2 frames repositories as
a boundary over persistence details; this module had two persistence paths and
the model in one place.

Code or product assumption affected:
Slice 119 keeps topic command and index behavior unchanged. Invalid topic YAML
still becomes an empty topic set for index refresh, while organization commands
still raise validation errors and preserve comments, quotes, and line endings
when rewriting `topics.yaml`.

Follow-up test:
Future `topics.yaml` schema or mutation changes should add topic/read-model
tests and land in the module for that path: parsed shape in `topic_models.py`,
index reads in `topic_read.py`, and round-trip organization writes in
`topic_file.py`.

## 2026-07-01 - Serve Server App Is A Composition Root

Old hypothesis:
`server/app.py` could own FastAPI creation, viewer API route bodies, static
asset validation, package-resource reads, selected-wiki precedence, and
exception mapping because all of those details support `codealmanac serve`.

New hypothesis:
`server/app.py` should only compose the local viewer server. API route request
construction belongs in `server/api_routes.py`, browser-shell route
registration belongs in `server/static_routes.py`, asset validation/reads
belong in `server/static_assets.py`, and product/Pydantic exception mapping
belongs in `server/errors.py`.

Evidence that forced the change:
`server/app.py` reached 271 lines and mixed composition-root work with unrelated
HTTP-edge mechanics. Cosmic Python chapter 13 describes a bootstrapper as the
place that wires dependencies together; route bodies and package-resource reads
are not dependency wiring.

Code or product assumption affected:
Slice 118 keeps `serve` behavior unchanged. Default multi-wiki browsing,
`serve --wiki` narrowing, jobs API reads, static ES modules, asset validation,
and JSON error bodies stay covered by the existing server tests.

Follow-up test:
Future server endpoints should land in `api_routes.py` only when they adapt a
viewer-service use case. Static file behavior belongs in `static_routes.py` or
`static_assets.py`, and HTTP error mapping belongs in `errors.py`.

## 2026-07-01 - Automation Needs Selection And Job Construction Modules

Old hypothesis:
`AutomationService` could own install/status/uninstall orchestration, task
defaulting, validation, scheduler command arguments, plist paths, launch PATH,
intervals, and Garden working-directory resolution in one service file.

New hypothesis:
`AutomationService` should remain the use-case facade. Task selection and
install validation belong in `services/automation/selection.py`, static task
metadata belongs in `definitions.py`, and `ScheduledJob` construction belongs in
`jobs.py`.

Evidence that forced the change:
`services/automation/service.py` reached 278 lines and mixed application-service
orchestration with command construction mechanics. Cosmic Python chapter 4
defines application services as orchestration; scheduled-job construction is a
mechanism that should be testable without reading the install/status/uninstall
verb flow.

Code or product assumption affected:
Architecture tests now keep command argv construction, launch PATH assembly,
plist path construction, interval policy, selection validation, and task
definition helpers out of `service.py`.

Follow-up test:
Future automation changes should add behavior tests around the public
install/status/uninstall verb and update `jobs.py` or `selection.py` based on
which reason to change is involved.

## 2026-07-01 - Source Address Resolution Needs Family Modules

Old hypothesis:
After slice 106, one `services/sources/address_resolution.py` module could own
all source-address syntax because it was already outside `SourcesService`.

New hypothesis:
`address_resolution.py` should be a dispatcher facade. Git, GitHub, web URL,
local path, transcript, prompt hints, and shared number parsing now have
separate modules named for their reason to change.

Evidence that forced the change:
`address_resolution.py` reached 290 lines and mixed unrelated grammars:
GitHub shorthand, GitHub URL decomposition, HTTP URL validation, Git range/diff
syntax, transcript refs, local path classification, and file hashing. Cosmic
Python chapter 3 argues for simple abstractions that hide messy details; each
address family now hides its own grammar details behind the same
`SourceBrief` contract.

Code or product assumption affected:
Architecture tests now keep `AnyHttpUrl`, `sha256`, GitHub/Git/path resolver
definitions, prompt hints, and positive-int parsing out of the dispatcher
facade.

Follow-up test:
Future source-address syntax changes should add focused source-resolution tests
and update only the module for that source family.

## 2026-07-01 - Topic Commands Need A Graph Boundary

Old hypothesis:
`TopicsService` could own topic command orchestration, workspace selection,
topic existence checks, self-parent validation, and DAG cycle traversal in one
file because all of those behaviors support one CLI command group.

New hypothesis:
`TopicsService` should stay the service-facing use-case entrypoint, while
`services/topics/graph.py` owns topic graph rules,
`services/topics/read_model.py` owns topic slug lookup through the derived
index, and `services/topics/workspace.py` owns selected-wiki resolution.

Evidence that forced the change:
`services/topics/service.py` reached 297 lines and mixed public topic verbs with
mechanical DAG traversal and workspace request construction. Cosmic Python's
service-layer guidance keeps service methods as the entrypoint to use cases,
not as the home for every internal helper those use cases need.

Code or product assumption affected:
Architecture tests now keep `TopicDefinition`, `SelectWorkspaceRequest`, graph
helper definitions, read-model helper definitions, and workspace helper
definitions out of `service.py`.

Follow-up test:
Future topic changes should add behavior tests around the public topic verb and
keep graph traversal tests or architecture checks near `services/topics/graph.py`.

## 2026-07-01 - Index Reads Need Query-Family Modules

Old hypothesis:
The index read side could live in one `services/index/views.py` file because
all functions were read-only SQL over the same derived SQLite model.

New hypothesis:
`views.py` should be a small facade over query-family modules. Search and
file-mention SQL lives in `search_views.py`, summary counts live in
`summary_views.py`, page detail projection lives in `page_views.py`, topic DAG
reads live in `topic_views.py`, and health findings live in `health_views.py`.

Evidence that forced the change:
`views.py` reached 627 lines. The read side now serves search/show/topics,
health, and the local viewer. Those surfaces share a database but have
different reasons to change, especially after structured page `sources:` added
health findings and page provenance projections.

Code or product assumption affected:
Architecture tests now keep `views.py` tiny, require the query-family modules,
keep each read-view module under 260 lines, and prevent migrations,
projection-write SQL, and page-document loading from entering any view module.

Follow-up test:
Future index changes should add behavior tests for the affected public read
surface and keep read-only SQL in the module named for that query family.

## 2026-07-01 - Run Ledger Needs A Transition Writer

Old hypothesis:
`RunStore` could remain one service-facing repository file that owned run paths,
JSON record/spec IO, JSONL event appends, worker locks, queue selection, and
state transitions.

New hypothesis:
`RunStore` should stay the repository facade, but the persistence details need
named modules. `paths.py` owns run-id validation and file naming, `io.py` owns
record/spec/event file mechanics, `locks.py` owns worker locks, and
`transitions.py` owns grouped record-plus-event writes.

Evidence that forced the change:
`store.py` reached 486 lines and the status transitions in `mark_running`,
`finish`, and `cancel` manually wrote the run JSON record and then appended a
matching status event. Jobs attach/cancel, background workers, sync
reconciliation, and viewer job detail all depend on the record and event log
staying coherent.

Code or product assumption affected:
Architecture tests now require the split modules, keep `store.py` under 280
lines, and prevent JSON parsing, path validation, worker lock ownership, and
manual append-file mechanics from moving back into the store facade.

Follow-up test:
Future run-ledger changes should test public `RunsService` behavior, and
transition-level failure tests should keep proving that event append failure
restores the previous record or removes an uncommitted queue spec.

## 2026-07-01 - Filesystem Runtime Is A Small Adapter Plus Modules

Old hypothesis:
The filesystem source-runtime adapter could own path support end to end:
adapter dispatch, text decoding, ignore rules, Git listing, Python walking,
selection, rendering, and path display helpers.

New hypothesis:
`FilesystemSourceRuntimeAdapter` should only implement the service-owned
`SourceRuntimeAdapter` port and assemble `SourceRuntime` values. Text document
decoding, directory material listing, selection ranking, rendering, and path
display are separate filesystem-integration modules.

Evidence that forced the change:
`integrations/sources/filesystem/adapter.py` reached 685 lines and had several
independent reasons to change. The source-runtime contract is stable, but the
inside of the filesystem adapter mixes provider-like Git mechanics,
prompt-facing rendering, document parsing, and directory traversal policy.

Code or product assumption affected:
Architecture tests now require `documents.py`, `listing.py`, `paths.py`,
`rendering.py`, and `selection.py`, keep `adapter.py` under 220 lines, and
prevent charset decoding, pathspec/Git walking, document models, and rendering
helpers from regrowing in the adapter.

Follow-up test:
Future filesystem behavior changes should test the module that owns the
behavior directly, then keep one source-runtime test proving public output.

## 2026-07-01 - Sync Needs A Policy Module

Old hypothesis:
`SyncWorkflow` could own transcript discovery, run reconciliation, ledger cursor
rules, pending-entry transitions, and ingest startup in one service module
because `sync` is one workflow.

New hypothesis:
`SyncWorkflow` should remain the service-layer use case, but deterministic
ledger/cursor policy deserves a first-class module. The service coordinates
sources, runs, queue, ingest, and ledger persistence; `workflows/sync/policy.py`
owns ledger identity, cursor decisions, pending-run reconciliation, and cursor
guidance.

Evidence that forced the change:
`workflows/sync/service.py` reached 693 lines and mixed orchestration with pure
cursor transition rules. The old TypeScript wiki already treated the sync
ledger as the dedupe and recovery contract, not a side detail of the scheduler.
Cosmic Python's service-layer guidance points toward workflows that coordinate
repositories/services while keeping policy rules separately testable.

Code or product assumption affected:
Architecture tests now keep sync cursor helpers out of `service.py` and prevent
`policy.py` from importing orchestration services or integrations.

Follow-up test:
Future sync behavior changes should add focused tests around the policy
function that owns the cursor or ledger transition, plus a workflow-level test
for the public `sync` behavior.

## 2026-07-01 - Viewer Scope Is A Read Model

Old hypothesis:
`serve` could capture one resolved workspace at startup and treat `--wiki` as
the only cross-wiki mechanism.

New hypothesis:
The local viewer needs an explicit read-scope model. `ViewerService` selects the
current or requested workspace, exposes available registered local wikis, skips
unavailable entries without mutating the registry, and lets the browser pass a
stable `workspace_id` selector for detail routes.

Evidence that forced the change:
The live agreement says `serve` should browse all registered local wikis, while
the current FastAPI routes and viewer requests always resolved exactly one
workspace. That made the archive-era multi-wiki scope impossible without
teaching the viewer service about the registry.

Code or product assumption affected:
`ViewerOverview` now carries `workspaces`; `/api/*` viewer routes accept a
`wiki` query selector; `serve --wiki` is a narrowing flag that exposes only the
selected wiki in overview.

Follow-up test:
Keep server/viewer tests that prove default `serve` lists available registered
wikis, skips missing entries, and locked `serve --wiki` returns a single-wiki
overview.

## 2026-07-01 - Page Writing Has One Lifecycle

Old hypothesis:
`ingest` and `garden` could each own their own run ledger, harness, safety, and
failure flow because the workflows were still small enough to read.

New hypothesis:
Page-writing operations need a shared lifecycle workflow. Operation workflows
prepare their own context and prompt; `PageRunWorkflow` owns the repeated run
transition, preflight, harness, normalized event recording, safety validation,
index refresh, and terminal state.

Evidence that forced the change:
`IngestWorkflow` and `GardenWorkflow` duplicated the same harness/run machinery.
The archived Almanac engine already has a page-run workflow for the same
reason, and the Python files had grown large enough that ownership was no
longer obvious.

Code or product assumption affected:
`workflows/page_run/` is now the only place page-writing operations should call
`RunHarnessRequest`, record harness transcripts, translate harness events, and
finish or fail runs.

Follow-up test:
Keep architecture coverage that prevents `ingest` and `garden` from importing
shared harness/run plumbing directly.

## 2026-07-01 - App-Server And Background Jobs Are Active Work Again

Old hypothesis:
Public-beta readiness was close enough that richer Codex app-server support,
Claude SDK events, and queue/worker background jobs could wait for later
evidence.

New hypothesis:
The active rewrite goal is no longer just public-beta packaging. Restoring the
archived behavior means bringing back rich harness events and background job
machinery in Python shape before calling the architecture good enough.

Evidence that forced the change:
The user explicitly reopened architecture quality work and named Codex
app-server, Claude SDK/event harnesses, background jobs, attach/cancel, and
durable run events as required restoration targets.

Code or product assumption affected:
`codex exec` is a temporary adapter, not the final Codex lifecycle path.
Foreground `sync` pending claims remain useful, but they do not replace the
background queue/worker surface now required by the active goal.

Follow-up test:
When those slices land, add behavior tests for attach/cancel/run-event replay
and provider tests that prove rich normalized events survive into `jobs logs`.

## 2026-07-01 - Cancellation Is Run State

Old hypothesis:
Background jobs should probably start with a queue and worker, because that is
the most visible missing machinery from the archived implementation.

New hypothesis:
The queue and worker need a stable control contract first. `cancel` must be
durable run state, `attach` must read the durable event stream, and terminal
finalization must not overwrite cancelled records.

Evidence that forced the change:
The archive's worker, finalization, and CLI all depended on cancellation being
visible in storage before and after execution. The current Python code could
finish a run after any external actor marked it cancelled because `finish(...)`
did not re-check terminal cancellation.

Code or product assumption affected:
`RunStore.finish(...)` now preserves `cancelled`; `RunsService.cancel(...)`
owns cancellation; `jobs cancel` no longer needs storage knowledge; and future
background workers should use the same service/store behavior instead of
inventing a separate cancellation marker path.

Follow-up test:
The worker slice should prove that queued cancellation prevents execution and
running cancellation cannot be rewritten by finalization.

## 2026-06-29 - Public Release Is A Gate, Not A Feeling

Old hypothesis:
After the local product surface existed, the remaining question was mostly
whether more architecture polish was worth it.

New hypothesis:
Architecture-only work is now diminishing returns. Public release should be
judged by concrete evidence: clean install, accurate README and package
metadata, real lifecycle dogfood, prompt quality, sync proof, viewer browser
checks, safety checks, and contract guards.

Evidence that forced the change:
`README.md` still advertised the old Node/npm `almanac` and hosted-dashboard
flow after the Python CLI had moved to `codealmanac`, Python 3.12+, local-only
behavior, and a default `almanac/` root.

Code or product assumption affected:
The release bar now lives in `docs/python-port/public-release-readiness.md`.
Public-contract tests check package README/license metadata and reject stale
README install language.

Follow-up test:
Keep the README contract test and run a clean wheel install dogfood before any
public release claim.

## 2026-06-29 - Transcript Visibility Belongs To Harness Events

Old hypothesis:
`HarnessTranscriptRef` plus one harness output log line might be enough for
local lifecycle auditability.

New hypothesis:
The durable CodeAlmanac contract is a normalized harness event stream. Provider
transcript identity is useful for sync exclusion and debugging, but run logs
should not depend on raw Claude or Codex transcript file shapes.

Evidence that forced the change:
The user wants to see actual transcripts through a normalized harness surface.
`codex exec` can still serve v1 one-shot wiki updates, but it does not expose
the same complete text/tool/usage lifecycle surface as Codex app-server.

Code or product assumption affected:
`HarnessRunResult.events` now carries typed `HarnessEvent` values.
`ingest` and `garden` persist those events into `jobs logs`; adapters without
rich events fall back to a terminal `done` event.

Follow-up test:
Keep workflow tests that prove multiple harness events are recorded in order.
Reopen Codex app-server only when real lifecycle dogfood requires event
completeness beyond the current terminal `codex exec` event.

## 2026-06-29 - Harness Results Are Run Facts Before Validation

Old hypothesis:
Lifecycle workflows could record harness output only after mutation safety and
success validation passed, because successful wiki updates were the main
observable outcome.

New hypothesis:
The returned harness result is itself a run-log fact. Workflows should record
its status and first output line before later validation so failed agent work
remains inspectable through `jobs logs`.

Evidence that forced the change:
A failed harness can also mutate a file outside the configured Almanac root.
The terminal run error should remain the mutation-safety violation, but without
an earlier output event the run log loses the direct evidence that the harness
returned `failed`.

Code or product assumption affected:
`ingest` and `garden` now record normalized harness events immediately after
the harness returns and before mutation-safety validation. `validate_harness_result`
still owns the command failure contract.

Follow-up test:
Keep workflow tests that assert failed harness runs record `output` before
terminal `error` for both normal failed status and failed-plus-unsafe mutation.

## 2026-06-29 - Scheduled Sync Is A Command Source

Old hypothesis:
Background sync might need a separate local queue owner or background worker
before automation could be considered safe.

New hypothesis:
For local v1, scheduled sync is a source of foreground `sync` commands. Launchd
starts the CLI, and automation translates its unattended policy into explicit
command fields: `claim_owner`, `pending_timeout`, and `max_failed_attempts`.

Evidence that forced the change:
Foreground sync already writes a pending claim before Ingest and reconciles
terminal linked runs. Adding another queue would duplicate the ledger. The
missing safety was not a worker; it was durable retry policy for repeated
failed attempts.

Code or product assumption affected:
`SyncLedgerEntry.failed_attempts` records failed transcript ingest attempts.
`SyncSelectionRequest.max_failed_attempts` controls exhaustion. Automation
installs scheduled sync with a stable claim owner and explicit retry flags.

Follow-up test:
Keep tests that failed attempts increment, exhausted failed attempts report
`sync-retry-budget-exhausted`, and automation sync argv includes the owner and
retry flags.

## 2026-06-29 - Source Runtime Context Carries Workspace Ignores

Old hypothesis:
Filesystem source runtime could exclude a small list of common wiki roots:
`almanac/`, `docs/almanac/`, and `.almanac/`.

New hypothesis:
The adapter should not know CodeAlmanac root names. Ingest resolves the
workspace, then passes `workspace.almanac_root` in `SourceRuntimeContext`.

Evidence that forced the change:
The product now allows arbitrary repo-relative roots. Adding more global ignore
guesses would make adapter behavior drift from the workspace registry.

Code or product assumption affected:
`InspectSourceRuntimeRequest.context.ignored_directories` is the service-owned
runtime policy. `integrations/sources/filesystem/` applies it for both Git
listing and Python/pathspec walking.

Follow-up test:
Keep coverage for a custom root such as `knowledge/` so directory ingest never
includes the repo wiki as source material.

## 2026-06-29 - Configured Root Is A Workspace Fact

Old hypothesis:
The configurable root decision could be handled as a setup flag and a path
rename from `.almanac/` to `almanac/`.

New hypothesis:
The configured root is a workspace fact. The registry stores the relative root,
`Workspace` exposes both `almanac_root` and `almanac_path`, and downstream
services must receive the workspace object or an explicit path from it.

Evidence that forced the change:
Nested roots such as `docs/almanac/` break assumptions that the repo root is
`almanac_path.parent`. Sync also broke the simpler model because transcript
discovery first maps provider transcript cwd values to a repo and then later
needs the exact wiki root for the sync ledger. A `repo_root` alone is not
enough.

Code or product assumption affected:
`services/workspaces/roots.py` owns root validation and nearest-root discovery.
The registry stores `almanac_root`. Transcript candidates now carry
`almanac_path`. `IndexService` passes the true repo root into health checks.
Prompts/manual/lifecycle errors describe the configured Almanac root instead of
literal `.almanac/`.

Follow-up test:
Keep tests for default `almanac/`, configured `docs/almanac/`, and explicit
`.almanac/`. If arbitrary custom roots matter for directory source runtime,
pass the configured root into that adapter instead of extending hard-coded
ignore lists.

## 2026-06-29 - Configured Almanac Root, Default `almanac/`

Old hypothesis:
The Python rewrite could keep `.almanac/` as the repo-owned wiki artifact
because that matched the TypeScript implementation and the early slices.

New hypothesis:
The Python rewrite targets new CodeAlmanac users, so the repo wiki root should
be configurable and should default to `almanac/`. Users can choose
`docs/almanac/` or `.almanac/`, but `.almanac/` is not the default and should
not be treated as a compatibility requirement.

Evidence that forced the change:
The user clarified that this product is being built for new users, not old
TypeScript-era users, and explicitly asked to stop storing everything in
`.almanac/` by default. The current code and docs still contain many hard-coded
`.almanac/` assumptions, so this must become a first-class workspace/config
boundary rather than a piecemeal rename.

Code or product assumption affected:
`workspaces` owns the configured Almanac root. Build/init, manual
materialization, `index.db`, page paths, topics, runs, sync ledger, config,
viewer routes, safety checks, and prompts must resolve paths through the
workspace root instead of concatenating `.almanac/`.

Follow-up test:
Add root-configuration tests proving a fresh repo defaults to `almanac/`, can
use `docs/almanac/`, and only uses `.almanac/` when explicitly configured.
Architecture tests should reject new hard-coded repo `.almanac/` path joins
outside the workspace root service.

## 2026-06-29 - Fresh Python Codebase, Not a Ported TS Shape

Old hypothesis:
Reuse the existing TypeScript repository shape while translating modules to
Python.

New hypothesis:
Archive the TypeScript implementation under `archive/code/` and rebuild a
Python codebase around service-owned verbs, store-owned persistence,
workflow-owned coordination, and `app.py` as the composition root.

Evidence that forced the change:
The live agreement defines a local-only Python v1 and says the old Node code is
behavior reference, not code to preserve. Cosmic Python's service-layer and
composition-root guidance also pushes entrypoints away from product decisions.

Code or product assumption affected:
CLI commands must dispatch into services/workflows. They must not become the
internal API used by automation or future server wrappers.

Follow-up test:
The first scaffold should include an architecture test that imports the CLI and
application root without importing archived TypeScript paths or constructing
service dependencies in command handlers.

## 2026-06-29 - Viewer Is A Read Service, Not Server-Owned Logic

Old hypothesis:
`serve` could be restored as a server adapter directly over existing read
services, because it is mostly HTTP routing and static assets.

New hypothesis:
`serve` deserves a first-class `viewer` service. The HTTP adapter is only a
primary entrypoint. `ViewerService` owns browser payload assembly over the
existing index service, while markdown truth remains in `wiki` and SQLite
projection truth remains in `index`.

Evidence that forced the change:
The archived `almanac-serve` page shows the viewer has product semantics:
overview, page, topic, search, backlinks, file refs, related pages, and rendered
wikilinks. Cosmic Python chapter 4 says controllers should stay thin once
orchestration starts creeping into the entrypoint.

Code or product assumption affected:
`server/app.py` must not open SQLite, parse pages, or shell out to CLI commands.
Future hosted/server read edges can call the same viewer service if they need
the local viewer contract.

Follow-up test:
Add an architecture test once more server/integration modules exist: server
routes may import app/service request and response models, but must not import
index store modules or wiki document parsers directly.

## 2026-06-29 - Index Freshness Is Not Forced Reindex

Old hypothesis:
Read commands could force a full `.almanac/index.db` rebuild on every
`ensure_fresh` call because the index is a derived local projection and
correctness mattered more than cost during the early read-model slices.

New hypothesis:
`ensure_fresh` should be stale-aware. It may refresh the derived projection
silently, but it should skip SQLite writes when the source wiki signature is
unchanged. `reindex` is the explicit command for a forced projection rebuild.

Evidence that forced the change:
The local `serve` viewer calls several read verbs per browser request. Repeated
overview/search/page traffic made the old force-rebuild behavior a bad default
for long-running local read surfaces. Cosmic Python chapter 12's CQS/CQRS
pressure also separates read adapters from write-side commands.

Code or product assumption affected:
`IndexStore.refresh` owns source-signature comparison and no-op refreshes.
`IndexStore.rebuild` remains the forced path behind `codealmanac reindex`.
`ViewerService` and `server/app.py` must not special-case freshness.

Follow-up test:
Keep the SQLite trigger regression proving unchanged `ensure_fresh` does not
delete/reinsert `pages`, and add a cheaper stat-manifest test only if large-repo
dogfood makes parsing cost visible.

## 2026-06-29 - Public Jobs, Internal Runs

Old hypothesis:
The Python rewrite might rename the public lifecycle inspection surface from
`jobs` to `runs` because `runs` is the service named in the live agreement.

New hypothesis:
Keep public `jobs` as the CLI noun and use internal `runs` as the service noun.
Users inspect jobs; workflows record runs. This avoids a user-facing rename
while still giving the Python code a service that owns execution records,
events, outputs, and lifecycle state.

Evidence that forced the change:
The live agreement lists `codealmanac jobs` as the public CLI command and names
`runs` as the service owner. The old lifecycle wiki also says `jobs` is the
durable queued-work term while recurring scheduled intent belongs to
automation. Cosmic Python chapter 10 made the split cleaner: starting work is a
command, while run-log entries are facts.

Code or product assumption affected:
`services/runs` owns `.almanac/jobs/` records and logs. `cli/main.py` exposes
that service as `codealmanac jobs`, `jobs show`, and `jobs logs`.

Follow-up test:
When foreground/background execution lands, workflows should call
`app.runs.start`, `app.runs.record_event`, and `app.runs.finish` directly
instead of shelling out to `codealmanac jobs`.

## 2026-06-29 - Source Inputs Are Boundary Messages

Old hypothesis:
The next lifecycle slice might start with a public `ingest` command and fill in
the source model as command parsing needed it.

New hypothesis:
Add `services/sources` first. Raw user inputs are external boundary messages
that should become typed `SourceAddress`, `SourceRef`, and `SourceBrief` values
before workflows or agents see them. The public `ingest` command should wait
until an ingest workflow can honestly start or run lifecycle work.

Evidence that forced the change:
The live agreement says `sources` owns observations, refs, fingerprints, and
local source state, while workflows own update verbs. The `evidence-bundles`
wiki page also distinguishes operation input from page provenance and rejects
durable candidate objects. Cosmic Python chapter 11 frames outside messages as
edge inputs that should be translated before entering the core.

Code or product assumption affected:
`app.sources.resolve(...)` is the source-input boundary. It may classify local
paths, GitHub refs, URLs, git refs, and transcript refs, but it does not write
pages, start runs, fetch external systems, or decide notability.

Follow-up test:
When `workflows/ingest` lands, test that it consumes `SourceBrief` values from
`SourcesService` and records the selected source refs in the run ledger without
letting CLI parsing become the workflow boundary.

## 2026-06-29 - Web URLs Are Runtime Material, Not A Source Library

Old hypothesis:
Generic web URLs might require a separate discovery or source-library shape
because they can represent external documents outside the repo.

New hypothesis:
For Python v1, web URLs are selected run input that fit the existing
`SourceRuntimeAdapter` seam. The adapter fetches and normalizes readable
material; Ingest still decides nothing about notability until the harness sees
the prompt.

Evidence that forced the change:
The live agreement says source input has the same
`SourceAddress -> SourceRef -> SourceBrief -> SourceRuntime` layers for Git,
GitHub, transcripts, and web. The existing Ingest workflow already consumes
source runtime snapshots without knowing source kinds. Cosmic Python chapter 11
keeps outside messages at the edge, and chapter 13 keeps concrete external
dependencies wired through the composition root.

Code or product assumption affected:
`integrations/sources/web/` owns HTTP and HTML/text parsing. `services/sources`
keeps only the product-owned ref/runtime contract. No `collect`, `capture`,
hosted upload, durable candidate, crawler, or web source catalog is introduced.

Follow-up test:
Keep an ingest workflow test where a real `WebSourceRuntimeAdapter` with
`httpx.MockTransport` feeds web runtime text into the prompt, and dogfood a real
public URL with a fake harness before committing the slice.

## 2026-06-29 - Path Refs Need Runtime Material

Old hypothesis:
Local path refs could start as observations: existence, kind, absolute path, and
file fingerprint. The harness could use tool access to read a selected file if
it needed more.

New hypothesis:
Path file and directory refs should produce bounded `SourceRuntime` snapshots
before the harness runs. A lifecycle prompt should include the selected local
material directly instead of forcing the writer to infer which files to read
from metadata.

Evidence that forced the change:
The product contract says `ingest <inputs...>` uses selected local material as
source material. Tests showed `note.md` ingest previously produced a skipped
runtime snapshot even though the user explicitly selected that file. The
existing source-runtime port already handles Git, GitHub, transcripts, and web
without changing Ingest.

Code or product assumption affected:
`integrations/sources/filesystem/` owns filesystem reading, directory filtering,
text decoding, and prompt-friendly rendering. `services/sources` keeps the
product contract. `workflows/ingest` remains source-kind agnostic.

Follow-up test:
Keep the ingest workflow test that proves a selected `note.md` reaches the
prompt as available runtime content, and dogfood local file plus directory
inputs through a temp repo before committing the slice.

## 2026-06-29 - Config Is A Local Defaults Seam, Not A Public Surface

Old hypothesis:
Config could wait because the current CLI defaults were small hardcoded values:
`--using claude` and `--quiet 45m`.

New hypothesis:
Add `services/config` now as a narrow local seam. User and project TOML config
own lifecycle defaults, while CLI flags still win at the command edge. The
service must not add a public `config` command, hosted account settings,
secret management, or environment-source machinery.

Evidence that forced the change:
The live agreement already names `config` as a service, and the implementation
had product defaults split between argparse and automation code. Cosmic Python
chapter 13 points setup and dependency wiring into the composition root rather
than primary entrypoints. Prior-art checks showed `pydantic-settings` and
Dynaconf as the mature Python settings libraries. We chose
`pydantic-settings` because it keeps the existing Pydantic model style while
providing first-class TOML source handling.

Code or product assumption affected:
`ConfigStore` builds a `CodeAlmanacConfig` from
`TomlConfigSettingsSource` values, `ConfigService` chooses project and user
source order, and `cli/main.py` resolves `flag > config` for lifecycle
defaults. Automation keeps a service fallback default but shares the config
constant.

Follow-up test:
Keep the CLI tests proving `ingest` can use configured default harness without
`--using`, and `sync status` can use configured quiet time without `--quiet`.

## 2026-06-29 - Update Is Package-Manager Policy, Not NPM Plumbing

Old hypothesis:
The Python port could leave `codealmanac update` pending until the rest of the
local product surface was complete.

New hypothesis:
`update` should land as a conservative service-owned command now because the
live CLI contract includes it, but it should not inherit the npm-era notifier
or installer assumptions from the archived TypeScript code.

Evidence that forced the change:
`uv run codealmanac --help` did not list `update` even though the live agreement
does. The current development install reports `INSTALLER=uv` and an editable
`direct_url.json`, which proves that a naive self-update would mutate the wrong
thing. uv and pip expose different update commands, so package-manager choice
must be explicit metadata-driven policy.

Code or product assumption affected:
`services/updates` owns the update plan and supported methods. The concrete
adapter under `integrations/updates/` reads package metadata and runs the chosen
foreground command. Editable/source installs are unsupported for mutation and
report `git pull && uv sync`.

Follow-up test:
Dogfood `codealmanac update --check` and default `codealmanac update` in the
editable repo. Add real non-editable `uv tool` and pip install dogfood before
scheduling update automation.

## 2026-06-29 - Harnesses Are Ports Before Adapters

Old hypothesis:
The next lifecycle slice might connect source refs directly to an `ingest`
workflow.

New hypothesis:
Add `services/harnesses` first. `ingest`, `sync`, and `garden` need a
normalized agent task/result/readiness contract before they can call Codex or
Claude without importing concrete adapters.

Evidence that forced the change:
The live agreement says harnesses own normalized Codex/Claude contracts and
ports, while integrations implement those ports. Cosmic Python chapter 4 keeps
use cases out of entrypoints, and chapter 13 keeps wiring in the composition
root.

Code or product assumption affected:
`app.harnesses.run(...)` is now the port-backed service call workflows will use
for agent execution. `cli/main.py` remains uninvolved in agent task execution.

Follow-up test:
When concrete Codex or Claude adapters land, add an architecture test that CLI
modules do not import `integrations/harnesses/*` and workflows only depend on
`services/harnesses`.

## 2026-06-29 - Ingest Is A Command, Run Logs Are Events

Old hypothesis:
The next step might expose a public `codealmanac ingest` command once source and
harness seams existed.

New hypothesis:
Add `workflows/ingest` internally first. It can prove the orchestration between
sources, harnesses, runs, and the index, but public CLI should wait for a real
harness adapter so the command does actual lifecycle work.

Evidence that forced the change:
The live agreement says CLI commands are not internal APIs and shows
`app.workflows.ingest.run(...)` as the correct app surface. Cosmic Python
chapter 10 also distinguishes commands, which fail loudly, from events, which
record past facts.

Code or product assumption affected:
`RunIngestRequest` is a command and raises on missing harnesses, unsafe changed
files, or failed harness statuses. `RunsService` records the past-tense facts
around the attempt.

Follow-up test:
When `codealmanac ingest` becomes public, test that the CLI only adapts argv
into `RunIngestRequest` and does not own source parsing, prompt rendering,
harness selection, run finishing, or index refresh.

## 2026-06-29 - Claude Is The First Concrete Harness Adapter

Old hypothesis:
The first concrete harness might need to start with Codex because CodeAlmanac is
used from Codex sessions.

New hypothesis:
Start with Claude CLI because it has a simple non-interactive `--print` JSON
contract. Codex remains important, but the archived implementation shows Codex
needs the app-server path for the richer lifecycle semantics.

Evidence that forced the change:
Local `claude -p --output-format json` returned a structured result, while the
archived Codex provider delegates to the Codex app-server adapter. Cosmic
Python chapter 3 says a useful abstraction hides messy external details; the
Claude adapter can exercise the `HarnessAdapter` port without importing
provider details into workflows.

Code or product assumption affected:
Slice 14 wired `ClaudeCliHarnessAdapter` by default to prove the harness port.
Slice 84 replaced that runtime path with `ClaudeSdkHarnessAdapter` while keeping
CLI, services, and workflows guarded by architecture tests that forbid direct
imports from `codealmanac.integrations`.

Follow-up test:
Before public `codealmanac ingest`, add stronger dirty-worktree or sandbox
preflight so a real provider run cannot silently change application code.

## 2026-06-29 - Git Snapshot Policy Before Public Ingest

Old hypothesis:
The Claude adapter's `changed_files` output plus workflow validation might be
enough to make real-provider ingest safe.

New hypothesis:
The workflow needs its own mutation policy around every harness run. Adapter
reported changes are diagnostic, but product safety must use a provider-neutral
before/after snapshot.

Evidence that forced the change:
The Claude adapter can only report changes it observes through its own Git
status delta. If application code is already dirty before ingest, a provider
could mutate that same path without creating a new status entry. Cosmic Python
chapter 6 frames a Unit of Work as a stable snapshot around an operation; for
filesystem writes, Git snapshots are the honest audit boundary.

Code or product assumption affected:
`workflows/ingest` now runs `IngestMutationPolicy` before and after harness
execution. `.almanac/` must be clean before the run, pre-existing dirty app
files are allowed, and any non-wiki mutation during the run fails the job.

Follow-up test:
Before public CLI ingest, add a real CLI smoke over a Git repo once the command
exists, and decide whether failure should offer a one-line fix such as
committing or stashing `.almanac/` changes.

## 2026-06-29 - Public Ingest After Safety, Before Codex Parity

Old hypothesis:
Public `codealmanac ingest` might wait until both Claude and Codex adapters
exist, so the command does not expose an incomplete provider matrix.

New hypothesis:
Expose public ingest once the workflow has source contracts, run records, a
real Claude adapter, and mutation safety. Codex parity can land as a provider
slice later because the CLI already targets the provider-neutral `HarnessKind`
contract.

Evidence that forced the change:
The live agreement lists `codealmanac ingest <inputs...>` in the v1 CLI
contract. Slice 15 made real-provider mutation safe enough to expose locally.
Cosmic Python chapter 10 also keeps the command as intent: the CLI sends
`RunIngestRequest`, while the workflow remains the handler.

Code or product assumption affected:
`cli/main.py` now exposes `ingest` with `--using claude|codex`, `--wiki`,
`--title`, and `--guidance`. The CLI prints a short result summary and does
not own source parsing, prompt rendering, harness execution, mutation safety,
runs, or index refresh.

Follow-up test:
When the Codex adapter lands, add one CLI test that `--using codex` reaches the
adapter without changing the CLI command shape.

## 2026-06-29 - Codex CLI Adapter Before App-Server

Old hypothesis:
The Python rewrite might need to port the archived TypeScript Codex app-server
adapter before `--using codex` could be real.

New hypothesis:
Use `codex exec` for Python v1 and defer app-server until the harness contract
needs streaming events, provider usage telemetry, structured tool display,
programmatic subagents, or richer provider lifecycle control.

Evidence that forced the change:
Current `codex exec` supports stdin prompts, `--ephemeral`, workspace
sandboxing, config overrides, and `--output-last-message`. The current
`HarnessAdapter` contract needs readiness, one prompt run, final text, status,
and changed files. A real `codex exec` smoke returned `ok`, and real
`codealmanac ingest note.md --using codex` dogfood created one wiki page in a
temp repo.

Code or product assumption affected:
`integrations/harnesses/codex/adapter.py` is a CLI adapter. Shared subprocess
and Git-status helpers live in `integrations/harnesses/`, not under Claude.
The archived app-server modules remain behavior reference for future richer
harness requirements, not a shape to preserve now.

Follow-up test:
If a later slice adds streaming job attach, usage accounting, structured
output schemas, or provider-owned subagents to the Python harness contract,
reopen the app-server path and add fake-protocol tests before changing
`CodexCliHarnessAdapter`.

## 2026-06-29 - Garden Uses Wiki State, Not Source Inputs

Old hypothesis:
The next lifecycle operation might reuse most of Ingest directly because both
operations ask a harness to update `.almanac/`.

New hypothesis:
Garden should be a separate workflow over wiki state. It shares prompt
doctrine, harness execution, run logging, index refresh, and mutation safety,
but it does not consume `SourceBrief` values or preserve a selected-source
boundary.

Evidence that forced the change:
The live agreement and lifecycle wiki describe Garden as whole-graph
maintenance: topics, links, stale pages, boundaries, hubs, and no-op judgment.
That is different from Ingest, which starts from bounded selected material.
The first implementation stayed clean once prompt rendering and mutation
safety were shared below the operation workflows.

Code or product assumption affected:
`workflows/garden` receives index and health context. `workflows/ingest`
receives selected source briefs. Both use `PromptRenderer`,
`HarnessesService`, `RunsService`, and `LifecycleMutationPolicy`.

Follow-up test:
If `sync` lands next, keep discovery separate from both operations: Sync may
select material and call Ingest, but it should not become another prompt-writing
workflow unless product behavior requires it.

## 2026-06-29 - Sync Status Before Sync Execution

Old hypothesis:
The next sync slice could discover quiet local transcripts and immediately run
ordinary local ingest for ready material.

New hypothesis:
Expose `codealmanac sync status` first. It should discover transcripts, map
them to `.almanac/` repos, apply quiet-window and cursor checks, and report
readiness without writing the wiki or starting ingest.

Evidence that forced the change:
Ingest and Garden now create provider transcripts through Claude and Codex
harness runs. The current `HarnessRunResult` and `RunRecord` do not store the
provider session id or transcript path, so sync execution cannot yet exclude
CodeAlmanac's own lifecycle transcripts reliably. Cosmic Python chapter 13 also
keeps the concrete transcript scanners behind `app.py` composition instead of
letting CLI import them directly.

Code or product assumption affected:
`workflows/sync` owns cursor evaluation and quiet-window status. `sources`
owns transcript discovery ports and typed candidates. Full `codealmanac sync`
execution remains gated until harness runs feed provider transcript identity
back into the run ledger.

Follow-up test:
When sync execution lands, add a regression where an Ingest or Garden provider
run creates a transcript and `codealmanac sync` skips that internal transcript
while still reporting a separate user transcript from the same repo as ready.

## 2026-06-29 - Transcript Feedback Before Sync Skip Policy

Old hypothesis:
Full sync execution could add internal-transcript exclusion and ingest queuing
in one slice.

New hypothesis:
Add harness transcript feedback first. Provider adapters should return a typed
`HarnessTranscriptRef` when they can identify the session they created, and
`runs` should persist that ref before the lifecycle run reaches a terminal
state.

Evidence that forced the change:
Sync status already discovers transcript candidates. The missing information is
not in sync; it is in the lifecycle runs that create internal provider
transcripts. Claude exposes a structured `session_id`; Codex requires a
best-effort local session lookup because `codex exec --output-last-message`
does not include the run id in its final message.

Code or product assumption affected:
`HarnessRunResult` now owns optional provider transcript identity.
`RunRecord` persists it as `harness_transcript`. Future sync execution can
compare discovered candidates against run records without parsing provider
output text or lifecycle logs.

Follow-up test:
Before public `codealmanac sync` runs ingest, add a sync test that creates a
run record with `harness_transcript` and verifies the matching discovered
transcript is skipped while a different transcript remains ready.

## 2026-06-29 - Internal Transcript Exclusion Belongs In Sync Status First

Old hypothesis:
Internal lifecycle transcript exclusion could be added alongside the first
write-capable `codealmanac sync` command.

New hypothesis:
Add the exclusion to `sync status` first. Status is the read-only form of sync
eligibility, so it should already hide transcripts that live sync must never
ingest.

Evidence that forced the change:
Slice 20 gave run records the provider transcript identity needed for the
filter. Adding the filter before execution keeps CLI thin and avoids a future
split where status and live sync disagree about what is eligible.

Code or product assumption affected:
`SyncWorkflow` now reads repo-local run records through `RunsService` and skips
candidates whose provider kind matches and whose session id or transcript path
matches a stored `RunRecord.harness_transcript`.

Follow-up test:
When live `codealmanac sync` lands, assert that it uses the same internal skip
rule before it queues Ingest and records pending cursor state.

## 2026-06-29 - Foreground Sync Commits Cursor After Ingest

Old hypothesis:
The first write-capable sync slice should record pending cursor state before
invoking Ingest, mirroring the archived TypeScript background sweep.

New hypothesis:
For the Python port's current foreground lifecycle shape, `sync` should advance
the ledger only after `IngestWorkflow.run(...)` succeeds. Pending cursor state
belongs with a background runner and a reconciliation loop, neither of which
exists yet.

Evidence that forced the change:
Cosmic Python chapter 6 frames Unit of Work as the boundary for atomic
operations and recommends explicit commits after success. A foreground sync
command already knows whether Ingest succeeded, so writing a pending cursor
beforehand would create stale state on process death without a real pending
owner to reconcile.

Code or product assumption affected:
`SyncWorkflow.run(...)` reuses the same evaluation gates as `status(...)`, calls
Ingest with a `transcript:<path>` source and cursor guidance, then writes the
sync ledger after each successful Ingest. Failed Ingest attempts mark the
ledger entry `failed` and appear in `needs_attention`.

Follow-up test:
When background jobs land, introduce pending cursor fields and reconcile them
against durable run records before scheduled automation uses sync unattended.

## 2026-06-29 - Automation Is Scheduler State, Not Sync Ownership

Old hypothesis:
The next automation slice might need to port the archived TypeScript background
sync pending model before installing scheduled jobs.

New hypothesis:
Install/status/uninstall can land first if automation only owns scheduler
state. The scheduled sync job invokes the foreground `sync` workflow; it does
not own transcript eligibility, cursor mutation, pending runs, or wiki writes.

Evidence that forced the change:
Slice 22 made foreground sync safe by committing the cursor only after Ingest
succeeds. Cosmic Python chapter 13 points dependency construction to the
composition root, so the scheduler should be an injected adapter behind an
automation port rather than a CLI helper or sync dependency.

Code or product assumption affected:
`services/automation` owns `AutomationTask`, `ScheduledJob`, and
install/status/uninstall requests. `integrations/automation/scheduler/launchd`
owns plist serialization and launchctl calls. `app.py` wires the launchd
adapter; tests inject fake schedulers.

Follow-up test:
If scheduled sync needs pending state, add a background owner and reconciliation
tests before changing the sync ledger model.

## 2026-06-29 - Source Runtime Comes After Source Briefs

Old hypothesis:
`SourceBrief` metadata and prompt hints might be enough for Git and GitHub
source-aware Ingest because the agent can inspect the repository itself.

New hypothesis:
Source-aware Ingest needs an explicit `SourceRuntime` layer. The brief names
the selected source and provenance hint; runtime snapshots carry bounded
readable material gathered before model execution.

Evidence that forced the change:
The `evidence-bundles` wiki page records the intended local path as
`SourceAddress -> SourceRef -> SourceBrief -> SourceRuntime -> AbsorbRequest`.
The Python port had the first three layers, but `git:diff` and `git:range`
only reached the prompt as identifiers. Cosmic Python chapter 13 supports a
proper adapter boundary for multi-operation external dependencies.

Code or product assumption affected:
`services/sources` now owns `SourceRuntime` and `SourceRuntimeAdapter`.
`integrations/sources/git` implements local Git runtime snapshots through the
Git CLI. `IngestWorkflow` renders source runtime alongside source briefs.

Follow-up test:
Add GitHub PR/issue source runtime through the same port and test that a PR ref
does not become a one-off Ingest prompt branch.

## 2026-06-29 - GitHub Runtime Is A Local Adapter, Not Hosted Capture

Old hypothesis:
GitHub source refs could stay as prompt hints until a hosted GitHub integration
exists.

New hypothesis:
Local v1 should read GitHub PR and issue material through `gh` behind
`SourceRuntimeAdapter`. The adapter turns `gh --json` output into Pydantic
models and renders bounded source text before the harness starts.

Evidence that forced the change:
`github:pr` and `github:issue` were already valid selected source refs, but
Ingest only supplied their identifiers to the agent. GitHub CLI is available in
the local environment, accepts PR and issue URLs, returns structured JSON for
`view`, and returns PR patch text through `pr diff --patch --color never`.

Code or product assumption affected:
`integrations/sources/github` is now a peer of `integrations/sources/git`.
Both implement the same source-runtime port. Ingest remains source-kind neutral,
and hosted GitHub app behavior remains out of scope.

Follow-up test:
If GitHub runtime needs review threads, checks, linked issues, or pagination,
extend the GitHub adapter contract and tests before changing Ingest.

## 2026-06-29 - Transcript Runtime Belongs Behind Source Runtime

Old hypothesis:
Foreground `sync` could pass transcript paths plus cursor guidance and trust
the harness to inspect the transcript file itself.

New hypothesis:
`transcript:<path>` should produce a bounded `SourceRuntime` snapshot before
the harness starts. Sync still owns transcript eligibility and cursor state;
the transcript adapter owns local JSONL reading and provider-shape translation.

Evidence that forced the change:
Foreground sync already passes `transcript:<absolute path>` into Ingest, but
those files often live outside the repo. Codex sandboxing and future hosted
workers should not depend on direct provider transcript filesystem access after
the harness starts.

Code or product assumption affected:
`integrations/sources/transcripts` now implements both discovery and runtime
translation. Runtime uses `jsonlines` for JSONL reading, Pydantic models for
known Codex/Claude shapes, and tail truncation because transcripts are
append-only and recent lines usually contain the new sync material.

Follow-up test:
If source runtime needs cursor-aware slicing, extend
`InspectSourceRuntimeRequest` with explicit line or byte bounds instead of
teaching Ingest to parse sync guidance text.

## 2026-06-29 - Viewer File Route Is Graph Navigation

Old hypothesis:
The next viewer hardening slice might read referenced repo files so the local
browser could preview source material.

New hypothesis:
The viewer file route should restore the old `/file?path=...` behavior: list
wiki pages that mention a file or folder reference. It should not read repo
source contents.

Evidence that forced the change:
The current `almanac-serve` wiki page describes `/file?path=src/foo.ts` as
"pages mentioning a file" and says the viewer is a read-only client over the
existing wiki/index primitives. The source runtime system already owns reading
selected local files for lifecycle prompts.

Code or product assumption affected:
`ViewerService.file()` delegates to the index mentions query. `server/app.py`
exposes `/api/file` as a query route. The frontend links page rail file refs to
`#/file/<path>` and renders matching pages.

Follow-up test:
Keep service and server tests that prove file and folder refs use index mention
semantics and that parent traversal is rejected before querying.

## 2026-06-29 - Filesystem Directory Runtime Should Ask Git First

Old hypothesis:
Filesystem directory runtime could use a Python walk plus root `.gitignore`
patterns for v1, then tune only if dogfood showed noise.

New hypothesis:
When the selected directory is inside a Git worktree, the runtime should ask
Git for tracked files plus untracked non-ignored files, then apply
CodeAlmanac's default private/generated skips. The Python/pathspec walk remains
the non-Git fallback.

Evidence that forced the change:
The current adapter only applied root `.gitignore` patterns, while real repos
use nested `.gitignore`, `.git/info/exclude`, and global excludes. Git's
`ls-files --cached --others --exclude-standard` already owns that contract.
Cosmic Python chapter 13 also supports explicit dependency injection here:
the adapter takes a `CommandRunner` rather than hardcoding subprocess mechanics
inside product code.

Code or product assumption affected:
`FilesystemSourceRuntimeAdapter` now has a Git-backed directory listing path
for `SourceKind.PATH_DIRECTORY`. It does not add a new source kind, change
Ingest, or make services aware of Git mechanics.

Follow-up test:
If large directories remain noisy, add ranking or selection rules inside the
filesystem integration after dogfood proves which files are wrong, rather than
teaching Ingest to branch on directory shape.

## 2026-06-29 - Directory Runtime Selection Is Changed-First

Old hypothesis:
After Git listing, deterministic path order might be enough for bounded
directory runtime material.

New hypothesis:
Git-listed directory runtime should rank changed and untracked files before
unchanged files, then fall back to deterministic content/path ordering for the
remaining files. The prompt should show whether an included file was selected
as changed or unchanged.

Evidence that forced the change:
Dogfood against this repo's `src/codealmanac/` directory selected package
markers and broad unchanged modules before the files in the current slice, then
hit the file-count bound. Git's porcelain status format gives a stable
machine-readable changed-file signal for this exact path.

Code or product assumption affected:
`integrations/sources/filesystem` now owns a typed directory-selection policy.
The source service, Ingest workflow, and CLI still see one directory
`SourceRuntime`; there is no durable source-pool or candidate object.

Follow-up test:
If clean large directories remain noisy after changed-first selection, add a
separate semantic-diversity or recency policy with dogfood proving the wrong
unchanged files.

## 2026-06-29 - Local-Only Public Surface Needs Executable Guards

Old hypothesis:
The live agreement and CLI implementation were enough evidence that v1 has no
hosted commands, aliases, SDK, or MCP surface.

New hypothesis:
The local-only public surface should have a small executable guard because
future agents may add hosted-looking verbs or aliases while porting old
TypeScript behavior.

Evidence that forced the change:
The verification matrix still marked "No hosted CLI/MCP/SDK/aliases" as
pending even after the CLI existed. The old TypeScript and hosted discussions
contain enough `capture`, `absorb`, hosted worker, SDK, and MCP vocabulary that
a text-only agreement is too easy to miss.

Code or product assumption affected:
`tests/test_public_contract.py` now guards `pyproject.toml` scripts, parser
rejection for hosted/alias top-level commands, and absence of `sdk`/`mcp`
Python modules. No production code changed.

Follow-up test:
When a new public command is intentionally added, update the public-contract
test in the same slice so the local-only boundary remains deliberate.

## 2026-06-29 - Manual Is A Support Package, Not A Command

Old hypothesis:
The existing prompt package might be enough to satisfy the prompts/manual
surface until lifecycle dogfood showed more need.

New hypothesis:
The target Python package shape needs a concrete `manual/` package now, but it
should not create a public CLI command. The right boundary is a support
library that bundles doctrine, copies missing files into `.almanac/manual/`,
and lets diagnostics verify that both package and workspace manuals exist.

Evidence that forced the change:
The live agreement's target root includes `manual/`, and the current Almanac
engine separates prompts as the job bootloader from manual files as the
rulebook. The public CLI contract does not include `manual`, so adding a
command would widen the surface without agreement.

Code or product assumption affected:
`ManualLibrary` is wired in `app.py` and injected into `WikiService` and
`DiagnosticsService`. `build` and `init` materialize missing manual files under
`.almanac/manual/`. `doctor` reports manual readiness. Prompts tell lifecycle
agents to read the relevant manual files before editing.

Follow-up test:
If manual files need package-driven updates later, add an explicit local
maintenance policy that distinguishes bundled doctrine updates from user-edited
workspace conventions.

## 2026-06-29 - Sync Needs Pending Claims Before Ingest Side Effects

Old hypothesis:
Foreground `sync` could advance the cursor only after Ingest succeeded or
failed.

New hypothesis:
`sync` needs a durable pending claim before it invokes Ingest. Active pending
claims should skip the transcript range; stale pending claims should surface as
needs-attention; terminal success or failure should clear the pending fields.

Evidence that forced the change:
The sync ledger already had a `PENDING` status, but `SyncWorkflow.run()` never
wrote it before the side-effecting Ingest call. A scheduled foreground sync
that crashed mid-run could leave no durable record of the claimed range.
Cosmic Python chapter 6 frames a Unit of Work as the place to make an atomic
persistence boundary around side effects. Dogfood also showed that raw
transcript paths were not stable enough for ledger keys on macOS, where temp
paths can appear as `/var/...` or `/private/var/...`.

Code or product assumption affected:
`workflows/sync` now owns pending ledger cursor policy. Transcript discovery
adapters still only return typed local transcript candidates, and Ingest remains
the normal wiki-writing workflow. Sync ledger lookup uses normalized transcript
identity instead of treating the raw key string as the only truth.

Follow-up test:
When background execution exists, decide whether stale pending work should
retry automatically, require manual attention, or use a bounded retry policy.

## 2026-06-29 - Run Records Are Lifecycle Consistency Boundaries

Old hypothesis:
Run records could be created as `queued` and then finished as `done` or
`failed` by the foreground workflow.

New hypothesis:
The run record needs an explicit `running` transition before lifecycle side
effects begin. The `runs` service owns that transition and rejects attempts to
restart terminal records.

Evidence that forced the change:
Sync pending claims need future reconciliation against run state. A record that
jumps from `queued` to a terminal state cannot distinguish queued work from
started work after a crash. Cosmic Python chapter 7 frames an aggregate as the
consistency boundary for related state changes; here the run record is that
boundary for lifecycle execution state.

Code or product assumption affected:
`RunsService.mark_running(...)` is now the only path from `queued` to
`running`. Ingest and Garden call it immediately after creating the run record.
The public CLI still exposes `jobs` as the inspection noun.

Follow-up test:
When sync stores pending run ids, reconcile stale pending entries against
`queued`, `running`, and terminal run records instead of treating age alone as
the only signal.

## 2026-06-29 - Sync Pending Claims Need Run Linkage

Old hypothesis:
Pending sync claims could rely on owner/start/range fields plus a stale timeout
until a background worker existed.

New hypothesis:
The pending claim needs to store the Ingest run id and the claimed cursor
snapshot now. `sync status` can then distinguish active linked work from
terminal work that needs reconciliation, and foreground `sync` can reconcile
terminal linked runs before choosing the next transcript range.

Evidence that forced the change:
Slice 36 made run records a lifecycle consistency boundary with
`queued -> running -> terminal` state. Without `pending_run_id`, sync still had
to infer from age even when the local run ledger already knew whether the
claimed Ingest run had finished. Cosmic Python chapter 8's event/message-bus
pattern pushed the split: record the fact in one place, then react to it at the
workflow boundary.

Code or product assumption affected:
`IngestWorkflow` now exposes `start(...)` and `run_with_run(...)` beneath the
unchanged public `run(...)` method. `SyncWorkflow` creates the run, writes the
pending claim with run id and cursor hash/size, and then executes Ingest with
that run. Transcript adapters still do no policy work.

Follow-up test:
When a real background queue exists, add an owner/retry budget that uses the
same pending run linkage instead of adding a second queue ledger.

## 2026-06-29 - Clean Directory Runtime Needs Diversity, Not Recency Yet

Old hypothesis:
Clean large directories might need semantic diversity or recency ranking after
changed-first selection.

New hypothesis:
Use semantic diversity first. Directory runtime should gather candidates from
Git or the Python walk, then use a pure selection function that interleaves
directory groups and prefers role-bearing files such as `service.py`,
`adapter.py`, `app.py`, and `main.py`. Recency stays out until dogfood shows a
freshness-specific failure.

Evidence that forced the change:
Dogfood against clean `src/codealmanac/` selected shallow package files before
the source, workflow, and service owners that explain the repo. Cosmic Python
chapter 3 separates filesystem interrogation from the decision algorithm: the
adapter should gather file candidates, then a functional core should choose the
bounded prompt material.

Code or product assumption affected:
`integrations/sources/filesystem/selection.py` now owns group-diverse ranking.
`FilesystemSourceRuntimeAdapter` still owns Git/walk I/O and file reads. The
source service, Ingest workflow, and CLI still see one directory
`SourceRuntime`; there is no durable source pool or `candidate` object.

Follow-up test:
If a real clean repo still misses the right files, add a failing source-runtime
dogfood case before adding recency or repository-specific ranking.

## 2026-06-29 - Viewer Wikilinks Are Token Rewrites

Old hypothesis:
The local viewer still had a risk that wikilink rewriting might affect code
spans or fenced code blocks.

New hypothesis:
The renderer should treat Markdown parsing as the boundary. `markdown-it-py`
parses the page into tokens, and CodeAlmanac rewrites only inline `text`
tokens. Inline code and fenced code remain raw wiki source text, while link
labels are rendered as escaped text.

Evidence that forced the change:
The current `MarkdownRenderer` already used `MarkdownIt("commonmark")` and
rewrote child tokens only when `token.type == "inline"`. Focused renderer
tests now prove `[[inline-code]]` and fenced `[[fenced-code]]` do not become
links, while a normal text `[[page-link]]` does.

Code or product assumption affected:
`services/viewer/renderer.py` is the sole owner of local viewer Markdown
rendering and wikilink token rewriting. The server adapter and viewer service
do not perform string-level HTML rewrites.

Follow-up test:
If future Markdown extensions add new inline token types, extend renderer tests
before changing the rewrite logic.

## 2026-06-29 - SQLite Mechanics Belong In Database Package

Old hypothesis:
The index store could own SQLite connection setup and schema application
because it is the only SQLite-backed store in the current Python port.

New hypothesis:
`database/` should own connection setup and migration application now, while
`services/index/store.py` keeps the read-model schema and query semantics.

Evidence that forced the change:
The live agreement already names `database/` as the owner of SQLite
connections and migration application. `IndexStore` had accumulated
`sqlite3.connect`, row-factory setup, PRAGMA policy, and schema migration in
the same file as FTS/search/topic/health query behavior. Cosmic Python chapter
2 separates repository behavior from persistence mechanics, and chapter 6
frames transaction/migration setup as infrastructure around repositories.

Code or product assumption affected:
`codealmanac.database` now exposes `connect_sqlite(...)`,
`apply_migrations(...)`, and typed `SQLiteMigration` values. `IndexStore` still
owns the index schema and all read-model SQL. An architecture test rejects
direct `sqlite3` imports outside the database package.

Follow-up test:
If a second durable SQLite store appears, decide whether migrations need a
shared migration catalog or whether each store should continue to supply typed
store-owned migrations to `database.apply_migrations(...)`.

## 2026-06-29 - Serve Visual Port Is Not Hosted Wiki IA

Old hypothesis:
The local viewer could copy more of UseAlmanac's wiki experience after the
visual direction looked useful.

New hypothesis:
Copy the UseAlmanac design language, not the hosted product flow. `serve`
should keep CodeAlmanac's local wiki browsing model: sidebar navigation,
page/topic/search/file-reference graph movement, and read-only repo-owned
pages.

Evidence that forced the change:
The user clarified that the earlier sidebar-oriented CodeAlmanac viewer was
closer to the desired wiki shape. The problem was the visual treatment, not
the local wiki IA. UseAlmanac's hosted search/page flow is not the target for
this local CLI product. The current UseAlmanac page/search presentation should
not be copied back into CodeAlmanac; use the previous sidebar-first local
viewer as the shape reference and apply better design over it.

Code or product assumption affected:
`src/codealmanac/server/assets/` may borrow UseAlmanac colors, shell polish,
account-picker styling, and dashboard chrome. It should not add hosted account
routes, billing/settings surfaces, hosted wording, or the hosted wiki page
list/search UX.

Follow-up test:
Future viewer changes should be checked in browser-harness against desktop and
mobile viewports. A review should explicitly check that search, page reading,
and navigation still feel like the local sidebar wiki, not the current hosted
UseAlmanac wiki surface.

## 2026-06-29 - Static Viewer Needs Modules, Not React Yet

Old hypothesis:
The visual port could stay as one packaged `app.js` file until the viewer
became a React or Next.js app.

New hypothesis:
Apply the frontend boundary now with static ES modules. API calls, hash routes,
shared DOM components, and screen renderers should live in separate package
assets. React, Next.js, Vite, and component libraries remain deferred until
the viewer has complex client state or UI machinery that static modules cannot
carry cleanly.

Evidence that forced the change:
After the UseAlmanac visual port, `app.js` mixed route parsing, HTTP calls,
state wiring, DOM components, and screen renderers. Bulletproof React's
project-structure guidance recommends feature boundaries and direct imports,
and Cosmic Python chapter 3 frames simple abstractions as the way to reduce
coupling to messy details.

Code or product assumption affected:
`server/assets/viewer/` now owns the browser feature modules. The FastAPI
server validates and serves nested package assets through `/assets/{path}`.
`pyproject.toml` explicitly packages `server/assets/viewer/*.js`.

Follow-up test:
If future viewer work adds complex client state, accessible popovers, routing
machinery, or reusable component state, reassess React/Next.js with a failing
maintenance example rather than adding a build step speculatively.

## 2026-06-29 - Update Success Means Completed, Not Definitely Changed

Old hypothesis:
`codealmanac update` could report `updated` whenever the package-manager
command exited 0.

New hypothesis:
The service should report `completed` for exit code 0. It knows the
package-manager command completed, but it does not know from structured data
whether installed files changed.

Evidence that forced the change:
Uv-tool dogfood installed CodeAlmanac from a local wheel, then ran
`uv tool upgrade codealmanac` through the actual `codealmanac update` command.
The command exited 0 and emitted `Nothing to upgrade`. Cosmic Python's
service-layer boundary and this repo's structured-contract rule both push
against scraping package-manager prose to infer a stronger domain fact.

Code or product assumption affected:
`UpdateStatus.UPDATED` became `UpdateStatus.COMPLETED`. The CLI still exits 0
when the foreground package-manager command succeeds and exits non-zero for
unsupported or failed runs.

Follow-up test:
Add scheduled update automation only after update-notification cadence,
dismissal behavior, and release-channel policy are agreed. Do not infer those
from the manual update command.

## 2026-06-29 - Admin CLI Is The First Dispatch/Render Domain Split

Old hypothesis:
`cli/dispatch/root.py` and `cli/render/root.py` could stay broad after the
slice-40 CLI edge split until a larger CLI rewrite happened.

New hypothesis:
Split only the admin edge now. `doctor`, `update`, `jobs`, and `automation`
change for install/status/runtime-administration reasons, while wiki and
lifecycle commands still have enough cohesion to remain in the root modules
until their next concrete pressure.

Evidence that forced the change:
Slice 48 added update install dogfood on top of existing doctor, automation,
and jobs behavior. The root dispatch/render modules had become a mixed admin,
wiki, lifecycle, and viewer adapter. Cosmic Python chapter 4 separates
entrypoint code from use-case orchestration, so this split keeps admin
entrypoint code separate without moving product logic out of services.

Code or product assumption affected:
`cli/dispatch/admin.py` owns admin command request construction.
`cli/render/admin.py` owns admin output. `cli/dispatch/config.py` owns shared
CLI config and duration parsing helpers. Services, workflows, and the app
composition root remain the stable internal API.

Follow-up test:
Architecture tests should keep admin request/result types out of
`dispatch/root.py` and `render/root.py`. Split wiki or lifecycle dispatch only
when a future command change provides a concrete reason-to-change.

## 2026-06-29 - Index Reads Are Views Over A Derived Projection

Old hypothesis:
`services/index/store.py` could own schema, refresh, replacement writes, search,
page reads, topic reads, and health queries as one SQLite store.

New hypothesis:
Keep `IndexStore` as the service-facing facade, but split read-only queries
into `services/index/views.py`. The store owns the projection lifecycle;
views own SQL that retrieves data from the projection and maps rows into
Pydantic view models.

Evidence that forced the change:
`store.py` became the largest Python file and mixed two reasons to change:
freshness/write-projection mechanics and read/query/health shape. Cosmic
Python chapter 12 recommends keeping read-only views separate from
state-changing code even without full CQRS machinery. The local `index.db` is
already a derived read model, so the smaller read-view split fits without
adding a bus, event handlers, or new storage.

Code or product assumption affected:
`services/index/store.py` still owns migrations, source signatures, source
loading, and `replace_documents(...)`. `services/index/views.py` owns
`search_pages(...)`, `get_page_view(...)`, `get_topic_detail(...)`, and
`build_health_report(...)`. `IndexService` and public CLI behavior are
unchanged.

Follow-up test:
Architecture tests should keep `views.py` read-only: no markdown loading,
migrations, or write SQL. Optimize index refresh cost only after large-repo
dogfood proves source-signature parsing is too slow.

## 2026-06-29 - Serve Shell Polish Stays Inside Static Viewer Modules

Old hypothesis:
The remaining serve work might need a larger frontend move, especially after
borrowing UseAlmanac visual language.

New hypothesis:
Keep `serve` as static package data for now. The product correction is not a
React or Next.js trigger; it is a local wiki-reader interaction correction.
The existing `server/assets/viewer` modules can own sidebar active state and
rail polish while `services/viewer` remains the API contract.

Evidence that forced the change:
The live agreement now says the earlier CodeAlmanac sidebar shell is the target
and the current UseAlmanac page/search UX is a non-target reference. The
current static viewer already has separate API, route, component, renderer, and
main modules, so active page/topic rail state is additive.

Code or product assumption affected:
UseAlmanac colors and shell styling remain reference material. Hosted account
routes, hosted wiki search/page list behavior, and a frontend build step remain
out of scope. The viewer route state lives in `viewer/main.js` over existing
hash routes.

Follow-up test:
Use an isolated temporary Chrome profile with explicit `BU_CDP_URL` for future
browser-harness checks if the default Chrome profile requests the manual
remote-debugging Allow click. Do not add React, Next.js, or Vite unless the
static viewer stops fitting real UI complexity.

## 2026-06-29 - Manual Drift Is Diagnostic, Not Self-Healing

Old hypothesis:
The manual package update debt might need a command or build-time overwrite
policy once bundled doctrine changes.

New hypothesis:
Report drift through `doctor` first. A complete workspace manual that differs
from bundled package docs is review work, not an automatic mutation target.

Evidence that forced the change:
`<almanac-root>/manual/` is repo-owned text that agents read before changing
wiki pages. The tool cannot distinguish an intentional local edit from an older
bundled copy without adding a baseline store, and slice 34 explicitly preserved
local manual edits during build/init.

Code or product assumption affected:
`ManualLibrary.workspace_status(...)` now reports changed files alongside
missing files. `DiagnosticsService` treats missing manual files as a problem
fixed by `codealmanac build`, but changed files as informational drift with an
explicit review message. There is still no public `manual` command.

Follow-up test:
Only add replacement machinery if users need a real workflow for accepting
package manual updates. That workflow must preserve local edits explicitly
instead of making `build` destructive.

## 2026-06-29 - Registry Cleanup Is Explicit Local Management

Old hypothesis:
The next pressure after manual drift was more source-runtime selection work,
possibly recency or another ranking layer.

New hypothesis:
Do not add source-runtime ranking machinery yet. The real local-product gap is
registry management: `list` can contain stale temp workspaces from dogfood or
tests, and users need explicit cleanup without read commands mutating registry
state.

Evidence that forced the change:
Real-repo source-runtime dogfood against `src/codealmanac/`,
`src/codealmanac/services/sources/`, and
`src/codealmanac/integrations/sources/filesystem/` selected role-bearing,
directory-diverse files with `listing_source: git` and no changed files. During
that pass, `codealmanac list` exposed many stale temp registry entries and no
JSON/drop management surface.

Code or product assumption affected:
`WorkspacesService` now owns registry status and explicit cleanup use cases.
`codealmanac list --json` exposes `available`, `missing_repo`, and
`missing_almanac`; `list --drop <selector>` removes one selected entry; and
`list --drop-missing` removes unreachable entries only when the user asks.
Plain `list` keeps the established three-column output.

Follow-up test:
Do not add automatic pruning to `search`, `show`, `serve`, `doctor`, or plain
`list`. Add richer registry filtering only if real local wiki management
requires it.

## 2026-06-29 - Real Dogfood Tests Writing Contracts

Old hypothesis:
The next public-release pressure might expose a missing workflow seam or a
need to port Codex app-server sooner.

New hypothesis:
The current service-layer seam is holding. Real lifecycle dogfood is more
likely to expose prompt/manual writing-contract gaps than missing orchestration
machinery.

Evidence that forced the change:
A real `CodexCliHarnessAdapter` ingest run created a useful page under
`almanac/pages/`, preserved non-wiki files, produced readable `jobs logs`, and
was queryable through search/show. The concrete failure was that the writer
used `[[workos]]` and `[[autumn]]` as page links for entities that had no
pages. Health caught this as broken links.

Code or product assumption affected:
The fix belongs in packaged prompts and manual doctrine, not in workflow code.
Page wikilinks are real graph edges. They must target existing pages or pages
created in the same run. Entity names without pages stay plain text.

Follow-up test:
Keep real provider dogfood in the release gate. Add orchestration only when a
real run fails because the service boundary cannot express the needed product
operation.

## 2026-06-29 - Release Risk Is Maintainer-Facing Drift

Old hypothesis:
After final wheel/sdist package rehearsal, the next release pressure was mostly
prompt-quality dogfood and product judgment.

New hypothesis:
Run a maintainer-facing release-surface audit before more dogfood. A stale
release guide can invalidate an otherwise working package by sending the
maintainer down the archived npm path.

Evidence that forced the change:
`RELEASE.md` still described `npm test`, `npm run build`, `npm pack`, npm
dist-tags, `NPM_TOKEN`, and `npm publish` after the repo had moved to Python
packaging, PyPI-style install, and `codealmanac` as the only public command.

Code or product assumption affected:
Release docs are public product surface. `RELEASE.md` now names the Python
release use case directly: pytest, ruff, diff check, `uv build`, `uvx twine
check`, clean wheel/sdist install smoke, and `uvx twine upload`. Public-contract
tests guard the guide and the PyPI metadata.

Follow-up test:
Before any publish attempt, run `uvx twine check dist/*` against freshly built
artifacts and smoke both wheel and sdist from clean Python 3.12 environments.

## 2026-06-30 - Runtime State Does Not Prove Wiki Availability

Old hypothesis:
The public-release pressure after package rehearsal was mostly release review
and prompt-quality dogfood.

New hypothesis:
Keep doing release review, but first fix evidence found by local dogfood:
availability must distinguish initialized wiki source files from derived
runtime state.

Evidence that forced the change:
Running `codealmanac doctor --json` in this checkout with a registered default
`almanac/` root but no built wiki created `almanac/index.db`. The old registry
status then treated the directory as available even though it contained no
source marker pair.

Code or product assumption affected:
`workspaces` now owns a marker-based definition of an initialized Almanac root.
`index` validates that root before opening SQLite, so read and diagnostic
commands cannot create an index-only fake wiki. `doctor` reports a missing
registered root directly and stops before index/manual/health checks.

Follow-up test:
Keep clean-install and real-repo dogfood in the release gate. If future runtime
state moves outside the repo root, preserve this invariant: source wiki markers
identify the wiki; derived state never does.

## 2026-06-30 - Release Docs Must Preserve Source vs Runtime

Old hypothesis:
After marker-based root detection, the remaining release review would likely be
only broad product judgment.

New hypothesis:
Release review still needs small factual pressure tests. Public docs can drift
even when command behavior and package metadata are correct.

Evidence that forced the change:
The README's "What Gets Created" tree listed `config.toml`, `jobs/`, and
`index.db` as peers of the initialized wiki files. A temp `codealmanac init`
created `.gitignore`, `almanac/README.md`, `almanac/topics.yaml`,
`almanac/pages/getting-started.md`, and `almanac/manual/*.md`; it did not
create runtime state.

Code or product assumption affected:
The public docs now separate init scaffold from runtime state. This reinforces
the slice 63 rule that source wiki markers identify the artifact and derived
SQLite/job files are local machine state.

Follow-up test:
Keep public-contract tests close to user-facing claims, not only forbidden
words. When a README section describes command effects, add a guard for the
actual file or command contract.

## 2026-06-30 - Quickstarts Are Executable Use Cases

Old hypothesis:
After the README scaffold tree was fixed, remaining public-doc work was likely
only wording polish.

New hypothesis:
Treat quickstarts as runnable use cases. A public README command sequence is
wrong if it exits successfully but teaches a new user an empty first result.

Evidence that forced the change:
A temp repo initialized with `codealmanac init` returned `# 0 results` for the
README's `codealmanac search "auth"` command. The same starter wiki returned
`getting-started` for `codealmanac search "getting"` and rendered
`# Getting Started` through `codealmanac show getting-started --lead`.

Code or product assumption affected:
The README quickstart now uses a search term that matches the starter wiki.
Public-contract tests guard the quickstart section directly instead of only
checking that the README mentions `codealmanac search`.

Follow-up test:
For future public examples, distinguish runnable quickstart commands from
illustrative daily-use commands. Runnable examples should be dogfooded in a
fresh temp repo.

## 2026-06-30 - Public Examples Should Hit Product Abstractions

Old hypothesis:
After the quickstart fix, the next release-surface risk was probably another
full install smoke.

New hypothesis:
Keep checking README examples against the product abstractions they describe.
Parser-valid examples can still teach the wrong thing when they resolve as
missing source material.

Evidence that forced the change:
The README lifecycle example `codealmanac ingest docs/adr.md --using codex`
parsed, but `SourcesService.resolve(...)` classified `docs/adr.md` as
`path.unknown` in this checkout. `README.md` resolves as a real `path.file`,
and `github:pr:123` resolves as `github.pull_request`.

Code or product assumption affected:
Public-contract tests now parse the lifecycle examples and call
`SourcesService` for documented source refs. The README local-file ingest
example now uses `README.md`.

Follow-up test:
When public docs describe source inputs, use source-resolution tests rather
than only forbidden-word checks. This keeps examples aligned with the current
source grammar and runtime expectations.

## 2026-06-30 - Continuation Briefs Need Executable Freshness

Old hypothesis:
The next-agent brief could be kept current through the normal steering-doc
habit.

New hypothesis:
The next-agent brief is load-bearing enough that freshness needs a cheap test.
If it lags the slice trail, future agents can resume from a stale checkpoint
even when the worklog and verification matrix contain the newer facts.

Evidence that forced the change:
After slice 66 landed, `docs/python-port/next-agent-brief.md` still described
slice 62 as the latest implementation slice. The rest of the steering trail had
advanced, so the problem was the recovery summary itself.

Code or product assumption affected:
Public-contract tests now discover the highest
`docs/python-port/slice-N-*.md` number and require the brief's current-state
section to mention it. This keeps the long-running goal recoverable after
compaction.

Follow-up test:
Future slices that add a new slice note must update the next-agent brief before
`tests/test_public_contract.py` passes.

## 2026-06-30 - Public Beta Gate Needs An Audited Outcome

Old hypothesis:
The next useful pressure test was "release review against the public beta gate,"
but the result could live in the readiness doc and worklog.

New hypothesis:
The public beta gate needs its own audit artifact with one status, one evidence
summary, and one remaining-risk statement per gate. Otherwise future agents keep
re-deciding what "release review" means.

Evidence that forced the change:
`public-release-readiness.md` had a clear gate table and evidence list, but did
not classify each gate as ready, needing final rerun, or needing more dogfood.
The actual remaining work was discoverable only by reading several slice notes.

Code or product assumption affected:
`docs/python-port/public-beta-gate-audit.md` now records the current release
judgment. Public-contract tests compare the audit's gate areas against the
release gate's gate areas, so adding or changing a gate requires updating the
audit.

Follow-up test:
Before any publish attempt, rerun the package rehearsal from current HEAD and
run one more real lifecycle dogfood pass against a non-toy project source
shape.

## 2026-06-30 - Package Proof Is Now Current-Head Evidence

Old hypothesis:
Public beta still needed current-head package rehearsal and real lifecycle
dogfood before the gate audit could move forward.

New hypothesis:
Current-head package rehearsal is now complete. The remaining product blocker
is prompt-quality dogfood against a non-toy source shape.

Evidence that forced the change:
Slice 69 built current-head wheel and sdist artifacts, passed `twine check`,
inspected wheel/sdist package data, installed both artifacts into clean
uv-managed Python 3.12.9 environments, ran installed read-path and serve smoke,
and checked installed `update --check`.

Code or product assumption affected:
`public-beta-gate-audit.md` now marks `Fresh install` and `Package metadata`
ready. Public-contract tests guard those rows so the audit cannot keep claiming
the package rerun is missing after slice 69.

Follow-up test:
Run one real lifecycle dogfood pass against a non-toy project source shape and
judge the produced wiki diff, links, topics, health, and `jobs` readability.

## 2026-06-30 - User State Belongs To `.codealmanac`

Old hypothesis:
The Python rewrite could keep using `~/.almanac/` for global registry and user
config while moving the repo-owned wiki root to `almanac/`.

New hypothesis:
The repo-owned wiki root remains `almanac/`, but global user state belongs under
`~/.codealmanac/`. The hidden user-state directory is product machinery, not the
repo wiki artifact.

Evidence that forced the change:
The slice 70 real lifecycle dogfood needed real `HOME` so Claude auth was
visible. With real `HOME`, the Python CLI read this machine's old
`~/.almanac/config.toml`, which contained stale `[agent]` config from a previous
product shape and failed strict Pydantic validation. The Python rewrite is not
backward-compatible, so accepting the stale config would preserve the wrong
contract.

Code or product assumption affected:
`AppConfig()` now defaults registry and config paths to `~/.codealmanac/`.
Automation logs use the same product-specific state directory. Public docs say
the repo wiki root is `almanac/` and user/global state is `~/.codealmanac/`.

Follow-up test:
Public-contract tests pin default `AppConfig()` paths and reject old
`~/.almanac` README language.

## 2026-06-30 - Non-Toy Lifecycle Dogfood Clears The Beta Blocker

Old hypothesis:
Public beta still needed one more lifecycle dogfood pass against a non-toy
source shape to judge prompt quality.

New hypothesis:
The current lifecycle prompt/write path has enough public-beta evidence. More
dogfood remains useful, but it is no longer an implementation blocker.

Evidence that forced the change:
Slice 70 ran real Claude-backed `codealmanac ingest` against a temp repo
containing CodeAlmanac source-runtime, filesystem adapter, ingest workflow,
prompt, and live-agreement files. The run created `source-runtime-flow.md`,
health was clean, and job logs were readable.

Code or product assumption affected:
`public-beta-gate-audit.md` now marks the lifecycle write path ready. Remaining
public-release work is release operations: version, changelog, PyPI credentials,
and the human publish decision.

Follow-up test:
Rerun package/install smoke if package metadata, README, prompts, manual docs,
or server assets change before publish.

## 2026-06-30 - Package Proof Must Follow State Path Changes

Old hypothesis:
After slice 70, the implementation gates were covered and release operations
were next.

New hypothesis:
Because slice 70 changed README metadata and installed default state behavior,
package proof needed one more current-head rerun before the implementation
could be treated as complete.

Evidence that forced the change:
The package audit itself says package/install smoke should be rerun when README,
package data, or installed behavior changes before publishing. Slice 70 changed
README package metadata and moved default registry/config paths to
`~/.codealmanac/`.

Code or product assumption affected:
Slice 71 built fresh wheel/sdist artifacts, installed each into clean Python
3.12.9 environments, and proved installed `init` writes
`~/.codealmanac/registry.json` without creating `~/.almanac/registry.json`.

Follow-up test:
Only rerun package smoke again if package metadata, README, prompts, manual
docs, server assets, or installed behavior changes before publish.

## 2026-06-30 - Wiki Detection Requires Source Markers, Not README

Old hypothesis:
A configured Almanac root could be considered initialized when it contained any
one marker: `README.md`, `topics.yaml`, or `pages/`.

New hypothesis:
An initialized CodeAlmanac wiki requires both `topics.yaml` and `pages/`.
`README.md` is useful guidance, but it is too generic to identify a wiki.

Evidence that forced the change:
Running diagnostics from this repo resolved `/Users/rohan/Desktop/Projects` as
the workspace because that directory has a sibling `almanac/` project folder
with a `README.md`. That made a separate project look like the default
`almanac/` wiki root for the parent directory.

Code or product assumption affected:
Root detection, transcript discovery, registry availability, README language,
the bundled manual, and contributor fixtures now use `topics.yaml + pages/` as
the source marker pair. The repo root remains configurable; the marker rule
applies inside whichever root was configured.

Follow-up test:
Keep a regression test where `Projects/almanac/README.md` exists beside
`Projects/codealmanac/`; resolving from `Projects/codealmanac/` must not
register `Projects` as a wiki workspace.

## 2026-07-01 - Architecture Quality Reopens Before Release

Old hypothesis:
After slice 71, implementation work was effectively complete and the next work
was release operations.

New hypothesis:
The active goal is to keep improving the Python architecture while implementing
the missing behavior until further cleanup is genuinely diminishing returns.
The first renewed pressure point is the CLI edge because `dispatch/root.py`
still owned lifecycle and wiki request construction.

Evidence that forced the change:
Comparing the current Python rewrite against `../almanac` showed that Almanac's
CLI edge has a smaller root router and clearer product-noun dispatch modules.
`MANUAL.md` says the unit of work is evolving the codebase so the feature fits,
and Cosmic Python chapter 4 treats the service layer as the use-case boundary
rather than letting entrypoints become product logic.

Code or product assumption affected:
Slice 72 splits dispatch into lifecycle, wiki, and admin modules. The root
dispatcher is now only a domain delegator, which leaves background jobs,
setup/uninstall, structured page sources, and richer harness work with cleaner
CLI landing zones.

Follow-up test:
Run focused CLI/architecture tests after each CLI-domain change, and split
`cli/render/root.py` only when output behavior creates enough pressure to make
that split pay for itself.

## 2026-07-01 - Background Queue Membership Is Spec-Backed

Old hypothesis:
The next background-jobs step was simply to drain runs whose status was
`queued`.

New hypothesis:
A background queue item is a queued run with a durable executable spec. A
foreground lifecycle run also starts as `queued`, so worker eligibility must be
marked by `<run-id>.spec.json`, not status alone.

Evidence that forced the change:
`IngestWorkflow.run(...)` and `GardenWorkflow.run(...)` create a queued record
before `PageRunWorkflow.begin(...)` marks it running. A worker that selected
all queued records could steal or fail a foreground run during that legitimate
transition window.

Code or product assumption affected:
`RunStore.next_queued(...)` now selects only queued records with a spec file.
`RunQueueWorkflow` persists and consumes `RunSpec` values for Ingest/Garden,
and missing detached process spawning remains a separate slice.

Follow-up test:
When public foreground/background flags land, prove foreground lifecycle runs
still execute immediately and background lifecycle runs enqueue a spec-backed
record that a worker can drain.

## 2026-07-01 - Background Mode Without Default-Mode Drift

Old hypothesis:
Restoring the archive's background machinery might also mean restoring archived
operation defaults in the same slice.

New hypothesis:
Restore the machinery first and keep the default-mode decision explicit. Python
v1 can support `ingest --background` and `garden --background` without silently
changing the behavior of plain `ingest` and `garden`.

Evidence that forced the change:
The current Python CLI has shipped and tested foreground lifecycle semantics.
The archive defaulted some operations to background, but the live agreement
also says behavior reference should not silently simplify away lifecycle
semantics. Changing defaults is a product decision, not a prerequisite for the
process boundary.

Code or product assumption affected:
Slice 76 adds a worker-spawner port, subprocess adapter, hidden `__run-worker`
entrypoint, and explicit background flags. It leaves default foreground
behavior intact.

Follow-up test:
When deciding defaults, update README/manual/CLI tests together and dogfood the
chosen default from an installed CLI, not only through the app API.

## 2026-07-01 - Sync Can Enqueue Without Changing Automation Defaults

Old hypothesis:
Sync could stay purely foreground until the default background policy was
settled.

New hypothesis:
Manual sync needs explicit background mode because sync owns transcript
discovery, cursors, and pending ledger claims, but scheduled automation should
keep its current foreground behavior until unattended background policy is
decided.

Evidence that forced the change:
Background workers need durable Ingest specs, but sync also needs to remember
which transcript ranges have been claimed. The pending ledger claim must be
saved with the queued run id before a worker is spawned, otherwise a worker
failure or retry can leave transcript progress ambiguous.

Code or product assumption affected:
Slice 77 adds `SyncExecution` and `codealmanac sync --background`. Plain
`sync` and installed automation still run foreground.

Follow-up test:
If automation defaults change later, update the scheduler command contract,
README/manual text, and sync ledger tests in the same slice.

## 2026-07-01 - Page Sources Are Read-Model Provenance

Old hypothesis:
Structured page `sources:` could wait until a broader migration/source command
surface existed.

New hypothesis:
Structured page `sources:` are a core read-model contract and should land before
any migration command. The index can parse, project, display, and health-check
source provenance without adding source snapshots or source-query machinery.

Evidence that forced the change:
The live agreement and repo wiki both define `sources:` as the page evidence
model. The Python read model still derived file-aware search only from legacy
`files:` and wikilinks, so canonical `sources[type=file]` entries were invisible
to `search --mentions`, `show`, the viewer, and health.

Code or product assumption affected:
Slice 78 adds typed `PageSource` parsing, the `page_sources` SQLite projection,
file-ref derivation from file sources, source readback, and source-health
warnings. It does not add migration or source-catalog machinery.

Follow-up test:
When `migrate legacy-sources` is reopened, prove it rewrites frontmatter only
and preserves body bytes; do not fold that migration into health or Garden.

## 2026-07-01 - Setup Starts With Owned Instructions, Not Old Hosted Setup

Old hypothesis:
Restoring archived setup might mean porting the whole Node setup experience at
once, including auto-update scheduling, hosted/self-managed branches, and
legacy instruction cleanup.

New hypothesis:
Restore the setup-owned instruction contract first and keep the larger TUI,
automation-choice, provider-selection, and auto-update decisions for later
slices. The Python v1 command should use `codealmanac` markers and must not
remove old `almanac` artifacts that may belong to another install.

Evidence that forced the change:
The live agreement says setup/uninstall are required, but also says scheduled
update policy is not agreed and hosted setup wording is out of scope. The
archive shows Codex inline AGENTS blocks and Claude guide imports as the stable
instruction contract.

Code or product assumption affected:
Slice 79 adds `services/setup` plus `integrations/setup` and wires
`codealmanac setup` / `codealmanac uninstall` through the admin CLI edge. It
does not add raw-mode prompts, automation installation, provider model choice,
or auto-update scheduling.

Follow-up test:
The next setup slice should dogfood the terminal UX in a real TTY and decide
whether setup owns `automation install` choices directly or only presents
explicit next-step commands.

## 2026-07-01 - Setup Polish Belongs To The CLI Adapter

Old hypothesis:
Setup terminal polish might require expanding the setup service result model or
copying the archived ANSI helper machinery.

New hypothesis:
The setup service should stay fact-shaped, while the CLI render edge owns all
terminal presentation. Use Rich for panels and status layout instead of
hand-rolled ANSI helpers.

Evidence that forced the change:
Slice 79 already made setup/uninstall return typed results. The live agreement
requires terminal quality, and the user explicitly prefers common libraries
over hand-rolled implementations when mature libraries exist.

Code or product assumption affected:
Slice 80 adds `rich` as a runtime dependency and places all Rich imports in
`cli/render/setup.py`. Architecture tests now fail if Rich leaks outside the
CLI render edge.

Follow-up test:
The future raw-mode target selector should either use a terminal-input library
or explicitly document why Rich output plus argparse flags is enough for v1.

## 2026-07-01 - Serve Job Polling Belongs To The Read Edge

Old hypothesis:
The first `serve` jobs surface could stay static because browser run control was
out of scope and the CLI already had `jobs attach`.

New hypothesis:
The read-only jobs viewer should poll active runs because background jobs can
change durable run records after the browser has rendered them. Polling is a
read-side freshness behavior, not run-control machinery.

Evidence that forced the change:
Slice 89 restored `/api/jobs` and `/api/jobs/{run_id}` as read APIs, and slice
90 made the detail page useful enough to watch. A running background job detail
that never moves to `done` without manual refresh makes the local viewer feel
stale even though the run ledger is correct.

Code or product assumption affected:
Slice 91 keeps polling in `server/assets/viewer/jobs.js` and clears it from
`server/assets/viewer/main.js` on route/wiki changes. `ViewerService`,
`server/app.py`, and `RunsService` remain read-only from the viewer path.

Follow-up test:
If `serve` later adds richer live updates, start with this same read boundary:
replace the browser polling transport, but do not add browser-side cancel,
retry, or attach controls without a separate product decision.

## 2026-07-01 - Run IDs Are A Shared Product Type

Old hypothesis:
The viewer could protect `/api/jobs/{run_id}` with a local safe-character
validator while core run requests accepted plain strings.

New hypothesis:
Run-id safety belongs to the runs product model because run ids become filenames
under `<almanac-root>/jobs/` in every CLI, workflow, and viewer path.

Evidence that forced the change:
`ViewerJobRequest` rejected `../secret`, but `ShowRunRequest`,
`ReadRunLogRequest`, `AttachRunRequest`, and `CancelRunRequest` still accepted
plain strings before `RunStore` built paths such as `jobs/{run_id}.json`.
The live agreement says validation belongs at product boundaries and the user
explicitly preferred Pydantic validation over hand-rolled parsing.

Code or product assumption affected:
Slice 92 adds `RunId` as a Pydantic-constrained type in
`services/runs/models.py` and applies it to run records, run log events, run
requests, viewer job requests, and page-run workflow requests.
`services/runs/store.py` also validates path helper inputs through a Pydantic
`TypeAdapter(RunId)` before constructing filenames. The sync ledger still
stores `pending_run_id` as tolerant persisted recovery state; tightening that
would need its own migration/read-tolerance decision.

Follow-up test:
If future code adds a public request with an existing run id, import `RunId`
instead of adding a local `field_validator` or string character set.

## 2026-07-01 - GitHub Automation Is A Public Product Surface

Old hypothesis:
The Python release guide and README were enough to guard public packaging
language after slice 62 removed the npm release guide.

New hypothesis:
GitHub workflows and templates are public entrypoints too. They must run or ask
for the same uv/PyPI gates as local docs, and public-contract tests should
reject npm-era GitHub wording.

Evidence that forced the change:
`.github/workflows/ci.yml` still ran Node 20/22, `npm ci`, `npm run build`,
`npx tsc`, and `npm test`. `pack-check.yml` still ran `npm pack --dry-run`.
`publish.yml` still discussed `NPM_TOKEN`, and the templates still asked for
Node or npm details.

Code or product assumption affected:
Slice 93 rewrites GitHub CI/package-check/publish placeholders and GitHub
templates for the Python local product. The publish workflow stays disabled
until PyPI credentials, Trusted Publishing, and release provenance are
explicitly decided.

Follow-up test:
Any future `.github/` edit should keep `tests/test_public_contract.py` green
and should not introduce npm/Node/npx language outside archived reference docs.

## 2026-07-01 - GitHub Contract Tests Should Parse The Entry Point

Old hypothesis:
Text-fragment checks over `.github/` were enough to keep the GitHub surface
aligned with the Python product.

New hypothesis:
The workflow files should also be parsed as YAML so the contract test proves
the project entrypoint keeps a workflow-like shape before checking command
fragments.

Evidence that forced the change:
After slice 93, `uv sync --locked` passed locally, but the test only searched
raw text. The bug template also still had one stale "expected Almanac" sentence
that the broad stale npm guard missed.

Code or product assumption affected:
Slice 94 imports `ruamel-yaml` in `tests/test_public_contract.py`, parses each
workflow file, asserts `name`, `on`, and `jobs`, then checks the CI and package
workflow commands. The template now uses CodeAlmanac for product behavior while
still allowing "Almanac root" where it names the repo-local wiki root.

Follow-up test:
If a future change adds another workflow, update the parsed workflow list and
include the intended gate commands in the public-contract test.

## 2026-07-01 - Source Target Is A Parser Fallback, Not A Read-Model Branch

Old hypothesis:
Structured page sources could stay strictly type-specific because the manual
teaches `path:` for file sources and `url:` for web sources.

New hypothesis:
The parser should accept generic `target:` as a fallback for every page source
type, while keeping type-specific fields preferred. `PageSource.target` is
already the internal model and prompt-shaped lifecycle outputs can naturally use
`target:`.

Evidence that forced the change:
Slice 78 restored structured page sources, but `source_target()` ignored
generic `target:`. Existing fake lifecycle harnesses in the test suite write
`sources[type=file].target`, which meant the page kept a source id but did not
derive a file ref for `search --mentions`.

Code or product assumption affected:
Slice 99 keeps the alternate spelling inside
`services/wiki/frontmatter.py`. Downstream index, search, show, health, and
viewer code still consume normalized `PageSource.target` values and do not
branch on raw frontmatter spellings.

Follow-up test:
If a new source type is added, add its type-specific address fields to
`SOURCE_TARGET_FIELDS` and keep generic `target:` as the last fallback.

## 2026-07-01 - Codex App-Server Events Need The Same Boundary As Claude

Old hypothesis:
The Codex app-server event mapper could stay in one file because the transport
had only recently replaced `codex exec`, and tests covered the event payloads.

New hypothesis:
Codex app-server is the default lifecycle harness, so its provider-event edge
needs the same named boundaries as the Claude SDK edge. Dispatch, mutable run
state, actor attribution, item mapping, helper-agent traces, and final result
events have different reasons to change.

Evidence that forced the change:
After slice 99, the largest remaining production module was
`integrations/harnesses/codex/events.py` at 460 lines. Claude's mapper had
already been split and guarded in slice 85, while Codex still mixed state,
dispatch, base64 output decoding, tool display result construction, helper
agent lifecycle traces, usage messages, and done events.

Code or product assumption affected:
Slice 100 keeps `CodexAppServerClient` and the service-facing `HarnessAdapter`
contract unchanged. The split is entirely inside
`integrations/harnesses/codex/`, and the architecture guard now prevents the
Codex event dispatch module from regrowing item, actor, trace, usage, or output
decoding responsibilities.

Follow-up test:
If a future Codex app-server notification type adds a new event family, add a
named mapper module when the responsibility is not already owned by
`item_events.py`, `agent_events.py`, or `result.py`.

## 2026-07-01 - Codex App-Server Client Should Not Own Provider Policy

Old hypothesis:
After the event mapper split, `CodexAppServerClient` could keep server-request
responses, sandbox policy, timeout-env parsing, root-turn completion detection,
and result projection in `app_server.py` because those helpers were small.

New hypothesis:
`app_server.py` should own process startup, handshake requests, JSON-RPC reads,
and turn flow only. Provider policy helpers deserve named modules because they
change for different reasons than the transport loop.

Evidence that forced the change:
After slice 100, `app_server.py` was still the largest Codex module at 359
lines and still held `noninteractive_response`, `sandbox_policy`,
`root_turn_completion`, `result_from_state`, `failed_result`, and
`env_milliseconds`. The repo wiki's Codex app-server page and the archived
provider shape both distinguish response policy, sandbox construction, result
projection, and app-server process handling.

Code or product assumption affected:
Slice 101 keeps `CodexAppServerClient` and app-server behavior unchanged while
moving helpers into `responses.py`, `sandbox.py`, `turn_completion.py`,
`run_result.py`, and `timeouts.py`. Architecture tests now reject those helper
definitions inside `app_server.py`.

Follow-up test:
If Codex app-server later supports model/effort/output-schema request
construction, add a request-construction module instead of growing `_run()` with
more inline payload policy.

## 2026-07-01 - IndexStore Should Be A Facade, Not The Whole Indexer

Old hypothesis:
After the read-view split, `IndexStore` could keep schema DDL, migration setup,
markdown source loading, freshness signatures, and projection writes because
those mechanics all belong to the derived SQLite index.

New hypothesis:
`IndexStore` should stay the service-facing store facade, while schema,
source-loading, and projection writes live in named modules. Those three
mechanics all belong to the index service, but they change for different
reasons.

Evidence that forced the change:
After slice 101, `services/index/store.py` was still the largest production
module at 447 lines. The file mixed facade methods with `SCHEMA_DDL`, migration
construction, `load_page_document`, `load_topics_yaml`, source hashing,
signature persistence, and every `INSERT`/`DELETE` used to rebuild `index.db`.

Code or product assumption affected:
Slice 102 keeps query and refresh behavior unchanged. `schema.py` owns derived
`index.db` schema and connection setup, `sources.py` owns page/topic source
loading and freshness signatures, `projection.py` owns replacement writes and
stored signatures, and `store.py` delegates across those modules.

Follow-up test:
If future index work adds source filters, schema migrations, or projection
tables, extend the named module that owns the reason to change instead of
growing `store.py`.

## 2026-07-01 - Sync Policy Needs Rule-Family Modules

Old hypothesis:
After slice 95, one `workflows/sync/policy.py` module could own all
deterministic sync policy because `SyncWorkflow` had already been separated
from ledger and cursor mechanics.

New hypothesis:
`policy.py` should be a facade, not the whole sync-policy implementation.
Cursor decisions, ledger-entry transitions, identity matching, transcript
snapshots, summary rows, and generated prompt guidance each have separate
change triggers.

Evidence that forced the change:
After slice 102, `workflows/sync/policy.py` was the largest production module
at 417 lines. It mixed transcript file IO, SHA-256 cursor hashing, ledger-key
compatibility, pending/absorbed/failed transitions, retry-budget decisions,
pending-run reconciliation, skip/start result construction, and prompt guidance.

Code or product assumption affected:
Slice 103 keeps the public workflow and service imports unchanged.
`SyncWorkflow` still imports from `workflows/sync/policy.py`, while the facade
delegates to `decisions.py`, `entries.py`, `identity.py`, `snapshots.py`,
`reporting.py`, and `guidance.py`.

Follow-up test:
If a future sync feature adds new retry policy, source-app filtering, scheduler
ownership, or prompt guidance, extend the module that owns that rule family and
keep `service.py` orchestration-only.

## 2026-07-01 - GitHub Source Runtime Needs Adapter Internals Split

Old hypothesis:
`GitHubSourceRuntimeAdapter` could own GitHub PR and issue source runtime end to
end because it is one concrete `SourceRuntimeAdapter` implementation.

New hypothesis:
The public adapter should stay small, while GitHub CLI execution, typed payload
models, target argument policy, prompt-facing rendering, and unavailable
diagnostics live in focused modules under `integrations/sources/github/`.

Evidence that forced the change:
After slice 103, `integrations/sources/github/adapter.py` was the largest
production module at 413 lines. It mixed Pydantic `gh --json` payload models,
`gh` subprocess execution, PR/issue inspection flow, ref-to-target argument
policy, error shaping, and prompt rendering.

Code or product assumption affected:
Slice 104 keeps `GitHubSourceRuntimeAdapter` and all fake-runner behavior
unchanged. `client.py` owns `gh` execution and typed retrieval, `models.py` owns
payload models, `targets.py` owns target args, `rendering.py` owns source text,
and `errors.py` owns unavailable-runtime diagnostics.

Follow-up test:
If future GitHub source runtime adds reviews, linked issues, check runs, or a
native API/MCP transport, add the behavior to the module that owns the reason to
change instead of growing `adapter.py`.

## 2026-07-01 - Transcript Source Runtime Needs Entry And Rendering Boundaries

Old hypothesis:
`TranscriptSourceRuntimeAdapter` could own path resolution, JSONL reading,
Pydantic provider shapes, entry normalization, prompt rendering, and truncation
because it is one concrete `SourceRuntimeAdapter` implementation.

New hypothesis:
The public adapter should stay small, while transcript line models, file
reading, line-to-entry normalization, prompt-facing rendering, path resolution,
and unavailable diagnostics live in focused modules under
`integrations/sources/transcripts/`.

Evidence that forced the change:
After slice 104, `integrations/sources/transcripts/runtime.py` became the
largest production module at 387 lines. It mixed source-runtime orchestration
with provider JSON shape models, `jsonlines` parsing, tolerant Pydantic
validation, Codex/Claude entry normalization, text extraction, and tail
truncation.

Code or product assumption affected:
Slice 105 keeps `TranscriptSourceRuntimeAdapter` behavior unchanged. `models.py`
owns typed line and entry models, `reader.py` owns JSONL IO, `entries.py` owns
normalization, `rendering.py` owns prompt text and truncation, `paths.py` owns
path resolution, and `errors.py` owns unavailable-runtime diagnostics.

Follow-up test:
If future transcript runtime adds another provider shape, richer event
reconstruction, or alternate transcript storage, add it to the module that owns
that reason to change instead of growing `runtime.py`.

## 2026-07-01 - Source Address Resolution Should Not Live In The Service Facade

Old hypothesis:
`SourcesService` could own source address parsing because resolving selected
material is one of its product verbs.

New hypothesis:
`SourcesService` should own resolve/discover/inspect orchestration only.
Source-address syntax, prompt hints, URL validation, GitHub URL parsing, local
path classification, and file fingerprinting have their own reason to change
and belong in `services/sources/address_resolution.py`.

Evidence that forced the change:
After slice 105, `services/sources/service.py` was the largest production file
at 351 lines. It mixed injected adapter orchestration with GitHub shorthand
parsing, Git range/diff parsing, transcript address parsing, HTTP URL
validation, GitHub URL decomposition, local path classification, SHA-256 file
fingerprinting, and prompt-hint constants.

Code or product assumption affected:
Slice 106 keeps the public `SourcesService` API and source address grammar
unchanged. `address_resolution.py` owns the syntax mechanics and returns
`SourceBrief`; `transcripts.py` owns transcript candidate ordering; `service.py`
stays the facade over request models and ports.

Follow-up test:
If a future source kind adds syntax or classification rules, add it to
`address_resolution.py` or a source-family helper it delegates to. Do not grow
`SourcesService` unless the product verb itself changes.

## 2026-07-01 - CLI Render Root Should Be A Facade

Old hypothesis:
`cli/render/root.py` could own all ordinary text and JSON renderers because it
was already separate from parser and dispatch code.

New hypothesis:
`cli/render/root.py` should mirror `cli/parser/root.py` and
`cli/dispatch/root.py`: a stable facade over domain modules. Lifecycle output,
wiki read/mutation output, workspace registry output, admin output, Rich setup
presentation, and shared formatting each change for different reasons.

Evidence that forced the change:
After slice 106, `cli/render/root.py` was the largest production file at 338
lines. It mixed build/ingest/garden/sync output, run queue start output, local
wiki list/drop output, search/show/topics/health/tagging output, JSON dumping,
metadata formatting, and shared page-word/index helpers.

Code or product assumption affected:
Slice 107 keeps dispatcher imports stable by making `root.py` re-export the
same render function names. `lifecycle.py`, `wiki.py`, `workspaces.py`, and
`common.py` own the implementation details.

Follow-up test:
Future CLI output changes should land in the domain renderer that owns the
command family. Do not add rendering logic or service model imports to
`root.py`.

## 2026-07-01 - Wiki Render Is An Output-Family Facade

Old hypothesis:
After slice 107, `cli/render/wiki.py` could own all search, page, topic,
health, and tagging output because those commands all read or organize a local
wiki.

New hypothesis:
`cli/render/wiki.py` should mirror `cli/dispatch/wiki.py`: a stable facade
over smaller output-family modules. Search/reindex output, page display,
topic display/mutations, health sections, and tag summaries each change for
different reasons.

Evidence that forced the change:
After slice 121, `cli/render/wiki.py` was 223 lines and mixed
`SearchPageResult`, `PageView`, `TopicDetail`, `HealthReport`, and
`TaggingResult` formatting. The sibling Almanac CLI keeps hosted renderers
small by noun, and the CodeAlmanac CLI now has the same pressure inside the
local wiki surface.

Code or product assumption affected:
Slice 122 keeps every public render function name available through
`cli/render/wiki.py` and `cli/render/root.py`. Only ownership changes:
`search.py`, `pages.py`, `topics.py`, `health.py`, and `tagging.py` own their
respective text output.

Follow-up test:
Future wiki output changes should add or extend the output-family module that
owns the displayed result. Architecture tests should keep service model imports
and `render_*` definitions out of `cli/render/wiki.py`.

## 2026-07-01 - Admin Render Is An Output-Family Facade

Old hypothesis:
`cli/render/admin.py` could own setup, doctor, update, jobs, and automation
output because those commands share the admin dispatch module.

New hypothesis:
`cli/render/admin.py` should be a facade over admin output-family modules.
Automation, diagnostics, jobs, updates, and setup/uninstall presentation each
depend on different service result models and change for different product
reasons.

Evidence that forced the change:
After slice 122, `cli/render/admin.py` was the largest CLI render file at 232
lines. It mixed scheduler output, doctor sections, package update text, run
ledger output, JSON dumping, job log JSON exclusions, and setup/uninstall
Rich handoff.

Code or product assumption affected:
Slice 123 keeps every public admin render function available through
`cli/render/admin.py`. Only ownership changes: `automation.py`,
`diagnostics.py`, `jobs.py`, `updates.py`, and `setup.py` own their output.

Follow-up test:
Future admin output changes should land in the output-family module that owns
the service result. Architecture tests should keep service model imports and
`render_*` definitions out of `cli/render/admin.py`.

## 2026-07-01 - Admin Dispatch Is A Command-Family Facade

Old hypothesis:
`cli/dispatch/admin.py` could own setup/uninstall, doctor, update, jobs, and
automation request construction because those commands share the admin CLI
domain.

New hypothesis:
`cli/dispatch/admin.py` should be a facade over admin command-family modules.
Setup/uninstall, diagnostics, updates, jobs, and automation each construct
different request models and have different helper parsing rules.

Evidence that forced the change:
After slice 123, `cli/dispatch/admin.py` was the largest CLI file at 203
lines. It mixed setup target parsing, setup automation policy, automation task
deduplication, duration resolution, update exit status, jobs subcommand
routing, and doctor request construction.

Code or product assumption affected:
Slice 124 keeps the public admin command surface unchanged. Only ownership
changes: `setup.py`, `diagnostics.py`, `updates.py`, `jobs.py`, and
`automation.py` own their request construction.

Follow-up test:
Future admin command behavior should land in the command-family dispatcher that
owns the service request. Architecture tests should keep request imports and
helper parsers out of `cli/dispatch/admin.py`.

## 2026-07-01 - Filesystem Listing Needs Mechanic-Level Boundaries

Old hypothesis:
`integrations/sources/filesystem/listing.py` could own ignore specs, Python
walking, Git directory listing, Git status parsing, repo-root probing, and
directory document assembly because those all contribute to one filesystem
source runtime.

New hypothesis:
`listing.py` should assemble directory documents and choose Git-vs-walk source
policy, while ignore rules, Python walking, and Git mechanics each live in
their own modules. Those mechanics change for different reasons and already
have enough behavior to deserve names.

Evidence that forced the change:
After slice 107, `integrations/sources/filesystem/listing.py` became the
largest production module at 327 lines. It mixed `.gitignore` loading,
configured wiki-root ignores, recursive directory walking, Git `ls-files`,
Git `status --porcelain -z`, rename-source skipping, repo-root detection,
subprocess failure tolerance, and final document assembly.

Code or product assumption affected:
Slice 108 keeps filesystem runtime behavior unchanged. `ignore.py` owns ignore
spec construction, `walk.py` owns Python walking, `git.py` owns Git listing and
status mechanics, and `listing.py` remains the assembly facade.

Follow-up test:
Future filesystem material-selection changes should land in `selection.py` or
`listing.py` only when the assembly policy changes. Ignore, walking, and Git
mechanics should stay in their focused modules.

## 2026-07-01 - Sync Workflow Needs An Execution Effects Boundary

Old hypothesis:
`SyncWorkflow.run(...)` could own evaluated work-item execution because sync is
one workflow and policy had already been split out.

New hypothesis:
`SyncWorkflow` should own the public status/run/evaluate/scoping surface, while
`SyncRunExecutor` owns the side effects that turn evaluated work items into run
records, queue specs, ledger transitions, and started/needs-attention rows.

Evidence that forced the change:
After slice 108, `workflows/sync/service.py` was the largest production module
at 310 lines. The `run(...)` method mixed claim-owner selection with foreground
Ingest execution, background queueing, worker-spawn failure handling,
pending-entry writes, failed-entry writes, absorbed-entry writes, and summary
row construction.

Code or product assumption affected:
Slice 109 keeps foreground/background sync behavior unchanged. `service.py`
still exposes `status`, `run`, `evaluate`, and `scope_candidates`.
`execution.py` owns the execution effects and receives explicit workflow/service
dependencies through `SyncRunExecutor`.

Follow-up test:
Future sync execution changes such as default background mode, spawn retry, or
terminal-run reconciliation should land in `execution.py` unless they change
candidate selection or cursor policy.

## 2026-07-01 - Viewer Scope And Projection Are Separate From Use Cases

Old hypothesis:
`ViewerService` could own local workspace fallback, available-registry
filtering, multi-wiki navigation ordering, index-to-viewer DTO mapping, and the
viewer use cases because all of those details feed the `serve` payloads.

New hypothesis:
`ViewerService` should read as the local reader use-case facade.
`ViewerWorkspaceScope` should own selected-wiki fallback and registered-wiki
navigation. `projections.py` should own conversion from index/workspace models
to viewer DTOs.

Evidence that forced the change:
After slice 109, `services/viewer/service.py` was tied for the largest
production file at 303 lines. It mixed product verbs with registry filtering,
workspace fallback, topic summary construction, page source conversion, and
page summary projection.

Code or product assumption affected:
Slice 110 keeps `serve` behavior unchanged. Default `serve` still lists
available registered local wikis, `serve --wiki` still narrows scope, and the
browser still passes stable `workspace_id` selectors. The split only moves
selection and DTO mechanics behind named collaborators.

Follow-up test:
Future viewer payload changes should land in `service.py` only when they alter
the use case. Registry selection belongs in `workspace_scope.py`; browser DTO
conversion belongs in `projections.py`.

## 2026-07-01 - Web Runtime Needs Edge-Mechanic Boundaries

Old hypothesis:
`WebSourceRuntimeAdapter` could own HTTP streaming, response validation,
content-type classification, HTML text extraction, prompt rendering, and error
projection because all of those steps happen inside one web URL runtime.

New hypothesis:
The adapter should implement the `SourceRuntimeAdapter` port and coordinate the
web runtime path. `client.py` should own `httpx` response fetching,
`models.py` should own typed web payloads, `documents.py` should own content
classification and HTML/text parsing, `rendering.py` should own prompt-facing
text, and `errors.py` should own unavailable-runtime projection.

Evidence that forced the change:
After slice 110, `integrations/sources/web/adapter.py` was tied for the
largest production file at 303 lines. It mixed dependency wiring with
streaming HTTP reads, Pydantic response/document models, Beautiful Soup
cleanup, text normalization, metadata rendering, and diagnostic shaping.

Code or product assumption affected:
Slice 111 keeps web URLs as selected runtime material, not a source library or
crawler. The same `SourceAddress -> SourceRef -> SourceBrief -> SourceRuntime`
path feeds Ingest, and the adapter still accepts an injected `httpx.Client` for
testable dependency inversion.

Follow-up test:
Future web runtime changes should land in the module that owns the mechanism:
HTTP behavior in `client.py`, parsed document behavior in `documents.py`,
prompt text in `rendering.py`, and source-runtime port behavior in
`adapter.py`.

## 2026-07-01 - Workspace Service Should Not Own Every Workspace Mechanic

Old hypothesis:
`WorkspacesService` could own registration, resolution, selector matching,
path containment, workspace name/id generation, and marker-based registry
status because all of those mechanics support the workspace product surface.

New hypothesis:
`WorkspacesService` should be the workspace use-case facade. Workspace identity
belongs in `identity.py`, selector and containment mechanics belong in
`selection.py`, and marker-based registry availability belongs in `status.py`.
`store.py` should import identity directly instead of reaching back into the
service module.

Evidence that forced the change:
After slice 111, `services/workspaces/service.py` was the largest production
file at 303 lines. It mixed initialization-target logic, registration,
resolve/select/drop verbs, `sha256` id generation, kebab-case name generation,
ambiguous-name conflict handling, relative path selector expansion, path
containment checks, and `topics.yaml` + `pages/` availability status checks.

Code or product assumption affected:
Slice 112 keeps the configured-root and registry behavior unchanged. New repos
still default to `almanac/`, registered roots are preserved, unavailable
registry entries are not auto-dropped, and explicit `drop` / `drop_missing`
remain the only cleanup verbs.

Follow-up test:
Future workspace changes should land in the module that owns the mechanic:
identity in `identity.py`, selector/path rules in `selection.py`, marker/status
rules in `status.py`, root discovery in `roots.py`, and use-case orchestration
in `service.py`.

## 2026-07-01 - Harness Models Are A Contract Family, Not One Model File

Old hypothesis:
`services/harnesses/models.py` could own all normalized harness data because
provider adapters, workflows, runs, and the viewer all needed the same public
objects.

New hypothesis:
The service-owned harness contract should be split by product meaning, with
`models.py` kept only as a compatibility facade. Provider/status enums belong
in `kinds.py`, root/helper attribution belongs in `actors.py`, normalized
transcript event payloads belong in `events.py`, and readiness/transcript/result
objects plus terminal helper events belong in `results.py`.

Evidence that forced the change:
After slice 112, `services/harnesses/models.py` was the largest production file
at 302 lines. It mixed provider identity, run status, actor confidence, tool
display, usage accounting, failure metadata, agent trace payloads, event
records, run results, readiness, transcript refs, and terminal-message helpers.
The live agreement now treats normalized harness events as the inspectable
transcript surface, so this contract needs durable names and regression guards.

Code or product assumption affected:
Slice 113 keeps the old `services.harnesses.models` import path working for
callers, but the owning definitions now live in focused modules. Future provider
event fields should extend `events.py`; future result/readiness fields should
extend `results.py`.

Follow-up test:
Architecture tests should keep `models.py` facade-only and keep the focused
harness contract modules below their size bounds.

## 2026-07-01 - Doctor Checks Are Check Families, Not One Service Method

Old hypothesis:
`DiagnosticsService` could own install checks, wiki checks, manual checks,
index summaries, health summaries, workspace selection, registry checks, and
message formatting because all of those facts appear in one `doctor` report.

New hypothesis:
`DiagnosticsService` should stay the service-facing `doctor` facade. Install
readiness belongs in `diagnostics/install.py`, selected-wiki readiness belongs
in `diagnostics/wiki.py`, and shared doctor message formatting belongs in
`diagnostics/messages.py`.

Evidence that forced the change:
After slice 113, `services/diagnostics/service.py` was the third-largest
production module at 283 lines. It mixed package/runtime checks with workspace
selection, registry status, index rebuild handling, workspace manual drift,
wiki health, pluralization, and error-line formatting. The command is a public
readiness surface, so adding future doctor checks would have kept growing one
service file.

Code or product assumption affected:
Slice 114 keeps public `codealmanac doctor` output unchanged. The split only
changes ownership: install checks do not know wiki status, wiki checks do not
know package inventory, and the service remains the single public use-case
entrypoint.

Follow-up test:
Future doctor expansion should add a focused check-family module or extend the
existing family module, while architecture tests keep `service.py` facade-only.

## 2026-07-01 - Lifecycle Dispatch Is A Command-Family Facade

Old hypothesis:
`cli/dispatch/lifecycle.py` could own init, build, ingest, garden, hidden
worker, sync, and sync status request construction because those commands share
the lifecycle CLI domain.

New hypothesis:
`cli/dispatch/lifecycle.py` should be a facade over lifecycle command-family
modules. Init/build, page-writing operations, sync, and hidden worker draining
construct different workflow requests and have different helper parsing rules.

Evidence that forced the change:
After slice 124, `cli/dispatch/lifecycle.py` was the largest CLI edge file at
176 lines. It mixed workspace initialization, index build output, ingest/garden
foreground/background policy, hidden queue draining, transcript app parsing,
pending timeout resolution, sync execution mode, and harness resolution.

Code or product assumption affected:
Slice 125 keeps the public lifecycle command surface unchanged. Only ownership
changes: `build.py`, `operations.py`, `sync.py`, and `worker.py` own their
request construction.

Follow-up test:
Future lifecycle command behavior should land in the command-family dispatcher
that owns the workflow request. Architecture tests should keep workflow request
imports and sync helper parsers out of `cli/dispatch/lifecycle.py`.

## 2026-07-01 - Admin Parser Is A Command-Family Facade

Old hypothesis:
`cli/parser/admin.py` could own setup, uninstall, doctor, update, jobs, and
automation flag construction because those commands share the admin CLI domain.

New hypothesis:
`cli/parser/admin.py` should be a facade over admin command-family parser
modules. Setup/uninstall, diagnostics, updates, jobs, and automation have
different flag sets and change for different product reasons.

Evidence that forced the change:
After slice 125, `cli/parser/admin.py` was the largest parser file at 147
lines. It mixed setup automation flags, uninstall preservation flags, doctor
flags, update flags, jobs subcommands, and automation task choices.

Code or product assumption affected:
Slice 126 keeps all admin flags, defaults, and help text unchanged. Only
ownership changes: `setup.py`, `diagnostics.py`, `updates.py`, `jobs.py`, and
`automation.py` own command flag construction.

Follow-up test:
Future admin flags should land in the parser module that owns the command
family. Architecture tests should keep `add_parser(...)` command construction
and flag strings out of `cli/parser/admin.py`.

## 2026-07-01 - Git History Is The Page Archive

Old hypothesis:
The Python port could keep archive-era page lineage fields in the read model
while leaving the UI mostly focused on current pages.

New hypothesis:
Page archive lineage is not part of the Python v1 product model. The active
index, page DTOs, viewer summaries, and search flags should model only current
wiki pages; git history is the archive.

Evidence that forced the change:
The live agreement explicitly removed `archived_at`, `superseded_by`,
`supersedes`, `--include-archive`, and `--archived`, but active Python source
still stored archive columns and exposed public archive search flags.

Code or product assumption affected:
Slice 127 removes the active fields and flags. Obsolete archive keys in
frontmatter are ignored as extra metadata rather than projected into product
state. Run page-change summaries now use created/updated/deleted only.

Follow-up test:
Future page-lifecycle work should add a new explicit product decision before
adding archive-like fields. `test_active_python_model_has_no_page_archive_lineage`
guards active Python source against silent reintroduction.

## 2026-07-01 - Nearest Initialized Wiki Beats Broad Registry

Old hypothesis:
The workspace resolver could choose the nearest containing registry entry
before scanning for initialized roots on disk.

New hypothesis:
Current-repo lookup should first scan for the nearest initialized wiki root
using conventional and registered roots. Registry containment is a fallback
only when the selected registered workspace's configured root is initialized.

Evidence that forced the change:
This checkout has `.almanac/topics.yaml` and `.almanac/pages/`, but
`uv run codealmanac search "topic service"` failed because
`~/.codealmanac/registry.json` contained a broad
`/Users/rohan/Desktop/Projects` workspace whose `almanac/` root does not
exist. The broad parent entry shadowed the current repo wiki.

Code or product assumption affected:
Slice 128 makes `almanac/`, `docs/almanac/`, and `.almanac/` conventional
discoverable roots. Other custom roots remain registry-discovered. Stale broad
registry entries stay visible and removable through `codealmanac list`, but
they no longer win over a nearer initialized repo wiki.

Follow-up test:
Future workspace selection changes should preserve
`test_resolve_prefers_nearest_initialized_root_over_broad_parent_registry` and
real-checkout dogfood with a broad parent registry entry.

## 2026-07-01 - Health Has Two Finding Families

Old hypothesis:
`health_views.py` could own all health SQL and helper logic because `health`
is one read-view query family.

New hypothesis:
`health_views.py` should assemble the report, while graph/file/page checks and
source/citation checks live in separate modules. Those two finding families
change for different reasons.

Evidence that forced the change:
After slice 128, `health_views.py` still mixed orphan detection, dead file
stat calls, broken page links, cross-wiki checks, empty topic/page checks,
source citation regex parsing, source-id lookup, duplicate source SQL, and
the final `HealthReport` constructor.

Code or product assumption affected:
Slice 129 does not change `codealmanac health` output. It changes ownership:
`health_graph_views.py` owns page/topic/link/file findings,
`health_source_views.py` owns sources/citations findings, and
`health_views.py` remains the facade imported by `views.py`.

Follow-up test:
Future health categories should land in the finding-family module that owns
the reason to change. Architecture tests keep SQL and regex helpers out of
`health_views.py`.
