cjm-transcript-decomp-core 0.0.1__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {cjm_transcript_decomp_core-0.0.1/cjm_transcript_decomp_core.egg-info → cjm_transcript_decomp_core-0.0.4}/PKG-INFO +9 -49
  2. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/README.md +5 -45
  3. cjm_transcript_decomp_core-0.0.4/cjm_transcript_decomp_core/__init__.py +1 -0
  4. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core/cli.py +60 -18
  5. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core/models.py +9 -2
  6. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core/pipeline.py +32 -4
  7. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4/cjm_transcript_decomp_core.egg-info}/PKG-INFO +9 -49
  8. cjm_transcript_decomp_core-0.0.4/cjm_transcript_decomp_core.egg-info/entry_points.txt +5 -0
  9. cjm_transcript_decomp_core-0.0.4/cjm_transcript_decomp_core.egg-info/requires.txt +5 -0
  10. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/pyproject.toml +4 -1
  11. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/tests/test_cli.py +14 -1
  12. cjm_transcript_decomp_core-0.0.1/cjm_transcript_decomp_core/__init__.py +0 -1
  13. cjm_transcript_decomp_core-0.0.1/cjm_transcript_decomp_core.egg-info/entry_points.txt +0 -2
  14. cjm_transcript_decomp_core-0.0.1/cjm_transcript_decomp_core.egg-info/requires.txt +0 -5
  15. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/LICENSE +0 -0
  16. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/MANIFEST.in +0 -0
  17. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core/alignment.py +0 -0
  18. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core/graph.py +0 -0
  19. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core.egg-info/SOURCES.txt +0 -0
  20. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core.egg-info/dependency_links.txt +0 -0
  21. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/cjm_transcript_decomp_core.egg-info/top_level.txt +0 -0
  22. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/setup.cfg +0 -0
  23. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/tests/test_alignment.py +0 -0
  24. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/tests/test_graph.py +0 -0
  25. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/tests/test_models.py +0 -0
  26. {cjm_transcript_decomp_core-0.0.1 → cjm_transcript_decomp_core-0.0.4}/tests/test_pipeline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-decomp-core
3
- Version: 0.0.1
3
+ Version: 0.0.4
4
4
  Summary: A frontend-agnostic core for the transcript decomposition workflow — composes isolated capability workers (forced alignment, VAD, graph storage) into a headless pipeline that decomposes transcription run manifests into a VAD-aligned context-graph spine with traceable provenance, and a CLI as its first driver.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -10,10 +10,10 @@ Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: cjm_substrate>=0.0.51
14
- Requires-Dist: cjm_context_graph_primitives>=0.0.12
13
+ Requires-Dist: cjm_substrate>=0.0.57
14
+ Requires-Dist: cjm_context_graph_primitives>=0.0.15
15
15
  Requires-Dist: cjm_capability_primitives>=0.0.10
16
- Requires-Dist: cjm-context-graph-layer>=0.0.8
16
+ Requires-Dist: cjm-context-graph-layer>=0.0.11
17
17
  Requires-Dist: cjm-transcript-graph-schema>=0.0.7
18
18
  Dynamic: license-file
19
19
 
@@ -26,15 +26,10 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
26
26
  ## Modules
27
27
 
28
28
  - **`cjm_transcript_decomp_core.alignment`** — Pure forced-alignment logic (no capability calls): map FA words back to character spans in the original text, assign words to VAD chunks by timestamp, and build one text segment per VAD chunk. Extracted from the page-centric ForcedAlignmentService (Tier-1 logic).
29
- - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend. Ships in-package as the cjm-transcript-decomp-core console script so the driver can never skew from the core. GUI presentation drivers come later and consume the same pipeline module; they never reimplement it (CLI-first / headless-core principle).
29
+ - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend.
30
30
  - **`cjm_transcript_decomp_core.graph`** — Graph-spine EXTENSION + skeptical-lens verification (stage 5, CR-18 revolution 2). Decomp no longer creates a Document: it RECOMPUTES the transcription-emitted root's deterministic node ids from the consumed manifest (no search), verifies the root exists, and attaches the fine Segment spine under the existing AudioSegment nodes — PART_OF to the owning rendition, STARTS_WITH per rendition (the coarse-seam jump anchor), source-wide NEXT. Each Segment carries the audio TimeSlice ref plus per-transcriber CharSlice refs into the Transcript nodes (the D4/P10 framing, finally expressible). Commit goes through the layer's idempotent extend_graph.
31
31
  - **`cjm_transcript_decomp_core.models`** — Lean data shapes for the transcript-decomposition pipeline: in-core mirrors of the forced-alignment / VAD / text DTOs (no FastHTML deps), run configuration, the committed graph-segment carrier, and the decomposition run manifest (proto-bundle).
32
32
  - **`cjm_transcript_decomp_core.pipeline`** — The headless decomposition pipeline (stage 5: decomp is an EXTENDER). Load a transcription run manifest, verify the transcription-emitted graph root exists (the graph begins at transcription), then per source per pipeline-segment run VAD + per-transcriber forced alignment, build one aligned segment per VAD chunk with per-transcriber text variants, and attach the fine spine under the existing AudioSegment nodes via the layer's idempotent extend_graph — with HITL approval seams between alignment, commit, and the next source.
