Skip to content
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/debt/r3-debt-paydown-ledger-2026-05-02.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
56 changes: 37 additions & 19 deletions dsl/extdeps/github/auth.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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<GitHubScope>, 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<String> = [
"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
}
}
}
41 changes: 41 additions & 0 deletions src/v2/tests/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Rc<GitHubAuthToken>"),
"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<GitHubTokenMetadataAuthority>")
&& 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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"
);
}
Loading