cjm-transcript-correction-core 0.0.1__tar.gz → 0.0.2__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_correction_core-0.0.1/cjm_transcript_correction_core.egg-info → cjm_transcript_correction_core-0.0.2}/PKG-INFO +16 -30
  2. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/README.md +14 -28
  3. cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core/__init__.py +1 -0
  4. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core/graph.py +60 -5
  5. cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core/journal.py +116 -0
  6. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core.egg-info}/PKG-INFO +16 -30
  7. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core.egg-info/SOURCES.txt +2 -0
  8. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core.egg-info/requires.txt +1 -1
  9. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/pyproject.toml +1 -1
  10. cjm_transcript_correction_core-0.0.2/tests/test_journal.py +36 -0
  11. cjm_transcript_correction_core-0.0.1/cjm_transcript_correction_core/__init__.py +0 -1
  12. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/LICENSE +0 -0
  13. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/MANIFEST.in +0 -0
  14. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core/cli.py +0 -0
  15. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core/models.py +0 -0
  16. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core/pipeline.py +0 -0
  17. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core/signals.py +0 -0
  18. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core.egg-info/dependency_links.txt +0 -0
  19. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core.egg-info/entry_points.txt +0 -0
  20. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/cjm_transcript_correction_core.egg-info/top_level.txt +0 -0
  21. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/setup.cfg +0 -0
  22. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/tests/test_cli.py +0 -0
  23. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/tests/test_graph.py +0 -0
  24. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/tests/test_models.py +0 -0
  25. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/tests/test_pipeline.py +0 -0
  26. {cjm_transcript_correction_core-0.0.1 → cjm_transcript_correction_core-0.0.2}/tests/test_signals.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-correction-core
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A frontend-agnostic core for the transcript correction workflow — the first downstream graph-extension core; composes the graph-storage capability worker into a headless pipeline that applies unified, non-destructive corrections (text, punctuation, segmentation) as a supersede-able overlay on a committed decomposition spine, recomputes the review worklist from deterministic signals, and exposes 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
@@ -11,7 +11,7 @@ Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: cjm_substrate>=0.0.51
14
- Requires-Dist: cjm_context_graph_primitives>=0.0.12
14
+ Requires-Dist: cjm_context_graph_primitives>=0.0.14
15
15
  Requires-Dist: cjm-context-graph-layer>=0.0.8
16
16
  Requires-Dist: cjm-transcript-graph-schema>=0.0.7
17
17
  Dynamic: license-file
@@ -26,12 +26,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
26
26
 
27
27
  - **`cjm_transcript_correction_core.cli`** — The CLI driver — the correction core's first (and currently only) frontend. run <decomp-manifest> corrects the committed spine in the decomp graph DB, pointing the graph worker at that shared DB via load-time config, with optional session resume/reopen; review runs the interactive text-correction loop (the cross-transcriber diff is intra-graph since stage 5).
28
28
  - **`cjm_transcript_correction_core.graph`** — The correction overlay's graph I/O: targeted (scale-shaped) reads of a committed spine via the graph-storage query action, construction of Correction / CorrectionSession nodes + CORRECTS / SUPERSEDES / DERIVED_FROM / REVIEWED edges, the in-core effective-spine projection (layer-0 + applied corrections), and commit through the job queue. Hand-rolled (revolution-1) = direct CR-18 spec material; append-only on layer-0 (never update/delete a Segment).
29
+ - **`cjm_transcript_correction_core.journal`**
29
30
  - **`cjm_transcript_correction_core.models`** — Overlay data shapes for the transcript-correction workflow: the Correction / CorrectionSession graph nodes + their relation registry, the read view of a committed spine segment, the worklist item, run configuration, and the correction run manifest (proto-bundle that chains decomp -> correction).
30
31
  - **`cjm_transcript_correction_core.pipeline`** — The headless correction workflow: load a decomp run manifest, resolve the shared graph DB, start/resume/reopen a CorrectionSession, recompute the worklist from deterministic signals + persisted review state, run the D14 empty-segment prune (first operation), and record a chainable correction run manifest — with a cheapest-form HITL approval seam.
31
32
  - **`cjm_transcript_correction_core.signals`** — Pure deterministic Tier-1 signal functions (no capability calls): empty-segment detection, bidirectional boundary punctuation/capitalization heuristics, forced-alignment coverage flags, positional cross-transcriber diff, and phonetic + edit-distance variant clustering. The worklist is recomputed from these each session; revolution-1 builds ZERO new capabilities.
32
- - **`tests.test_graph`** — Tests for cjm_transcript_correction_core.graph — overlay construction + effective spine.
33
- - **`tests.test_models`** — Tests for cjm_transcript_correction_core.models — correction overlay data shapes.
34
- - **`tests.test_signals`** — Tests for cjm_transcript_correction_core.signals — pure deterministic Tier-1 signals.
35
33
 
