cjm-transcript-correction-core 0.0.2__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.
- {cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core.egg-info → cjm_transcript_correction_core-0.0.4}/PKG-INFO +11 -6
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/README.md +7 -2
- cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core/__init__.py +1 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/cli.py +33 -6
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/graph.py +191 -5
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/journal.py +12 -23
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/models.py +28 -3
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/pipeline.py +6 -2
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core.egg-info}/PKG-INFO +11 -6
- cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core.egg-info/entry_points.txt +5 -0
- cjm_transcript_correction_core-0.0.4/cjm_transcript_correction_core.egg-info/requires.txt +4 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/pyproject.toml +4 -1
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_graph.py +94 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_journal.py +10 -3
- cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core/__init__.py +0 -1
- cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core.egg-info/entry_points.txt +0 -2
- cjm_transcript_correction_core-0.0.2/cjm_transcript_correction_core.egg-info/requires.txt +0 -4
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/LICENSE +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/MANIFEST.in +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core/signals.py +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core.egg-info/SOURCES.txt +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core.egg-info/dependency_links.txt +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/cjm_transcript_correction_core.egg-info/top_level.txt +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/setup.cfg +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_cli.py +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_models.py +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_pipeline.py +0 -0
- {cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/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.
|
|
3
|
+
Version: 0.0.4
|
|
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
|
|
@@ -10,9 +10,9 @@ 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.
|
|
14
|
-
Requires-Dist: cjm_context_graph_primitives>=0.0.
|
|
15
|
-
Requires-Dist: cjm-context-graph-layer>=0.0.
|
|
13
|
+
Requires-Dist: cjm_substrate>=0.0.57
|
|
14
|
+
Requires-Dist: cjm_context_graph_primitives>=0.0.15
|
|
15
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.11
|
|
16
16
|
Requires-Dist: cjm-transcript-graph-schema>=0.0.7
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
@@ -26,7 +26,7 @@ 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
|
+
- **`cjm_transcript_correction_core.journal`** — Live append-through for the correction verbs — the workflow journal's domain half.
|
|
30
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).
|
|
31
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.
|
|
32
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.
|
|
@@ -46,11 +46,14 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
46
46
|
- `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
|
|
47
47
|
- `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
|
|
48
48
|
- `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
|
|
49
|
+
- `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
|
|
49
50
|
- `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
|
|
50
51
|
- `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
|
|
51
52
|
- `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
|
|
52
53
|
- `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
|
|
53
54
|
- `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
|
|
55
|
+
- `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
|
|
56
|
+
- `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
|
|
54
57
|
- `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
|
|
55
58
|
- `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
|
|
56
59
|
- `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
|
|
@@ -66,7 +69,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
66
69
|
- `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
|
|
67
70
|
- `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition (typed query surface).
|
|
68
71
|
- `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
|
|
72
|
+
- `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
|
|
73
|
+
- `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
|
|
69
74
|
- `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
|
|
75
|
+
- `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
|
|
70
76
|
- `record_review_markers` _function_ — Persist per-(session, segment) review markers as REVIEWED edges.
|
|
71
77
|
- `resolve_source_renditions` _function_ — Pick the AudioRendition set whose fine Segment spine correction operates on.
|
|
72
78
|
- `set_session_status` _function_ — Update a session's status + updated_at.
|
|
@@ -79,7 +85,6 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
79
85
|
- `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
|
|
80
86
|
- `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
|
|
81
87
|
- `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
88
|
|
|
84
89
|
### `cjm_transcript_correction_core.models`
|
|
85
90
|
|
|
@@ -8,7 +8,7 @@ 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
|
+
- **`cjm_transcript_correction_core.journal`** — Live append-through for the correction verbs — the workflow journal's domain half.
|
|
12
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).
|
|
13
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.
|
|
14
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.
|
|
@@ -28,11 +28,14 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
28
28
|
- `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
|
|
29
29
|
- `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
|
|
30
30
|
- `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
|
|
31
|
+
- `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
|
|
31
32
|
- `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
|
|
32
33
|
- `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
|
|
33
34
|
- `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
|
|
34
35
|
- `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
|
|
35
36
|
- `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
|
|
37
|
+
- `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
|
|
38
|
+
- `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
|
|
36
39
|
- `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
|
|
37
40
|
- `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
|
|
38
41
|
- `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
|
|
@@ -48,7 +51,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
48
51
|
- `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
|
|
49
52
|
- `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition (typed query surface).
|
|
50
53
|
- `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
|
|
54
|
+
- `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
|
|
55
|
+
- `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
|
|
51
56
|
- `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
|
|
57
|
+
- `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
|
|
52
58
|
- `record_review_markers` _function_ — Persist per-(session, segment) review markers as REVIEWED edges.
|
|
53
59
|
- `resolve_source_renditions` _function_ — Pick the AudioRendition set whose fine Segment spine correction operates on.
|
|
54
60
|
- `set_session_status` _function_ — Update a session's status + updated_at.
|
|
@@ -61,7 +67,6 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
61
67
|
- `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
|
|
62
68
|
- `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
|
|
63
69
|
- `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
70
|
|
|
66
71
|
### `cjm_transcript_correction_core.models`
|
|
67
72
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.4"
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import Dict, List, Optional
|
|
8
9
|
|
|
9
10
|
from cjm_substrate.core.manager import CapabilityManager
|
|
10
11
|
from cjm_substrate.core.queue import JobQueue
|
|
12
|
+
from cjm_substrate.core.workspace import resolve_workspace
|
|
11
13
|
from cjm_transcript_correction_core.models import CorrectionConfig
|
|
12
14
|
from cjm_transcript_correction_core.pipeline import (load_decomp_manifest, resolve_graph_db_path,
|
|
13
15
|
run_correction, run_review)
|
|
@@ -18,7 +20,13 @@ logger = logging.getLogger(__name__)
|
|
|
18
20
|
def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/review arguments
|
|
19
21
|
"""Attach the capability / session / output arguments shared by `run` and `review`."""
|
|
20
22
|
p.add_argument("manifest", help="Decomp-core run manifest JSON (the committed spine)")
|
|
21
|
-
p.add_argument("--manifests-dir", default=
|
|
23
|
+
p.add_argument("--manifests-dir", default=None,
|
|
24
|
+
help="Capability manifests directory (default: the workspace's .cjm/manifests "
|
|
25
|
+
"when one is active, else .cjm/manifests under the cwd)")
|
|
26
|
+
p.add_argument("--workspace", default=None,
|
|
27
|
+
help="Workspace root (5daadfc4; default: CJM_WORKSPACE env, else upward walk "
|
|
28
|
+
"from cwd). Supplies manifests/output defaults and is exported so "
|
|
29
|
+
"capability workers resolve workspace-scoped paths")
|
|
22
30
|
p.add_argument("--graph-capability", default="cjm-capability-graph-sqlite", help="Graph-storage capability name")
|
|
23
31
|
p.add_argument("--graph-db-path", default=None,
|
|
24
32
|
help="Override graph DB path (default: the decomp manifest's recorded db_path)")
|
|
@@ -31,7 +39,9 @@ def _add_common_run_args(p: argparse.ArgumentParser) -> None: # Shared run/revi
|
|
|
31
39
|
p.add_argument("--session", default=None, help="Resume an existing CorrectionSession id")
|
|
32
40
|
p.add_argument("--reopen", action="store_true", help="Reopen a completed session (with --session)")
|
|
33
41
|
p.add_argument("--actor", default="human", help="Actor recorded on corrections + review markers")
|
|
34
|
-
p.add_argument("--output", default=None,
|
|
42
|
+
p.add_argument("--output", default=None,
|
|
43
|
+
help="Correction-manifest output path (default: <workspace>/runs/<run_id>.json "
|
|
44
|
+
"when a workspace is active, else runs/<run_id>.json under the cwd)")
|
|
35
45
|
p.add_argument("-v", "--verbose", action="store_true", help="DEBUG-level logging")
|
|
36
46
|
|
|
37
47
|
|
|
@@ -84,6 +94,14 @@ async def run_command(
|
|
|
84
94
|
args: argparse.Namespace, # Parsed args for the `run` subcommand
|
|
85
95
|
) -> int: # Process exit code
|
|
86
96
|
"""Execute the `run` subcommand: correct a decomp manifest's committed spine."""
|
|
97
|
+
# 5daadfc4 workspace: resolve BEFORE any substrate config loads; export so
|
|
98
|
+
# the process tree (substrate config, capability workers) is workspace-scoped.
|
|
99
|
+
ws = resolve_workspace(explicit=getattr(args, "workspace", None))
|
|
100
|
+
if ws is not None:
|
|
101
|
+
os.environ["CJM_WORKSPACE"] = str(ws.root)
|
|
102
|
+
if args.manifests_dir is None:
|
|
103
|
+
args.manifests_dir = (str(ws.substrate_data_dir / "manifests")
|
|
104
|
+
if ws is not None else ".cjm/manifests")
|
|
87
105
|
manifest_path = str(Path(args.manifest).resolve())
|
|
88
106
|
if not Path(manifest_path).exists():
|
|
89
107
|
raise SystemExit(f"decomp manifest not found: {manifest_path}")
|
|
@@ -122,8 +140,9 @@ async def run_command(
|
|
|
122
140
|
except Exception as e: # Best-effort teardown; never mask the run's outcome
|
|
123
141
|
logger.warning(f"unload {iid} failed: {e}")
|
|
124
142
|
|
|
125
|
-
out = Path(args.output) if args.output
|
|
126
|
-
|
|
143
|
+
out = (Path(args.output) if args.output
|
|
144
|
+
else (ws.runs_dir if ws is not None else Path("runs")) / f"{manifest.run_id}.json")
|
|
145
|
+
manifest.save(out, workspace=ws)
|
|
127
146
|
n_sources = len(manifest.sources)
|
|
128
147
|
n_pruned = sum(s.get("pruned", 0) for s in manifest.sources)
|
|
129
148
|
n_flagged = sum(s.get("worklist_flagged", 0) for s in manifest.sources)
|
|
@@ -137,6 +156,13 @@ async def review_command(
|
|
|
137
156
|
args: argparse.Namespace, # Parsed args for the `review` subcommand
|
|
138
157
|
) -> int: # Process exit code
|
|
139
158
|
"""Execute the `review` subcommand: interactive text corrections over the flagged worklist."""
|
|
159
|
+
# 5daadfc4 workspace: same early resolution + export as run_command.
|
|
160
|
+
ws = resolve_workspace(explicit=getattr(args, "workspace", None))
|
|
161
|
+
if ws is not None:
|
|
162
|
+
os.environ["CJM_WORKSPACE"] = str(ws.root)
|
|
163
|
+
if args.manifests_dir is None:
|
|
164
|
+
args.manifests_dir = (str(ws.substrate_data_dir / "manifests")
|
|
165
|
+
if ws is not None else ".cjm/manifests")
|
|
140
166
|
manifest_path = str(Path(args.manifest).resolve())
|
|
141
167
|
if not Path(manifest_path).exists():
|
|
142
168
|
raise SystemExit(f"decomp manifest not found: {manifest_path}")
|
|
@@ -167,8 +193,9 @@ async def review_command(
|
|
|
167
193
|
except Exception as e: # Best-effort teardown; never mask the run's outcome
|
|
168
194
|
logger.warning(f"unload {iid} failed: {e}")
|
|
169
195
|
|
|
170
|
-
out = Path(args.output) if args.output
|
|
171
|
-
|
|
196
|
+
out = (Path(args.output) if args.output
|
|
197
|
+
else (ws.runs_dir if ws is not None else Path("runs")) / f"{manifest.run_id}.json")
|
|
198
|
+
manifest.save(out, workspace=ws)
|
|
172
199
|
n_corr = sum(s.get("corrected", 0) for s in manifest.sources)
|
|
173
200
|
n_active = sum(s.get("active_corrections", 0) for s in manifest.sources)
|
|
174
201
|
print(f"correction manifest: {out}")
|
|
@@ -259,7 +259,7 @@ def _edge(
|
|
|
259
259
|
|
|
260
260
|
|
|
261
261
|
def build_correction_node(
|
|
262
|
-
correction_type: str, # "text_content" | "punctuation" | "grouping" | "review"
|
|
262
|
+
correction_type: str, # "text_content" | "punctuation" | "grouping" | "review" | "mark"
|
|
263
263
|
session_id: str, # Owning session id
|
|
264
264
|
payload: Dict[str, Any], # Type-specific payload
|
|
265
265
|
actor: str = "human", # Actor
|
|
@@ -376,7 +376,8 @@ async def record_review_markers(
|
|
|
376
376
|
journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
|
|
377
377
|
) -> int: # Number of REVIEWED edges committed
|
|
378
378
|
"""Persist per-(session, segment) review markers as REVIEWED edges."""
|
|
379
|
-
edges = [_edge(session_id, seg_id, CorrectionRelations.REVIEWED,
|
|
379
|
+
edges = [_edge(session_id, seg_id, CorrectionRelations.REVIEWED,
|
|
380
|
+
{"decision": dec, "ts": time.time()})
|
|
380
381
|
for seg_id, dec in decisions]
|
|
381
382
|
n = (await commit_nodes_edges(queue, graph_id, [], edges))["edges"]
|
|
382
383
|
if journal_path:
|
|
@@ -395,11 +396,21 @@ async def load_review_markers(
|
|
|
395
396
|
graph_id: str, # Graph-storage capability id
|
|
396
397
|
session_id: str, # Owning session id
|
|
397
398
|
) -> Dict[str, str]: # segment_id -> decision for the session
|
|
398
|
-
"""Load a session's review markers (typed edge projection over REVIEWED edges).
|
|
399
|
+
"""Load a session's review markers (typed edge projection over REVIEWED edges).
|
|
400
|
+
|
|
401
|
+
Markers are EVENTS (a re-decision appends — the _edge docstring's contract),
|
|
402
|
+
so the read is LATEST-WINS by the marker's ts, and a latest decision of
|
|
403
|
+
"unreviewed" means UNDECIDED: the segment drops out of the returned map
|
|
404
|
+
entirely (the space-undo gesture; compute_worklist then re-surfaces it).
|
|
405
|
+
Historical edges without ts sort first, so any re-decision beats them.
|
|
406
|
+
"""
|
|
399
407
|
q = EdgeQuery(source_id=session_id, relation_type=CorrectionRelations.REVIEWED,
|
|
400
|
-
project=["decision"])
|
|
408
|
+
project=["decision", "ts"])
|
|
401
409
|
res = await graph_task(queue, graph_id, "query_edges", query=q.to_dict())
|
|
402
|
-
|
|
410
|
+
latest: Dict[str, str] = {}
|
|
411
|
+
for r in sorted((res.rows or []), key=lambda r: float(r.get("ts") or 0.0)):
|
|
412
|
+
latest[r["target_id"]] = r["decision"]
|
|
413
|
+
return {sid: dec for sid, dec in latest.items() if dec != "unreviewed"}
|
|
403
414
|
|
|
404
415
|
|
|
405
416
|
def _node_to_correction_dict(
|
|
@@ -836,3 +847,178 @@ async def commit_prune_amendment(
|
|
|
836
847
|
anchor=await segment_anchor(queue, graph_id, unprune_ids),
|
|
837
848
|
nodes=[node], edges=edges, op_id=node["id"])
|
|
838
849
|
return _node_to_correction_dict(node)
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def mark_anchor_segments(
|
|
853
|
+
anchor: Dict[str, Any], # Mark anchor: {"kind": "segment" | "boundary" | "span", ...}
|
|
854
|
+
) -> List[str]: # The layer-0 Segment ids the anchor touches (CORRECTS targets)
|
|
855
|
+
"""Validate a mark anchor and list the Segment ids it touches.
|
|
856
|
+
|
|
857
|
+
The three anchor shapes (DEC 2a231843): `segment` {segment_id} ·
|
|
858
|
+
`boundary` {boundary_after, right_segment_id} (the boundary AFTER a
|
|
859
|
+
segment — the shift gesture's coordinates) · `span` {segment_id,
|
|
860
|
+
char_start, char_end, text_snapshot} (offsets into the segment's effective
|
|
861
|
+
text AT MARK TIME; the snapshot is what re-anchoring trusts — see
|
|
862
|
+
`reanchor_span` — the offsets are only its hint).
|
|
863
|
+
"""
|
|
864
|
+
kind = anchor.get("kind")
|
|
865
|
+
if kind == "segment":
|
|
866
|
+
ids = [anchor.get("segment_id")]
|
|
867
|
+
elif kind == "boundary":
|
|
868
|
+
ids = [anchor.get("boundary_after"), anchor.get("right_segment_id")]
|
|
869
|
+
elif kind == "span":
|
|
870
|
+
ids = [anchor.get("segment_id")]
|
|
871
|
+
if not anchor.get("text_snapshot") or anchor.get("char_start") is None \
|
|
872
|
+
or anchor.get("char_end") is None:
|
|
873
|
+
raise ValueError("span mark anchor needs char_start, char_end and a text_snapshot")
|
|
874
|
+
else:
|
|
875
|
+
raise ValueError(f"mark anchor kind must be 'segment' | 'boundary' | 'span', got {kind!r}")
|
|
876
|
+
if any(not sid for sid in ids):
|
|
877
|
+
raise ValueError(f"{kind} mark anchor is missing its segment id(s): {anchor!r}")
|
|
878
|
+
return [str(sid) for sid in ids]
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def build_mark_correction(
|
|
882
|
+
source_id: str, # Source the marked segment(s) belong to
|
|
883
|
+
anchor: Dict[str, Any], # Anchor: segment | boundary | span (see mark_anchor_segments)
|
|
884
|
+
mark_class: str, # Open-vocabulary class (RECOMMENDED_MARK_CLASSES is the slate)
|
|
885
|
+
session_id: str, # Owning session id
|
|
886
|
+
supersedes_id: Optional[str] = None, # Prior mark this one replaces (re-mark)
|
|
887
|
+
actor: str = "human", # Actor ("human" | "capability:<name>" for pass-2 assists)
|
|
888
|
+
note: Optional[str] = None, # Optional free-text note
|
|
889
|
+
) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]: # (correction node dict, edge dicts)
|
|
890
|
+
"""Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
|
|
891
|
+
|
|
892
|
+
A mark is a placeholder correction: it records that a slot needs judgment
|
|
893
|
+
(an omission-entangled boundary, a suspect entity, ...) WITHOUT resolving
|
|
894
|
+
anything — `corrections_to_edits` has no arm for it, so it can never touch
|
|
895
|
+
the effective view. It closes by SUPERSESSION: the real correction that
|
|
896
|
+
discharges it passes `supersedes_id=<mark id>`, or a dismissal supersedes
|
|
897
|
+
it (`commit_mark_dismissal`, riding the reject-review shape). CORRECTS
|
|
898
|
+
edges anchor every touched segment so marks surface in segment-scoped
|
|
899
|
+
reads.
|
|
900
|
+
"""
|
|
901
|
+
mc = (mark_class or "").strip()
|
|
902
|
+
if not mc:
|
|
903
|
+
raise ValueError("mark_class must be a non-empty string")
|
|
904
|
+
if not mc[:1].isalnum():
|
|
905
|
+
raise ValueError(f"mark_class must start with a letter or digit, got {mark_class!r}"
|
|
906
|
+
" — punctuation-led tokens are reserved for gestures ('-' dismissal)")
|
|
907
|
+
seg_ids = mark_anchor_segments(anchor)
|
|
908
|
+
payload = {"operation": "mark", "source_id": source_id,
|
|
909
|
+
"anchor": dict(anchor), "mark_class": mc}
|
|
910
|
+
node = build_correction_node("mark", session_id, payload, actor=actor,
|
|
911
|
+
rationale=note).to_graph_node()
|
|
912
|
+
edges = [make_edge(node.id, sid, CorrectionRelations.CORRECTS) for sid in seg_ids]
|
|
913
|
+
if supersedes_id:
|
|
914
|
+
edges.append(make_edge(node.id, supersedes_id, CorrectionRelations.SUPERSEDES))
|
|
915
|
+
return node.to_dict(), edges
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
async def commit_mark_correction(
|
|
919
|
+
queue: JobQueue, # Started job queue
|
|
920
|
+
graph_id: str, # Graph-storage capability id
|
|
921
|
+
source_id: str, # Source the marked segment(s) belong to
|
|
922
|
+
anchor: Dict[str, Any], # Anchor: segment | boundary | span
|
|
923
|
+
mark_class: str, # Open-vocabulary class
|
|
924
|
+
session_id: str, # Owning session id
|
|
925
|
+
supersedes_id: Optional[str] = None, # Prior mark to supersede (re-mark)
|
|
926
|
+
actor: str = "human", # Actor
|
|
927
|
+
note: Optional[str] = None, # Optional free-text note
|
|
928
|
+
journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
|
|
929
|
+
) -> str: # The new mark Correction node id
|
|
930
|
+
"""Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
|
|
931
|
+
|
|
932
|
+
NO review marker: a mark is routed attention, not a review decision — the
|
|
933
|
+
walked-past state stays exactly as the operator left it.
|
|
934
|
+
"""
|
|
935
|
+
node, edges = build_mark_correction(source_id, anchor, mark_class, session_id,
|
|
936
|
+
supersedes_id=supersedes_id, actor=actor, note=note)
|
|
937
|
+
await commit_nodes_edges(queue, graph_id, [node], edges)
|
|
938
|
+
if journal_path:
|
|
939
|
+
journal_correction_op(journal_path, "mark", actor=actor, session_id=session_id,
|
|
940
|
+
args={"source_id": source_id, "anchor": dict(anchor),
|
|
941
|
+
"mark_class": mark_class, "note": note,
|
|
942
|
+
"supersedes_id": supersedes_id},
|
|
943
|
+
anchor=await segment_anchor(queue, graph_id,
|
|
944
|
+
mark_anchor_segments(anchor)),
|
|
945
|
+
nodes=[node], edges=edges, op_id=node["id"])
|
|
946
|
+
return node["id"]
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
async def commit_mark_dismissal(
|
|
950
|
+
queue: JobQueue, # Started job queue
|
|
951
|
+
graph_id: str, # Graph-storage capability id
|
|
952
|
+
source_id: str, # Source the mark belongs to
|
|
953
|
+
mark_id: str, # The open mark being dismissed
|
|
954
|
+
session_id: str, # Owning session id
|
|
955
|
+
actor: str = "human", # Actor (the dismisser)
|
|
956
|
+
note: Optional[str] = None, # Optional note (why dismissed)
|
|
957
|
+
journal_path: Optional[str] = None, # Sidecar journal — append the op on success (None = unjournaled)
|
|
958
|
+
) -> str: # The review node id that superseded the mark
|
|
959
|
+
"""Dismiss an open mark WITHOUT a correction (reject-as-supersede).
|
|
960
|
+
|
|
961
|
+
Rides the review verdict shape (`build_reject_review`): dismissal is a
|
|
962
|
+
SUPERSEDES edge from a small review node — append-only, carrying
|
|
963
|
+
actor/note/timestamp — and `open_marks` then excludes the mark. No anchor
|
|
964
|
+
on the journal op: dismissal is derivative session state, like review
|
|
965
|
+
markers (the ccbab9f5 anchor scope).
|
|
966
|
+
"""
|
|
967
|
+
node, edges = build_reject_review(source_id, mark_id, session_id,
|
|
968
|
+
actor=actor, rationale=note)
|
|
969
|
+
await commit_nodes_edges(queue, graph_id, [node], edges)
|
|
970
|
+
if journal_path:
|
|
971
|
+
journal_correction_op(journal_path, "mark-dismiss", actor=actor,
|
|
972
|
+
session_id=session_id,
|
|
973
|
+
args={"source_id": source_id, "mark_id": mark_id, "note": note},
|
|
974
|
+
nodes=[node], edges=edges, op_id=node["id"])
|
|
975
|
+
return node["id"]
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def open_marks(
|
|
979
|
+
corrections: List[Dict[str, Any]], # Corrections (e.g. from load_source_corrections)
|
|
980
|
+
superseded_ids: set, # Ids that are SUPERSEDES targets
|
|
981
|
+
) -> List[Dict[str, Any]]: # The OPEN marks (not yet discharged/dismissed), oldest first
|
|
982
|
+
"""Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
|
|
983
|
+
|
|
984
|
+
A mark leaves this set the moment anything supersedes it (the discharging
|
|
985
|
+
correction or a dismissal review) — resolution is read from SUPERSEDES
|
|
986
|
+
edges, never a status mutation.
|
|
987
|
+
"""
|
|
988
|
+
marks = [c for c in corrections
|
|
989
|
+
if c.get("correction_type") == "mark"
|
|
990
|
+
and (c.get("payload") or {}).get("operation") == "mark"
|
|
991
|
+
and c.get("id") not in superseded_ids]
|
|
992
|
+
marks.sort(key=lambda c: c.get("created_at") or 0.0)
|
|
993
|
+
return marks
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def reanchor_span(
|
|
997
|
+
anchor: Dict[str, Any], # A span mark anchor (char_start / char_end / text_snapshot)
|
|
998
|
+
effective_text: str, # The segment's CURRENT effective text
|
|
999
|
+
) -> Optional[Tuple[int, int]]: # Current (char_start, char_end); None = degrade to segment level
|
|
1000
|
+
"""Re-locate a span anchor in text that may have been edited since mark time.
|
|
1001
|
+
|
|
1002
|
+
The SNAPSHOT is the anchor's truth; the recorded offsets are only its hint
|
|
1003
|
+
(DEC 2a231843): exact offsets verified first, then the snapshot occurrence
|
|
1004
|
+
NEAREST the recorded start, else None — the caller degrades the mark to
|
|
1005
|
+
segment level (which usually means the marked text was addressed).
|
|
1006
|
+
"""
|
|
1007
|
+
snap = anchor.get("text_snapshot")
|
|
1008
|
+
if not snap:
|
|
1009
|
+
return None
|
|
1010
|
+
try:
|
|
1011
|
+
cs, ce = int(anchor.get("char_start")), int(anchor.get("char_end"))
|
|
1012
|
+
except (TypeError, ValueError):
|
|
1013
|
+
cs, ce = -1, -1
|
|
1014
|
+
if 0 <= cs <= ce <= len(effective_text) and effective_text[cs:ce] == snap:
|
|
1015
|
+
return cs, ce
|
|
1016
|
+
hits: List[int] = []
|
|
1017
|
+
at = effective_text.find(snap)
|
|
1018
|
+
while at != -1:
|
|
1019
|
+
hits.append(at)
|
|
1020
|
+
at = effective_text.find(snap, at + 1)
|
|
1021
|
+
if not hits:
|
|
1022
|
+
return None
|
|
1023
|
+
best = min(hits, key=lambda h: abs(h - cs) if cs >= 0 else h)
|
|
1024
|
+
return best, best + len(snap)
|
|
@@ -11,22 +11,12 @@ append discipline lives in `cjm_context_graph_primitives.journal`.
|
|
|
11
11
|
|
|
12
12
|
from typing import Any, Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
from cjm_context_graph_layer.
|
|
14
|
+
from cjm_context_graph_layer.journal import wires_handlers
|
|
15
|
+
from cjm_context_graph_layer.ops import graph_task
|
|
15
16
|
from cjm_context_graph_primitives.journal import append_op
|
|
16
17
|
from cjm_context_graph_primitives.query import NodeQuery
|
|
17
18
|
|
|
18
19
|
|
|
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
20
|
async def segment_anchor(
|
|
31
21
|
queue: Any, # Started job queue
|
|
32
22
|
graph_id: str, # Graph-storage capability id
|
|
@@ -92,22 +82,21 @@ def journal_correction_op(
|
|
|
92
82
|
def correction_replay_handlers() -> Dict[str, Any]: # verb -> async handler(queue, graph_id, op)
|
|
93
83
|
"""The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
|
|
94
84
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await extend_graph(queue, graph_id, w.get("nodes") or [], w.get("edges") or [])
|
|
102
|
-
|
|
85
|
+
Exported through the `cjm_context_graph_layer.replay` entry-point group (DEC
|
|
86
|
+
426658f1) and unioned by `composed_replay_handlers`: wire-carrying verbs register
|
|
87
|
+
the layer's shared `apply_wires` — the single wires-replay authority, identity-
|
|
88
|
+
comparable across cores, which keeps cross-core verb collisions checkable;
|
|
89
|
+
`session-status` is the core's only update_node write, replayed last-wins in
|
|
90
|
+
append order."""
|
|
103
91
|
async def _apply_session_status(queue: Any, graph_id: str, op: Dict[str, Any]) -> None:
|
|
104
92
|
a = op["args"]
|
|
105
93
|
await graph_task(queue, graph_id, "update_node", node_id=a["session_id"],
|
|
106
94
|
properties={"status": a["status"], "updated_at": a["updated_at"]})
|
|
107
95
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
96
|
+
handlers = wires_handlers("session-start", "boundary-shift", "text-correction",
|
|
97
|
+
"prune-amendment", "mark", "mark-dismiss", "review-markers")
|
|
98
|
+
handlers["session-status"] = _apply_session_status
|
|
99
|
+
return handlers
|
|
111
100
|
|
|
112
101
|
|
|
113
102
|
# A Source's content hash is IMMUTABLE — read once per process, not per commit
|
|
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional, Union
|
|
|
9
9
|
from uuid import uuid4
|
|
10
10
|
|
|
11
11
|
from cjm_context_graph_primitives.graph import GraphNode
|
|
12
|
+
from cjm_substrate.core.workspace import relativize_recorded
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class CorrectionRelations:
|
|
@@ -35,7 +36,7 @@ class Correction:
|
|
|
35
36
|
generic GraphNode. Corrections are DECISIONS (asserted events) — they keep
|
|
36
37
|
GENERATED ids, the FLIP-TRIGGER-protected class.
|
|
37
38
|
"""
|
|
38
|
-
correction_type: str # "text_content" | "punctuation" | "grouping" | "review"
|
|
39
|
+
correction_type: str # "text_content" | "punctuation" | "grouping" | "review" | "mark"
|
|
39
40
|
status: str = "applied" # "proposed" | "applied" | "superseded"
|
|
40
41
|
session_id: str = "" # Owning CorrectionSession id
|
|
41
42
|
payload: Dict[str, Any] = field(default_factory=dict) # Type-specific data (new text, prune set, ...)
|
|
@@ -158,14 +159,38 @@ class CorrectionManifest:
|
|
|
158
159
|
def save(
|
|
159
160
|
self,
|
|
160
161
|
path: Union[str, Path], # Destination JSON file (parent dirs created)
|
|
162
|
+
workspace=None, # Active Workspace; owned paths record as ${WS}/<rel> (5daadfc4 rung f)
|
|
161
163
|
) -> Path: # The written path
|
|
162
|
-
"""Write the manifest as pretty-printed JSON.
|
|
164
|
+
"""Write the manifest as pretty-printed JSON.
|
|
165
|
+
|
|
166
|
+
With `workspace`, recorded paths under its root take the ${WS}/ token
|
|
167
|
+
form (relativize_recorded), so the manifest relocates with the
|
|
168
|
+
workspace; readers resolve via resolve_recorded_tree at load."""
|
|
163
169
|
out = Path(path)
|
|
164
170
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
-
out.write_text(json.dumps(self.to_dict(), indent=2))
|
|
171
|
+
out.write_text(json.dumps(relativize_recorded(self.to_dict(), workspace), indent=2))
|
|
166
172
|
return out
|
|
167
173
|
|
|
168
174
|
|
|
169
175
|
def new_run_id() -> str: # e.g. "correct_20260608_153000_1a2b3c4d"
|
|
170
176
|
"""Generate a unique, sortable correction run id."""
|
|
171
177
|
return f"correct_{time.strftime('%Y%m%d_%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# The RECOMMENDED mark-class slate (DEC 2a231843) — an OPEN vocabulary: classes are
|
|
181
|
+
# DATA, not schema. A mark commits with any non-empty class string; this tuple is
|
|
182
|
+
# the evidence-derived starting set (census dc31c33c + the drive evidence chain)
|
|
183
|
+
# that pickers and status hints surface, so a new class found mid-walk is a journal
|
|
184
|
+
# entry, never a core release.
|
|
185
|
+
RECOMMENDED_MARK_CLASSES = (
|
|
186
|
+
"hesitation-omission", # single Um / 'you know' slots dropped (fill dominates)
|
|
187
|
+
"repeat-omission", # dropped repeated words/stutters — the omission-entangled boundary case
|
|
188
|
+
"meta-speech-omission", # spoken 'Quote' / 'End Quote' markers dropped
|
|
189
|
+
"meta-speech-executed", # meta-speech rendered AS punctuation instead
|
|
190
|
+
"homophone-substitution", # context-vs-acoustics substitution (where/were)
|
|
191
|
+
"proper-noun-suspect", # entity spelling suspect (Hiroo/Hiro/Hero)
|
|
192
|
+
"orthographic-drift", # decoder-state capitalization/orthography decay
|
|
193
|
+
"granularity-mismatch", # VAD split lands mid-word/mid-token
|
|
194
|
+
"foreign-speech", # non-English speech garbled into English (montage/quote cases; drive-minted 2026-07-19)
|
|
195
|
+
"suspect", # free-note catch-all — flag now, judge later
|
|
196
|
+
)
|
|
@@ -9,6 +9,7 @@ from typing import Any, Dict, List, Optional
|
|
|
9
9
|
from cjm_substrate.core.journal_store import JournalEvent, SubstrateEventType
|
|
10
10
|
from cjm_substrate.core.manager import CapabilityManager
|
|
11
11
|
from cjm_substrate.core.queue import JobQueue
|
|
12
|
+
from cjm_substrate.core.workspace import resolve_recorded_tree
|
|
12
13
|
from cjm_transcript_correction_core.graph import (active_corrections, build_prune_correction,
|
|
13
14
|
commit_nodes_edges, commit_text_correction,
|
|
14
15
|
count_source_segments,
|
|
@@ -31,8 +32,11 @@ logger = logging.getLogger(__name__)
|
|
|
31
32
|
def load_decomp_manifest(
|
|
32
33
|
path: str, # Path to a decomp-core run manifest JSON
|
|
33
34
|
) -> Dict[str, Any]: # Parsed manifest dict
|
|
34
|
-
"""Load + lightly validate a decomp-core run manifest (untyped JSON; CR-20 interchange).
|
|
35
|
-
|
|
35
|
+
"""Load + lightly validate a decomp-core run manifest (untyped JSON; CR-20 interchange).
|
|
36
|
+
|
|
37
|
+
${WS}/ recorded paths (5daadfc4 rung f) resolve to absolute here, anchored
|
|
38
|
+
at the manifest's own location (active workspace as fallback)."""
|
|
39
|
+
data = resolve_recorded_tree(json.loads(Path(path).read_text()), Path(path))
|
|
36
40
|
fmt = data.get("format", "")
|
|
37
41
|
if "decomp-core" not in fmt:
|
|
38
42
|
logger.warning(f"unexpected decomp manifest format: {fmt!r} (continuing)")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cjm-transcript-correction-core
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
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
|
|
@@ -10,9 +10,9 @@ 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.
|
|
14
|
-
Requires-Dist: cjm_context_graph_primitives>=0.0.
|
|
15
|
-
Requires-Dist: cjm-context-graph-layer>=0.0.
|
|
13
|
+
Requires-Dist: cjm_substrate>=0.0.57
|
|
14
|
+
Requires-Dist: cjm_context_graph_primitives>=0.0.15
|
|
15
|
+
Requires-Dist: cjm-context-graph-layer>=0.0.11
|
|
16
16
|
Requires-Dist: cjm-transcript-graph-schema>=0.0.7
|
|
17
17
|
Dynamic: license-file
|
|
18
18
|
|
|
@@ -26,7 +26,7 @@ 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
|
+
- **`cjm_transcript_correction_core.journal`** — Live append-through for the correction verbs — the workflow journal's domain half.
|
|
30
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).
|
|
31
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.
|
|
32
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.
|
|
@@ -46,11 +46,14 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
46
46
|
- `active_corrections` _function_ — Filter to the effective correction set (the layer's resolve_active over a read superseded set).
|
|
47
47
|
- `build_boundary_shift_correction` _function_ — Build a grouping Correction that moves text across one segment boundary.
|
|
48
48
|
- `build_correction_node` _function_ — Construct a Correction overlay node (pure; commit happens separately).
|
|
49
|
+
- `build_mark_correction` _function_ — Build a NON-MUTATING mark Correction (DEC 2a231843: routed attention).
|
|
49
50
|
- `build_prune_amendment` _function_ — Build a grouping Correction that supersedes a prune with a REDUCED set (unprune).
|
|
50
51
|
- `build_prune_correction` _function_ — Build one batch grouping Correction that prunes empty segments (D14).
|
|
51
52
|
- `build_reject_review` _function_ — Build a review Correction that REJECTS a prior correction (reject-as-supersede).
|
|
52
53
|
- `build_text_correction` _function_ — Build a text_content Correction + its CORRECTS (+ optional SUPERSEDES) edges.
|
|
53
54
|
- `commit_boundary_shift_correction` _function_ — Commit a boundary-shift correction (node + CORRECTS x2 [+ SUPERSEDES]) + REVIEWED markers on both segments.
|
|
55
|
+
- `commit_mark_correction` _function_ — Commit a mark (node + CORRECTS per anchored segment [+ SUPERSEDES]).
|
|
56
|
+
- `commit_mark_dismissal` _function_ — Dismiss an open mark WITHOUT a correction (reject-as-supersede).
|
|
54
57
|
- `commit_nodes_edges` _function_ — Commit overlay nodes/edges through the layer's idempotent extend_graph.
|
|
55
58
|
- `commit_prune_amendment` _function_ — Commit an unprune amendment (node + DERIVED_FROM edges + SUPERSEDES).
|
|
56
59
|
- `commit_text_correction` _function_ — Commit a text_content correction (node + CORRECTS [+ SUPERSEDES]) + a REVIEWED marker.
|
|
@@ -66,7 +69,10 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
66
69
|
- `load_source_corrections` _function_ — Load every Correction targeting a Source (across sessions) + the superseded-id set.
|
|
67
70
|
- `load_source_segments` _function_ — Load a Source's fine Segment spine under its chosen rendition (typed query surface).
|
|
68
71
|
- `load_variant_texts` _function_ — Resolve per-transcriber chunk texts from the segments' CharSlice refs.
|
|
72
|
+
- `mark_anchor_segments` _function_ — Validate a mark anchor and list the Segment ids it touches.
|
|
73
|
+
- `open_marks` _function_ — Filter to the OPEN marks — the pass-2 worklist ('query open marks, walk them').
|
|
69
74
|
- `project_effective_spine` _function_ — Project the effective spine = layer-0 + applied corrections.
|
|
75
|
+
- `reanchor_span` _function_ — Re-locate a span anchor in text that may have been edited since mark time.
|
|
70
76
|
- `record_review_markers` _function_ — Persist per-(session, segment) review markers as REVIEWED edges.
|
|
71
77
|
- `resolve_source_renditions` _function_ — Pick the AudioRendition set whose fine Segment spine correction operates on.
|
|
72
78
|
- `set_session_status` _function_ — Update a session's status + updated_at.
|
|
@@ -79,7 +85,6 @@ A frontend-agnostic core for the transcript correction workflow — the first do
|
|
|
79
85
|
- `correction_replay_handlers` _function_ — The correction core's registered replay vocabulary (replay stays DOMAIN-OWNED).
|
|
80
86
|
- `journal_correction_op` _function_ — Append one correction op — envelope + semantic args + the EXACT wires committed.
|
|
81
87
|
- `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
88
|
|
|
84
89
|
### `cjm_transcript_correction_core.models`
|
|
85
90
|
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/pyproject.toml
RENAMED
|
@@ -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.
|
|
17
|
+
dependencies = ['cjm_substrate>=0.0.57', 'cjm_context_graph_primitives>=0.0.15', '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-correction-core"
|
|
@@ -22,6 +22,9 @@ Repository = "https://github.com/cj-mills/cjm-transcript-correction-core"
|
|
|
22
22
|
[project.scripts]
|
|
23
23
|
cjm-transcript-correction-core = "cjm_transcript_correction_core.cli:main"
|
|
24
24
|
|
|
25
|
+
[project.entry-points."cjm_context_graph_layer.replay"]
|
|
26
|
+
correction = "cjm_transcript_correction_core.journal:correction_replay_handlers"
|
|
27
|
+
|
|
25
28
|
[tool.setuptools.dynamic]
|
|
26
29
|
version = {attr = "cjm_transcript_correction_core.__version__"}
|
|
27
30
|
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_graph.py
RENAMED
|
@@ -5,12 +5,15 @@ Projected from the graph notebook's two pure-check cells at the golden-reference
|
|
|
5
5
|
from cjm_transcript_correction_core.graph import (
|
|
6
6
|
active_corrections,
|
|
7
7
|
build_boundary_shift_correction,
|
|
8
|
+
build_mark_correction,
|
|
8
9
|
build_prune_amendment,
|
|
9
10
|
build_prune_correction,
|
|
10
11
|
build_reject_review,
|
|
11
12
|
build_text_correction,
|
|
12
13
|
corrections_to_edits,
|
|
14
|
+
open_marks,
|
|
13
15
|
project_effective_spine,
|
|
16
|
+
reanchor_span,
|
|
14
17
|
)
|
|
15
18
|
from cjm_transcript_correction_core.models import SpineSegment
|
|
16
19
|
|
|
@@ -178,3 +181,94 @@ def test_prune_amendment_rescues_boundary_shift_target():
|
|
|
178
181
|
eff2 = project_effective_spine(segs, active)
|
|
179
182
|
assert [s.id for s in eff2] == ["a", "b", "c"]
|
|
180
183
|
assert eff2[0].text == "largest naval battle" and eff2[1].text == "in history"
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_build_mark_correction_anchor_shapes():
|
|
187
|
+
# segment: one CORRECTS edge, non-mutating payload vocabulary
|
|
188
|
+
node, edges = build_mark_correction("src1", {"kind": "segment", "segment_id": "a"},
|
|
189
|
+
"suspect", session_id="s1", note="check later")
|
|
190
|
+
props = node["properties"]
|
|
191
|
+
assert props["correction_type"] == "mark"
|
|
192
|
+
assert props["payload"]["operation"] == "mark"
|
|
193
|
+
assert props["payload"]["mark_class"] == "suspect"
|
|
194
|
+
assert props["rationale"] == "check later"
|
|
195
|
+
assert [(e["relation_type"], e["target_id"]) for e in edges] == [("CORRECTS", "a")]
|
|
196
|
+
# boundary: CORRECTS x2 (the shift gesture's coordinates; seams allowed)
|
|
197
|
+
_, edges = build_mark_correction(
|
|
198
|
+
"src1", {"kind": "boundary", "boundary_after": "a", "right_segment_id": "b"},
|
|
199
|
+
"repeat-omission", session_id="s1")
|
|
200
|
+
assert [(e["relation_type"], e["target_id"]) for e in edges] == [
|
|
201
|
+
("CORRECTS", "a"), ("CORRECTS", "b")]
|
|
202
|
+
# span: offsets + verbatim snapshot ride the anchor; supersession is an edge
|
|
203
|
+
node, edges = build_mark_correction(
|
|
204
|
+
"src1", {"kind": "span", "segment_id": "c", "char_start": 0, "char_end": 5,
|
|
205
|
+
"text_snapshot": "world"},
|
|
206
|
+
"homophone-substitution", session_id="s1", supersedes_id="m0")
|
|
207
|
+
assert node["properties"]["payload"]["anchor"]["text_snapshot"] == "world"
|
|
208
|
+
assert ("SUPERSEDES", "m0") in [(e["relation_type"], e["target_id"]) for e in edges]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_build_mark_correction_validation():
|
|
212
|
+
def rejects(anchor, mark_class="suspect"):
|
|
213
|
+
try:
|
|
214
|
+
build_mark_correction("src1", anchor, mark_class, session_id="s1")
|
|
215
|
+
except ValueError:
|
|
216
|
+
return True
|
|
217
|
+
return False
|
|
218
|
+
assert rejects({"kind": "segment", "segment_id": "a"}, mark_class=" ")
|
|
219
|
+
# punctuation-led classes are reserved for gestures (the '`-`' junk-mark drive find)
|
|
220
|
+
assert rejects({"kind": "segment", "segment_id": "a"}, mark_class="-")
|
|
221
|
+
assert rejects({"kind": "segment", "segment_id": "a"}, mark_class="`-`")
|
|
222
|
+
assert rejects({"kind": "sentence", "segment_id": "a"})
|
|
223
|
+
assert rejects({"kind": "segment"})
|
|
224
|
+
assert rejects({"kind": "boundary", "boundary_after": "a"})
|
|
225
|
+
# a span without its snapshot could never re-anchor — refused at build time
|
|
226
|
+
assert rejects({"kind": "span", "segment_id": "a", "char_start": 0, "char_end": 2})
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_mark_never_touches_projection():
|
|
230
|
+
"""The DEC 2a231843 invariant: a mark is invisible to the effective view BY
|
|
231
|
+
CONSTRUCTION — corrections_to_edits has no arm for correction_type "mark"."""
|
|
232
|
+
node, _ = build_mark_correction("src1", {"kind": "segment", "segment_id": "b"},
|
|
233
|
+
"hesitation-omission", session_id="s1")
|
|
234
|
+
props = dict(node["properties"])
|
|
235
|
+
props["id"] = node["id"]
|
|
236
|
+
assert corrections_to_edits([props]) == []
|
|
237
|
+
out = project_effective_spine(SEGS, [props])
|
|
238
|
+
assert [(s.id, s.text) for s in out] == [(s.id, s.text) for s in SEGS]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_open_marks_lifecycle():
|
|
242
|
+
m1, _ = build_mark_correction("src1", {"kind": "segment", "segment_id": "a"},
|
|
243
|
+
"suspect", session_id="s1")
|
|
244
|
+
m2, _ = build_mark_correction("src1", {"kind": "boundary", "boundary_after": "a",
|
|
245
|
+
"right_segment_id": "b"},
|
|
246
|
+
"repeat-omission", session_id="s1")
|
|
247
|
+
fix, _ = build_text_correction("src1", "a", "Hello", session_id="s1",
|
|
248
|
+
supersedes_id=m1["id"])
|
|
249
|
+
rows = []
|
|
250
|
+
for n in (m1, m2, fix):
|
|
251
|
+
p = dict(n["properties"])
|
|
252
|
+
p["id"] = n["id"]
|
|
253
|
+
rows.append(p)
|
|
254
|
+
# open until something supersedes: the discharging correction closes m1
|
|
255
|
+
assert [m["id"] for m in open_marks(rows, set())] == [m1["id"], m2["id"]]
|
|
256
|
+
assert [m["id"] for m in open_marks(rows, {m1["id"]})] == [m2["id"]]
|
|
257
|
+
# a dismissal review (reject-as-supersede) closes m2 and is not itself a mark
|
|
258
|
+
rej, _ = build_reject_review("src1", m2["id"], session_id="s1")
|
|
259
|
+
p = dict(rej["properties"])
|
|
260
|
+
p["id"] = rej["id"]
|
|
261
|
+
assert open_marks(rows + [p], {m1["id"], m2["id"]}) == []
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def test_reanchor_span():
|
|
265
|
+
a = {"char_start": 6, "char_end": 11, "text_snapshot": "world"}
|
|
266
|
+
assert reanchor_span(a, "hello world") == (6, 11) # exact offsets verified
|
|
267
|
+
assert reanchor_span(a, "well, hello world") == (12, 17) # edited text -> snapshot re-found
|
|
268
|
+
assert reanchor_span(a, "goodbye moon") is None # gone -> degrade to segment level
|
|
269
|
+
# multiple occurrences: the one nearest the recorded start wins
|
|
270
|
+
assert reanchor_span({"char_start": 0, "char_end": 2, "text_snapshot": "ab"},
|
|
271
|
+
"ab ab") == (0, 2)
|
|
272
|
+
assert reanchor_span({"char_start": 3, "char_end": 5, "text_snapshot": "ab"},
|
|
273
|
+
"ab ab") == (3, 5)
|
|
274
|
+
assert reanchor_span({"text_snapshot": ""}, "x") is None
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_journal.py
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"""The correction journal: sidecar path + op envelope (pure half; replay is e2e-proven)."""
|
|
2
2
|
|
|
3
3
|
from cjm_context_graph_primitives.journal import read_journal
|
|
4
|
-
from cjm_transcript_correction_core.journal import
|
|
5
|
-
|
|
4
|
+
from cjm_transcript_correction_core.journal import correction_replay_handlers, journal_correction_op
|
|
5
|
+
from cjm_context_graph_layer.journal import sidecar_journal_path
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def test_sidecar_journal_path():
|
|
@@ -33,4 +33,11 @@ def test_journal_correction_op_envelope(tmp_path):
|
|
|
33
33
|
assert len(read_journal(j)) == 2
|
|
34
34
|
# every registered replay verb has a handler
|
|
35
35
|
assert set(correction_replay_handlers()) == {"session-start", "boundary-shift", "text-correction",
|
|
36
|
-
"prune-amendment", "
|
|
36
|
+
"prune-amendment", "mark", "mark-dismiss",
|
|
37
|
+
"review-markers", "session-status"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_replay_handlers_cover_mark_verbs():
|
|
41
|
+
"""Marks are BORN JOURNALED (DEC 2a231843): both verbs replay as wire ops."""
|
|
42
|
+
handlers = correction_replay_handlers()
|
|
43
|
+
assert "mark" in handlers and "mark-dismiss" in handlers
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.2"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_cli.py
RENAMED
|
File without changes
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_models.py
RENAMED
|
File without changes
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_pipeline.py
RENAMED
|
File without changes
|
{cjm_transcript_correction_core-0.0.2 → cjm_transcript_correction_core-0.0.4}/tests/test_signals.py
RENAMED
|
File without changes
|