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
39 changes: 39 additions & 0 deletions docs/briefs/r3-omni-openapi-backend-bridge-dissolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# R3 Omni OpenAPI Backend Bridge Dissolution

Status: QUEUED

Owner lane: T-PB-B, coordinated with T-Omni-Shape-B.

## Scope

Retire the R3 gate #25 hand-Rust demo bridge introduced for the omni OpenAPI
backend emission receipt:

- `src/v3/compiler/src/omni_shape_b_openapi.rs`
`project_openapi_yaml`, `project_markdown_documentation`, and
`project_rust_backend_service`
- `src/v3/compiler/tests/integration/m1_5_omni_shape_b_openapi_test.rs`
direct `rustc` generated-backend roundtrip helper

These are fixture-scoped demo projections, not compiler targets and not a
general backend framework.

## Dissolution Trigger

Retire this bridge when `.dag` TestClaim execution can materialize a generated
artifact, invoke `ExecuteCommand` against it, and reuse the produced binary or
artifact handle across multiple assertions. At that point:

- OpenAPI and Markdown projection move to Shape B `.dag` artifact emitters.
- The runnable backend uses the normal Shape A Rust emission path or generated
artifact materialization path, not a compiler-local string projector.
- The route roundtrip moves from Rust-authored `Command::new("rustc")` to an
`ExecuteCommand`-based `.dag` TestClaim.

## ROADMAP Receipt

`ROADMAP.md:170` — Hand-Rust census split; the test-driven bridge retires

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

BLOCKING: The P5 receipt is not checkable because ROADMAP.md:170 currently points at Post-R1 Grounding lanes, not the Hand-Rust census split or T-PB-B row.

