Skip to content

Commit 1dfd008

Browse files
feat(rusty-red): harden redcore and rebuild indexes
1 parent 62c38f0 commit 1dfd008

14 files changed

Lines changed: 2806 additions & 183 deletions

File tree

Cargo.lock

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

README.md

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,42 @@ This builds an `abi3-py312` wheel and installs it into the active Python environ
3232

3333
Rusty Red is the productized THG runtime profile: it keeps the THG command model
3434
for existing harness flows while adding first-class graph node, edge, adjacency,
35-
exact scalar property index, stats, verify, and MCP routes. The code should stay
36-
shared through `thg-core` rather than being copied into a second implementation:
37-
Context Theorem can use the same graph-store components internally, while Rusty
38-
Red packages them as a standalone database service. It is not a raw Redis
39-
protocol, RedisGraph compatibility layer, FalkorDB replacement, or complete
40-
OpenCypher/GQL engine yet. Redis-compatible storage is the current durable
41-
backing store; Rusty Red owns the graph API and index semantics above that
42-
store.
35+
exact scalar property index, stats, verify, and MCP routes. By default, it runs
36+
in `RUSTY_RED_MODE=embedded` with RedCore RAM-first storage and local AOF/snapshot
37+
persistence.
38+
39+
It is not a raw Redis protocol, RedisGraph compatibility layer, FalkorDB
40+
replacement, or complete OpenCypher/GQL engine yet. `RUSTY_RED_MODE=redis` keeps
41+
legacy run/context THG state commands.
42+
43+
It is also not release-ready as a full database yet. The current embedded
44+
RedCore path has AOF/snapshot recovery tests, staged `GraphMutationBatch` /
45+
`GraphTransaction` commits, pre-publish durability guards for AOF, snapshot, and
46+
manifest write failures, per-tenant single-writer execution, committed read
47+
snapshots, an internal read barrier, strict ACID config enforcement,
48+
per-directory `.redcore.lock` locking, fsynced temp/rename/dir snapshot and
49+
manifest writes, torn AOF tail truncation, previous-snapshot fallback with
50+
AOF replay, and rebuild-indexes admin tooling over canonical graph records.
51+
Railway restart/no-public-port evidence and the broader query/cache/search gates
52+
remain follow-up release gates.
4353

4454
Run the product server locally:
4555

4656
```bash
4757
cd theseus_native
48-
RUSTY_RED_REDIS_URL=redis://127.0.0.1:6379 cargo run -p thg-product-server
58+
RUSTY_RED_MODE=embedded RUSTY_RED_DATA_DIR=data/rusty-red cargo run -p thg-product-server
59+
```
60+
61+
Strict local durability mode is explicit:
62+
63+
```bash
64+
RUSTY_RED_MODE=embedded \
65+
RUSTY_RED_CONCURRENCY=single_writer \
66+
RUSTY_RED_TXN_ISOLATION=serializable \
67+
RUSTY_RED_STRICT_ACID=true \
68+
RUSTY_RED_DURABILITY=aof_always \
69+
RUSTY_RED_DATA_DIR=data/rusty-red \
70+
cargo run -p thg-product-server
4971
```
5072

