Skip to content

Commit 5f9f271

Browse files
feat: establish Phase 3 Rust editorial core (#57)
* feat: establish Phase 3 Rust editorial core * ci: validate Rust editorial core * ci: allow initial Rust lockfile generation * fix: stabilize JSONL queue contract * chore: ignore Rust build output * style: apply rustfmt to editorial core * style: finish rustfmt normalization * docs: clarify Phase 3 compatibility boundary * ci: diagnose runtime integrity around tests * ci: mirror primary Python validation environment * ci: mirror primary runtime environment * ci: isolate Python baseline validation failures * chore: remove Phase 3 diagnostic CI probe * chore: remove Phase 3 runtime diagnostic workflow * chore: add temporary Phase 3 Python failure diagnostic * ci: expose exact Phase 3 Python validation failure * ci: isolate Phase 3 Python validation commands * ci: split Phase 3 Python diagnostics into independent jobs * Fix baseline ledger binding and enforce Rust foundation invariants --------- Co-authored-by: hiidkaboutyou-spec <hiidkaboutyou-spec@users.noreply.github.com>
1 parent 377b511 commit 5f9f271

10 files changed

Lines changed: 739 additions & 2 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Rust Editorial Core
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- "agent/**"
8+
paths:
9+
- "Cargo.toml"
10+
- "Cargo.lock"
11+
- "rust/**"
12+
- ".github/workflows/rust-editorial-core.yml"
13+
pull_request:
14+
branches:
15+
- main
16+
paths:
17+
- "Cargo.toml"
18+
- "Cargo.lock"
19+
- "rust/**"
20+
- ".github/workflows/rust-editorial-core.yml"
21+
workflow_dispatch:
22+
23+
permissions:
24+
contents: read
25+
26+
concurrency:
27+
group: rust-editorial-core-${{ github.ref }}
28+
cancel-in-progress: true
29+
30+
jobs:
31+
validate-rust-core:
32+
runs-on: ubuntu-latest
33+
timeout-minutes: 10
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v6
37+
38+
- name: Install stable Rust
39+
uses: dtolnay/rust-toolchain@stable
40+
with:
41+
components: rustfmt, clippy
42+
43+
- name: Check formatting
44+
run: cargo fmt --all -- --check
45+
46+
- name: Clippy
47+
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
48+
49+
- name: Test workspace
50+
run: cargo test --workspace --all-features

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ __pycache__/
55
.venv/
66
venv/
77

8+
# Rust
9+
target/
10+
811
# Bot runtime/state
912
.state/
1013
*.tmp

Cargo.lock

Lines changed: 153 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[workspace]
2+
members = ["rust/editorial-core"]
3+
resolver = "2"

app/source_ledger_runtime.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from .raw_observation import RawObservationStore
99
from .source_ledger import SourceLedgerStore, SourceWindowResult, SourceWindowStatus
10-
from .x_client import _safe_error, normalize_handle
10+
from .x_client import XCollector, _safe_error, normalize_handle
1111
from .x_completeness import CompleteWindowXCollector, XCompletenessError
1212

1313
_INSTALLED = False
@@ -72,6 +72,7 @@ def install() -> None:
7272

7373
current = CompleteWindowXCollector._collect_source_timeline
7474
if getattr(current, "_source_ledger_hook", False):
75+
XCollector._collect_source_timeline = current
7576
CompleteWindowXCollector._source_ledger_installed = True
7677
_INSTALLED = True
7778
return
@@ -140,6 +141,9 @@ async def wrapped(self, handle, start, end, *, limit: int, include_replies: bool
140141

141142
wrapped._source_ledger_hook = True
142143
CompleteWindowXCollector._collect_source_timeline = wrapped
144+
# Recovery binds both classes explicitly; keep the runtime base collector on
145+
# the same ledger-aware path, including when install repairs a stale binding.
146+
XCollector._collect_source_timeline = wrapped
143147
CompleteWindowXCollector._source_ledger_installed = True
144148
_INSTALLED = True
145149

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Phase 3 — Rust Editorial Core Foundation
2+
3+
## Evidence-backed scope
4+
5+
Phase 3 introduces Rust only for deterministic truth/state. Python remains authoritative for volatile integrations (X/twscrape, media, AI and Telegram). This is deliberately not a rewrite.
6+
7+
The initial boundary is newline-delimited, versioned JSON over stdin/stdout. It is inspectable, language-neutral and keeps Python and Rust independently testable. PyO3 is intentionally deferred until profiling proves subprocess IPC is a real bottleneck.
8+
9+
Requests require `contract_version: 1` alongside `op`; missing or unsupported versions are rejected. Cursor timestamps must be RFC3339 and are compared as instants, including offsets. Candidates must fall within the complete window. Source rank ties use normalized source identity before post order so sources remain grouped.
10+
11+
SQLite remains the application persistence format established by Phases 1–2. The Rust foundation does not create a second database or fork source truth.
12+
13+
The existing Python recovery/checkpoint modules (`phase3_recovery` and its hardening layer) remain compatibility-critical collection infrastructure. The Rust core does not replace, rename, or bypass them in this phase.
14+
15+
## Rust invariants introduced
16+
17+
- only `COMPLETE` source windows can advance a complete-through cursor;
18+
- cursor advancement is monotonic;
19+
- source/post idempotency keys are deterministic and normalized;
20+
- duplicate queue identities are rejected;
21+
- source-first queue ordering is deterministic;
22+
- invalid editorial queue transitions are rejected;
23+
- JSON contract version is explicit (`1`).
24+
25+
## Rollout
26+
27+
This phase is shadow/foundation only. Existing Python production behavior remains usable and no public/Telegram delivery path is replaced. Later phases may call the JSONL executable behind a feature flag after contract parity tests exist.
28+
29+
## Validation gate
30+
31+
The Python validation failure was reproduced on main `377b511`: Phase 2 wrapped only `CompleteWindowXCollector`, leaving the base `XCollector` on the recovery method without ledger recording. Installation now binds both classes to the same wrapper and repairs stale bindings idempotently. Existing authority tests and a focused once-only ledger regression cover this baseline fix. Telegram posting logic is unchanged.
32+
33+
`cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets --all-features -- -D warnings`, and `cargo test --workspace --all-features` must pass in CI before merge. Existing Python runtime audit/tests must also remain green. Phase 3 does not close merely because the Rust crate compiles: tests must demonstrate the invalid transitions above are impossible through the core API.

rust/editorial-core/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "jeonghan-editorial-core"
3+
version = "0.1.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[dependencies]
8+
chrono = { version = "0.4", default-features = false, features = ["std"] }
9+
serde = { version = "1", features = ["derive"] }
10+
serde_json = "1"
11+
thiserror = "2"
12+
13+
[dev-dependencies]

0 commit comments

Comments
 (0)