Skip to content

[ROUTE6-1:T3] Durable model-canary attribution across resumes and model switches - #6

Draft
nullStack65 wants to merge 1 commit into
feat/usage-attribution-proof-20260923from
feat/durable-model-canary-attribution-20260923
Draft

nullStack65 wants to merge 1 commit into
feat/usage-attribution-proof-20260923from
feat/durable-model-canary-attribution-20260923

Conversation

@nullStack65

@nullStack65 nullStack65 commented Sep 23, 2026

Copy link
Copy Markdown
Owner

What this is

Narrow follow-on to #4, restacked onto its corrected branch head
daa55eb88966665faa8c1770c35fc3c53a68f6fa (Round-2 R2-T3). It does not
change #4; it adds the minimum durable identity/history and pre-execution
experiment metadata that #4 explicitly deferred, so the ROUTE6-1 coding-model
canary can be measured across resumes, provider-session replacement, model
switches, child sessions, retries/fallbacks, multiple PRs, and manager/agent
assignments.

Draft; not for merge. No endpoint, UI, PostHog, collector, gateway, routing, or
transport change. Upstream pingdotgg/t3code is untouched. T3 is not the
canonical model router — agent-config remains policy authority; T3 only carries
and measures metadata.

Round-2 restack (R2-T3)

Restacked with git rebase --onto daa55eb88 e4f36af5e. The single #6 commit was
replayed; only usageAttributionSources.test.ts conflicted (both sides appended a
test to the same describe), resolved by keeping both tests and extending the
M1C interchange fixture expectation with the additive history/routeEvents
fields. Every #4 Round-3 correction is preserved — nothing here reverts the
measurement/invalid/completeness, dedupeKeyScope, cost-only conflict, snapshot
replacement, warm-cache enrichment, or readResumeCursor preference behavior.
The files that implement them (usageTranscripts.ts, usageScanCache.ts,
UsageService.ts) are untouched here.

Why #4 needed this

#4's own limitation: a native session maps to a thread only through the single current provider_session_runtime.resume_cursor_json. A resume, fork, or model switch overwrites it and earlier usage becomes unbound. #4 recorded that as the follow-on it did not adopt.

Schema decision (migration only where required)

A migration is used because the existing schema cannot truthfully preserve history — the cursor is one row per thread. Two additive, forward-compatible tables (existing databases are safe; existing thread/session behavior is unchanged). Ids 054/055 were confirmed free on the corrected #4 base (its highest existing migration is 053). No collision, no renumbering.

  • 054_provider_session_history — append-only identity, UNIQUE (thread_id, provider_name, native_session_id). ProviderSessionRuntime.upsert appends one row per distinct native session; a repeat only advances last_seen_at. The migration backfills the current cursor of an upgraded database. A conflicting onConflict: "ignore" write appends nothing (that cursor was never applied). parent_native_session_id carries a true sub-agent parent when known.
  • 055_thread_route_events — append-only, content-free pre-execution route/experiment metadata. Nullable route_event_kind keeps an automatic "requested" record distinct from a declared canary/fallback/review/escalation event.

Changed paths