36
34
  ## API
37
35
 
@@ -46,10 +44,15 @@ A frontend-agnostic core for the transcript correction workflow — the first do
46
44
  ### `cjm_transcript_correction_core.graph`
47
45
 
48
46
  - `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
47
+ - `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
49
48
  - `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
49
+ - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
50
50
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
51
+ - `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
51
52
  - `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
53
+ - `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
52
54
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
55
+ - `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
53
56
  - `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
54
57
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
55
58
  - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition (typed count mode).
@@ -71,6 +74,13 @@ A frontend-agnostic core for the transcript correction workflow — the first do
71
74
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
72
75
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
73
76
 
77
+ ### `cjm_transcript_correction_core.journal`
78
+
79
+ - `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
80
+ - `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
81
+ - `segment_anchor` _function_ — The run-independent anchor stamped on every correction op (DEC ccbab9f5 point 5).
82
+ - `sidecar_journal_path` _function_ — The db's sidecar journal path (DEC ccbab9f5 point 3: placement is per-workflow,
83
+
74
84
  ### `cjm_transcript_correction_core.models`
75
85
 
76
86
  - `Correction` _class_ — A single non-destructive correction over the committed spine (overlay node).
@@ -105,31 +115,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
105
115
  - `phonetic_key` _function_ — Compute a coarse phonetic key for a word (groups like-sounding variants).
106
116
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
107
117
 
108
- ### `tests.test_graph`
109
-
110
- - `test_build_prune_correction` _function_
111
- - `test_build_text_correction_and_active_filter` _function_
112
- - `test_latest_wins_ordering_from_layer` _function_
113
- - `test_project_effective_spine_prune_replace_supersede` _function_
114
-
115
- ### `tests.test_models`
116
-
117
- - `test_correction_graph_node_mapping` _function_
118
- - `test_correction_relations_registry` _function_
119
- - `test_correction_session_node` _function_
120
- - `test_manifest_save_round_trip` _function_
121
- - `test_manifest_shape_and_run_id` _function_
122
- - `test_spine_segment_and_worklist_item` _function_
123
-
124
- ### `tests.test_signals`
125
-
126
- - `test_boundary_punct_caps_flags` _function_
127
- - `test_clustering_primitives` _function_
128
- - `test_compute_signal_flags_combined` _function_
129
- - `test_empty_and_coverage_flags` _function_
130
- - `test_variant_divergence_within_segment` _function_
131
-
132
118
  ## Dependencies
133
119
 
134
- **Depends on:** `cjm-substrate`
120
+ **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
135
121
  **Used by:** `cjm-transcript-correction-tui`
@@ -8,12 +8,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
8
8
 
9
9
  - **`cjm_transcript_correction_core.cli`** — The CLI driver — the correction core's first (and currently only) frontend. run <decomp-manifest> corrects the committed spine in the decomp graph DB, pointing the graph worker at that shared DB via load-time config, with optional session resume/reopen; review runs the interactive text-correction loop (the cross-transcriber diff is intra-graph since stage 5).
10
10
  - **`cjm_transcript_correction_core.graph`** — The correction overlay's graph I/O: targeted (scale-shaped) reads of a committed spine via the graph-storage query action, construction of Correction / CorrectionSession nodes + CORRECTS / SUPERSEDES / DERIVED_FROM / REVIEWED edges, the in-core effective-spine projection (layer-0 + applied corrections), and commit through the job queue. Hand-rolled (revolution-1) = direct CR-18 spec material; append-only on layer-0 (never update/delete a Segment).
11
+ - **`cjm_transcript_correction_core.journal`**
11
12
  - **`cjm_transcript_correction_core.models`** — Overlay data shapes for the transcript-correction workflow: the Correction / CorrectionSession graph nodes + their relation registry, the read view of a committed spine segment, the worklist item, run configuration, and the correction run manifest (proto-bundle that chains decomp -> correction).
12
13
  - **`cjm_transcript_correction_core.pipeline`** — The headless correction workflow: load a decomp run manifest, resolve the shared graph DB, start/resume/reopen a CorrectionSession, recompute the worklist from deterministic signals + persisted review state, run the D14 empty-segment prune (first operation), and record a chainable correction run manifest — with a cheapest-form HITL approval seam.
13
14
  - **`cjm_transcript_correction_core.signals`** — Pure deterministic Tier-1 signal functions (no capability calls): empty-segment detection, bidirectional boundary punctuation/capitalization heuristics, forced-alignment coverage flags, positional cross-transcriber diff, and phonetic + edit-distance variant clustering. The worklist is recomputed from these each session; revolution-1 builds ZERO new capabilities.
14
- - **`tests.test_graph`** — Tests for cjm_transcript_correction_core.graph — overlay construction + effective spine.
15
- - **`tests.test_models`** — Tests for cjm_transcript_correction_core.models — correction overlay data shapes.
16
- - **`tests.test_signals`** — Tests for cjm_transcript_correction_core.signals — pure deterministic Tier-1 signals.
17
15
 