through T-PB-B by migrating the generated-backend roundtrip to a `.dag`
TestClaim declaration. The adjacent production projection is in this brief's
scope only because that test bridge is its sole consumer for the R3 gate #25
receipt; it retires with the same T-PB-B TestClaim migration.
12 changes: 6 additions & 6 deletions src/v3/compiler/src/bootstrap_generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89137,7 +89137,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(3038),
Expand All @@ -89151,7 +89151,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(3039),
Expand All @@ -89176,7 +89176,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(3040),
Expand All @@ -89195,7 +89195,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(3041),
Expand All @@ -89209,7 +89209,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(3042),
Expand All @@ -89234,7 +89234,7 @@ fn bootstrapped_fixture_dag_declarations() -> Vec<Declaration> {
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(3043),
Expand Down
12 changes: 6 additions & 6 deletions src/v3/compiler/src/bootstrap_generated_without_parse_surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84702,7 +84702,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(2951),
Expand All @@ -84716,7 +84716,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(2952),
Expand All @@ -84741,7 +84741,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8805, 8812),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8783, 8790),
});
declarations.push(Declaration {
id: DeclarationId(2953),
Expand All @@ -84760,7 +84760,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(2954),
Expand All @@ -84774,7 +84774,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(2955),
Expand All @@ -84799,7 +84799,7 @@ fn bootstrapped_fixture_without_parse_surface_dag_declarations() -> Vec<Declarat
value_body: None,
refinement: None,
nominal_opacity: None,
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8873, 8878),
span: SourceSpan::new("src/v3/std/anthropic_schema.dag", 8851, 8856),
});
declarations.push(Declaration {
id: DeclarationId(2956),
Expand Down
174 changes: 174 additions & 0 deletions src/v3/compiler/src/omni_shape_b_openapi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
//! `emit.rs` remains scoped to Shape A programming-language targets. This
//! module provides the narrow Rust-side receipt used by the R3 demo until the
//! equivalent `.dag` programs can own the artifact projections.
//!
//! P5 bridge bound: `project_openapi_yaml`, `project_markdown_documentation`,

This comment was marked as resolved.

//! and `project_rust_backend_service` are one fixture-scoped R3
//! T-Omni-Shape-B receipt over `RestEndpointBinding` rows. They are not new
//! compiler targets and must not grow into a general backend framework.
//! Dissolution trigger: queued brief
//! `docs/briefs/r3-omni-openapi-backend-bridge-dissolution.md`.

use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write as _;
Expand Down Expand Up @@ -250,6 +257,154 @@ pub fn project_markdown_documentation(dag: &Dag) -> Result<String, ProjectOpenAp
Ok(out)
}

/// Emits the narrow runnable-backend side of the R3 omni OpenAPI demo.
///
/// This is deliberately fixture-scoped bridge code. It consumes the same
/// canonical `RestRoute` projection as OpenAPI/Markdown to prove the full-stack
/// same-DAG property now, then dissolves via
/// `docs/briefs/r3-omni-openapi-backend-bridge-dissolution.md`.
pub fn project_rust_backend_service(dag: &Dag) -> Result<String, ProjectOpenApiError> {
let routes = extract_rest_routes(dag)?;
let mut out = String::from(
r#"use std::io::{Read, Write};
use std::net::TcpListener;

#[derive(Clone, Copy)]
struct Route {
method: &'static str,
path: &'static str,
}

const ROUTES: &[Route] = &[
"#,
);
for route in &routes {
out.push_str(" Route { method: ");
out.push_str(&rust_string_literal(&route.method));
out.push_str(", path: ");
out.push_str(&rust_string_literal(&route.path));
out.push_str(" },\n");
}
out.push_str(
r#"];

fn route_status(method: &str, path: &str) -> u16 {
if ROUTES
.iter()
.any(|route| route.method == method && path_matches(route.path, path))
{
200
} else {
404
}
}

fn path_matches(template: &str, path: &str) -> bool {
let template = strip_leading_slash(template);
let path = strip_leading_slash(path);
let template_parts: Vec<_> = template.split('/').collect();
let path_parts: Vec<_> = path.split('/').collect();
if template_parts.len() != path_parts.len() {
return false;
}
template_parts
.iter()
.zip(path_parts.iter())
.all(|(template_part, path_part)| segment_matches(template_part, path_part))
}

fn strip_leading_slash(value: &str) -> &str {
value.strip_prefix('/').unwrap_or(value)
}

fn segment_matches(template: &str, path: &str) -> bool {
let mut remainder = path;
let mut rest = template;
while let Some(start) = rest.find('{') {
let literal = &rest[..start];
if !remainder.starts_with(literal) {
return false;
}
remainder = &remainder[literal.len()..];
let after_open = &rest[start + 1..];
let Some(end) = after_open.find('}') else {
return false;
};
let after_param = &after_open[end + 1..];
if let Some(next_literal_start) = after_param.find('{') {
let next_literal = &after_param[..next_literal_start];
if next_literal.is_empty() {
return false;
}
let Some(boundary) = remainder.find(next_literal) else {
return false;
};
let param_value = &remainder[..boundary];
if !slash_free_non_empty(param_value) {
return false;
}
remainder = &remainder[boundary..];
rest = after_param;
} else {
if after_param.is_empty() {
return slash_free_non_empty(remainder);
}
let Some(value) = remainder.strip_suffix(after_param) else {
return false;
};
return slash_free_non_empty(value);
}
}
remainder == rest
}

fn slash_free_non_empty(value: &str) -> bool {
!value.is_empty() && !value.contains('/')
}

fn respond(request: &str) -> String {
let request_line = request.lines().next().unwrap_or("");
let mut parts = request_line.split_whitespace();
let method = parts.next().unwrap_or("");
let path = parts.next().unwrap_or("");
let status = route_status(method, path);
let reason = if status == 200 { "OK" } else { "Not Found" };
format!("HTTP/1.1 {status} {reason}\r\ncontent-length: 0\r\n\r\n")
}

fn serve(addr: &str) -> std::io::Result<()> {
let listener = TcpListener::bind(addr)?;
for stream in listener.incoming() {
let mut stream = stream?;
let mut request = [0_u8; 2048];
let bytes = stream.read(&mut request)?;
let response = respond(&String::from_utf8_lossy(&request[..bytes]));
stream.write_all(response.as_bytes())?;
}
Ok(())
}

fn main() {
let args: Vec<String> = std::env::args().collect();
match args.as_slice() {
[_, flag, method, path] if flag == "--probe" => {
println!("{}", route_status(method, path));
}
[_, flag, addr] if flag == "--serve" => {
serve(addr).expect("serve backend");
}
_ => {
for route in ROUTES {
println!("{} {}", route.method, route.path);
}
}
}
}
"#,
);
Ok(out)
}

fn append_path_parameter_yaml(out: &mut String, parameter: &str) {
out.push_str(" - name: ");
out.push_str(&yaml_double_quoted(parameter));
Expand Down Expand Up @@ -437,6 +592,25 @@ fn yaml_quoted(value: &str) -> String {
format!("'{}'", value.replace('\'', "''"))
}

fn rust_string_literal(value: &str) -> String {
let mut literal = String::from("\"");
for ch in value.chars() {
match ch {
'\\' => literal.push_str("\\\\"),
'"' => literal.push_str("\\\""),
'\n' => literal.push_str("\\n"),
'\r' => literal.push_str("\\r"),
'\t' => literal.push_str("\\t"),
other if other.is_control() => {
write!(&mut literal, "\\u{{{:X}}}", other as u32).expect("write to String");
}
other => literal.push(other),
}
}
literal.push('"');
literal
}

fn yaml_double_quoted(value: &str) -> String {
let mut quoted = String::from("\"");
for ch in value.chars() {
Expand Down
Loading
Loading