diff --git a/ROADMAP.md b/ROADMAP.md index 1c38fb4bea0..3b7ac155257 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -373,7 +373,7 @@ Per-row tagging is a scheduled follow-up sweep — **trigger:** post-merge of th - **~~`declaration_by_name(...)` pattern in emit~~** — **RETIRED 2026-05-04 (PR #1638)**. Audit found the production emit callers were fixed substrate / emit-model anchors, not caller-provided program strings: `MethodEmitTemplate`, `MethodTemplateContract`, collection method anchors (`concat_method`, `length_method`, `fold_method`, `is_empty_method`), and fixed emit-model coproduct variants (`PatternStrategy`, `FieldAccess`, `ParameterDisposition`, `MemoryModel`, `ScopeModel`, Rust rendering/computation-model variants). Dissolved by extending the bootstrap-populated `Dag` typed caches (`EmitAnchorCache` / `EmitModelVariants`) and replacing emit-time string lookups with cached `DeclarationId` reads. Ratchet: `emit_production_code_has_no_declaration_by_name_calls` fails closed if production `src/v3/compiler/src/emit*` code reintroduces `Dag::declaration_by_name`; test-only fixture lookups remain outside the row scope. - **`pipeline_authority.rs` (case 2c):** runtime stage order is **structural** (`PipelineStageBinding` declaration order only). `fn compile` body vs binding drift check is **suspended** (PR #1171): `compile` stays `ArrowBody::Unparsed` — no lowered ordered stage list without a source-text channel; neither `include_str!` nor `read_to_string`+slice satisfies R3 for that reconcile. Dissolution: single typed carrier or structural compile body. - **LLM service flattening**: `dsl/extdeps/llm/llm.dag` declares `Role`, `ContentBlock`, `LlmMessage`, `StopReason`; Anthropic/OpenAI service operations still take `model: String`, `messages: Json`, and extract outputs by string path. Dissolution: service operations consume the typed carriers; outputs returned as typed responses. **Typed request JSON vs wire (e2e risk):** closure trigger identifier `rest_request_wire_serde_alignment` — Anthropic request payload coproducts carry source-level `CoproductWireContract` rows for message `role` and content-block `type` tagging, ratcheted by `anthropic_request_coproduct_wire_contracts_emit_targeted_serde` and `anthropic_messages_request_body_json_matches_messages_wire_tags`; `structural_coverage_gap_anthropic_messages_request_wire` is retired for those outer request coproduct discriminators. Nested Anthropic tool-result `content` still needs scalar-text vs content-block-array wire serde and remains tracked by `structural_coverage_gap_anthropic_tool_result_content_wire_shape`; additive document/search/tool-reference nested blocks remain tracked separately by `structural_coverage_gap_anthropic_tool_result_additive_blocks`. OpenAI Chat Completions narrow-row `messages[].role` for `OpenAiChatMessageRole` is ratcheted by pipeline test `openai_chat_message_role_wire_matches_llm_snake_contract` (`src/v2/tests/src/pipeline.rs`) against `data wire_contract: VariantEncoding = llm_snake_wire_contract` in `dsl/extdeps/llm/openai.dag` (same M2 pattern as Anthropic: module-local `wire_contract` names the single `llm_snake_wire_contract` authority in `extdeps.llm.llm`; emitter derefs `init.inferred` / `Resolved` per `resolve_wire_serde_policy_for_coproduct` in `05_emit_rust.dag`). `v2.compiler.emit_rust` authorizes coproduct serde from `CoproductWireContract` rows targeting local coproduct declarations, then module `wire_contract`, then the tagged-object default; stale local contract targets emit `compile_error!`. The old `structural_coverage_gap_openai_chat_request_messages_wire` row was removed when that receipt landed; tracker [#987](https://github.com/gunb-ai/gunbc/issues/987). -- **GitHub auth model bypass**: `dsl/extdeps/github/github.dag:42-46` declares `GitHubAuthToken { token, scopes, expires_at }` but `dsl/extdeps/github/auth.dag:13-23` returns only `{ token: Secret }` and hardcodes GCP secret-manager policy. Dissolution: `github_token()` returns the full typed token; remove the hardcoded provider policy. +- **GitHub auth model bypass — CLOSED 2026-05-04 (ROADMAP:376)**: `dsl/extdeps/github/github.dag:42-46` declares `GitHubAuthToken { token, scopes, expires_at }`; `dsl/extdeps/github/auth.dag` now returns that full typed token from `github_token()`. Token bytes are materialized through `default_github_auth_source.credential_source: CredentialSource = EnvVar { name: "GITHUB_TOKEN" }`, while token metadata (`scopes`, `expires_at`) is carried by the same typed `GitHubAuthSource` source/policy record as `GitHubTokenMetadataAuthority::DeclaredGitHubTokenMetadata`. The declared/unverified metadata bridge is tracked by `structural_coverage_gap_github_token_metadata_verification` with trigger `GitHub token introspection or installation-token issuance surface`, instead of being silently fabricated beside an env-sourced secret. The hardcoded GCP Secret Manager literals/path, `uses net: Network`, and narrow `GitHubSecretManagerPat` bypass are removed. Follow-up class: per-call credential-source override remains separate if a real consumer needs SecretManager or multiple env-var policies; current callable-default rules reject non-primitive `CredentialSource` defaults on parameters, so the bounded row closes on the fixed typed source. - **~~`errors.dag` dead generic layer~~** — **RETIRED 2026-05-04 (PR #1697)**. Exact-name audit found `HttpErrorShape`, `AuthError`, `RateLimitError`, `ConflictError`, and `ProviderError` had zero non-doc consumers; live extdeps import only provider-specific envelopes (`GitHubErrorShape`, `GcpErrorShape`, `AnthropicErrorShape`, `OpenAiErrorShape`). Dissolved by deleting the five generic declarations from `dsl/std/errors.dag`; provider-specific shapes remain. ### P4 — type refinement / modeling faithfulness diff --git a/docs/debt/r3-debt-paydown-ledger-2026-05-02.md b/docs/debt/r3-debt-paydown-ledger-2026-05-02.md index 653b34274aa..7684a361b81 100644 --- a/docs/debt/r3-debt-paydown-ledger-2026-05-02.md +++ b/docs/debt/r3-debt-paydown-ledger-2026-05-02.md @@ -51,7 +51,7 @@ The highest concentration is in Substrate-adjacent rows: operator authority, val | `declaration_by_name(...)` emit pattern | 2026-04 | Substrate / emit | Open | Typed substrate access with cached declaration ids. | | `pipeline_authority.rs` dual-authoring drift | 2026-04 / 2026-04-30 | Substrate / PB | Open | Generate one surface from the other or mark `fn compile` non-authoritative until structural lowering. | | LLM service flattening | 2026-04 | Grounding services | Open | Service operations consume typed carriers and return typed outputs. | -| GitHub auth model bypass | 2026-04 | Grounding GitHub extdeps | Open | `github_token()` returns full `GitHubAuthToken`; remove hardcoded provider policy. | +| GitHub auth model bypass | 2026-04 | Grounding GitHub extdeps | Closed 2026-05-04 | `github_token()` now returns full `GitHubAuthToken`; hardcoded GCP Secret Manager policy removed in favor of typed `GitHubAuthSource { credential_source, token_metadata }` using `CredentialSource::EnvVar` + `env_credential`, with declared metadata verification tracked separately. | | `errors.dag` dead generic layer | 2026-04 | Grounding / std errors | Open | Wire generic layer or delete it. | | Fixed-width types not structurally fixed | 2026-04 | T-Numeric-Construction | Open | Alias / field refinement or cardinality carrier. | | Surface int literals host-narrowed too early | 2026-04-24 | T-Numeric-Construction | Open | Concept-layer unbounded magnitude; target narrowing at reconciliation. | diff --git a/dsl/extdeps/github/auth.dag b/dsl/extdeps/github/auth.dag index ee1e74c8d54..ca10697c6ff 100644 --- a/dsl/extdeps/github/auth.dag +++ b/dsl/extdeps/github/auth.dag @@ -6,27 +6,45 @@ module extdeps.github.auth -import gunbc.auth.credentials { gcp_secret_credential } -import std.resources { Network } -import std.types { NonEmptyStr, Secret } +import extdeps.github.github { GitHubAuthToken, GitHubScope, ScopeGist, ScopeRepo } +import std.credentials { CredentialSource, EnvVar, env_credential } +import std.types { NonEmptyStr, Timestamp } -// 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 +// The default PAT path cannot introspect GitHub-issued token claims. Keep the +// locally declared policy explicit so callers can distinguish configured +// assumptions from provider-verified token metadata when that surface lands. +type GitHubTokenMetadataAuthority + = DeclaredGitHubTokenMetadata { scopes: List, expires_at: Timestamp? } + +type GitHubAuthSource { + credential_source: CredentialSource + token_metadata: GitHubTokenMetadataAuthority +} + +data default_github_auth_source: GitHubAuthSource = { + credential_source: EnvVar { name: "GITHUB_TOKEN" }, + token_metadata: DeclaredGitHubTokenMetadata { + scopes: [ScopeRepo, ScopeGist], + expires_at: None + } } -func github_token( - project_id: NonEmptyStr = "gunbai-secrets", - secret_name: NonEmptyStr = "github-token" -) -> GitHubSecretManagerPat - uses net: Network +data structural_coverage_gap_github_token_metadata_verification: List = [ + "closure:github_token_metadata_verification|GitHubAuthToken|declared_metadata:scopes|declared_metadata:expires_at|trigger:GitHub token introspection or installation-token issuance surface" +] + +func github_token() -> GitHubAuthToken { - cred = gcp_secret_credential( - project_id: project_id, - secret_name: secret_name - ) - return { token: cred.token } + cred = match default_github_auth_source.credential_source { + EnvVar { name } => env_credential(env_var: name as NonEmptyStr) + } + return { + token: cred.token, + scopes: match default_github_auth_source.token_metadata { + DeclaredGitHubTokenMetadata { scopes, expires_at } => scopes + }, + expires_at: match default_github_auth_source.token_metadata { + DeclaredGitHubTokenMetadata { scopes, expires_at } => expires_at + } + } } diff --git a/src/v2/tests/src/pipeline.rs b/src/v2/tests/src/pipeline.rs index 2c5cc71ad51..ba244db1422 100644 --- a/src/v2/tests/src/pipeline.rs +++ b/src/v2/tests/src/pipeline.rs @@ -5676,6 +5676,47 @@ service test.Api { ); } +#[test] +fn github_token_returns_typed_auth_token_from_credential_source() { + let ws = crate::helpers::workspace_root(); + let source_path = ws.join("dsl/extdeps/github/auth.dag"); + let source = std::fs::read_to_string(&source_path).expect("read github auth.dag"); + let result = compile_dag_named("dsl/extdeps/github/auth.dag", &source, RenderTarget::Rust); + assert_no_diagnostics(&result); + let content = find_file(&result, "src/extdeps_github_auth.rs"); + + assert!( + content.contains("pub use crate::extdeps_github_github::{GitHubAuthToken, GitHubScope}") + && content.contains("Result"), + "ROADMAP:376: expected github_token to return the typed GitHubAuthToken carrier, got:\n{content}" + ); + assert!( + content.contains("pub struct GitHubAuthSource") + && content.contains("pub token_metadata: Rc") + && content.contains("pub enum GitHubTokenMetadataAuthority") + && content.contains("DeclaredGitHubTokenMetadata"), + "ROADMAP:376: expected credential source metadata to be explicitly declared/unverified, got:\n{content}" + ); + assert!( + content.contains("structural_coverage_gap_github_token_metadata_verification") + && content.contains("declared_metadata:scopes") + && content.contains("declared_metadata:expires_at"), + "ROADMAP:376: expected declared token metadata verification gap to stay tracked, got:\n{content}" + ); + assert!( + content.contains("CredentialSource::EnvVar") + && content.contains("GITHUB_TOKEN") + && content.contains("env_credential"), + "ROADMAP:376: expected default credential source to use EnvVar via env_credential, got:\n{content}" + ); + assert!( + !content.contains("gunbai-secrets") + && !content.contains("github-token") + && !content.contains("SecretManagerAccessVersion"), + "ROADMAP:376: github_token must not hardcode the GCP Secret Manager policy, got:\n{content}" + ); +} + // ── RE-2: review.dag compiles to Rust ─────────────────────────────────── // Diagnostic-driven: compile review.dag + imports, write to disk, cargo check. // This is the acceptance gate for RE-2. diff --git a/src/v3/compiler/tests/integration/t_impossiblebugs_unenumerated_effects_test.rs b/src/v3/compiler/tests/integration/t_impossiblebugs_unenumerated_effects_test.rs index 756ec2edada..f68b045ff54 100644 --- a/src/v3/compiler/tests/integration/t_impossiblebugs_unenumerated_effects_test.rs +++ b/src/v3/compiler/tests/integration/t_impossiblebugs_unenumerated_effects_test.rs @@ -56,7 +56,7 @@ fn generated_effect_enumeration_lens_is_exported_and_consumed() { } #[test] -fn audit_receipt_proves_path_ii_existence_case() { +fn github_auth_no_longer_derives_network_from_ambient_uses() { let resources = read_workspace_file("dsl/std/resources.dag"); let primitives = read_workspace_file("dsl/std/primitives.dag"); let shell = read_workspace_file("dsl/extdeps/shell.dag"); @@ -75,7 +75,9 @@ fn audit_receipt_proves_path_ii_existence_case() { "shell.Exec.Run remains transport-effectful without resource-threaded signature shape" ); assert!( - github_auth.contains("uses net: Network"), - "github_token still derives resource use from ambient `uses`, not its return shape" + github_auth.contains("CredentialSource") + && github_auth.contains("-> GitHubAuthToken") + && !github_auth.contains("uses net: Network"), + "github_token should consume typed CredentialSource and return GitHubAuthToken without ambient Network effect" ); }