18
16
  ## API
19
17
 
@@ -28,10 +26,15 @@ A frontend-agnostic core for the transcript correction workflow — the first do
28
26
  ### `cjm_transcript_correction_core.graph`
29
27
 
30
28
  - `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
29
+ - `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
31
30
  - `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
31
+ - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
32
32
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
33
+ - `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
33
34
  - `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
35
+ - `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
34
36
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
37
+ - `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
35
38
  - `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
36
39
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
37
40
  - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition (typed count mode).
@@ -53,6 +56,13 @@ A frontend-agnostic core for the transcript correction workflow — the first do
53
56
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
54
57
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
55
58
 
59
+ ### `cjm_transcript_correction_core.journal`
60
+
61
+ - `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
62
+ - `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
63
+ - `segment_anchor` _function_ — The run-independent anchor stamped on every correction op (DEC ccbab9f5 point 5).
64
+ - `sidecar_journal_path` _function_ — The db's sidecar journal path (DEC ccbab9f5 point 3: placement is per-workflow,
65
+
56
66
  ### `cjm_transcript_correction_core.models`
57
67
 
58
68
  - `Correction` _class_ — A single non-destructive correction over the committed spine (overlay node).
@@ -87,31 +97,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
87
97
  - `phonetic_key` _function_ — Compute a coarse phonetic key for a word (groups like-sounding variants).
88
98
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
89
99
 
90
- ### `tests.test_graph`
91
-
92
- - `test_build_prune_correction` _function_
93
- - `test_build_text_correction_and_active_filter` _function_
94
- - `test_latest_wins_ordering_from_layer` _function_
95
- - `test_project_effective_spine_prune_replace_supersede` _function_
96
-
97
- ### `tests.test_models`
98
-
99
- - `test_correction_graph_node_mapping` _function_
100
- - `test_correction_relations_registry` _function_
101
- - `test_correction_session_node` _function_
102
- - `test_manifest_save_round_trip` _function_
103
- - `test_manifest_shape_and_run_id` _function_
104
- - `test_spine_segment_and_worklist_item` _function_
105
-
106
- ### `tests.test_signals`
107
-
108
- - `test_boundary_punct_caps_flags` _function_
109
- - `test_clustering_primitives` _function_
110
- - `test_compute_signal_flags_combined` _function_
111
- - `test_empty_and_coverage_flags` _function_
112
- - `test_variant_divergence_within_segment` _function_
113
-
114
100
  ## Dependencies
115
101
 
116
- **Depends on:** `cjm-substrate`
102
+ **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
117
103
  **Used by:** `cjm-transcript-correction-tui`
@@ -14,6 +14,7 @@ from cjm_context_graph_primitives.query import (EdgeQuery, EdgeQueryResult, Node
14
14
  NodeQueryResult, OrderBy, PropertyPredicate,
15
15
  RelationPredicate, SourcePredicate)
16
16
  from cjm_substrate.core.queue import JobQueue, JobStatus
17
+ from cjm_transcript_correction_core.journal import journal_correction_op, segment_anchor
17
18
  from cjm_transcript_correction_core.models import (Correction, CorrectionRelations,
18
19
  CorrectionSession, SpineSegment)
19
20
  from cjm_transcript_graph_schema.schema import TranscriptGraphLabels
@@ -318,10 +319,16 @@ async def start_session(
318
319
  queue: JobQueue, # Started job queue
319
320
  graph_id: str, # Graph-storage capability id
320
321
  scope: List[str], # Source node ids in scope
322
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
321
323
  ) -> CorrectionSession: # The committed CorrectionSession
322
324
  """Create + commit a new CorrectionSession node."""
323
325
  sess = CorrectionSession(scope=scope)
324
- await commit_nodes_edges(queue, graph_id, [sess.to_graph_node().to_dict()], [])
326
+ node = sess.to_graph_node().to_dict()
327
+ await commit_nodes_edges(queue, graph_id, [node], [])
328
+ if journal_path:
329
+ journal_correction_op(journal_path, "session-start", actor="human",
330
+ session_id=node["id"], args={"scope": scope},
331
+ nodes=[node], edges=[], op_id=node["id"])
325
332
  return sess
326
333
 
327
334
 
@@ -342,14 +349,23 @@ async def set_session_status(
342
349
  graph_id: str, # Graph-storage capability id
343
350
  session_id: str, # CorrectionSession node id
344
351
  status: str, # New status ("in_progress" | "completed" | "reopened")
352
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
345
353
  ) -> None:
