Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.codegraph
.git
node_modules
target
3 changes: 3 additions & 0 deletions CHANGELOG.d/lineageweave-temporal-context-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Package the existing cutoff-safe `POST /v1/temporal-context` contract as the loopback-only `tepp-loopback` binary and container for trusted same-host consumers such as LineageWeave.
20 changes: 20 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM rust:1.97.1-bookworm AS build
WORKDIR /src
COPY . .
RUN cargo build --locked --release -p tepp_api --bin tepp-loopback

FROM debian:bookworm-slim
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /src/target/release/tepp-loopback /usr/local/bin/tepp-loopback
USER 65532:65532
HEALTHCHECK --interval=10s --timeout=3s --start-period=2s --retries=5 \
CMD curl --fail --silent --show-error \
--header "content-type: application/json" \
--header "tepp-consumer: lineageweave" \
--header "tepp-contract-version: 1" \
--data '{"contract_version":1,"consumer_code":"lineageweave","knowledge_cutoff":"2026-08-20T00:00:00Z","subject_post_id":"health-post","events":[{"event_id":"health-event","source_post_id":"health-post","event_type_code":"health_probe","event_label":"Health probe","event_time":"2026-08-20T00:00:00Z","available_time":"2026-08-20T00:00:00Z","project_reference":null,"actor_references":["health-actor"]}]}' \
http://127.0.0.1:18081/v1/temporal-context >/dev/null \
|| exit 1
Comment on lines +12 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Hardcoded healthcheck payload can drift from the contract

The container health probe posts a fixed temporal-context JSON body. It matches current validation, but any change to the temporal-context wire DTO will silently break the probe with no compile-time link. Keep it in sync with the contract.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +12 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Healthcheck consumes the served-request budget

The HEALTHCHECK issues a real POST /v1/temporal-context every 10s, and each one is counted against request_limit by the serve loop. Harmless with the default unbounded limit, but if an operator ever runs the container with a bounded limit, healthcheck traffic silently drains that budget and shuts the server down early.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

ENTRYPOINT ["/usr/local/bin/tepp-loopback"]
6 changes: 6 additions & 0 deletions crates/tepp_api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,11 @@ serde_json = { workspace = true }
sha2 = { workspace = true }
temporal_core = { path = "../temporal_core", version = "0.1.0" }

[[bin]]
name = "tepp-loopback"
path = "src/bin/tepp_loopback.rs"
test = false
bench = false

[lints]
workspace = true
24 changes: 24 additions & 0 deletions crates/tepp_api/src/bin/tepp_loopback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//! Runnable loopback ingress for trusted same-host TEPP consumers.

use std::net::SocketAddr;

use tepp_api::AnalysisRunLiveService;

const DEFAULT_BIND_ADDR: &str = "127.0.0.1:18081";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Container reachable only via shared network namespace

With no args the entrypoint binds 127.0.0.1:18081, the container's own loopback. Consumers must share the network namespace (host network or shared pod netns); published ports will not reach it. This matches the stated same-host intent but constrains deployment.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut arguments = std::env::args().skip(1);
let bind_addr = arguments
.next()
.unwrap_or(DEFAULT_BIND_ADDR.to_owned())
.parse::<SocketAddr>()?;
let request_limit = arguments
.next()
.map(|value| value.parse::<usize>())
.transpose()?
.unwrap_or(usize::MAX);
let mut service = AnalysisRunLiveService::bind(bind_addr)?;
println!("{}", service.local_addr()?);
(0..request_limit).for_each(|_| drop(service.serve_one()));
Comment on lines +15 to +22

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Bounded limit counts failed attempts

(0..request_limit).for_each(|_| drop(service.serve_one())) counts every iteration toward the limit, including iterations where serve_one returns an I/O error. A bounded limit N therefore caps attempts, not successful requests. Harmless in the container (default usize::MAX) but the numeric bound means 'attempts served'.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Ok(())
}
31 changes: 31 additions & 0 deletions crates/tepp_api/tests/loopback_binary_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! The packaged loopback binary serves the published temporal-context wire.

use std::io::{BufRead, BufReader, Read, Write};
use std::net::TcpStream;
use std::process::{Command, Stdio};

#[test]
fn binary_serves_one_bounded_temporal_context_request() {
let mut child = Command::new(env!("CARGO_BIN_EXE_tepp-loopback"))
.args(["127.0.0.1:0", "1"])
.stdout(Stdio::piped())
.spawn()
.expect("spawn loopback service");
let mut address = String::new();
BufReader::new(child.stdout.take().expect("stdout"))
.read_line(&mut address)
.expect("bound address");
let body = r#"{"contract_version":1,"consumer_code":"lineageweave","knowledge_cutoff":"2026-08-20T00:00:00Z","subject_post_id":"post-1","events":[{"event_id":"event-1","source_post_id":"post-1","event_type_code":"health_probe","event_label":"Health probe","event_time":"2026-08-20T00:00:00Z","available_time":"2026-08-20T00:00:00Z","project_reference":null,"actor_references":["actor-1"]}]}"#;
let request = format!(
"POST /v1/temporal-context HTTP/1.1\r\nHost: {}\r\ncontent-type: application/json\r\ntepp-consumer: lineageweave\r\ntepp-contract-version: 1\r\ncontent-length: {}\r\n\r\n{body}",
address.trim(),
body.len()
);
let mut stream = TcpStream::connect(address.trim()).expect("connect");
stream.write_all(request.as_bytes()).expect("request");
let mut response = String::new();
stream.read_to_string(&mut response).expect("response");
assert!(response.starts_with("HTTP/1.1 200 OK"));
assert!(response.contains("association_not_causal"));
assert!(child.wait().expect("wait").success());
}
2 changes: 1 addition & 1 deletion docs/API_CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

TEPP must work both as a standalone product and as a modular CWL component. Integrations with `naruon`, `contextual-orchestrator`, `.github`, or other repositories use explicit versioned API/artifact contracts. Cross-service direct table access is prohibited.

Current protected main exposes Rust library/domain contracts. The active PR adds a loopback HTTP/1.1 listener for naruon analysis-run, LineageWeave temporal-context, and export POSTs. That listener is not a production TLS/`$PORT` service. Endpoint examples below that are not covered by `NaruonLiveService` remain target interface shapes.
Current protected main exposes Rust library/domain contracts. The active stack adds a loopback HTTP/1.1 listener for naruon analysis-run, LineageWeave temporal-context, and export POSTs. `tepp-loopback` runs the shared consumer listener on `127.0.0.1:18081` by default; a caller may pass another loopback socket address and an optional maximum request count as its two arguments. The container is intended for a trusted same-host or shared-network-namespace sidecar, checks readiness through a synthetic bounded temporal-context request, and deliberately cannot bind a public or bridge address. It is not a production TLS/`$PORT` service. Endpoint examples below that are not covered by `NaruonLiveService` remain target interface shapes.

## 2. Contract families

Expand Down