33
- - **`tests.test_alignment`** — Tests for cjm_transcript_decomp_core.alignment — pure forced-alignment logic.
34
- - **`tests.test_graph`** — Tests for cjm_transcript_decomp_core.graph — fine-spine extension payload.
35
- - **`tests.test_models`** — Tests for cjm_transcript_decomp_core.models — decomposition data shapes.
36
- - **`tests_manual.measure_cold_fanout_overlap_e2e`** — Cold-run M×(VAD ∥ T×FA) fan-out overlap measurement (stage-5 closeout;
37
- - **`tests_manual.validate_stage7_volume_journal_e2e`** — Stage-7 stress part 6 — volume regression on the REAL corpus.
38
33
 
39
34
  ## API
40
35
 
@@ -50,7 +45,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
50
45
  - `build_parser` _function_ — Build the CLI parser (subcommands: run).
51
46
  - `load_capabilities` _function_ — Discover manifests + load each requested capability (default instance).
52
47
  - `main` _function_ — CLI entry point (console script: `cjm-transcript-decomp-core`).
53
- - `run_command` _function_ — Execute the `run` subcommand: extend a transcription-emitted root with the fine spine.
48
+ - `run_command` _function_ — Execute the `run` subcommand: extend transcription-run manifest(s) with the fine spine.
54
49
 
55
50
  ### `cjm_transcript_decomp_core.graph`
56
51
 
@@ -76,6 +71,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
76
71
  - `build_alignment_composition` _function_ — Build the whole-source M×(VAD ∥ T×FA) composition (D8 fan-in, stage-5 variants).
77
72
  - `collect_capability_info` _function_ — Record capability identity + data-DB pointers for the run manifest (provenance).
78
73
  - `confirm_seam` _function_ — HITL approval seam in its cheapest viable form (log + optional CLI prompt).
74
+ - `decomp_replay_handlers` _function_ — The decomp core's replay vocabulary (DEC 426658f1, replay stays DOMAIN-OWNED).
79
75
  - `decompose_source` _function_ — Decompose one source into aligned fine segments with per-transcriber variants.
80
76
  - `fa_words_from_result` _function_ — Normalize a typed forced-alignment result into FA words (pure; stage 3).
81
77
  - `load_source_manifest` _function_ — Load + lightly validate a transcription-core run manifest.
@@ -83,43 +79,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
83
79
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, and return its result (raise on failure).
84
80
  - `vad_chunks_from_result` _function_ — Normalize a typed VAD result into segment-local VAD chunks.
85
81
 
86
- ### `tests.test_alignment`
87
-
88
- - `test_assign_words_to_chunks_by_timestamp` _function_
89
- - `test_build_segments_per_chunk` _function_
90
- - `test_empty_chunk_yields_empty_segment_warning` _function_
91
- - `test_map_fa_words_to_text_spans` _function_
92
-
93
- ### `tests.test_graph`
94
-
95
- - `test_edge_topology_spans_coarse_boundary` _function_
96
- - `test_extension_payload_deterministic_ids` _function_
97
- - `test_preprocessed_manifest_distinct_renditions_same_boundaries` _function_
98
- - `test_provenance_refs_and_text_from` _function_
99
- - `test_resolve_root_ids_recomputation` _function_
100
-
101
- ### `tests.test_models`
102
-
103
- - `test_faword_from_wire_tolerates_extra_keys` _function_
104
- - `test_manifest_save_round_trip` _function_
105
- - `test_manifest_shape_and_run_id` _function_
106
- - `test_stage5_segment_shape` _function_
107
- - `test_vad_chunk_duration` _function_
108
-
109
- ### `tests_manual.measure_cold_fanout_overlap_e2e`
110
-
111
- - `lane_intervals` _function_ — (start, end) UTC intervals for completed jobs of one instance.
112
- - `main` _function_
113
- - `merge` _function_ — Merge overlapping intervals so a side can't double-count itself.
114
- - `overlap_seconds` _function_ — Total seconds where any interval of A overlaps any interval of B.
115
- - `self_overlap_max` _function_ — Max simultaneously in-flight jobs within one lane + seconds at depth >= 2.
116
-
117
- ### `tests_manual.validate_stage7_volume_journal_e2e`
118
-
119
- - `checkpoint_copy` _function_ — Checkpoint-then-copy (the stage-3 G3 backup discipline — never copy
120
- - `journal_rows` _function_
121
- - `main` _function_
122
-
123
82
  ## Dependencies
124
83
 
125
- **Depends on:** `cjm-substrate`
84
+ **Depends on:** `cjm-capability-primitives`, `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
85
+ **Used by:** `cjm-transcript-decomp-tui`
@@ -7,15 +7,10 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
7
7
  ## Modules
8
8
 
9
9
  - **`cjm_transcript_decomp_core.alignment`** — Pure forced-alignment logic (no capability calls): map FA words back to character spans in the original text, assign words to VAD chunks by timestamp, and build one text segment per VAD chunk. Extracted from the page-centric ForcedAlignmentService (Tier-1 logic).
10
- - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend. Ships in-package as the cjm-transcript-decomp-core console script so the driver can never skew from the core. GUI presentation drivers come later and consume the same pipeline module; they never reimplement it (CLI-first / headless-core principle).
10
+ - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend.
11
11
  - **`cjm_transcript_decomp_core.graph`** — Graph-spine EXTENSION + skeptical-lens verification (stage 5, CR-18 revolution 2). Decomp no longer creates a Document: it RECOMPUTES the transcription-emitted root's deterministic node ids from the consumed manifest (no search), verifies the root exists, and attaches the fine Segment spine under the existing AudioSegment nodes — PART_OF to the owning rendition, STARTS_WITH per rendition (the coarse-seam jump anchor), source-wide NEXT. Each Segment carries the audio TimeSlice ref plus per-transcriber CharSlice refs into the Transcript nodes (the D4/P10 framing, finally expressible). Commit goes through the layer's idempotent extend_graph.
12
12
  - **`cjm_transcript_decomp_core.models`** — Lean data shapes for the transcript-decomposition pipeline: in-core mirrors of the forced-alignment / VAD / text DTOs (no FastHTML deps), run configuration, the committed graph-segment carrier, and the decomposition run manifest (proto-bundle).
13
13
  - **`cjm_transcript_decomp_core.pipeline`** — The headless decomposition pipeline (stage 5: decomp is an EXTENDER). Load a transcription run manifest, verify the transcription-emitted graph root exists (the graph begins at transcription), then per source per pipeline-segment run VAD + per-transcriber forced alignment, build one aligned segment per VAD chunk with per-transcriber text variants, and attach the fine spine under the existing AudioSegment nodes via the layer's idempotent extend_graph — with HITL approval seams between alignment, commit, and the next source.
14
- - **`tests.test_alignment`** — Tests for cjm_transcript_decomp_core.alignment — pure forced-alignment logic.
15
- - **`tests.test_graph`** — Tests for cjm_transcript_decomp_core.graph — fine-spine extension payload.
16
- - **`tests.test_models`** — Tests for cjm_transcript_decomp_core.models — decomposition data shapes.
17
- - **`tests_manual.measure_cold_fanout_overlap_e2e`** — Cold-run M×(VAD ∥ T×FA) fan-out overlap measurement (stage-5 closeout;
18
- - **`tests_manual.validate_stage7_volume_journal_e2e`** — Stage-7 stress part 6 — volume regression on the REAL corpus.
19
14
 
20
15
  ## API
21
16
 
@@ -31,7 +26,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
31
26
  - `build_parser` _function_ — Build the CLI parser (subcommands: run).
32
27
  - `load_capabilities` _function_ — Discover manifests + load each requested capability (default instance).
33
28
  - `main` _function_ — CLI entry point (console script: `cjm-transcript-decomp-core`).
34
- - `run_command` _function_ — Execute the `run` subcommand: extend a transcription-emitted root with the fine spine.
29
+ - `run_command` _function_ — Execute the `run` subcommand: extend transcription-run manifest(s) with the fine spine.
35
30
 
36
31
  ### `cjm_transcript_decomp_core.graph`
37
32
 
@@ -57,6 +52,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
57
52
  - `build_alignment_composition` _function_ — Build the whole-source M×(VAD ∥ T×FA) composition (D8 fan-in, stage-5 variants).
58
53
  - `collect_capability_info` _function_ — Record capability identity + data-DB pointers for the run manifest (provenance).
59
54
  - `confirm_seam` _function_ — HITL approval seam in its cheapest viable form (log + optional CLI prompt).
55
+ - `decomp_replay_handlers` _function_ — The decomp core's replay vocabulary (DEC 426658f1, replay stays DOMAIN-OWNED).
60
56
  - `decompose_source` _function_ — Decompose one source into aligned fine segments with per-transcriber variants.
61
57
  - `fa_words_from_result` _function_ — Normalize a typed forced-alignment result into FA words (pure; stage 3).
62
58
  - `load_source_manifest` _function_ — Load + lightly validate a transcription-core run manifest.
@@ -64,43 +60,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
64
60
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, and return its result (raise on failure).
65
61
  - `vad_chunks_from_result` _function_ — Normalize a typed VAD result into segment-local VAD chunks.
66
62
 
67
- ### `tests.test_alignment`
68
-
69
- - `test_assign_words_to_chunks_by_timestamp` _function_
70
- - `test_build_segments_per_chunk` _function_
71
- - `test_empty_chunk_yields_empty_segment_warning` _function_
72
- - `test_map_fa_words_to_text_spans` _function_
73
-
74
- ### `tests.test_graph`
75
-
76
- - `test_edge_topology_spans_coarse_boundary` _function_
77
- - `test_extension_payload_deterministic_ids` _function_
78
- - `test_preprocessed_manifest_distinct_renditions_same_boundaries` _function_
79
- - `test_provenance_refs_and_text_from` _function_
80
- - `test_resolve_root_ids_recomputation` _function_
81
-
82
- ### `tests.test_models`
83
-
84
- - `test_faword_from_wire_tolerates_extra_keys` _function_
85
- - `test_manifest_save_round_trip` _function_
86
- - `test_manifest_shape_and_run_id` _function_
87
- - `test_stage5_segment_shape` _function_
88
- - `test_vad_chunk_duration` _function_
89
-
90
- ### `tests_manual.measure_cold_fanout_overlap_e2e`
91
-
92
- - `lane_intervals` _function_ — (start, end) UTC intervals for completed jobs of one instance.
93
- - `main` _function_
94
- - `merge` _function_ — Merge overlapping intervals so a side can't double-count itself.
95
- - `overlap_seconds` _function_ — Total seconds where any interval of A overlaps any interval of B.
96
- - `self_overlap_max` _function_ — Max simultaneously in-flight jobs within one lane + seconds at depth >= 2.
97
-
98
- ### `tests_manual.validate_stage7_volume_journal_e2e`
99
-
100
- - `checkpoint_copy` _function_ — Checkpoint-then-copy (the stage-3 G3 backup discipline — never copy
101
- - `journal_rows` _function_
102
- - `main` _function_
103
-
104
63
  ## Dependencies
105
64
 
106
- **Depends on:** `cjm-substrate`
65
+ **Depends on:** `cjm-capability-primitives`, `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
66
+ **Used by:** `cjm-transcript-decomp-tui`
@@ -0,0 +1 @@
1
+ __version__ = "0.0.4"
@@ -10,9 +10,11 @@ Prerequisite runtime (once, from the repo root):
10
10
  cjm-ctl --cjm-config cjm.yaml setup-runtime
11
11
  cjm-ctl --cjm-config cjm.yaml install-all --capabilities capabilities_test.yaml --force
12
12
 
13
- Then decompose a transcription-core run manifest:
13
+ Then decompose transcription-core run manifest(s) — a multi-manifest batch
14
+ shares ONE loaded capability stack (models load once, per-manifest runs):
14
15
 
15
16
  cjm-transcript-decomp-core run path/to/transcription-run.json --yes
17
+ cjm-transcript-decomp-core run runs/run_a.json runs/run_b.json --yes
16
18
  # GPU runs: opt into CR-7 GPU subtree attribution
17
19
  cjm-transcript-decomp-core run run.json --yes --sysmon-capability cjm-capability-monitor-nvidia
18
20
  """
@@ -21,11 +23,13 @@ import argparse
21
23
  import asyncio
22
24
  import getpass
23
25
  import logging
26
+ import os
24
27
  from pathlib import Path
25
28
  from typing import Any, Dict, List, Optional
26
29
 
27
30
  from cjm_substrate.core.manager import CapabilityManager
28
31
  from cjm_substrate.core.queue import JobQueue
32
+ from cjm_substrate.core.workspace import resolve_workspace
29
33
  from cjm_transcript_decomp_core.models import DecompConfig
30
34
  from cjm_transcript_decomp_core.pipeline import load_source_manifest, run_decomp
31
35
 
@@ -41,7 +45,8 @@ def build_parser() -> argparse.ArgumentParser: # Configured CLI parser
41
45
  sub = parser.add_subparsers(dest="command", required=True)
42
46
 
43
47
  run = sub.add_parser("run", help="Extend a transcription-emitted graph root with the fine spine")
44
- run.add_argument("manifest", help="Transcription-core run manifest JSON (the source to decompose)")
48
+ run.add_argument("manifests", nargs="+",
49
+ help="Transcription-core run manifest JSON(s) — a batch shares ONE loaded capability stack")
45
50
  run.add_argument("--manifests-dir", default=".cjm/manifests", help="Capability manifests directory")
46
51
  run.add_argument("--vad-capability", default="cjm-capability-silero-vad", help="VAD capability name")
47
52
  run.add_argument("--fa-capability", default="cjm-capability-qwen3-forced-aligner", help="Forced-alignment capability name")
@@ -56,7 +61,15 @@ def build_parser() -> argparse.ArgumentParser: # Configured CLI parser
56
61
  run.add_argument("--language", default="English", help="Forced-alignment language")
57
62
  run.add_argument("--force", action="store_true", help="Bypass capability-side caches (VAD + FA)")
58
63
  run.add_argument("-y", "--yes", action="store_true", help="Auto-accept HITL seams (headless mode)")
59
- run.add_argument("--output", default=None, help="Decomp-manifest output path (default: runs/<run_id>.json)")
64
+ run.add_argument("--output", default=None, help="Decomp-manifest output path (single-manifest runs only; default: <output-dir>/<run_id>.json)")
65
+ run.add_argument("--output-dir", default=None,
66
+ help="Decomp-manifest output directory (callers with a shared runs dir "
67
+ "pass it so manifests land beside their sources; default: the "
68
+ "workspace's runs/ when one is active, else runs/ under the cwd)")
69
+ run.add_argument("--workspace", default=None,
70
+ help="Workspace root (5daadfc4; default: CJM_WORKSPACE env, else upward walk "
71
+ "from cwd). Supplies the --output-dir default and is exported so "
72
+ "substrate config + capability workers resolve workspace-scoped paths")
60
73
  run.add_argument("--actor", default=None,
61
74
  help="Journal attribution for who/what initiated this run (default: cli:<username>)")
62
75
  run.add_argument("-v", "--verbose", action="store_true", help="DEBUG-level logging")
@@ -85,11 +98,31 @@ def load_capabilities(
85
98
 
86
99
  async def run_command(
87
100
  args: argparse.Namespace, # Parsed CLI arguments for the `run` subcommand
88
- ) -> int: # Process exit code (0 = all sources extended + verified)
89
- """Execute the `run` subcommand: extend a transcription-emitted root with the fine spine."""
90
- manifest_path = str(Path(args.manifest).resolve())
91
- if not Path(manifest_path).exists():
92
- raise SystemExit(f"source manifest not found: {manifest_path}")
101
+ ) -> int: # Process exit code (0 = every manifest fully extended + verified)
102
+ """Execute the `run` subcommand: extend transcription-run manifest(s) with the fine spine.
103
+
104
+ Batch shape (decomp-TUI demand, work item 0ff6bf0f): N manifests ride ONE
105
+ capability stack — the models load once and every manifest reuses them,
106
+ where N CLI invocations would pay N model loads. Each manifest keeps its
107
+ own run_id, decomp manifest, and RUN_STARTED/RUN_FINISHED journal bracket,
108
+ so a batch member is indistinguishable from a solo run downstream. A failed
109
+ member records and the batch continues (unattended queueing must not sink
110
+ the rest); the exit code aggregates across members.
111
+ """
112
+ # 5daadfc4 workspace: resolve BEFORE any substrate config loads; export so
113
+ # the process tree (substrate config, capability workers) is workspace-scoped.
114
+ ws = resolve_workspace(explicit=getattr(args, "workspace", None))
115
+ if ws is not None:
116
+ os.environ["CJM_WORKSPACE"] = str(ws.root)
117
+ output_dir = (Path(args.output_dir) if args.output_dir
118
+ else (ws.runs_dir if ws is not None else Path("runs")))
119
+ manifest_paths = [str(Path(m).resolve()) for m in args.manifests]
120
+ missing = [m for m in manifest_paths if not Path(m).exists()]
121
+ if missing:
122
+ raise SystemExit(f"source manifest(s) not found: {', '.join(missing)}")
123
+ if args.output and len(manifest_paths) > 1:
124
+ raise SystemExit("--output names ONE decomp manifest — omit it for batch runs "
125
+ "(each lands at runs/<run_id>.json)")
93
126
 
94
127
  cfg = DecompConfig(
95
128
  vad_capability=args.vad_capability,
@@ -118,11 +151,28 @@ async def run_command(
118
151
 
119
152
  queue = JobQueue(deps=manager, sysmon_capability_name=args.sysmon_capability)
120
153
  await queue.start()
154
+ all_ok = True
121
155
  try:
122
156
  # CR-14 follow-up: actor attribution (operator identity by default;
123
157
  # agents/services pass --actor explicitly).
124
158
  actor = args.actor or f"cli:{getpass.getuser()}"
125
- manifest = await run_decomp(manager, queue, cfg, manifest_path, actor=actor)
159
+ for mp in manifest_paths:
160
+ try:
161
+ manifest = await run_decomp(manager, queue, cfg, mp, actor=actor)
162
+ except Exception as e: # One member's failure must not sink the batch
163
+ logger.error(f"decomp failed for {mp}: {e}")
164
+ print(f"FAILED {mp}: {e}")
165
+ all_ok = False
166
+ continue
167
+ out = (Path(args.output) if args.output
168
+ else output_dir / f"{manifest.run_id}.json")
169
+ manifest.save(out, workspace=ws)
170
+ n_manifest_sources = len(load_source_manifest(mp).get("sources", []) or [])
171
+ n_sources = len(manifest.sources)
172
+ n_segs = sum(s.segment_count for s in manifest.sources)
173
+ print(f"decomp manifest: {out}")
174
+ print(f"sources extended: {n_sources}/{n_manifest_sources} segment nodes: {n_segs}")
175
+ all_ok = all_ok and (n_sources == n_manifest_sources)
126
176
  finally:
127
177
  await queue.stop()
128
178
  for iid in reversed(load_order): # Reverse load order; the monitor unloads last
@@ -130,15 +180,7 @@ async def run_command(
130
180
  manager.unload_capability(iid)
131
181
  except Exception as e: # Best-effort teardown; never mask the run's outcome
132
182
  logger.warning(f"unload {iid} failed: {e}")
133
-
134
- out = Path(args.output) if args.output else Path("runs") / f"{manifest.run_id}.json"
135
- manifest.save(out)
136
- n_manifest_sources = len(load_source_manifest(manifest_path).get("sources", []) or [])
137
- n_sources = len(manifest.sources)
138
- n_segs = sum(s.segment_count for s in manifest.sources)
139
- print(f"decomp manifest: {out}")
140
- print(f"sources extended: {n_sources}/{n_manifest_sources} segment nodes: {n_segs}")
141
- return 0 if n_sources == n_manifest_sources else 1
183
+ return 0 if all_ok else 1
142
184
 
143
185
 
144
186
  def main(
@@ -7,6 +7,8 @@ from dataclasses import asdict, dataclass, field
7
7
  from pathlib import Path
8
8
  from typing import Any, Dict, List, Optional, Union
9
9
 
10
+ from cjm_substrate.core.workspace import relativize_recorded
11
+
10
12
 
11
13
  @dataclass
12
14
  class FAWord:
@@ -173,11 +175,16 @@ class DecompManifest:
173
175
  def save(
174
176
  self,
175
177
  path: Union[str, Path], # Destination JSON file (parent dirs created)
178
+ workspace=None, # Active Workspace; owned paths record as ${WS}/<rel> (5daadfc4 rung f)
176
179
  ) -> Path: # The written path
177
- """Write the manifest as pretty-printed JSON."""
180
+ """Write the manifest as pretty-printed JSON.
181
+
182
+ With `workspace`, recorded paths under its root take the ${WS}/ token
183
+ form (relativize_recorded), so the manifest relocates with the
184
+ workspace; readers resolve via resolve_recorded_tree at load."""
178
185
  out = Path(path)
179
186
  out.parent.mkdir(parents=True, exist_ok=True)
180
- out.write_text(json.dumps(self.to_dict(), indent=2))
187
+ out.write_text(json.dumps(relativize_recorded(self.to_dict(), workspace), indent=2))
181
188
  return out
182
189
 
183
190
 
@@ -10,12 +10,14 @@ from typing import Any, Dict, List, Optional, Tuple
10
10
  from cjm_capability_primitives.forced_alignment import ForcedAlignResult
11
11
  from cjm_capability_primitives.vad import VADResult
12
12
  from cjm_context_graph_layer.declare import Derivation, derivation_to_graph
13
- from cjm_context_graph_layer.ops import extend_graph, graph_task
13
+ from cjm_context_graph_layer.journal import journal_extend, sidecar_journal_path, wires_handlers
14
+ from cjm_context_graph_layer.ops import graph_task
14
15
  from cjm_substrate.core.empirical_store import compute_config_hash
15
16
  from cjm_substrate.core.journal_store import JournalEvent, SubstrateEventType
16
17
  from cjm_substrate.core.manager import CapabilityManager
17
18
  from cjm_substrate.core.ports import Composition, CompositionNode, NodeState
18
19
  from cjm_substrate.core.queue import JobQueue, JobStatus
20
+ from cjm_substrate.core.workspace import resolve_recorded_tree
19
21
  from cjm_transcript_decomp_core.alignment import (assign_words_to_chunks,
20
22
  build_segments_from_alignment,
21
23
  map_fa_words_to_text, tier1_alignment_checks)
@@ -58,8 +60,11 @@ def load_source_manifest(
58
60
 
59
61
  Consumed as untyped JSON — the format/version tags are the only interchange
60
62
  contract (no shared schema type across cores; manifest-as-interchange, CR-20).
63
+ ${WS}/ recorded paths (5daadfc4 rung f) resolve to absolute here, anchored
64
+ at the manifest's own location (active workspace as fallback), so all
65
+ downstream consumers keep seeing absolute paths.
61
66
  """
62
- data = json.loads(Path(path).read_text())
67
+ data = resolve_recorded_tree(json.loads(Path(path).read_text()), Path(path))
63
68
  fmt = data.get("format", "")
64
69
  if "transcription-core" not in fmt:
65
70
  logger.warning(f"unexpected source manifest format: {fmt!r} (continuing)")
@@ -380,6 +385,10 @@ async def run_decomp(
380
385
  capabilities=collect_capability_info(manager, [cfg.vad_capability, cfg.fa_capability, cfg.graph_capability]),
381
386
  )
382
387
  vad_config_hash = str((manifest.capabilities.get(cfg.vad_capability) or {}).get("config_hash") or "")
388
+ # Pipeline writes append through to the graph db's SIDECAR journal (DEC ccbab9f5 /
389
+ # finding 4219da27): unjournaled ingestion would erode db-rebuildability.
390
+ graph_db_path = (manifest.capabilities.get(cfg.graph_capability) or {}).get("db_path")
391
+ graph_journal_path = sidecar_journal_path(str(graph_db_path)) if graph_db_path else None
383
392
 
384
393
  sources = src.get("sources", []) or []
385
394
  status = "completed"
@@ -419,7 +428,12 @@ async def run_decomp(
419
428
  status = "aborted"
420
429
  break
421
430
 
422
- res = await extend_graph(queue, cfg.graph_capability, nodes, edges)
431
+ res = await journal_extend(queue, cfg.graph_capability, nodes, edges,
432
+ journal_path=graph_journal_path, verb="spine-extension",
433
+ actor="pipeline:cjm-transcript-decomp-core", run=run_id,
434
+ args={"source_id": ids["source"],
435
+ "segments": len(ids["segments"]),
436
+ "text_from": text_from})
423
437
  logger.info(f"[src {i}] extension: +{res.nodes_added} node(s) "
424
438
  f"({res.nodes_verified} verified present), +{res.edges_added} edge(s) "
425
439
  f"({res.edges_existing} existing)")
@@ -431,7 +445,10 @@ async def run_decomp(
431
445
  "text_from": text_from},
432
446
  )
433
447
  dn, de = derivation_to_graph(d)
434
- await extend_graph(queue, cfg.graph_capability, [dn], de)
448
+ await journal_extend(queue, cfg.graph_capability, [dn], de,
449
+ journal_path=graph_journal_path, verb="derivation",
450
+ actor="pipeline:cjm-transcript-decomp-core", run=run_id,
451
+ args={"method": "alignment-fold/v1"})
435
452
 
436
453
  # Fine spine hangs under the run's renditions — verify scopes there.
437
454
  vr = await verify_source(queue, cfg.graph_capability, ids["source"], ids["renditions"])
@@ -473,3 +490,14 @@ async def run_decomp(
473
490
  "segments": sum(s.segment_count for s in manifest.sources),
474
491
  })
475
492
  return manifest
493
+
494
+
495
+ def decomp_replay_handlers() -> Dict[str, Any]: # verb -> async handler(queue, graph_id, op)
496
+ """The decomp core's replay vocabulary (DEC 426658f1, replay stays DOMAIN-OWNED).
497
+
498
+ Exported through the `cjm_context_graph_layer.replay` entry-point group and
499
+ unioned by `composed_replay_handlers`. Both verbs are `journal_extend` ops —
500
+ wire-carrying by construction — so they register the layer's shared
501
+ `apply_wires` (identity-comparable across cores: transcription also emits
502
+ `derivation`, and the shared handler keeps that collision legal)."""
503
+ return wires_handlers("spine-extension", "derivation")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-decomp-core
3
- Version: 0.0.1
3
+ Version: 0.0.4
4
4
  Summary: A frontend-agnostic core for the transcript decomposition workflow — composes isolated capability workers (forced alignment, VAD, graph storage) into a headless pipeline that decomposes transcription run manifests into a VAD-aligned context-graph spine with traceable provenance, and a CLI as its first driver.
5
5
  Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
6
6
  License: Apache-2.0
@@ -10,10 +10,10 @@ Classifier: Programming Language :: Python :: 3 :: Only
10
10
  Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: cjm_substrate>=0.0.51
14
- Requires-Dist: cjm_context_graph_primitives>=0.0.12
13
+ Requires-Dist: cjm_substrate>=0.0.57
14
+ Requires-Dist: cjm_context_graph_primitives>=0.0.15
15
15
  Requires-Dist: cjm_capability_primitives>=0.0.10
16
- Requires-Dist: cjm-context-graph-layer>=0.0.8
16
+ Requires-Dist: cjm-context-graph-layer>=0.0.11
17
17
  Requires-Dist: cjm-transcript-graph-schema>=0.0.7
18
18
  Dynamic: license-file
19
19
 
@@ -26,15 +26,10 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
26
26
  ## Modules
27
27
 
28
28
  - **`cjm_transcript_decomp_core.alignment`** — Pure forced-alignment logic (no capability calls): map FA words back to character spans in the original text, assign words to VAD chunks by timestamp, and build one text segment per VAD chunk. Extracted from the page-centric ForcedAlignmentService (Tier-1 logic).
29
- - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend. Ships in-package as the cjm-transcript-decomp-core console script so the driver can never skew from the core. GUI presentation drivers come later and consume the same pipeline module; they never reimplement it (CLI-first / headless-core principle).
29
+ - **`cjm_transcript_decomp_core.cli`** — The CLI driver — the decomposition core's first (and currently only) frontend.
30
30
  - **`cjm_transcript_decomp_core.graph`** — Graph-spine EXTENSION + skeptical-lens verification (stage 5, CR-18 revolution 2). Decomp no longer creates a Document: it RECOMPUTES the transcription-emitted root's deterministic node ids from the consumed manifest (no search), verifies the root exists, and attaches the fine Segment spine under the existing AudioSegment nodes — PART_OF to the owning rendition, STARTS_WITH per rendition (the coarse-seam jump anchor), source-wide NEXT. Each Segment carries the audio TimeSlice ref plus per-transcriber CharSlice refs into the Transcript nodes (the D4/P10 framing, finally expressible). Commit goes through the layer's idempotent extend_graph.
31
31
  - **`cjm_transcript_decomp_core.models`** — Lean data shapes for the transcript-decomposition pipeline: in-core mirrors of the forced-alignment / VAD / text DTOs (no FastHTML deps), run configuration, the committed graph-segment carrier, and the decomposition run manifest (proto-bundle).
32
32
  - **`cjm_transcript_decomp_core.pipeline`** — The headless decomposition pipeline (stage 5: decomp is an EXTENDER). Load a transcription run manifest, verify the transcription-emitted graph root exists (the graph begins at transcription), then per source per pipeline-segment run VAD + per-transcriber forced alignment, build one aligned segment per VAD chunk with per-transcriber text variants, and attach the fine spine under the existing AudioSegment nodes via the layer's idempotent extend_graph — with HITL approval seams between alignment, commit, and the next source.
33
- - **`tests.test_alignment`** — Tests for cjm_transcript_decomp_core.alignment — pure forced-alignment logic.
34
- - **`tests.test_graph`** — Tests for cjm_transcript_decomp_core.graph — fine-spine extension payload.
35
- - **`tests.test_models`** — Tests for cjm_transcript_decomp_core.models — decomposition data shapes.
36
- - **`tests_manual.measure_cold_fanout_overlap_e2e`** — Cold-run M×(VAD ∥ T×FA) fan-out overlap measurement (stage-5 closeout;
37
- - **`tests_manual.validate_stage7_volume_journal_e2e`** — Stage-7 stress part 6 — volume regression on the REAL corpus.
38
33
 
39
34
  ## API
40
35
 
@@ -50,7 +45,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
50
45
  - `build_parser` _function_ — Build the CLI parser (subcommands: run).
51
46
  - `load_capabilities` _function_ — Discover manifests + load each requested capability (default instance).
52
47
  - `main` _function_ — CLI entry point (console script: `cjm-transcript-decomp-core`).
53
- - `run_command` _function_ — Execute the `run` subcommand: extend a transcription-emitted root with the fine spine.
48
+ - `run_command` _function_ — Execute the `run` subcommand: extend transcription-run manifest(s) with the fine spine.
54
49
 
55
50
  ### `cjm_transcript_decomp_core.graph`
56
51
 
@@ -76,6 +71,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
76
71
  - `build_alignment_composition` _function_ — Build the whole-source M×(VAD ∥ T×FA) composition (D8 fan-in, stage-5 variants).
77
72
  - `collect_capability_info` _function_ — Record capability identity + data-DB pointers for the run manifest (provenance).
78
73
  - `confirm_seam` _function_ — HITL approval seam in its cheapest viable form (log + optional CLI prompt).
74
+ - `decomp_replay_handlers` _function_ — The decomp core's replay vocabulary (DEC 426658f1, replay stays DOMAIN-OWNED).
79
75
  - `decompose_source` _function_ — Decompose one source into aligned fine segments with per-transcriber variants.
80
76
  - `fa_words_from_result` _function_ — Normalize a typed forced-alignment result into FA words (pure; stage 3).
81
77
  - `load_source_manifest` _function_ — Load + lightly validate a transcription-core run manifest.
@@ -83,43 +79,7 @@ A frontend-agnostic core for the transcript decomposition workflow — composes
83
79
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, and return its result (raise on failure).
84
80
  - `vad_chunks_from_result` _function_ — Normalize a typed VAD result into segment-local VAD chunks.
85
81
 
86
- ### `tests.test_alignment`
87
-
88
- - `test_assign_words_to_chunks_by_timestamp` _function_
89
- - `test_build_segments_per_chunk` _function_
90
- - `test_empty_chunk_yields_empty_segment_warning` _function_
91
- - `test_map_fa_words_to_text_spans` _function_
92
-
93
- ### `tests.test_graph`
94
-
95
- - `test_edge_topology_spans_coarse_boundary` _function_
96
- - `test_extension_payload_deterministic_ids` _function_
97
- - `test_preprocessed_manifest_distinct_renditions_same_boundaries` _function_
98
- - `test_provenance_refs_and_text_from` _function_
99
- - `test_resolve_root_ids_recomputation` _function_
100
-
101
- ### `tests.test_models`
102
-
103
- - `test_faword_from_wire_tolerates_extra_keys` _function_
104
- - `test_manifest_save_round_trip` _function_
105
- - `test_manifest_shape_and_run_id` _function_
106
- - `test_stage5_segment_shape` _function_
107
- - `test_vad_chunk_duration` _function_
108
-
109
- ### `tests_manual.measure_cold_fanout_overlap_e2e`
110
-
111
- - `lane_intervals` _function_ — (start, end) UTC intervals for completed jobs of one instance.
112
- - `main` _function_
113
- - `merge` _function_ — Merge overlapping intervals so a side can't double-count itself.
114
- - `overlap_seconds` _function_ — Total seconds where any interval of A overlaps any interval of B.
115
- - `self_overlap_max` _function_ — Max simultaneously in-flight jobs within one lane + seconds at depth >= 2.
116
-
117
- ### `tests_manual.validate_stage7_volume_journal_e2e`
118
-
119
- - `checkpoint_copy` _function_ — Checkpoint-then-copy (the stage-3 G3 backup discipline — never copy
120
- - `journal_rows` _function_
121
- - `main` _function_
122
-
123
82
  ## Dependencies
124
83
 
125
- **Depends on:** `cjm-substrate`
84
+ **Depends on:** `cjm-capability-primitives`, `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
85
+ **Used by:** `cjm-transcript-decomp-tui`
@@ -0,0 +1,5 @@
1
+ [cjm_context_graph_layer.replay]
2
+ decomp = cjm_transcript_decomp_core.pipeline:decomp_replay_handlers
3
+
4
+ [console_scripts]
5
+ cjm-transcript-decomp-core = cjm_transcript_decomp_core.cli:main
@@ -0,0 +1,5 @@
1
+ cjm_substrate>=0.0.57
2
+ cjm_context_graph_primitives>=0.0.15
3
+ cjm_capability_primitives>=0.0.10
4
+ cjm-context-graph-layer>=0.0.11
5
+ cjm-transcript-graph-schema>=0.0.7
@@ -14,7 +14,7 @@ classifiers = [
14
14
  "Programming Language :: Python :: 3",
15
15
  "Programming Language :: Python :: 3 :: Only",
16
16
  ]
17
- dependencies = ['cjm_substrate>=0.0.51', 'cjm_context_graph_primitives>=0.0.12', 'cjm_capability_primitives>=0.0.10', 'cjm-context-graph-layer>=0.0.8', 'cjm-transcript-graph-schema>=0.0.7']
17
+ dependencies = ['cjm_substrate>=0.0.57', 'cjm_context_graph_primitives>=0.0.15', 'cjm_capability_primitives>=0.0.10', 'cjm-context-graph-layer>=0.0.11', 'cjm-transcript-graph-schema>=0.0.7']
18
18
 
19
19
  [project.urls]
20
20
  Repository = "https://github.com/cj-mills/cjm-transcript-decomp-core"
@@ -22,6 +22,9 @@ Repository = "https://github.com/cj-mills/cjm-transcript-decomp-core"
22
22
  [project.scripts]
23
23
  cjm-transcript-decomp-core = "cjm_transcript_decomp_core.cli:main"
24
24
 
25
+ [project.entry-points."cjm_context_graph_layer.replay"]
26
+ decomp = "cjm_transcript_decomp_core.pipeline:decomp_replay_handlers"
27
+
25
28
  [tool.setuptools.dynamic]
26
29
  version = {attr = "cjm_transcript_decomp_core.__version__"}
27
30
 
@@ -8,12 +8,16 @@ def test_run_defaults():
8
8
  p = build_parser()
9
9
  args = p.parse_args(["run", "m.json", "--yes", "--language", "English"])
10
10
  assert args.command == "run"
11
- assert args.manifest == "m.json"
11
+ assert args.manifests == ["m.json"] # single manifest = a batch of one
12
12
  assert args.yes is True
13
13
  assert args.fa_capability == "cjm-capability-qwen3-forced-aligner"
14
14
  assert args.graph_capability == "cjm-capability-graph-sqlite"
15
15
  assert args.text_from is None # authority defaults to the manifest's sole transcriber
16
16
  assert args.sysmon_capability is None
17
+ # 5daadfc4: None sentinel — run_command resolves the workspace's runs/ when
18
+ # one is active, else the legacy cwd-relative runs/; TUIs pin their browsed dir
19
+ assert args.output_dir is None
20
+ assert args.workspace is None
17
21
 
18
22
 
19
23
  def test_text_from_and_sysmon_flags():
@@ -22,3 +26,12 @@ def test_text_from_and_sysmon_flags():
22
26
  "--sysmon-capability", "cjm-capability-monitor-nvidia"])
23
27
  assert args.text_from == "cjm-capability-voxtral-hf"
24
28
  assert args.sysmon_capability == "cjm-capability-monitor-nvidia"
29
+
30
+
31
+ def test_batch_manifests():
32
+ # Batch shape (work item 0ff6bf0f): N manifests, one invocation, one stack.
33
+ p = build_parser()
34
+ args = p.parse_args(["run", "a.json", "b.json", "c.json", "--yes",
35
+ "--text-from", "cjm-capability-voxtral-hf"])
36
+ assert args.manifests == ["a.json", "b.json", "c.json"]
37
+ assert args.text_from == "cjm-capability-voxtral-hf"
@@ -1 +0,0 @@
1
- __version__ = "0.0.1"
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- cjm-transcript-decomp-core = cjm_transcript_decomp_core.cli:main
@@ -1,5 +0,0 @@
1
- cjm_substrate>=0.0.51
2
- cjm_context_graph_primitives>=0.0.12
3
- cjm_capability_primitives>=0.0.10
4
- cjm-context-graph-layer>=0.0.8
5
- cjm-transcript-graph-schema>=0.0.7