5173
Core routes:
@@ -67,16 +89,18 @@ GET /v1/tenants/{tenant_id}/graph/edges/{edge_id}
6789
POST /v1/tenants/{tenant_id}/graph/neighbors
6890
GET /v1/tenants/{tenant_id}/graph/stats
6991
GET /v1/tenants/{tenant_id}/graph/verify
92+
POST /v1/tenants/{tenant_id}/graph/rebuild-indexes
7093
POST /v1/tenants/{tenant_id}/context/pack
7194
```
7295

7396
The shared THG command API also exposes the Rusty Red graph-store core through
7497
`THG.GRAPH.NODE.UPSERT`, `THG.GRAPH.EDGE.UPSERT`,
75-
`THG.GRAPH.NODES.QUERY`, `THG.GRAPH.NEIGHBORS`, `THG.GRAPH.STATS`, and
76-
`THG.GRAPH.VERIFY`. This lets Context Theorem adopt Rusty Red-grade graph
98+
`THG.GRAPH.NODES.QUERY`, `THG.GRAPH.NEIGHBORS`, `THG.GRAPH.STATS`,
99+
`THG.GRAPH.VERIFY`, and `THG.GRAPH.REBUILD_INDEXES`. This lets Context
100+
Theorem adopt Rusty Red-grade graph
77101
records, exact scalar property indexes, adjacency traversal, and verification
78102
through the existing THG command surface instead of depending on a separate
79-
runtime name.
103+
runtime name. In this slice, run/context state commands remain Redis-mode.
80104

81105
The OpenAPI document is served at `/openapi.json`. It exists because Rusty Red
82106
is exposed through HTTP and MCP even though the underlying storage engine is a
@@ -86,10 +110,10 @@ well-known manifests.
86110

87111
Railway template readiness follows the public template guidance: use a GitHub
88112
source repo, keep the service root minimal, set `/ready` as the health check,
89-
wire Redis through private networking/reference variables, attach persistent
90-
storage to stateful dependencies, generate any public-ingress tokens with
91-
Railway template variable functions, and replace the badge placeholder above
92-
once Railway assigns the final template URL.
113+
wire Redis only for explicit `RUSTY_RED_MODE=redis` deployments through private
114+
networking/reference variables, attach persistent storage to stateful dependencies,
115+
generate any public-ingress tokens with Railway template variable functions, and
116+
replace the badge placeholder above once Railway assigns the final template URL.
93117

94118
Railway can deploy this directory directly:
95119

crates/thg-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ default = []
1111
redis-store = ["dep:redis"]
1212

1313
[dependencies]
14+
fs2 = "0.4"
1415
redis = { version = "0.27", optional = true }
1516
serde = { version = "1.0", features = ["derive"] }
1617
serde_json = "1.0"

crates/thg-core/src/commands.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub enum ThgCommand {
2424
GraphNeighbors,
2525
GraphStats,
2626
GraphVerify,
27+
GraphRebuildIndexes,
2728
}
2829

2930
impl ThgCommand {
@@ -46,6 +47,7 @@ impl ThgCommand {
4647
"THG.GRAPH.NEIGHBORS" => Ok(Self::GraphNeighbors),
4748
"THG.GRAPH.STATS" => Ok(Self::GraphStats),
4849
"THG.GRAPH.VERIFY" => Ok(Self::GraphVerify),
50+
"THG.GRAPH.REBUILD_INDEXES" | "THG.GRAPH.REBUILD" => Ok(Self::GraphRebuildIndexes),
4951
_ => Err(ThgError::unsupported_command(name)),
5052
}
5153
}
@@ -69,6 +71,7 @@ impl ThgCommand {
6971
Self::GraphNeighbors => "THG.GRAPH.NEIGHBORS",
7072
Self::GraphStats => "THG.GRAPH.STATS",
7173
Self::GraphVerify => "THG.GRAPH.VERIFY",
74+
Self::GraphRebuildIndexes => "THG.GRAPH.REBUILD_INDEXES",
7275
}
7376
}
7477
}

crates/thg-core/src/executor.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,26 @@ impl InMemoryThgExecutor {
424424
self.state_hash(),
425425
)
426426
}
427+
428+
fn graph_rebuild_indexes(&mut self) -> ThgResponse {
429+
match self.graph_store.rebuild_indexes() {
430+
Ok(report) => ThgResponse::ok(
431+
ThgCommand::GraphRebuildIndexes.name(),
432+
if report.after.ok {
433+
"ok"
434+
} else {
435+
"canonical_graph_problem"
436+
},
437+
json!({ "report": report }),
438+
self.state_hash(),
439+
),
440+
Err(error) => ThgResponse::err(
441+
ThgCommand::GraphRebuildIndexes.name(),
442+
ThgError::new(error.code, error.message),
443+
self.state_hash(),
444+
),
445+
}
446+
}
427447
}
428448

429449
#[derive(Clone, Debug)]
@@ -488,6 +508,7 @@ impl ThgExecutor for InMemoryThgExecutor {
488508
ThgCommand::GraphNeighbors => self.graph_neighbors(args),
489509
ThgCommand::GraphStats => self.graph_stats(),
490510
ThgCommand::GraphVerify => self.graph_verify(),
511+
ThgCommand::GraphRebuildIndexes => self.graph_rebuild_indexes(),
491512
})
492513
}
493514

@@ -830,6 +851,10 @@ mod tests {
830851
));
831852
let verify =
832853
executor.execute_request(ThgRequest::new(ThgCommand::GraphVerify.name(), json!({})));
854+
let rebuild = executor.execute_request(ThgRequest::new(
855+
ThgCommand::GraphRebuildIndexes.name(),
856+
json!({}),
857+
));
833858

834859
assert!(node_a.ok);
835860
assert!(node_b.ok);
@@ -840,6 +865,7 @@ mod tests {
840865
assert_eq!(neighbors.payload["plan"]["operation"], "adjacency_seek");
841866
assert_eq!(neighbors.payload["neighbors"][0]["node_id"], "node:b");
842867
assert_eq!(verify.payload["report"]["ok"], true);
868+
assert_eq!(rebuild.payload["report"]["after"]["ok"], true);
843869
}
844870

845871
#[test]

0 commit comments

Comments
 (0)