346
354
  """Update a session's status + updated_at.
347
355
 
348
356
  The ONLY update_node use in this core: a CorrectionSession is OVERLAY metadata
349
357
  whose lifecycle is mutable. Layer-0 Segments + Corrections stay append-only.
358
+ Its journal op replays via update_node (last-wins in append order), not wires.
350
359
  """
360
+ ts = time.time()
351
361
  await graph_task(queue, graph_id, "update_node", node_id=session_id,
352
- properties={"status": status, "updated_at": time.time()})
362
+ properties={"status": status, "updated_at": ts})
363
+ if journal_path:
364
+ journal_correction_op(journal_path, "session-status", actor="human",
365
+ session_id=session_id,
366
+ args={"session_id": session_id, "status": status,
367
+ "updated_at": ts},
368
+ nodes=[], edges=[])
353
369
 
354
370
 
355
371
  async def record_review_markers(
@@ -357,11 +373,21 @@ async def record_review_markers(
357
373
  graph_id: str, # Graph-storage capability id
358
374
  session_id: str, # Owning session id
359
375
  decisions: List[Tuple[str, str]], # (segment_id, decision) pairs
376
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
360
377
  ) -> int: # Number of REVIEWED edges committed
361
378
  """Persist per-(session, segment) review markers as REVIEWED edges."""
362
379
  edges = [_edge(session_id, seg_id, CorrectionRelations.REVIEWED, {"decision": dec})
363
380
  for seg_id, dec in decisions]
364
- return (await commit_nodes_edges(queue, graph_id, [], edges))["edges"]
381
+ n = (await commit_nodes_edges(queue, graph_id, [], edges))["edges"]
382
+ if journal_path:
383
+ # No anchor: markers are derivative session state — the CORRECTION op carries
384
+ # the anchor (DEC ccbab9f5 point 5 scopes anchors to correction ops), and the
385
+ # extra segment reads priced the TUI keystroke (journal-lag finding).
386
+ journal_correction_op(journal_path, "review-markers", actor="human",
387
+ session_id=session_id,
388
+ args={"decisions": [list(d) for d in decisions]},
389
+ nodes=[], edges=edges)
390
+ return n
365
391
 
366
392
 
367
393
  async def load_review_markers(
@@ -526,13 +552,24 @@ async def commit_text_correction(
526
552
  supersedes_id: Optional[str] = None, # Prior Correction to supersede (re-edit)
527
553
  actor: str = "human", # Actor
528
554
  canonical_form: Optional[str] = None, # Optional entity key
555
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
529
556
  ) -> str: # The new Correction node id
530
557
  """Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker."""
531
558
  node, edges = build_text_correction(
532
559
  source_id, segment_id, new_text, session_id, old_text=old_text,
533
560
  supersedes_id=supersedes_id, actor=actor, canonical_form=canonical_form)
534
561
  await commit_nodes_edges(queue, graph_id, [node], edges)
535
- await record_review_markers(queue, graph_id, session_id, [(segment_id, "corrected")])
562
+ if journal_path:
563
+ journal_correction_op(journal_path, "text-correction", actor=actor,
564
+ session_id=session_id,
565
+ args={"source_id": source_id, "segment_id": segment_id,
566
+ "new_text": new_text, "old_text": old_text,
567
+ "supersedes_id": supersedes_id,
568
+ "canonical_form": canonical_form},
569
+ anchor=await segment_anchor(queue, graph_id, [segment_id]),
570
+ nodes=[node], edges=edges, op_id=node["id"])
571
+ await record_review_markers(queue, graph_id, session_id, [(segment_id, "corrected")],
572
+ journal_path=journal_path)
536
573
  return node["id"]
537
574
 
538
575
 
@@ -730,14 +767,25 @@ async def commit_boundary_shift_correction(
730
767
  session_id: str, # Owning session id
731
768
  supersedes_id: Optional[str] = None, # Prior Correction to supersede (re-edit)
732
769
  actor: str = "human", # Actor
770
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
733
771
  ) -> str: # The new Correction node id
734
772
  """Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments."""
735
773
  node, edges = build_boundary_shift_correction(
736
774
  source_id, left_segment_id, right_segment_id, text, direction, session_id,
737
775
  supersedes_id=supersedes_id, actor=actor)
738
776
  await commit_nodes_edges(queue, graph_id, [node], edges)
777
+ if journal_path:
778
+ journal_correction_op(journal_path, "boundary-shift", actor=actor,
779
+ session_id=session_id,
780
+ args={"source_id": source_id, "left_segment_id": left_segment_id,
781
+ "right_segment_id": right_segment_id, "text": text,
782
+ "direction": direction, "supersedes_id": supersedes_id},
783
+ anchor=await segment_anchor(queue, graph_id,
784
+ [left_segment_id, right_segment_id]),
785
+ nodes=[node], edges=edges, op_id=node["id"])
739
786
  await record_review_markers(queue, graph_id, session_id,
740
- [(left_segment_id, "corrected"), (right_segment_id, "corrected")])
787
+ [(left_segment_id, "corrected"), (right_segment_id, "corrected")],
788
+ journal_path=journal_path)
741
789
  return node["id"]
742
790
 
743
791
 
@@ -776,8 +824,15 @@ async def commit_prune_amendment(
776
824
  unprune_ids: List[str], # Segment ids rescued from the prune set
777
825
  session_id: str, # Owning session id
778
826
  actor: str = "human", # Actor
827
+ journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
779
828
  ) -> Dict[str, Any]: # The amended correction as a property dict + id (local-echo ready)
780
829
  """Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES)."""
781
830
  node, edges = build_prune_amendment(prior, unprune_ids, session_id, actor=actor)
782
831
  await commit_nodes_edges(queue, graph_id, [node], edges)
832
+ if journal_path:
833
+ journal_correction_op(journal_path, "prune-amendment", actor=actor,
834
+ session_id=session_id,
835
+ args={"prior_id": prior.get("id"), "unprune_ids": unprune_ids},
836
+ anchor=await segment_anchor(queue, graph_id, unprune_ids),
837
+ nodes=[node], edges=edges, op_id=node["id"])
783
838
  return _node_to_correction_dict(node)
@@ -0,0 +1,116 @@
1
+ """Live append-through for the correction verbs — the workflow journal's domain half.
2
+
3
+ Every correction commit appends its op to the sidecar journal (DEC ccbab9f5): the
4
+ ENVELOPE (op id = minted Correction id, actor, set = session id, run-independent
5
+ segment ANCHOR) keeps each op a legible dataset row; the exact WIRES committed make
6
+ replay faithful to what WAS written (builder evolution can never drift a rebuild —
7
+ store the effect, not the recipe). Replay handlers register onto
8
+ `cjm_context_graph_layer.journal.replay_journal` (replay stays domain-owned); the
9
+ append discipline lives in `cjm_context_graph_primitives.journal`.
10
+ """
11
+
12
+ from typing import Any, Dict, List, Optional
13
+
14
+ from cjm_context_graph_layer.ops import extend_graph, graph_task
15
+ from cjm_context_graph_primitives.journal import append_op
16
+ from cjm_context_graph_primitives.query import NodeQuery
17
+
18
+
19
+ def sidecar_journal_path(
20
+ db_path: str, # The workflow graph db path (e.g. .../context_graph.db)
21
+ ) -> str: # The sidecar write-journal path next to it (.../context_graph.writes.jsonl)
22
+ """The db's sidecar journal path (DEC ccbab9f5 point 3: placement is per-workflow,
23
+ NEXT TO the db it is the source of truth for). Derived, never configured — one
24
+ less ambient default (the explicit-db-path guardrail extended to the journal)."""
25
+ if db_path.endswith(".db"):
26
+ return db_path[: -len(".db")] + ".writes.jsonl"
27
+ return db_path + ".writes.jsonl"
28
+
29
+
30
+ async def segment_anchor(
31
+ queue: Any, # Started job queue
32
+ graph_id: str, # Graph-storage capability id
33
+ segment_ids: List[str], # The Segment ids the correction touches
34
+ ) -> Dict[str, Any]: # Run-independent anchor: source hashes + per-segment time span + verbatim text
35
+ """The run-independent anchor stamped on every correction op (DEC ccbab9f5 point 5).
36
+
37
+ Segment ids are THIS pipeline run's coordinates; (source content hash, time span,
38
+ verbatim layer-0 text) survive any re-run — the coordinates the orphan detector's
39
+ propose/confirm rebind (DEC point 6) matches against. ONE batched segments read
40
+ per call; Source media hashes are immutable, so they read once per process."""
41
+ res = await graph_task(queue, graph_id, "query_nodes",
42
+ query=NodeQuery(ids=list(segment_ids)).to_dict())
43
+ segs = sorted(res.nodes or [], key=lambda n: n.properties.get("index") or 0)
44
+ source_ids = sorted({s.properties.get("source_id") for s in segs
45
+ if s.properties.get("source_id")})
46
+ missing = [sid for sid in source_ids if sid not in _SOURCE_HASH_CACHE]
47
+ if missing:
48
+ sres = await graph_task(queue, graph_id, "query_nodes",
49
+ query=NodeQuery(ids=missing).to_dict())
50
+ for s in (sres.nodes or []):
51
+ hashes = [r.content_hash for r in s.sources if getattr(r, "content_hash", None)]
52
+ _SOURCE_HASH_CACHE[s.id] = hashes[0] if hashes else None
53
+ src_hash = {sid: _SOURCE_HASH_CACHE.get(sid) for sid in source_ids}
54
+ return {
55
+ "sources": [{"id": sid, "content_hash": src_hash.get(sid)} for sid in source_ids],
56
+ "segments": [{"id": s.id, "index": s.properties.get("index"),
57
+ "start": s.properties.get("start_time"),
58
+ "end": s.properties.get("end_time"),
59
+ "text": s.properties.get("text")} for s in segs],
60
+ }
61
+
62
+
63
+ def journal_correction_op(
64
+ journal_path: str, # Sidecar journal path
65
+ verb: str, # Domain verb ("boundary-shift" | "text-correction" | ...)
66
+ actor: str, # Who decided ("human" | "agent:*" | "import:*")
67
+ session_id: str, # The op's SET identity (per-run CorrectionSession)
68
+ args: Dict[str, Any], # The semantic call args (the dataset row)
69
+ nodes: List[Dict[str, Any]], # The exact node wires committed
70
+ edges: List[Dict[str, Any]], # The exact edge wires committed
71
+ anchor: Optional[Dict[str, Any]] = None, # Run-independent segment anchor
72
+ op_id: Optional[str] = None, # Minted Correction node id (exact-once dedup lane)
73
+ ) -> bool: # True if appended
74
+ """Append one correction op — envelope + semantic args + the EXACT wires committed.
75
+
76
+ Wires make replay faithful to what WAS written (a builder change can never drift a
77
+ rebuild); args + anchor keep the op a legible dataset row (the north-star pullable-
78
+ datasets requirement). Appended AFTER the commit succeeds, mirroring the dev-graph
79
+ append-on-success discipline. dedup=False: the append must stay UI-cheap (a dedup
80
+ rescan over a genesis-scale journal cost 111ms/op — the TUI keystroke lag finding);
81
+ id-carrying ops mint a fresh Correction uuid per commit so duplicates are
82
+ structurally impossible live, and replay converges on collisions regardless."""
83
+ op: Dict[str, Any] = {"verb": verb, "actor": actor, "set": session_id,
84
+ "args": args, "wires": {"nodes": nodes, "edges": edges}}
85
+ if op_id:
86
+ op["id"] = op_id
87
+ if anchor:
88
+ op["anchor"] = anchor
89
+ return append_op(journal_path, op, dedup=False)
90
+
91
+
92
+ def correction_replay_handlers() -> Dict[str, Any]: # verb -> async handler(queue, graph_id, op)
93
+ """The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
94
+
95
+ Pass to `cjm_context_graph_layer.journal.replay_journal(handlers=...)`: wire-carrying
96
+ ops re-apply through the layer's idempotent extend (replayed wires collide into
97
+ verified no-ops); `session-status` is the core's only update_node write, replayed
98
+ last-wins in append order."""
99
+ async def _apply_wires(queue: Any, graph_id: str, op: Dict[str, Any]) -> None:
100
+ w = op.get("wires") or {}
101
+ await extend_graph(queue, graph_id, w.get("nodes") or [], w.get("edges") or [])
102
+
103
+ async def _apply_session_status(queue: Any, graph_id: str, op: Dict[str, Any]) -> None:
104
+ a = op["args"]
105
+ await graph_task(queue, graph_id, "update_node", node_id=a["session_id"],
106
+ properties={"status": a["status"], "updated_at": a["updated_at"]})
107
+
108
+ return {"session-start": _apply_wires, "boundary-shift": _apply_wires,
109
+ "text-correction": _apply_wires, "prune-amendment": _apply_wires,
110
+ "review-markers": _apply_wires, "session-status": _apply_session_status}
111
+
112
+
113
+ # A Source's content hash is IMMUTABLE — read once per process, not per commit
114
+ # (the per-keystroke Source re-read priced the TUI; ids are uuids, so cross-db
115
+ # collisions within one process cannot occur).
116
+ _SOURCE_HASH_CACHE: Dict[str, Optional[str]] = {}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-transcript-correction-core
3
- Version: 0.0.1
3
+ Version: 0.0.2
4
4
  Summary: A frontend-agnostic core for the transcript correction workflow — the first downstream graph-extension core; composes the graph-storage capability worker into a headless pipeline that applies unified, non-destructive corrections (text, punctuation, segmentation) as a supersede-able overlay on a committed decomposition spine, recomputes the review worklist from deterministic signals, and exposes 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
@@ -11,7 +11,7 @@ Requires-Python: >=3.12
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: cjm_substrate>=0.0.51
14
- Requires-Dist: cjm_context_graph_primitives>=0.0.12
14
+ Requires-Dist: cjm_context_graph_primitives>=0.0.14
15
15
  Requires-Dist: cjm-context-graph-layer>=0.0.8
16
16
  Requires-Dist: cjm-transcript-graph-schema>=0.0.7
17
17
  Dynamic: license-file
@@ -26,12 +26,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
26
26
 
27
27
  - **`cjm_transcript_correction_core.cli`** — The CLI driver — the correction core's first (and currently only) frontend. run <decomp-manifest> corrects the committed spine in the decomp graph DB, pointing the graph worker at that shared DB via load-time config, with optional session resume/reopen; review runs the interactive text-correction loop (the cross-transcriber diff is intra-graph since stage 5).
28
28
  - **`cjm_transcript_correction_core.graph`** — The correction overlay's graph I/O: targeted (scale-shaped) reads of a committed spine via the graph-storage query action, construction of Correction / CorrectionSession nodes + CORRECTS / SUPERSEDES / DERIVED_FROM / REVIEWED edges, the in-core effective-spine projection (layer-0 + applied corrections), and commit through the job queue. Hand-rolled (revolution-1) = direct CR-18 spec material; append-only on layer-0 (never update/delete a Segment).
29
+ - **`cjm_transcript_correction_core.journal`**
29
30
  - **`cjm_transcript_correction_core.models`** — Overlay data shapes for the transcript-correction workflow: the Correction / CorrectionSession graph nodes + their relation registry, the read view of a committed spine segment, the worklist item, run configuration, and the correction run manifest (proto-bundle that chains decomp -> correction).
30
31
  - **`cjm_transcript_correction_core.pipeline`** — The headless correction workflow: load a decomp run manifest, resolve the shared graph DB, start/resume/reopen a CorrectionSession, recompute the worklist from deterministic signals + persisted review state, run the D14 empty-segment prune (first operation), and record a chainable correction run manifest — with a cheapest-form HITL approval seam.
31
32
  - **`cjm_transcript_correction_core.signals`** — Pure deterministic Tier-1 signal functions (no capability calls): empty-segment detection, bidirectional boundary punctuation/capitalization heuristics, forced-alignment coverage flags, positional cross-transcriber diff, and phonetic + edit-distance variant clustering. The worklist is recomputed from these each session; revolution-1 builds ZERO new capabilities.
32
- - **`tests.test_graph`** — Tests for cjm_transcript_correction_core.graph — overlay construction + effective spine.
33
- - **`tests.test_models`** — Tests for cjm_transcript_correction_core.models — correction overlay data shapes.
34
- - **`tests.test_signals`** — Tests for cjm_transcript_correction_core.signals — pure deterministic Tier-1 signals.
35
33
 
36
34
  ## API
37
35
 
@@ -46,10 +44,15 @@ A frontend-agnostic core for the transcript correction workflow — the first do
46
44
  ### `cjm_transcript_correction_core.graph`
47
45
 
48
46
  - `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
47
+ - `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
49
48
  - `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
49
+ - `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
50
50
  - `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
51
+ - `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
51
52
  - `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
53
+ - `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
52
54
  - `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
55
+ - `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
53
56
  - `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
54
57
  - `corrections_to_edits` _function_ — Map this core's Correction payloads onto the layer's spine-edit vocabulary.
55
58
  - `count_source_segments` _function_ — Count a Source's segments server-side under its chosen rendition (typed count mode).
@@ -71,6 +74,13 @@ A frontend-agnostic core for the transcript correction workflow — the first do
71
74
  - `start_session` _function_ — Create + commit a new CorrectionSession node.
72
75
  - `submit_and_wait` _function_ — Submit one capability job, wait for it, return its result (raise on failure).
73
76
 
77
+ ### `cjm_transcript_correction_core.journal`
78
+
79
+ - `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
80
+ - `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
81
+ - `segment_anchor` _function_ — The run-independent anchor stamped on every correction op (DEC ccbab9f5 point 5).
82
+ - `sidecar_journal_path` _function_ — The db's sidecar journal path (DEC ccbab9f5 point 3: placement is per-workflow,
83
+
74
84
  ### `cjm_transcript_correction_core.models`
75
85
 
76
86
  - `Correction` _class_ — A single non-destructive correction over the committed spine (overlay node).
@@ -105,31 +115,7 @@ A frontend-agnostic core for the transcript correction workflow — the first do
105
115
  - `phonetic_key` _function_ — Compute a coarse phonetic key for a word (groups like-sounding variants).
106
116
  - `variant_divergence` _function_ — Within-segment cross-transcriber divergence (stage 5: intra-graph).
107
117
 
108
- ### `tests.test_graph`
109
-
110
- - `test_build_prune_correction` _function_
111
- - `test_build_text_correction_and_active_filter` _function_
112
- - `test_latest_wins_ordering_from_layer` _function_
113
- - `test_project_effective_spine_prune_replace_supersede` _function_
114
-
115
- ### `tests.test_models`
116
-
117
- - `test_correction_graph_node_mapping` _function_
118
- - `test_correction_relations_registry` _function_
119
- - `test_correction_session_node` _function_
120
- - `test_manifest_save_round_trip` _function_
121
- - `test_manifest_shape_and_run_id` _function_
122
- - `test_spine_segment_and_worklist_item` _function_
123
-
124
- ### `tests.test_signals`
125
-
126
- - `test_boundary_punct_caps_flags` _function_
127
- - `test_clustering_primitives` _function_
128
- - `test_compute_signal_flags_combined` _function_
129
- - `test_empty_and_coverage_flags` _function_
130
- - `test_variant_divergence_within_segment` _function_
131
-
132
118
  ## Dependencies
133
119
 
134
- **Depends on:** `cjm-substrate`
120
+ **Depends on:** `cjm-context-graph-layer`, `cjm-context-graph-primitives`, `cjm-substrate`, `cjm-transcript-graph-schema`
135
121
  **Used by:** `cjm-transcript-correction-tui`
@@ -5,6 +5,7 @@ pyproject.toml
5
5
  cjm_transcript_correction_core/__init__.py
6
6
  cjm_transcript_correction_core/cli.py
7
7
  cjm_transcript_correction_core/graph.py
8
+ cjm_transcript_correction_core/journal.py
8
9
  cjm_transcript_correction_core/models.py
9
10
  cjm_transcript_correction_core/pipeline.py
10
11
  cjm_transcript_correction_core/signals.py
@@ -16,6 +17,7 @@ cjm_transcript_correction_core.egg-info/requires.txt
16
17
  cjm_transcript_correction_core.egg-info/top_level.txt
17
18
  tests/test_cli.py
18
19
  tests/test_graph.py
20
+ tests/test_journal.py
19
21
  tests/test_models.py
20
22
  tests/test_pipeline.py
21
23
  tests/test_signals.py
@@ -1,4 +1,4 @@
1
1
  cjm_substrate>=0.0.51
2
- cjm_context_graph_primitives>=0.0.12
2
+ cjm_context_graph_primitives>=0.0.14
3
3
  cjm-context-graph-layer>=0.0.8
4
4
  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-context-graph-layer>=0.0.8', 'cjm-transcript-graph-schema>=0.0.7']
17
+ dependencies = ['cjm_substrate>=0.0.51', 'cjm_context_graph_primitives>=0.0.14', 'cjm-context-graph-layer>=0.0.8', 'cjm-transcript-graph-schema>=0.0.7']
18
18
 
19
19
  [project.urls]
20
20
  Repository = "https://github.com/cj-mills/cjm-transcript-correction-core"
@@ -0,0 +1,36 @@
1
+ """The correction journal: sidecar path + op envelope (pure half; replay is e2e-proven)."""
2
+
3
+ from cjm_context_graph_primitives.journal import read_journal
4
+ from cjm_transcript_correction_core.journal import (correction_replay_handlers,
5
+ journal_correction_op, sidecar_journal_path)
6
+
7
+
8
+ def test_sidecar_journal_path():
9
+ assert (sidecar_journal_path("/data/context_graph.db")
10
+ == "/data/context_graph.writes.jsonl")
11
+ assert sidecar_journal_path("/data/odd_name") == "/data/odd_name.writes.jsonl"
12
+
13
+
14
+ def test_journal_correction_op_envelope(tmp_path):
15
+ """Envelope: op id = minted Correction id, set = session, anchor + exact wires ride along."""
16
+ j = str(tmp_path / "w.jsonl")
17
+ node = {"id": "corr-1", "label": "Correction", "properties": {"correction_type": "grouping"}}
18
+ edge = {"id": "e-1", "source_id": "corr-1", "target_id": "seg-1", "relation_type": "CORRECTS"}
19
+ anchor = {"sources": [{"id": "src", "content_hash": "sha256:x"}],
20
+ "segments": [{"id": "seg-1", "start": 1.0, "end": 2.0, "text": "t"}]}
21
+ assert journal_correction_op(j, "boundary-shift", actor="human", session_id="sess-1",
22
+ args={"direction": "pull"}, nodes=[node], edges=[edge],
23
+ anchor=anchor, op_id="corr-1")
24
+ rec = read_journal(j)[0]
25
+ assert rec["id"] == "corr-1" and rec["set"] == "sess-1" and rec["actor"] == "human"
26
+ assert rec["wires"]["nodes"][0]["id"] == "corr-1" and rec["anchor"]["segments"][0]["end"] == 2.0
27
+ # Live appends ride the bulk lane (dedup=False — the 111ms/op rescan priced the TUI):
28
+ # a re-append lands as a duplicate LINE, and exactness is REPLAY's job (extend
29
+ # collides duplicate wires into verified no-ops).
30
+ assert journal_correction_op(j, "boundary-shift", actor="human", session_id="sess-1",
31
+ args={"direction": "pull"}, nodes=[node], edges=[edge],
32
+ anchor=anchor, op_id="corr-1")
33
+ assert len(read_journal(j)) == 2
34
+ # every registered replay verb has a handler
35
+ assert set(correction_replay_handlers()) == {"session-start", "boundary-shift", "text-correction",
36
+ "prune-amendment", "review-markers", "session-status"}