File Change
apps/server/src/persistence/Migrations/054_ProviderSessionHistory.ts new: history table + index + current-cursor backfill
apps/server/src/persistence/Migrations/055_ThreadRouteEvents.ts new: route/experiment event table + index
apps/server/src/persistence/Migrations.ts register 54, 55
apps/server/src/persistence/ProviderSessionRuntime.ts append history + optional route events on upsert
apps/server/src/usage/routeMetadata.ts new: pure route/experiment types + parsers
apps/server/src/usage/usageAttributionSources.ts extract history + route events; snapshot merges them
apps/server/src/usage/usageAttribution.ts add sessionHistory binding origin
apps/server/src/usage/usageRouteAttribution.ts new: pure identity/route/experiment projection; carries the #4 measurement/identity quality axes so the route view cannot drop them
apps/server/src/provider/Services/ProviderSessionDirectory.ts, Layers/ProviderSessionDirectory.ts carry parentNativeSessionId, requestedRoute, routeEvent on the binding
apps/server/src/provider/Layers/ProviderService.ts record the requested route at session start (from the model selection T3 was given)
tests usageRouteAttribution.test.ts (incl. #4 quality regression), usageAttributionSources.test.ts, ProviderSessionRuntime.history.test.ts, 054_ProviderSessionHistory.test.ts
docs/internals/usage-attribution.md durable-history and requested-vs-observed sections

Semantics encoded

  • Identity — T3 thread id, native provider session id, child/parent lineage, and the existing repository-qualified PR link table (projection_thread_pull_requests, reused, not replaced). A readable manager/agent id (ROUTE6-1 / T3) is a label, never a substitute for the native session id and never a join key.
  • Requested vs observed — requested provider/model/effort are captured at session start from what T3 was actually asked to run; the observed model comes from measured usage only. Never copied into each other. No scanned source exposes effort, so actualEffort is null with quality unsupported.
  • Measurement/identity quality — each route session carries the [METRICS-M3] Prove prompt request session and PR usage attribution #4 axes (measurement, identity, prompt, request, recordIdentity, conflict) or null when no usage was measured, so a partial/invalid measurement or a legacy identity-erased row is never presented as exact.
  • Experiment — task stratum, experiment/cohort id, route event kind (normal | availability_fallback | canary | independent_review | quality_escalation), and escalation/fallback reason. Unknown is allowed and is the default.
  • History — every contributing native session survives a cursor change, so a thread is never attributed wholly to the initial or most recent model.
  • Association ≠ allocation — a thread with several PRs or several sessions sums each session once; the base shared/attributed rules are unchanged.
  • Privacy — metadata only: no prompts, responses, code, tool-call bodies, secrets, or arbitrary user content.

Validation

# 17 files, 312 passed
vp test run apps/server/src/usage/ \
  apps/server/src/persistence/ProviderSessionRuntime.history.test.ts \
  apps/server/src/persistence/Migrations/054_ProviderSessionHistory.test.ts \
  apps/server/src/persistence/RepositoryErrorCorrelation.test.ts \
  apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts \
  apps/server/src/provider/Layers/ProviderSessionReaper.test.ts \
  apps/server/src/provider/Layers/ProviderService.test.ts \
  apps/server/src/project/AgentSessionImporter.test.ts
vp run --filter t3 typecheck        # exit 0 (effect suggestions only, no errors)
vp lint <changed files>             # exit 0
vp fmt --check <changed files>      # clean

Exact per-test receipts and CI state are in the RESULT comment. CI is not
green: the self-hosted blacksmith-* jobs stay queued on the fork.

Model/harness: opencode-go / deepseek-v4.1-flash via opencode (see RESULT).

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL labels Sep 23, 2026
@nullStack65

Copy link
Copy Markdown
Owner Author

RESULT ROUTE6-1:T3 — durable model-canary attribution

Status: complete (source + migrations + focused tests + docs; stacked draft PR). CI cannot be observed green on the fork (see Checks).

Relationship to t3code#4

Stacked on #4's branch head, not a rewrite. #4 remains the implementation line for the attribution proof; this PR is the narrow follow-on #4 itself named and deferred ("durable per-thread session history … specified but not adopted"). Committed diff is only the new commits on top of e4f36af5e; git diff e4f36af5e..97b4ceb5d is 16 files, +2231/−17. #4's branch was not force-pushed and its reviewed head is unchanged.

Schema / storage decision

A migration was used because existing schema cannot truthfully preserve the required history: provider_session_runtime is PRIMARY KEY (thread_id) and holds one current cursor. Two additive, forward-compatible tables; existing databases are safe and existing thread/session behavior is unchanged:

  • 054_ProviderSessionHistoryprovider_session_history: UNIQUE (thread_id, provider_name, native_session_id), columns parent_native_session_id, origin, first_seen_at, last_seen_at. Backfills the current cursor on upgrade. Index on (thread_id, first_seen_at).
  • 055_ThreadRouteEventsthread_route_events: append-only, content-free; nullable route_event_kind; task_stratum, experiment_id, manager_id, agent_id, requested_{provider,model,effort}, escalation_reason. Index on (thread_id, recorded_at).

Both registered in Migrations.ts (ids 54, 55); a migration test asserts the backfill and manifest registration.

Durable history semantics

  • ProviderSessionRuntime.upsert now appends one provider_session_history row per distinct native session on the update path, in the same logical write as the runtime row. A repeat of the same session only advances last_seen_at (CASE WHEN excluded > existing), never replacing a different session.
  • A conflicting onConflict: "ignore" write appends nothing (that cursor was never applied), so no invented history.
  • usageAttributionSources.extractAttributionHistory reads it into sessionHistory bindings, and extractAttributionSnapshot merges cursor + history + imported transcripts. A resume/model switch therefore leaves earlier sessions bound, so a thread is never attributed wholly to its initial or most recent model.
  • parent_native_session_id carries a true sub-agent parent when a caller knows it. OpenCode child ids are still not persisted by T3 (they exist only in the adapter's in-memory relatedSessionIds and native event log), so an OpenCode child is preserved as a distinct identity with usage: null — unknown, not flattened into the parent, not zero.

Experiment metadata semantics

  • thread_route_events is written through the existing session-binding seam (ProviderRuntimeBinding.routeEvent / requestedRouteProviderSessionRuntime.upsert options). No new layer, endpoint, or transport.
  • route_event_kindnormal | availability_fallback | canary | independent_review | quality_escalation, nullable. kind === null marks an automatic "what was requested" record, kept distinct from a declared canary/fallback/review/escalation event.
  • Task stratum is a bounded set with unknown as a first-class, non-guessed value (investigation | docs | tests | simple_edit | implementation | review | ci_repair | architecture | security | unknown).
  • T3 carries the metadata only. It does not decide "DeepSeek is default / Luna is challenger / Sol is escalation" — that stays in agent-config policy. The declared event is authored by the route authority.

Requested vs actual model handling

  • Requested provider/model/effort are captured at session start from the model selection T3 was actually given (ProviderService.upsertSessionBindingrequestedRouteOf). Requested effort reads reasoningEffort then effort.
  • The observed model is read from measured usage records. The two are never copied into each other.
  • No scanned source exposes reasoning effort, so actualEffort is always null with quality unsupported; it is never derived from the requested effort.
  • A readable manager/agent id is a label, asserted distinct from the native session id and never used as a join key.

Request-identity rule preserved from #4

Unsupported request/prompt identity stays null/unsupported; aggregate turns are not split into imaginary requests. This PR adds no request inference.

Privacy boundary

Metadata only. No raw prompts, responses, source code, tool-call bodies, secrets, or arbitrary user content are stored or logged anywhere in this path.

Tests (17 files, 285 passed)

vp test apps/server/src/usage/ apps/server/src/persistence/ProviderSessionRuntime.history.test.ts apps/server/src/persistence/Migrations/054_ProviderSessionHistory.test.ts apps/server/src/persistence/RepositoryErrorCorrelation.test.ts apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts apps/server/src/provider/Layers/ProviderSessionReaper.test.ts apps/server/src/provider/Layers/ProviderService.test.ts apps/server/src/project/AgentSessionImporter.test.ts
# Test Files 17 passed (17); Tests 285 passed (285)

Covers the twelve required semantics: (1) one thread DeepSeek→Luna keeps both; (2) resume into a new provider session keeps the earlier one; (3) child OpenCode identity distinct, usage: null, not flattened; (4) manager/agent id distinct from the session id; (5) one thread on multiple PRs sums each session once (asserts shared, no duplication); (6) requested ≠ observed; (7) actual effort null/unsupported; (8) availability fallback vs independent review distinct with separate reasons; (9) quality-escalation reason preserved; (10) historical contribution survives a cursor change; (11) stack-dismissed PR link creates no attribution; (12) missing usage stays null, not zero. Plus persistence-level tests for append-on-resume, last-seen advancement, no-append-on-ignore, idempotent route events, and the migration backfill.

Typecheck / lint / format

vp run --filter t3 typecheck        # no errors
vp lint <changed files>             # exit 0
vp fmt --check <changed files>      # clean

CI

Actual route used for THIS agent session

opencode-go / deepseek-v4.1-flash via opencode (the harness this session runs in). Gemini 3.8 Flash medium was the preferred independent-family canary but is not available through a current qualified route from here, and I did not mutate live routing to obtain it. Observed effort: unknown (not exposed).

Blockers / unknowns

  • Live child-session persistence: OpenCode child ids are not yet written to T3 storage by any adapter; this PR makes the projection ready (parent field + distinct identity) but wiring an adapter to persist childSessionId is a separate, larger change.
  • Observed effort is unsupported across all scanned sources; the requested effort is captured but actual effort remains unknown.
  • Experiment metadata has a real, tested write seam and durable storage, but no production caller populates the declared (kind != null) event yet; that authority is agent-config/Skill-owned. Only the automatic requested-route record is written live today.
  • CI is unpushable to green on the fork.

Exact next action

Request review of (a) the two append-only tables and the upsert-time write path, (b) the requested-vs-observed separation, and (c) the companion usageRouteAttribution projection. On approval, the follow-ons are: an adapter change to persist OpenCode child session ids, and the agent-config/Skill side that supplies declared route events. Do not merge.

@nullStack65

Copy link
Copy Markdown
Owner Author

ROUTE6-1:T3 (#6) — corrected #4 head to rebase onto

This follow-on is stacked on #4's old head e4f36af5e. #4 now has a corrected head after the METRICS-1:M3C round-3 repairs:

Compatibility notes for your rebase (your migrations 054/055 and durable-history work were not touched here):

  • UsageRecord / AttributionUsageRecord gained optional measurementCompleteness, invalidTokenFields, identityAvailable, and dedupeKeyScope. Existing fields are unchanged; the scan-cache v4 row format appended three columns after index 14, and measurement gained the invalid code (appended last, so older codes keep their values).
  • The projection now treats a legacy identityAvailable:false record as unavailable for request/prompt levels and as only-partial for measurement completeness.
  • readResumeCursor now prefers resume over threadId for a Claude cursor that carries both (imported-session shape); your usageAttributionSources extensions should assume a cursor may carry more than one field.
  • Warm scan-cache acceptance now requires identity === "declared", so an unchanged extant legacy file is cold re-parsed once.

Please rebase after review of #4. No merge.

@nullStack65

Copy link
Copy Markdown
Owner Author

START ROUTE6-1:R2-T3 - rebase durable attribution onto corrected #4 head

T3's single resume cursor lost earlier native sessions on a resume, fork, or model switch, so a thread could not say which models contributed. Add an append-only provider_session_history (migration 054, with a current-cursor backfill) written alongside the runtime upsert, and read it through usageAttributionSources as sessionHistory bindings.

Add a content-free thread_route_events store (migration 055) for pre-execution route/experiment metadata: requested provider/model/effort captured at session start, task stratum, experiment/cohort, route event kind, manager/agent ids, and escalation reason. Actual values are derived from measured usage, never copied from the request. usageRouteAttribution composes the base projection with this view and keeps child sessions distinct.

T3 carries the metadata only; agent-config remains the routing authority. No prompts, responses, code, or tool bodies are stored.
@nullStack65
nullStack65 force-pushed the feat/durable-model-canary-attribution-20260923 branch from 97b4ceb to 67b4c9c Compare September 24, 2026 00:35
@nullStack65

Copy link
Copy Markdown
Owner Author

ROUTE6-1:R2-T3 — RESULT

Status: complete (restacked source + migrations + focused tests + docs; draft stacked PR). CI cannot be observed green on the fork — see CI state. Fresh session, not a continuation of the Round-1 T3 session. No merge.

Heads

Rebase / restack method

  • Backup refs first: branch backup/r2t3-pre-rebase-97b4ceb5d and tag r2t3-pre-rebase-97b4ceb5d at the old head.
  • git rebase --onto daa55eb88966665faa8c1770c35fc3c53a68f6fa e4f36af5ef279246bcb0f8463adeee8a09b7bde1 feat/durable-model-canary-attribution-20260923.
  • One [ROUTE6-1:T3] Durable model-canary attribution across resumes and model switches #6 commit (97b4ceb5d633528bd8) replayed; then git commit --amend folded in the audit reconciliation (route-view quality axes + regression), giving final head 67b4c9c48.
  • Pushed with --force-with-lease=refs/heads/feat/durable-model-canary-attribution-20260923:97b4ceb5d… (expected old head confirmed by ls-remote immediately before). Single commit on top of corrected [METRICS-M3] Prove prompt request session and PR usage attribution #4.

Conflicts encountered

Exactly one conflicted path: apps/server/src/usage/usageAttributionSources.test.ts. Both #4 and #6 had appended a test to the same describe("extractAttributionSnapshot") block, and #6 changes the snapshot shape.

Resolution: kept both tests (the #4 M1C interchange fixture test and the #6 durable-history/route-event merge test). Because #6 adds history/routeEvents to ExtractedSnapshotExtraction, the #4 fixture's toEqual expectation was extended with the additive history: [], routeEvents: [], and diagnostics.history / diagnostics.routeEvents blocks — the fixture's inputs are unchanged. The three other overlapping paths auto-merged: usageAttribution.ts, usageAttributionSources.ts, docs/internals/usage-attribution.md. No conflict was resolved by taking stale #6 copies.

How each #4 correction was preserved

The #6 commit's deletion set is only its intended replacements (verified line-by-line):
usageTranscripts.ts, usageScanCache.ts, and UsageService.ts — the files that carry the Round-3 correctness work — are not touched by this PR at all, so the following are byte-for-byte preserved from corrected #4:

#4 correction Preserved how
measurementCompleteness, invalidTokenFields, identityAvailable, dedupeKeyScope on AttributionUsageRecord type remains in #4 head; #6 does not edit that block
measurement: "invalid" code in usageTranscripts.ts (#4); untouched
scan-cache v4 row format appended fields in usageScanCache.ts (#4); untouched
legacy identityAvailable:falserequestQuality:"unavailable" in usageAttribution.ts (#4 identityErased); #6 only adds the sessionHistory origin to the adjacent AttributionThreadBinding union
readResumeCursor prefers resume over threadId in usageAttributionSources.ts (#4 doc/order); #6 adds new functions around it, does not change it. The 054 backfill SQL COALESCE(resume, threadId, sessionId) matches the same order
warm legacy-cache enrichment (identity === "declared") in UsageService.ts (#4); untouched
content equality ≠ event identity; dedupeIdentity in usageAttribution.ts (#4); #6 does not touch the dedupe path
cost/provenance conflict behavior in observationContent (#4); untouched
snapshot replacement semantics in usageAttribution.ts (#4); untouched
orphan / reconciliation identity base projection (#4); #6 composes buildUsageAttribution and does not alter it

Round-2 reconciliation added: SessionRouteQuality (measurement, identity, prompt, request, recordIdentity, conflict) on SessionRouteReport, null when no usage was measured. This closes the one place the follow-on view could have dropped the #4 quality axes; the base is embedded in base.sessions and now mirrored per route session.

Migration id check

On corrected #4 (daa55eb88) the highest registered migration is 053 (PullRequestFilesViewed); there is no 054/055. No collision. #6 registers [54, "ProviderSessionHistory"] and [55, "ThreadRouteEvents"] unchanged. No renumbering was needed.

#6 intended additions preserved

  • provider_session_history — append-only native-session history (migration 054, current-cursor backfill at upgrade); upsert appends on the update path and a conflicting onConflict:"ignore" write appends nothing (no invented session). Earlier sessions survive resume/model switch.
  • thread_route_events — content-free pre-execution metadata (migration 055): normal | availability_fallback | canary | independent_review | quality_escalation, nullable kind.
  • requested-vs-observed split — requested provider/model/effort captured at session start; observed model only from measured usage; actualEffort null/unsupported.
  • manager/agent ids — labels only, never join keys (asserted distinct from the session id).
  • PR relation — reuses projection_thread_pull_requests; association ≠ allocation.
  • Privacy — metadata only; no prompts/responses/code/tool bodies.

Changed paths (all 16)

apps/server/src/persistence/{Migrations.ts, Migrations/054_ProviderSessionHistory.ts, Migrations/054_ProviderSessionHistory.test.ts, Migrations/055_ThreadRouteEvents.ts, ProviderSessionRuntime.ts, ProviderSessionRuntime.history.test.ts}, apps/server/src/provider/{Services/ProviderSessionDirectory.ts, Layers/ProviderSessionDirectory.ts, Layers/ProviderService.ts}, apps/server/src/usage/{routeMetadata.ts, usageAttribution.ts, usageAttributionSources.ts, usageAttributionSources.test.ts, usageRouteAttribution.ts, usageRouteAttribution.test.ts}, docs/internals/usage-attribution.md.

corrected #4..new #6 diff

git diff daa55eb88..67b4c9c4816 files, +2412 / −17, 1 commit. Every removed line is an intended #6 replacement (the upsert ternary, the modelSelection?: unknown annotation, options, the two-line binding-origin union, the two limitations strings, and the snapshot return / docs paragraph). Nothing outside the follow-on scope changed.

Tests (17 files, 312 passed)

vp test run \
  apps/server/src/usage/ \
  apps/server/src/persistence/ProviderSessionRuntime.history.test.ts \
  apps/server/src/persistence/Migrations/054_ProviderSessionHistory.test.ts \
  apps/server/src/persistence/RepositoryErrorCorrelation.test.ts \
  apps/server/src/provider/Layers/ProviderSessionDirectory.test.ts \
  apps/server/src/provider/Layers/ProviderSessionReaper.test.ts \
  apps/server/src/provider/Layers/ProviderService.test.ts \
  apps/server/src/project/AgentSessionImporter.test.ts
# Test Files 17 passed (17); Tests 312 passed (312)

Added regression (usageRouteAttribution.test.ts, "durable history keeps #4 measurement and identity quality"): a durable session with measurementCompleteness:"partial" reports quality.measurement === "partial" (not measured); a legacy identityAvailable:false claude row reports quality.request === "unavailable" (not missing); measurement:"invalid" stays invalid; a cost-only duplicate reports quality.conflict === true; a history-only OpenCode identity has usage === null and quality === null (no fabricated measured quality). The three #6 test counts previously reporting 285 now report 312 because the corrected #4 base contributes its own Round-3 tests.

Typecheck / lint / format

vp run --filter t3 typecheck   # exit 0 (effect lint *suggestions* only, no errors)
vp lint <16 changed files>     # exit 0
vp fmt --check <16 changed files>  # "All matched files use the correct format." exit 0

CI state (truthful)

Not green. At head 67b4c9c48 the self-hosted jobs Test, Test Server 1/2/3, Rust, Release Smoke, Mobile Native Changes, Check, Native fingerprint diff remain pending (queued) — the fork has no blacksmith-* self-hosted runners. GitHub-hosted jobs passed (Label PR 6, Label PR size, Prepare PR size config, Collect PR targets); Deploy web preview / EAS Preview / Sync PR size label definitions skipped; CodeRabbit skipped (draft). No green CI is claimed; the queued state is the same runner-capacity gap #4 already reported.

Actual agent route

opencode-go / deepseek-v4.1-flash via opencode (the harness this session runs in). Observed effort: unknown — no scanned source exposes it. Gemini 3.8 Flash was not obtained; routing was not mutated. (Also: in this environment tests must run with the repo-local vp; the prebuilt global vp resolves the wrong vite-plus/test binding and fails collection.)

Remaining gaps

Exact next action

Request independent exact-head review of #6 @ 67b4c9c48e5def2f9bb97547ecb893d794d11d2a against base daa55eb88…: (a) the two append-only tables and the upsert-time append path, (b) requested-vs-observed separation, (c) the usageRouteAttribution view and the new SessionRouteQuality axes. Do not merge #4 or #6. Merge ordering is a hub decision after #4 is reviewed and Round-2 integration ordering is set.

@nullStack65

Copy link
Copy Markdown
Owner Author

METRICS-1:M3D dependency update for this history owner

T3 #4 (the usage attribution/measurement PR this draft depends on) moved from daa55eb88966665faa8c1770c35fc3c53a68f6fa to head af70495442678a06e007d13bba477f123170c0f5 by fast-forward. This PR's durable migrations (054/055) were not touched or rebased here.

Two changes are relevant when you eventually rebase onto the new head:

  1. usageScanCache.ts: CachedFile gained qualityMetadata: "declared" | "predecessor". Warm-cache acceptance and incremental resume now require identity === "declared" and qualityMetadata === "declared". Any scan-cache construction you carry over must set it (a freshly parsed entry sets "declared"); a decoded predecessor 15-field v4 row is "predecessor" and its completeness decodes as partial.
  2. usageTranscripts.ts: Codex and Grok now emit eligible zero-total events (complete zero, known-zero subset, all-invalid) instead of dropping them; only a container with no recognised token field is no-usage.

RESULT with before/after receipts: #4 (comment)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant