20 KiB
Workspace Architecture Implementation Plan (Audit)
1. Executive Summary
Complexity assessment: heavy.
This is not a single path-helper refactor. The current codebase has a hybrid session/run model that works for current behavior, but diverges from docs/development/workspace.md in foundational places (workspace root shape, manifest responsibilities, stage output placement, and archive symmetry).
Highest-risk areas:
- Splitting the current single manifest model into durable session manifest vs per-invocation run manifest without regressing skip/force/resume UX.
- Migrating path helpers and artifact-store interfaces from session-only roots (
work/{session}) to campaign-aware roots (work/{campaign}/{session}) while preserving existing runs. - Introducing run-local stage outputs + immediate promotion while keeping stage tests and archive behavior stable.
- Avoiding stale downstream skips after forced upstream reruns.
Surprising findings:
- Code already has campaign/run-aware helpers (
SessionRunWorkDir,SessionSpoolAudioDir) but core session helpers and manifest pathing remain campaign-unaware. - Archive recently gained run/session fallback behavior for manifest/promotion sources, which confirms an existing hybrid-layout pressure point.
- Analyze input resolution is functional but ad hoc and stage-local; there is no centralized artifact registry/resolver.
2. Current-State Map
2.1 Workspace Path Construction
Primary path model:
internal/artifacts/paths.goSessionWorkDir(rootDir, sessionID)=>{root}/work/{session_id}buildSessionPaths(workspaceRoot, sessionID)roots all canonical paths under{root}/work/{session_id}SessionRunWorkDir(rootDir, campaign, sessionID, runID)exists, but is not the default session root helper.
Artifact store abstraction:
internal/artifacts/store.goSessionPaths(sessionID string)/EnsureLayout(sessionID string)are session-id-only (no campaign argument).
internal/artifacts/local.goEnsureLayoutcreates session-level folders underSessionWorkDir.
Path normalization helper:
internal/artifacts/resolve.goResolveSessionLocalPathForReadaccepts absolute/workspace/session-relative values and probes filesystem.
Where campaign-aware/run-aware support exists:
- Local run path helpers:
SessionRunWorkDir,SessionSpoolAudioDir. - S3 key helpers:
internal/artifacts/s3_keys.go(campaigns/sessions/runs/current).
Where session-root assumptions remain {workspace}/work/{session}:
- Manifest path computation:
internal/app/runner.gomanifestPathFor. - Artifact store layout and most stage
paths := env.ArtifactStore.SessionPaths(sessionID)calls. - Many tests hardcode
workspace/work/<session>/...(examples below).
Manual/ad hoc path construction (not through a single resolver API):
- Common stage patterns:
filepath.Join(paths.TranscriptsDir, "..."),filepath.Join(paths.ArtifactsDir, "..."), etc. preparerun/work selection:internal/stage/prepare.gopathsWorkDirForManifest.- Analyze input fallback path resolution:
resolveInputPathForReadininternal/stage/analyze.go.
2.2 Manifest and Run Identity
Current manifest model:
internal/manifest/manifest.goManifestincludes both session and run fields:SessionID,CampaignRunID,LocalWorkDir,LocalSpoolDirS3Bucket,S3SessionPrefix,S3RunPrefixStages,Inputs, stage outputs/logs/generated configs/metadata.
Current persistence:
internal/manifest/store.goLocalStorereads/writes one JSON manifest path.- Runner always loads/saves one manifest path via
manifestPathFor(cfg)(session-root path under current layout).
Identity initialization:
internal/app/runner.goensureManifestIdentitypopulates run fields if absent.RunIDis generated once for an empty manifest and reused thereafter (hybrid semantics).
Interpretation today:
- Best described as a hybrid session manifest with run identity fields, not as distinct session + run manifests.
What this means for redesign:
- Session-vs-run split is not just file relocation; it requires new responsibilities and write flows.
- A backward-compatible evolution path is possible by:
- preserving current fields in session manifest for migration/read-compat,
- adding explicit run-manifest type + path,
- gradually moving invocation-specific details to run manifests.
2.3 Stage Output Paths (Current)
All implemented stages currently write canonical artifacts directly into session-level paths.* roots (under current session root), with logs/configs typically also session-level.
prepare(internal/stage/prepare.go)
- Inputs copied to
inputs/(session.yml,pipeline.resolved.yml,speakers.yml,autocorrect.yml,glossary.yml) - Audio copied to
audio/ - Manifest input provenance recorded in
m.Inputs - S3 audio uses run-scoped spool/work helpers when
RunIDis present.
transcribe(internal/stage/transcribe.go)
- Outputs:
transcripts/raw/{speaker}.json - Stage metadata only (no stage logs/config files produced here).
merge(internal/stage/merge.go)
- Pre-normalize intermediates:
transcripts/raw/normalized/{basename}.normalized.json - Merge output:
transcripts/merged.json - Report:
artifacts/seriatim.report.json(if enabled) - Logs/configs:
- per-input normalize logs/configs in session-level
logs/+config/ - merge logs/config in session-level
logs/+config/
- per-input normalize logs/configs in session-level
polish(internal/stage/polish.go)
- Output:
transcripts/processed.json - Report:
artifacts/audita.report.json(if enabled) - Work dir:
artifacts/audita-work - Logs/config:
logs/audita.*,config/audita.generated.yml
normalize(internal/stage/normalize.go)
- Output:
transcripts/normalized.json(configurable) - Report:
artifacts/seriatim.normalize.report.json(if enabled) - Logs/config:
logs/seriatim.normalize.*,config/seriatim.normalize.generated.yml
trim(internal/stage/trim.go)
- Output:
transcripts/trimmed.json(configurable) - Bounds output path from config (default examples use
artifacts/session_bounds.json) - Logs/configs in session-level
logs/andconfig/for scriptorium + seriatim invocations
analyze(internal/stage/analyze.go)
- Current implemented artifact:
artifacts/session_recap.md - Logs/config in session-level
logs/+config/ - Optional render diagnostics under session-level artifacts/logs/config.
archive(internal/stage/archive.go)
- Reads from a "workDir" derived by
archiveWorkDir:- prefers
m.LocalWorkDirif it exists, - else tries run-scoped campaign/session/run path,
- else falls back to legacy session path.
- prefers
- Uploads run record and promotions.
- Current code now resolves manifest and promotion sources via run/session fallback.
notify
- Placeholder only in
internal/stage/placeholders.go; no durable outputs.
Durable-vs-diagnostic split today:
- Durable artifacts and diagnostics are mixed at session level.
- No run-local stage directories exist yet.
2.4 Idempotency / Force / Resume / Sparse Runs
Run control implementation:
internal/app/run_control.go- skip rule:
!force && stageSucceeded(manifest, stage) - stale detection TODO only; no invalidation logic.
- skip rule:
Command behavior:
run: full plan throughexecuteStages.run-stage: single selected stage throughexecuteStages.resume: starts at first non-succeeded stage unless--force.
Current assumptions:
- Command invocation mutates a single durable workspace + single manifest for the session path model.
- There is no per-invocation run manifest write path.
Smallest safe UX-preserving invariant to keep during migration:
- Session manifest remains source-of-truth for skip decisions across invocations.
2.5 Archive and S3 Alignment
S3 semantics are relatively mature:
internal/artifacts/s3_keys.go- session prefix + run prefix +
current/manifest.json+current/run_id.txt.
- session prefix + run prefix +
Archive stage behavior:
- uploads run records under run prefix,
- uploads promoted session outputs,
- uploads current manifest then current run pointer,
- current pointer is commit marker,
- required promotions fail; optional promotions skipped.
Local-vs-remote mismatch still present:
- Local canonical session root currently defaults to
work/{session}(artifact store), - while archive/run helpers expect campaign-aware run locations (
work/{campaign}/{session}/{run}), - causing fallback logic and hybrid handling in
archive.
2.6 Artifact Source Resolution in Analyze
Current implementation is stage-local and ad hoc:
- Transcript discoverers:
discoverProcessedTranscriptdiscoverNormalizedTranscriptdiscoverTrimmedTranscript
- Input source switch in
resolveScriptoriumInputsupports:processed_transcriptnormalized_transcripttrimmed_transcriptprevious_session_artifact
No first-class artifact registry exists yet. Aliases and canonical IDs are not modeled.
2.7 Stale/Invalidation
manifest.StageStatusalready definesstale(internal/manifest/status.go), but no stage uses it.- Skip logic ignores stale state and only checks
succeeded. - Forced upstream rerun does not invalidate downstream stage success markers.
2.8 Test Coverage Relevant to Redesign
High-value existing coverage:
- Workspace helpers:
- Runner semantics:
- Stage path/output behavior:
internal/stage/*_test.gofor prepare/transcribe/merge/polish/normalize/trim/analyze/archive
- Archive behavior:
Tests likely to fail during workspace redesign:
- Any tests hardcoding
work/{session}manifest and transcript paths (many ininternal/app/*test.go,internal/stage/*test.go). - Archive tests assuming current hybrid fallback behavior.
3. Gap Analysis Against docs/development/workspace.md
| Intended concept | Current status | Notes |
|---|---|---|
Session root at work/{campaign}/{session} |
Partial / mostly absent | SessionWorkDir and artifact store still use work/{session}. Campaign-aware run helper exists separately. |
| Distinct session manifest vs run manifest | Absent | One hybrid manifest model/file is used. |
Run-local stage dirs under runs/{run_id}/{stage} |
Absent | Stages write canonical outputs/logs/config directly at session level. |
| Immediate promotion run-local -> session canonical | Absent | No run-local staging area to promote from today. |
| Session manifest skip source of truth | Present | Skip/resume use single manifest stage statuses. |
Sparse runs represented under runs/{run_id} |
Absent | No run-manifest/per-run stage records yet. |
| Artifact resolver with canonical IDs + aliases | Absent | Analyze resolves via stage-local source-name switch and fallback helpers. |
| Downstream invalidation for forced upstream reruns | Absent | TODO only; no stale propagation or status clearing. |
| Local semantics mirror archive semantics | Partial | Archive/S3 side models runs/current, local workspace core still session-layout-centric. |
| Safe path helpers centralization | Partial | Good helper base exists, but many stage-level manual joins still encode conventions. |
4. Recommended Implementation Sequence
Step 1: Introduce campaign-aware session path model without behavior break
Purpose:
- Add first-class helpers for
work/{campaign}/{session}and make them available everywhere.
Expected changes:
internal/artifacts: add/extend path helpers andSessionPathsconstructor variants that accept campaign.internal/app: pass campaign into path-model entrypoints where available.
Behavior change:
- None initially (can keep legacy fallback reads).
Tests:
- Add campaign-aware path-model tests.
- Keep legacy-path compatibility tests.
Risks:
- Wide compile-time touch due
Storeinterface signatures.
Rollback:
- Keep legacy helper wrappers until full migration lands.
Step 2: Split manifest responsibilities (session manifest + run manifest scaffolding)
Purpose:
- Preserve current UX while introducing explicit run execution records.
Expected changes:
internal/manifest: add run manifest type/store helpers.internal/app/runner.go: create/load session manifest and initialize per-invocation run manifest path.
Behavior change:
- Session manifest remains skip truth source.
- Run manifest begins recording invocation metadata/stage actions.
Tests:
- New tests for both manifest files existing and being updated correctly.
Risks:
- Incorrect ordering of saves can regress crash consistency.
Rollback:
- Keep session-manifest-only decision logic until run manifest proves stable.
Step 3: Move stage execution products to run-local directories with promotion
Purpose:
- Align with workspace architecture (
runs/{run_id}/{stage}/...) while preserving canonical outputs.
Expected changes:
internal/stage: each implemented stage writes outputs/logs/config/reports to run-local paths.- Introduce shared promotion helpers (atomic copy/rename + output validation + manifest provenance).
Behavior change:
- Canonical outputs remain session-level; run-local diagnostics now preserved per run.
Tests:
- Stage tests updated to assert run-local outputs + promoted canonical outputs.
- New tests for immediate promotion and producer run metadata.
Risks:
- Highest regression risk (all implemented stages touched).
Rollback:
- Stage-by-stage migration flag or phased rollout by stage order.
Step 4: Archive alignment pass
Purpose:
- Remove hybrid fallback complexity once local layout is canonical.
Expected changes:
internal/stage/archive.go: resolve sources from canonical session outputs and run manifests deterministically.- Keep
current/manifest.json+current/run_id.txtpublication semantics.
Behavior change:
- Simpler source selection; fewer cross-layout heuristics.
Tests:
- Archive tests for run uploads, promotions, current pointers, and fallback removal/compat gates.
Risks:
- Breaking current mixed-layout compatibility too early.
Rollback:
- Retain fallback compatibility for one migration window.
Step 5: Artifact registry/resolver (analyze first consumer)
Purpose:
- Replace ad hoc analyze source resolution with canonical artifact IDs and aliases.
Expected changes:
- New resolver package (for example
internal/artifacts/registryorinternal/stage/artifactresolve) with IDs:narratio.transcript.mergednarratio.transcript.polishednarratio.transcript.fullnarratio.transcript.trimmednarratio.bounds.sessionnarratio.artifact.session_recap
- Backward-compatible alias map for current source names.
Behavior change:
- Analyze input resolution becomes centralized and consistent.
Tests:
- Resolver unit tests for canonical IDs + aliases + missing-input error clarity.
- Analyze tests updated to assert resolver usage.
Risks:
- Input resolution edge cases for
previous_session_artifactand required/optional handling.
Rollback:
- Keep old resolver path behind a temporary compatibility function.
Step 6: Minimal downstream invalidation after forced upstream reruns
Purpose:
- Prevent stale downstream skips before full checksum stale detection exists.
Expected changes:
internal/app/run_control.go+ manifest transition helpers.- On forced rerun success of stage
X, clear/mark downstream success states.
Behavior change:
- Subsequent runs no longer skip stale downstream stages.
Tests:
- New run-control tests for force-induced downstream invalidation.
- Resume tests with forced sparse runs.
Risks:
- Over-invalidating too broadly and degrading UX.
Rollback:
- Start with deterministic downstream stage list based on pipeline order only.
Step 7: Legacy layout migration strategy
Purpose:
- Handle existing
work/{session}data safely.
Expected changes:
- Startup detection/migration path in app layer.
- Clear failure messages for ambiguous legacy states.
Behavior change:
- Explicit migration semantics instead of implicit fallback drift.
Tests:
- Migration detection tests for legacy-only, new-only, and ambiguous layouts.
Risks:
- Silent duplication if both layouts are partially populated.
Rollback:
- Prefer fail-fast ambiguity policy over auto-merge.
5. Minimal Viable v1.0 Scope
Must-have for v1.0:
- Campaign-aware canonical session roots.
- Session manifest remains skip/resume authority.
- Introduce run manifests + run-local stage records.
- Immediate promotion from run-local outputs to canonical session outputs.
- Minimal downstream invalidation on forced upstream rerun.
- Archive/local semantic alignment with
current/*behavior preserved. - Backward-compatible analyze aliases (
processed_transcript,normalized_transcript,trimmed_transcript).
Nice-to-have / defer if risky:
- Full checksum-based stale detection graph.
- Broad manifest schema-version migration framework.
- Full artifact-registry rollout beyond analyze’s initial needs.
- Aggressive cleanup of all legacy fallback branches in one release.
6. Open Questions (with Recommendations)
- Should campaign be mandatory for all local path derivation immediately?
- Recommendation: yes for new layout writes; keep controlled read compatibility for legacy session-only roots during migration window.
- Session manifest location transition policy: auto-migrate vs explicit migrate command?
- Recommendation: if user base is small, prefer explicit fail-fast with actionable migration instructions to avoid silent split-brain state.
- Run manifest granularity: per-stage detailed records vs summary + references?
- Recommendation: start with summary + stage status/paths; avoid duplicating full session artifact state to keep write path simple.
- Invalidation marking: use
stalestatus now or clearsucceededmarkers?
- Recommendation: if invasive to propagate new status semantics quickly, clear/overwrite downstream success states first; add formal
staleusage in follow-up.
- Promotion timing: promote every stage immediately vs delayed at end of run?
- Recommendation: immediate per-stage promotion after validation (matches current idempotent skip expectations and simplifies resume behavior).
- Docs consistency order after implementation starts:
- Recommendation: update
README.mdandarchitecture.mdin lockstep with each migration step. Current conflict is explicit:- code/README/architecture still primarily describe session roots at
work/{session} docs/development/workspace.mddefineswork/{campaign}/{session}plus run manifests.
- code/README/architecture still primarily describe session roots at