diff --git a/docs/briefs/r2-manager-brief-authority-matrix.md b/docs/briefs/r2-manager-brief-authority-matrix.md index fc33ff4c168..937cd916f0a 100644 --- a/docs/briefs/r2-manager-brief-authority-matrix.md +++ b/docs/briefs/r2-manager-brief-authority-matrix.md @@ -148,7 +148,7 @@ Each deliverable owned by an R2 manager belongs to exactly one of categories 1-5 | Deliverable | Category | Notes | |---|---|---| | The manager brief itself | 5 (placeholder) | Graduates on R2 spawn | -| Tier 3 mirror dissolutions (termination/computation/induction/effect-carrier) | 1 (worker brief; 4+ sub-briefs) | Pending; mirror-by-mirror | +| Tier 3 mirror dissolutions (termination/computation/induction/effect-carrier) | 1 (worker brief; 4+ sub-briefs) | Authored in `docs/briefs/r2-pb-tier3-mirror-dissolution-workers.md`; mirror-by-mirror dispatch | | Tier 2 patch_lower_helpers_* retirement | 1 (worker brief) | Pending; if survives R1; B7 priority hint received | | `kernel_algebra_profile` mirror dissolution | 1 (worker brief) | Pending; gated on `ValueBody::Map` substrate | | Post-R1 emergent dissolutions | 1 (worker brief; ad-hoc) | Pending; emerge during R2 | diff --git a/docs/briefs/r2-pure-bootstrap-manager.md b/docs/briefs/r2-pure-bootstrap-manager.md index 047f2c570d5..66c937e4e06 100644 --- a/docs/briefs/r2-pure-bootstrap-manager.md +++ b/docs/briefs/r2-pure-bootstrap-manager.md @@ -19,10 +19,10 @@ | Lane | Size | Status (at brief authoring) | Description | |---|---|---|---| -| Tier 3 mirror dissolutions: termination | M | NOT YET AUTHORED | `DescentEvidence`, `PositiveDescentAmount`, `ProportionalDivisor`, `ShrinkFactor`, `evidence_rank`, `merge_evidence` Rust mirror at `dag.rs:628-790` dissolves as v3 lowers + evaluates `.dag` runtime values. Tier 3 #10 from #810. | -| Tier 3 mirror dissolutions: computation | M | NOT YET AUTHORED | `SizeBound`, `RecursionShape`-related Rust mirror at `dag.rs:839-915` dissolves with same substrate dependency. | -| Tier 3 mirror dissolutions: induction | M | NOT YET AUTHORED | `RecursionShape`, `InductiveField`, `SubValueRelation` Rust mirror at `dag.rs:916-980` dissolves with same substrate dependency. | -| Tier 3 mirror dissolutions: effect-carrier | S | NOT YET AUTHORED | `src/v3/compiler/src/dag/effects.rs` (216 LOC) + `compose_operation_effects` in `workflow_idempotency.rs` (105 LOC). Mechanical PB dissolution once self-hosting reaches it. Tier 3 #12 from #810. | +| Tier 3 mirror dissolutions: termination | M | AUTHORED in [`r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) | `DescentEvidence`, `PositiveDescentAmount`, `ProportionalDivisor`, `ShrinkFactor`, `evidence_rank`, `merge_evidence` Rust mirror at `dag.rs:628-790` dissolves as v3 lowers + evaluates `.dag` runtime values. Tier 3 #10 from #810. | +| Tier 3 mirror dissolutions: computation | M | AUTHORED in [`r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) | `SizeBound`, `RecursionShape`-related Rust mirror at `dag.rs:839-915` dissolves with same substrate dependency. | +| Tier 3 mirror dissolutions: induction | M | AUTHORED in [`r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) | `RecursionShape`, `InductiveField`, `SubValueRelation` Rust mirror at `dag.rs:916-980` dissolves with same substrate dependency. | +| Tier 3 mirror dissolutions: effect-carrier | S | AUTHORED in [`r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) | `src/v3/compiler/src/dag/effects.rs` (216 LOC) + `compose_operation_effects` in `workflow_idempotency.rs` (105 LOC). Mechanical PB dissolution once self-hosting reaches it. Tier 3 #12 from #810. | | `kernel_algebra_profile` mirror dissolution | M | GATED (waiting on Substrate Manager `ValueBody::Map` carrier; future T-Substrate sub-lane) | Map-shaped (not list/sum); requires `ValueBody::Map` substrate work. Substrate Manager future sub-lane. | | Tier 2 `patch_lower_helpers_*` retirement | S | NOT YET AUTHORED (queued for PB-Tier1 priority hint per #810 §5) | If survives R1, PB Manager retires `patch_lower_helpers_generated_type_alias_refinement` once generated `lower_helpers` can emit refinement field natively. | | Post-R1 emergent dissolutions | varies | NOT YET MATERIALIZED | Catch-all for new PB work that surfaces post-R1 (new mirror dissolutions discovered during R2; new Rust scaffolds inadvertently introduced and needing dissolution). | @@ -58,9 +58,9 @@ Authored: - Pre-R1 PB program briefs (in `pure-bootstrap-zero-manager.md`, archives on R2 promotion); content migration here covers post-R1 deliverables only. +- [`r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) — termination, computation, induction, and effect-carrier mirror dissolution worker pack. Pending — pre-spawn Director-authored per inbox #828 coordination split; post-spawn manager-authored autonomously per "Pre-spawn vs post-spawn authority" subsection above: -- Tier 3 mirror dissolution worker briefs (4: termination, computation, induction, effect-carrier) - `kernel_algebra_profile` worker brief (gated on Substrate Manager `ValueBody::Map`) - Tier 2 `patch_lower_helpers_*` retirement worker brief (if survives R1) @@ -73,5 +73,6 @@ Lane status table refreshes here as work lands. Pre-spawn placeholder. - Parent: `docs/r2-structure.md` §"Pure Bootstrap Manager" - Migrating from: `docs/briefs/pure-bootstrap-zero-manager.md` (archives on R2 promotion) - Self-hosting design: `docs/design-pure-bootstrap-zero.md` LIVE 2026-04-25 +- Tier 3 worker pack: [`docs/briefs/r2-pb-tier3-mirror-dissolution-workers.md`](r2-pb-tier3-mirror-dissolution-workers.md) - Tier 3 source: `docs/briefs/debt-paydown-synthesis-2026-04-25.md` items #10 + #12 - ROADMAP single authority on gate semantics: `ROADMAP.md §"Lane acceptance — .dag gates"` diff --git a/dsl/extdeps/github/auth.dag b/dsl/extdeps/github/auth.dag index 0a319ce9f55..ee1e74c8d54 100644 --- a/dsl/extdeps/github/auth.dag +++ b/dsl/extdeps/github/auth.dag @@ -8,12 +8,20 @@ module extdeps.github.auth import gunbc.auth.credentials { gcp_secret_credential } import std.resources { Network } -import std.types { NonEmptyStr } +import std.types { NonEmptyStr, Secret } + +// Secret Manager materializes secret bytes only — no GitHub-issued scope or +// expiry metadata on the wire (P3: do not fabricate empty scopes / absent +// expiry as if observed). Acquisition-path carrier lives in auth, not the +// GitHub core provider model. +type GitHubSecretManagerPat { + token: Secret +} func github_token( project_id: NonEmptyStr = "gunbai-secrets", secret_name: NonEmptyStr = "github-token" -) -> { token: Secret } +) -> GitHubSecretManagerPat uses net: Network { cred = gcp_secret_credential( diff --git a/dsl/extdeps/llm/anthropic.dag b/dsl/extdeps/llm/anthropic.dag index 58ecf108054..b9f44439e72 100644 --- a/dsl/extdeps/llm/anthropic.dag +++ b/dsl/extdeps/llm/anthropic.dag @@ -8,7 +8,7 @@ // module extdeps.llm.anthropic -import extdeps.llm.llm { Role, StopReason, TokenUsage, llm_snake_wire_contract } +import extdeps.llm.llm { StopReason, TokenUsage, llm_snake_wire_contract } import std.errors { AnthropicErrorShape } import std.credentials { CredentialSource, EnvVar } import std.serialization { VariantEncoding } @@ -40,10 +40,20 @@ data models: List = [ // -- Anthropic content blocks (provider-specific) --------------------- // Extends the shared ContentBlock (llm.dag) with tool-use blocks. -type AnthropicContentBlock - = TextBlock { text: String } - | ToolUseBlock { id: String, name: String, input: Json } - | ToolResultBlock { tool_use_id: String, content: String, is_error: Bool? } +// 🟢 TERMINAL provider-domain coproduct for user-message content. `UserTextBlock` +// and `UserToolResultBlock` are role-legal Anthropic Messages content rows; the +// wire `type` keys are emitter/transport obligations, not caller-authored model +// state. `UserToolResultBlock.content: String` is the narrow text-result slice; +// non-string/nested result content is gap-carried below. +type AnthropicUserContentBlock + = UserTextBlock { text: String } + | UserToolResultBlock { tool_use_id: String, content: String, is_error: Bool? } + +// 🟢 TERMINAL provider-domain coproduct for assistant-message content. +// Assistants can emit text or tool-use requests; tool results are user rows. +type AnthropicAssistantContentBlock + = AssistantTextBlock { text: String } + | AssistantToolUseBlock { id: String, name: String, input: Json } // -- System prompt ------------------------------------------------- @@ -87,6 +97,36 @@ data current_api_version: AnthropicApiVersion = { header: "anthropic-version" } +// Messages API conversation row: the outer variant is the role discriminator, +// so role-specific content legality is structural. User rows can carry text or +// tool results; assistant rows can carry text or tool-use requests. +// 🟢 TERMINAL provider-domain coproduct for the modeled Messages row roles. +// Q4.5 P1 closure (#808 / #856 Substrate escalation). +type AnthropicChatMessage + = UserMessage { content: List } + | AssistantMessage { content: List } + +// Outputs still JSON-path-project; same downstream closure as OpenAI slice. +data structural_coverage_gap_anthropic_messages_outputs: List = [ + "closure:rest_typed_response_body|llm.Anthropic.Messages|residue:output_fields_use_json_path_projection" +] + +// Request wire: Messages API expects string `role` + flat `content[]` objects +// keyed by `type` (https://docs.anthropic.com/en/api/messages). `type` is not +// caller-authored model state on `TextBlock`; the future +// `rest_request_wire_serde_alignment` closure must emit it from the variant. +data structural_coverage_gap_anthropic_messages_request_wire: List = [ + "closure:rest_request_wire_serde_alignment|llm.Anthropic.Messages|AnthropicChatMessage|residue:message_variant_must_emit_role_string_user_assistant", + "closure:rest_request_wire_serde_alignment|llm.Anthropic.Messages|AnthropicUserContentBlock.UserTextBlock|residue:variant_must_emit_flat_type_text_wire_key", + "closure:rest_request_wire_serde_alignment|llm.Anthropic.Messages|AnthropicAssistantContentBlock.AssistantTextBlock|residue:variant_must_emit_flat_type_text_wire_key", + "closure:rest_request_wire_serde_alignment|llm.Anthropic.Messages|AnthropicAssistantContentBlock.AssistantToolUseBlock|residue:variant_must_emit_flat_type_tool_use_wire_key", + "closure:rest_request_wire_serde_alignment|llm.Anthropic.Messages|AnthropicUserContentBlock.UserToolResultBlock|residue:variant_must_emit_flat_type_tool_result_wire_key" +] + +data structural_coverage_gap_anthropic_tool_result_full_content_surface: List = [ + "closure:anthropic_tool_result_full_content_surface|AnthropicUserContentBlock.UserToolResultBlock|residue:content_modeled_as_string_only|missing:nested_content_blocks|missing:image_content" +] + // -- Service: REST transport wiring ------------------------------ service llm.Anthropic { @@ -105,7 +145,7 @@ service llm.Anthropic { input { api_key: Secret model: String - messages: Json + messages: List max_tokens: Int = 4096 temperature: Float? system: String? diff --git a/dsl/extdeps/llm/openai.dag b/dsl/extdeps/llm/openai.dag index 86b1745f746..9f4240c3497 100644 --- a/dsl/extdeps/llm/openai.dag +++ b/dsl/extdeps/llm/openai.dag @@ -8,7 +8,7 @@ // module extdeps.llm.openai -import extdeps.llm.llm { Role, StopReason, TokenUsage, llm_snake_wire_contract } +import extdeps.llm.llm { StopReason, TokenUsage, llm_snake_wire_contract } import std.errors { OpenAiErrorShape } import std.serialization { VariantEncoding } import std.types { HttpMethod, AuthScheme } @@ -76,6 +76,43 @@ type ToolCall { function: FunctionCall } +// Roles on Chat Completions `messages[]` for this **narrow string-content** +// carrier: system / developer / user / assistant per OpenAI Chat Completions +// (https://platform.openai.com/docs/api-reference/chat/create). Tool rows and +// multimodal `content` arrays are not modeled here — gap-carried below. +// 🟢 TERMINAL provider-domain coproduct for the modeled narrow text-message +// row. Full Chat Completions message variants remain gap-carried by +// `structural_coverage_gap_openai_chat_message_full_api_surface`; request wire +// role-string serialization remains gap-carried separately. +type OpenAiChatMessageRole = System | Developer | User | Assistant + +// Chat Completions API message row: wire accepts plain-string `content` for +// text-only payloads (OpenAI docs). Typed here so callers cannot pass opaque +// Json; multimodal rows extend this product when needed (Q4.5 P1 / #808). +type OpenAiChatMessage { + role: OpenAiChatMessageRole + content: String +} + +// Typed inputs close the Json bypass; outputs still JSON-path-project until +// `response { 200 => Typed... }` lands. Queryable receipt for lenses/audits. +data structural_coverage_gap_openai_chat_completion_outputs: List = [ + "closure:rest_typed_response_body|llm.OpenAI.ChatCompletion|residue:output_fields_use_json_path_projection" +] + +// Full Chat Completions message coproduct (tool_call_id, tool role, function, +// multimodal content parts) is not this row — downstream closure trigger. +data structural_coverage_gap_openai_chat_message_full_api_surface: List = [ + "closure:openai_chat_message_full_coproduct|OpenAiChatMessage|missing:tool_role_with_tool_call_id|missing:function_role|missing:multimodal_content_array" +] + +// Chat Completions `messages[].role` must be wire strings (system/developer/ +// user/assistant). Emitter serde for unit enums may not match; same closure +// as Anthropic request wire — verify before relying on typed bodies for 200s. +data structural_coverage_gap_openai_chat_request_messages_wire: List = [ + "closure:rest_request_wire_serde_alignment|llm.OpenAI.ChatCompletion|OpenAiChatMessageRole|residue:unit_enum_role_must_serialize_as_openai_role_strings" +] + // -- Service: REST transport wiring ------------------------------ service llm.OpenAI { @@ -93,7 +130,7 @@ service llm.OpenAI { input { api_key: Secret model: String - messages: Json + messages: List temperature: Float? max_tokens: Int? } diff --git a/dsl/gunbc/tools/review.dag b/dsl/gunbc/tools/review.dag index 9e77f94644e..4fc6bb4e5b5 100644 --- a/dsl/gunbc/tools/review.dag +++ b/dsl/gunbc/tools/review.dag @@ -19,11 +19,11 @@ import extdeps.shell data default_model: String = "claude-sonnet-4-6-20250929" data cron_schedule: String = "*/10 * * * *" -// BRIDGE: messages should be [{ role: "user", content: "..." }] (Json array), -// but emit_simple_expr cannot render list/record literals in transport bodies. -// Passed as a JSON-encoded string; the emitter coerces via serde_json::from_str. -// Dissolves when emit_simple_expr handles ExprListLit/ExprRecordLit. -data review_messages: String = "[{\"role\": \"user\", \"content\": \"Review the PR described in the system prompt.\"}]" +data review_messages: List = [ + UserMessage { + content: [UserTextBlock { text: "Review the PR described in the system prompt." }] + } +] // -- Pure functions --------------------------------------------------- diff --git a/src/v2/tests/src/pipeline.rs b/src/v2/tests/src/pipeline.rs index 8c2856af55c..ec9f7b2b73a 100644 --- a/src/v2/tests/src/pipeline.rs +++ b/src/v2/tests/src/pipeline.rs @@ -6254,7 +6254,11 @@ fn anthropic_live_e2e() { import extdeps.llm.anthropic -data test_messages: String = "[{\"role\": \"user\", \"content\": \"Say hello in exactly 3 words.\"}]" +data test_messages: List = [ + UserMessage { + content: [UserTextBlock { text: "Say hello in exactly 3 words." }] + } +] func ask_claude( model: String = "claude-haiku-4-5-20251001",