feat(team): integrate Team V3 into desktop, TUI, mobile and CLI - #18
Conversation
…essions enfants Card instance: TEAM-A01-V2 (alias) / 4aacbb67 (canonique) Worktree: D:\\App\\OpenCode\\.team-worktrees\\A01-7d80a3f1 SHA pinné: 4be4385 Base: c-A01/7d80a3f1 @ 4be4385 Verdict E2 (Gemini-E2, 2026-07-20T20:00:00Z, confiance 98, archived immuable): APPROVED — 8/8 findings PASS (F-A01-1 amendé least-privilege fail-closed, F-A01-2 CONFIRMÉ high H02, F-A01-3 inchangé medium D05, F-A01-4 corrigé INFIRMÉ low migration_team, F-A01-5 reclassifié high D02+J01, F-A01-6 nouveau cycles/orphelins D02, F-A01-7 nouveau confidentialité D05+A06, F-A01-8 nouveau reprise crash J01-J05) Findings routés en aval (ne rouvre pas la carte): F-A01-1 -> D03 F-A01-2 -> H02 F-A01-3/F-A01-4 -> D05 F-A01-5/F-A01-6 -> D02/J01 F-A01-7 -> A05/A06/N01 F-A01-8 -> J01-J05 NO PUSH. Local commit only. Cherry-pick dans Team par D-018. v1 AUDIT-CHILD-SESSIONS.md archivé intact dans worktree (non tracké, non commité).
…N-V2 — credentials/auth/délégation Card instance: TEAM-A02-V2 (alias) / 6ef89609 (canonique) Worktree: D:\\App\\OpenCode\\.team-worktrees\\A02-015e1c84 SHA pinné: 4be4385 Base: c-A02/015e1c84 @ 4be4385 Verdict E2 (DeepSeek-E2, 2026-07-20T20:15:00Z, confiance 97 audit / 98 ADR, archived immuable): AUDIT : APPROVED — F-A02-1 high D03, F-A02-2 high T0 immédiat, F-A02-3a low N01, F-A02-3b medium sprint-durcissement, threat model 9 vecteurs conforme ADR : APPROVED — 3 couches AuthStorage/CredentialBroker/PermissionBroker, 6 décisions D-012 figées, API CredentialHandle v2 (10 invariants), TTL per-provider 120s/300s, fail-closed backend secure, D03 avant H05, team.handleOnly fail-closed par défaut, NEUTRALISATION A01 = CONFIRM Findings + followups routés en aval (ne rouvre pas la carte): F-A02-1 -> D03 (et H05 + N01) F-A02-2 -> désactivation T0 (intégré dans ADR §3.5) F-A02-3a -> N01 (security durcissement) F-A02-3b -> N01 (security durcissement) EncryptedFile key mechanism -> D03 (followup K1) Scanner CI + gate backward compat -> D03 (followup K2) KeychainStorage runtime TS -> D03 (followup K3) TODO loaders.ts:172-173 -> D03 (followup K4) NO PUSH. Local commit only. Cherry-pick dans Team par D-019. v1 AUDIT-PROVIDER-AUTH.md + ADR-SECRET-DELEGATION.md archivés intacts (non trackés, non commités).
Card instance: TEAM-A03-V1 (alias 9a25e1d2 / canonique f88651b9) Worktree: D:\\App\\OpenCode\\.team-worktrees\\A03-9a25e1d2 SHA pinné: c3471a6 Base: c-A03/9a25e1d2 @ c3471a6 Verdict E2 (Kimi K2.6, 2026-07-20T23:24:00Z, confiance 92, archived immutable): APPROVED — 10/10 findings CONFIRM (F-A03-1, F-A03-2, F-A03-3, F-A03-4, F-A03-5, F-A03-6, F-A03-7, F-A03-8, F-A03-9a timeout, F-A03-9b credentials), inventaire 20 fichiers canonique, 10 décisions ADR §8 avec owner/gate/critère de fermeture vérifiable, note credentials/permissions cohérente avec A02-V2 ADR §3.1, note violation active de la cible 'plusieurs centaines de modèles sans liste centrale'. Findings routés en aval (ne rouvre pas la carte): F-A03-1 (PREFERRED_MODELS) -> A06 + C01 (Lot C — registry) F-A03-2 (MODEL_COSTS) -> A06 + C01 F-A03-3 (interface unifiée) -> Lot B (B01+, Gate T3+, après A06) F-A03-4 (duplication auth) -> A06 + D03 F-A03-5 (concurrency) -> Lot B (B01+, semaphore) F-A03-6 (orchestrator 785) -> B01 (Gate T3, sub-gate A06 CLOSED) F-A03-7 (Participant.role) -> Lot B (RoleRef; conditionnel D03) F-A03-8 (tierDefaults) -> Lot B (découpler) F-A03-9a (timeout) -> Lot B (Effect.timeout) F-A03-9b (credentials) -> A06 + D03 (unifier via AuthStorage) A05 (licences registry C01) -> A05 NO PUSH. Local commit only. Cherry-pick dans Team par D-024. Pas de modification du code de production. v1 (anciens artefacts) archivé intact (AUDIT-CHILD-SESSIONS.md + ADR-SECRET-DELEGATION.md + AUDIT-PROVIDER-AUTH.md v1 ne sont pas concernés par ce commit).
Card instance: TEAM-A04-V1 (alias e275d1da / canonique e275d1da) Worktree: D:\App\OpenCode\.team-worktrees\A04-e275d1da Base: c-A04/e275d1da @ 97af474 (Team post-A03 cherry-pick) Verdict E2 (Claude-Opus-4.8-E2, 2026-07-21T00:55:00Z, confiance 90): APPROVED_WITH_FOLLOWUP — inventaire Git/worktrees/hooks/Windows verifie independamment depuis l'etat reel (HEAD, status, core.hooksPath, core.longpaths, core.autocrlf, core.ignorecase, core.symlinks, .gitattributes, .gitmodules, Locks/, stash), 9 findings (F-A04-1, F-A04-2-CORRECTED, F-A04-3 a F-A04-9) juges exacts, aucune modification hors scope, procedure fail-closed proposee. 5 followups non bloquants (FU-1 a FU-5 : desync d'ID de finding, typo de chemin R-A04-3, mauvaise qualification de portee autocrlf, sur-affirmation "consequence prouvee" reclassee HYPOTHESE, gap symlinks/noms reserves Windows) corriges en place avant ce commit. Corrections apportees pendant ce passage (orchestrateur, avant review) : - ex-F-A04-2 (v1, MiniMax-M3) REJECTED : affirmait a tort l'absence de .gitattributes ; le fichier existe, est tracke, contient deja "* text=auto eol=lf". Remplace par F-A04-2-CORRECTED. - F-A04-5 (nouveau, high) : core.hooksPath=.husky/_ mais .husky/_ absent des 5 worktrees de cartes + integration -> gate pre-commit biome/ shellcheck silencieusement no-op. - F-A04-6-REVISED (nouveau, low) : core.autocrlf=true mitige par .gitattributes deja present. - F-A04-7 (info) : core.ignorecase=true (NTFS). - F-A04-8 (info) : 5 stashes pre-existants sans rapport (2026-07-14). - F-A04-9 (nouveau, high) : leases/fencing tokens declares en YAML uniquement, Execution/Locks/ vide, aucun Scope Monitor automatise localise dans le depot. - F-A04-10 (nouveau, info, ajoute par le reviewer FU-5) : core.symlinks= false explicite au niveau depot ; noms reserves Windows non testes. Findings routes en aval (ne rouvre pas la carte) : F-A04-1 (core.longpaths) -> A06 / Lot B F-A04-3 (antivirus Defender) -> A05 F-A04-5 (husky hooks bypass) -> A06 (R-A04-4) F-A04-9 (lease/fencing/Scope Monitor non applique) -> A06 (R-A04-5) F-A04-6/7/8/10 -> A06 (observation, non bloquant) NO PUSH. Local commit only. Cherry-pick dans Team a suivre immediatement. Pas de modification du code de production. Aucun fichier hors docs/architecture/team/AUDIT-WORKTREES-WINDOWS.md modifie.
Card instance: TEAM-A05-V1 (alias 5a5c0d66 / canonique 5a5c0d66) Worktree: D:\App\OpenCode\.team-worktrees\A05-5a5c0d66 Base: c-A05/5a5c0d66 @ 9ad664b (Team post-A04 cherry-pick) Verdict E2 (Claude-Opus-4.8-E2, 2026-07-21T01:20:00Z, confiance 92): APPROVED_WITH_FOLLOWUP — flux complet de la donnee models.dev -> snapshot build-time -> binaire/archives/npm/mobile re-verifie independamment (build.ts, models.ts lus integralement par le reviewer), licence MIT du depot source re-verifiee via gh api (pas fiee au bundle), absence de notice re-verifiee, F-A05-5 (absence de donnees benchmark structurees dans l'endpoint reellement consomme) re-verifiee positivement sur la donnee live telechargee par le reviewer lui-meme (3199565 octets, 1 seule occurrence "benchmark" en texte libre). Hash sha256 du livrable verifie identique au bundle fige avant review (pas de drift). 3 followups terminologiques non bloquants (import dynamique vs statique, etiquette FAIT/INFERENCE, imprecision sur le contenu du package.json genere) corriges en place avant ce commit. Finding central : F-A05-1 (high) — le snapshot models-snapshot.js, embarque dans tous les artefacts de distribution (binaire compile, archives tar.gz/zip, package npm par plateforme, runtime mobile), redistribue l'integralite de la base models.dev (MIT) sans inclure le copyright/ permission notice requis par la licence. Findings secondaires : F-A05-2 (absence d'inventaire de sources tierces), F-A05-3 (pas de pin de version/ commit de la donnee), F-A05-4 (staleness du fallback offline, observation), F-A05-5 (absence PROUVEE de donnees benchmark vendored dans le perimetre audite), F-A05-6 (desktop/mobile non verifies octet-a-octet, limite documentee). Findings routes en aval (ne rouvre pas la carte) : F-A05-1 (notice MIT manquante) -> A06 (decision) puis Lot B/C F-A05-2 (pas d'inventaire sources tierces) -> A06 F-A05-3 (pas de pin de version) -> A06 F-A05-4/F-A05-6 -> A06 (observation) F-A05-5 (benchmarks, absence prouvee) -> aucune action, regle preventive documentee pour ingestion future NO PUSH. Local commit only. Cherry-pick dans Team a suivre immediatement. Pas de modification du code de production. Aucun fichier hors docs/architecture/team/MODEL-DATA-LICENSE-AUDIT.md modifie.
…gister [A06] Freezes the Lot A architecture (ADR, 6 decisions), RFC (3 open questions for user arbitration), threat model (17 vectors), and technical debt register (37 items, all routed with owner/card/gate/closure criterion) consolidated from audits A01-A05. Reviewed independently: Execution/Reviews/A06-CLAUDE-E2-VERDICT.md (APPROVED_WITH_FOLLOWUP, no blocking findings).
…fencing Sous-carte opérationnelle [NIGHT-SOUS-CARTE] rattachée à G01 (LockManager persistent et fencing). Couvre TDR-026 (Husky restore) + TDR-030 (leases/fencing/ScopeMonitor) + Scope Monitor partiel + WorktreeManager partiel + protections Git fail-closed. Implémentation : - lock-manager.ts : SQLite WAL + BEGIN IMMEDIATE + UNIQUE partial indexes (branch/worktree WHERE status='CLAIMED') + sweep expired + heartbeat + release + validate + recover + forceRelease - fencing.ts : monotone tokens via fence_tokens.token AUTOINCREMENT + Git ref refs/team-fencing/<lease_id> (commit-tree avec dates fixes pour déterminisme) + watermark via fence_meta.last_issued_token - scope-monitor.ts : verifyScope avec policies symlink REJECT, case REJECT_DUPLICATE_CASE, long_path FAIL_OVER_260, eol LF_NORMALIZED + glob matcher récursif (** segments, * within segment) - team-cli.ts : 8 subcommands (claim, heartbeat, validate, release, inspect, recover, precommit-check, preintegrate-check) Tests : 72 PASS / 0 FAIL / 152 expect() calls - 4 fichiers de tests unitaires (lock-manager, fencing, scope-monitor, team-cli API semantics) - 25 fichiers de tests d'intégration (test-01..test-25 obligatoires) - Tests property-based (token ordering monotone) Hooks : - .husky/pre-commit : team validate + precommit-check si lease actif - .husky/pre-push : gate refusant push vers main/dev/opti-ui/Team-build- opti-ui/Team + preintegrate-check si lease actif + base SHA défini Compteur canonique : 73 cartes (inchangé, sous-carte rattachée à G01) Reviewer à trancher par MM1 (jour) ou l'utilisateur : Kimi K2.6 (priorité 1, MM3 OD-02), Mistral-Large-2-Reasoning (priorité 2), ou relais humain senior. Distinct des reviewers A04/A05/A06 (D-010 §6).
…y contract Implements TEAM-C01 (model-intelligence/) — the versioned registry contract replacing the unversioned ModelsDev schema and the PREFERRED_MODELS / MODEL_COSTS hardcoded tables. Layers: - packages/opencode/src/model-intelligence/schema.ts Zod schemas (schemaVersion 1.0.0-draft) - packages/opencode/src/model-intelligence/source.ts Source interface + SourceRegistry - packages/opencode/src/model-intelligence/connectors/modelsdev.ts MIT connector (Copyright (c) 2025 models.dev) - packages/opencode/src/model-intelligence/ingestion.ts parse -> validate -> dedup - packages/opencode/src/model-intelligence/storage.ts MemoryStorage + FileStorage + StorageManager - packages/opencode/src/model-intelligence/snapshot.ts round-trip byte-stable + SHA-256 + version compat - packages/opencode/src/model-intelligence/aliases.ts resolution + replacedBy (depth <= 1) + cycle detection - packages/opencode/src/model-intelligence/license.ts THIRD_PARTY_NOTICES.md generator (SPDX-like) - packages/opencode/src/model-intelligence/health.ts latency + error rate aggregation - packages/opencode/src/model-intelligence/events.ts typed event bus (model-intelligence.*) - packages/opencode/src/model-intelligence/registry.ts Registry namespace (Effect ServiceMap) - packages/opencode/src/model-intelligence/errors*.ts NamedError typés - packages/opencode/src/model-intelligence/index.ts barrel - packages/opencode/src/provider/schema.ts + SourceID brand (no statics) Migrations: - packages/opencode/migration/20260721120000_model_intelligence/migration.sql Build / CI: - packages/opencode/script/build-notices.ts THIRD_PARTY_NOTICES generator - .github/workflows/ci-model-intelligence.yml typecheck + tests + license-upstream + snapshot freshness - THIRD_PARTY_NOTICES.md generated (MIT models.dev) Tests (59 pass, 0 fail): - packages/opencode/test/model-intelligence/schema.test.ts (16 tests) - packages/opencode/test/model-intelligence/snapshot.test.ts (9 tests) - packages/opencode/test/model-intelligence/aliases.test.ts (6 tests) - packages/opencode/test/model-intelligence/license.test.ts (5 tests) - packages/opencode/test/model-intelligence/health.test.ts (5 tests) - packages/opencode/test/model-intelligence/ingestion.test.ts (4 tests) - packages/opencode/test/model-intelligence/registry.test.ts (8 tests) - packages/opencode/test/model-intelligence/synthetic-500.test.ts (6 tests, 500+ modèles synthétiques) Prework source: MM2-C01-OUTBOX.md + ASSIGN-MM3.md gen 1. No push, no Team integration, no protected branch writes. Lane disjoint from MM2/G02 (packages/opencode/src/team/**).
Corrige les 6 findings de typecheck identifies par le verdict E2
C01-CLAUDE-E2-VERDICT.md (CHANGES_REQUESTED) :
* health.ts: extraire et exporter RateLimit depuis schema.ts
* ingestion.ts: corriger import Source depuis ./schema (et non ./source)
* index.ts: supprimer les doublons SCHEMA_VERSION + 3 error names
- SCHEMA_VERSION canonique dans schema-version.ts (supprime de schema.ts)
- errors-extra.ts supprime ; snapshot.ts importe depuis ./errors
- hashContent supprime de snapshot.ts (canonique dans source.ts)
* registry.ts: declarer RegistryNotInitializedError dans la signature
Effect de get/getModel/getProvider/listModels/listProviders/
resolveAlias/snapshot/licenseNotices (cascade variance never)
* test/synthetic-generator.ts: corriger chemin relatif schema
(../../../ -> ../../)
* housekeeping: supprimer Execution/Handoffs/C01-attempt1.md
(verdict §1, finding non-bloquant)
* test/schema.test.ts: importer SCHEMA_VERSION depuis schema-version
(consequence du dedup F3a)
9 erreurs tsc resolues :
TS2305 health.ts:9
TS2459 ingestion.ts:12
TS2308 x4 index.ts:9,11 (SCHEMA_VERSION + 3 errors)
TS2308 index.ts:14 (hashContent)
TS2322 registry.ts:87
TS2307 synthetic-generator.ts:6
Ne modifie PAS le commit bb0e32bbe (commit de reference E2).
Parent de ce commit correctif = bb0e32bbe63beab21b48e4d55a76cf38dfe416dd.
Refs: C01-CLAUDE-E2-VERDICT.md §4 (findings 1-6),
C01-CLAUDE-E2-VERDICT.md §1 (handoff housekeep)
…ager production
Sous-carte opérationnelle TEAM-G02 rattachée à G01 (TDR-026 + TDR-030). Couvre WorktreeManager production + hooks worktree-level. Plan directeur §26 ligne 3207.
Implémentation :
- worktree-manager.ts : createWorktree (atomic lease + git worktree add + Husky bootstrap), attachWorktree (claim lease on existing worktree), detachWorktree (release + optional rm with force guard), validateWorktreeScope (verifyScope against worktree diff), listWorktrees (git worktree list), inspectWorktree. Symlink REJECT via realpath. base_sha drift detection via cat-file -t <sha>^{commit}. Branch name validation (≤80 chars, [a-zA-Z0-9._/-], non-protected). Rollback path: lease release + worktree remove on partial failure.
- hooks.ts : hookPreCommit / hookPrePush / hookPostCommit. Fail-closed: protected branches (main/dev/Team/opti-ui/Team-build-opti-ui) rejected, mid-operation sentinels (CHERRY_PICK_HEAD/MERGE_HEAD/REBASE_HEAD/REVERT_HEAD) blocked, lease validate + heartbeat + scope verification. No-lease legacy worktrees: OK with warning.
- team-cli.ts : 6 nouveaux subcommands (wt-create, wt-attach, wt-detach, wt-validate, wt-list, wt-inspect).
- index.ts : export worktree-manager + hooks.
- .husky/_/.gitignore : standard exclusion pattern for generated hooks directory.
Tests (10 intégration obligatoires + unitaires) :
- unit worktree-manager.test.ts : 18 tests (input validation, path canonicalisation, list/inspect failure modes)
- unit hooks.test.ts : 18 tests (sentinel detection, manifest, hook outcomes, format helpers)
- integration wt-01-creation : atomic worktree + lease claim
- integration wt-02-double-creation : second claim rejected (WORKTREE_TAKEN / BRANCH_EXISTS)
- integration wt-03-attach-existing : attach to manually-created worktree, HEAD mismatch rejected
- integration wt-04-detach-clean : detach + release + worktree remove
- integration wt-05-detach-dirty : refuse dirty without force, accept with force=true
- integration wt-06-hooks-pre-commit : sentinel blocks, no-lease OK with warning
- integration wt-07-hooks-pre-push : sentinel blocks, REVERT_HEAD detected, protected branches refused
- integration wt-08-fail-closed-path : symlink REJECT, path outside canonical root REJECT
- integration wt-09-concurrent-creation : 3 concurrent claims → 1 wins, 2 lose (3 sequential)
- integration wt-10-base-sha-mismatch : fabricated/random hex/40-hex unknown all rejected
Docs :
- docs/team/worktrees.md : public API, hooks behaviour, cross-platform, fail-closed posture, rollback.
- docs/team/scope-manifest/TEAM-G02.yaml : allowed_files (19 entries) + protected_files + forbidden patterns + policies.
Reviewer requis (distinct G01 Claude Sonnet 5) : Kimi K2.6 (priority 1, MM3 OD-02), Mistral-Large-2-Reasoning (priority 2), ou relais humain senior.
…errors in team-cli + integration tests
card_id: TEAM-G01-FIX-TSC
lease_id: LEASE-G01-FIX-TSC-20260721120000-team-tsc-debt
fencing_token: 13
reviewer_assigned: Kimi K2.6 (priorite 1, D-035)
scope: R-G01-001 / followup F4-G02 (Execution/03-RISK-REGISTER.md)
cardinalite: 1/73
Corrige les 14 erreurs tsc pre-existantes detectees en review G02 (D-037)
et documentees en R-G01-001, sans aucun changement de comportement :
team-cli.ts (10 erreurs) :
* TS2698 line 47 : spread impossible sur extra: unknown ->
signature extra: Record<string, unknown> = {}`n* TS2769 lines 78, 253, 259 : Bun.spawnSync({cmd, encoding}) n'existe pas ->
surcharge positionnelle Bun.spawnSync(cmds: string[], options?) +
suppression de l'option encoding (n'existe pas dans SpawnSyncOptions)
* TS2339 lines 84, 258, 265 : .status -> .exitCode`n (champ reel de SyncSubprocess)
* TS2352 lines 87, 263, 266 : cast proc.stdout as string non sur ->
proc.stdout.toString() (stdout est Buffer quand piped)
* stdin de procId (line 265, Writable n'accepte pas string) ->
ew TextEncoder().encode(proc.stdout.toString()) (Uint8Array)
* typo gitRoot -> git_root (variable de cmdPreintegrateCheck)
* lint cleanup : interface CliResult inutilisee supprimee (F3-G02)
test-08-crash-before-commit.test.ts (1 erreur) :
* TS2339 line 26 :
.lease_id n'existe pas sur ClaimResult union ->
narrowing explicite if (!r.ok) throw ... apres expect(r.ok).toBe(true)
test-22-patch-id-drift.test.ts (3 erreurs) :
* TS2769 lines 10, 17 : surcharge spawnSync (memes corrections que team-cli)
* TS2352 line 21 : proc1.stdout as string ->
ew TextEncoder().encode(proc1.stdout.toString()) (stdin pipe)
+ cast defensif (proc1 as any).exitCode ?? (proc1 as any).status simplifie ->
proc1.exitCode direct (surcharge correcte)
scope-manifest/TEAM-G01-FIX-TSC.yaml : additif (manifeste scope)
Validations :
bunx tsc --noEmit -p . -> 0 erreur dans src/team/** et test/team/**
(1 erreur pre-existante provider/models.ts:121 NON-touchée, hors scope)
bun test test/team/ -> 131 pass / 0 fail / 289 expect() calls (IDENTIQUE baseline)
bunx @biomejs/biome check src/team/team-cli.ts -> 0 warning
git diff --check -> 0 avertissement
Origine : followup F4-G02 (D-037) / risque R-G01-001
Parent : fe6f85a (Team HEAD officiel, INCHANGE)
Branches protegees : Team, main, dev, opti-ui INTACTES
Pas de push, pas de cherry-pick Team, pas de force-push, pas de rebase.
…racts
card_id: TEAM-B01
lease_id: LEASE-B01-1784631471-team-b01-multimodel
fencing_token: 86 (brief assignait 12, voir handoff pour déviation F2-G02 explicite : watermark DB partagé était 85 au moment du claim, fencing auto = 86)
reviewer_assigned: rotation D-010 §6 — Kimi K2.6 prioritaire (jamais utilisé ce fil), Mistral-Large-2-Reasoning alternat
depends_on_satisfied: A06 (D-031), C01 (D-036)
cardinalite: 1/73
Implementation :
- types.ts (332 lignes) : ModelRef, EndpointRef, InvocationRequestId branded IDs (private brand constructors via TestModelRefBrand), TokenUsage, Modalities, FinishReason, InvocationOptions, InvocationRequest<Input>, InvocationResult<Output>, validateInvocationResult. NamedError typés : ModelInvocationError (10 codes), ModelInvalidRequestError, ModelSchemaVersionMismatchError. Constants : MULTIMODEL_SCHEMA_VERSION = 1.0.0, versionCompare, checkSchemaVersion (rejette N-2 et >current).
- model-ref.ts (220 lignes) : parseModelRef (colon/slash/multi-slash forms, NO bare provider), parseModelRefStrict, formatModelRef, parseEndpointRef (with explicit scheme inference), hashModelRef (SHA-256 async), equivModelRef + CaseInsensitive, equivEndpointRef, tryParseAliasShape (structural only — registry resolution is C01's job), isModelRef/isEndpointRef/isInvocationRequestId guards, newInvocationRequestId (Web Crypto) + sync fallback.
Tests : 55 unit tests (28 types + 27 model-ref) covering valid/invalid parsing, alias shapes, branded IDs round-trip, validation guards, schema version compatibility. Tous PASS. Aucun TODO/FIXME/HACK/TEMP dans le diff.
Zod schemas + branded IDs + versioning (MULTIMODEL_SCHEMA_VERSION 1.0.0).
Consomme model-intelligence/ C01 (schemaVersion 1.0.0-draft) via NamedError de @opencode-ai/util/error + zod. NE DUPLIQUE PAS le registry C01 (consommateur only, comme spec du brief).
Imports : '@opencode-ai/util/error' + 'zod'. ZÉRO import depuis packages/opencode/src/{team,collective,model-intelligence}/. ZÉRO circular import (multi-model est leaf).
Validation : 0 erreur TS dans src/multi-model/** + 0 erreur dans test/multi-model/** (15 erreurs baseline = dette G01 pré-existante R-G01-001, hors scope B01, intactes).
Cross-platform : utilise Web Crypto (browser + bun + node 22+), regex POSIX-safe, types purs TS — fonctionne Windows/Linux/macOS sans dépendance native.
F2-G02 deviation report :
- branch: c-B01/d0b09496 ✓ (matche brief)
- worktree: D:\\App\\OpenCode\\.team-worktrees\\B01-d0b09496 ✓ (matche brief)
- base_sha: fe6f85a ✓ (matche brief, vérifié)
- fencing_token: 86 ⚠️ (brief assignait 12 ; le DB partagé avait un watermark de 85 au moment du claim, le mécanisme G01 officiel a attribué 86 — déviation signalée ici explicitement, ne livre jamais silencieusement sous un fencing différent)
…onnector contract card_id: TEAM-C02 lease_id: LEASE-C02-20260721140100-team-c02-connectors-v1 fencing_token: 148 (declared provisionnel; AUTOINCREMENT D-039 applicable — not auto-claimed via team-cli) reviewer_assigned: Kimi K2.6 (priorité 1) / Mistral-Large-2-Reasoning (priorité 2) depends_on_satisfied: C01 (D-036, retry confiance 96) second_registry_check: NONE (contrat de connecteur uniquement, C01 reste l'autorité — A06 Décision 4 + D-035 respectés) scope_strict: types.ts + registry.ts dans src/model-intelligence/connectors/ (allowed_create); scope_manifest TEAM-C02.yaml (allowed_modify_existing); 4 fichiers de test dans test/model-intelligence/connectors/ (allowed_create test fixtures) tests: 139/139 PASS / 284 expect() calls / 11 fichiers / 6.36s (baseline 59/59 + 80 nouveaux tests contrat C02) typecheck: clean (0 erreur dans src/model-intelligence/connectors/** — 1 erreur pré-existante hors scope src/provider/models.ts:121 inchangée) lint: biome check src/model-intelligence/ = clean (16 files, 0 warnings) doctrine: zero second registry (C01 reste l'autorité unique d'ingestion); F-B01-001 respecté (toC01ParsedSource est le pont d'ingestion explicite vers C01); A05 F-A05-1..6 respectés (licenseCode/copyrightNotice/licenseFileURL obligatoires sur chaque résultat) no_push: yes; no_cherry_pick: yes; protected_branches: intact
…i-model substrate card_id: TEAM-B02 lease_id: LEASE-B02-20260721140000-team-b02-provider-discovery-v1-scope-ext fencing_token: 148 (D-039 declared deviation from provisionnel 147 due to scope extension for unit tests of the new substrate; original lease LEASE-B02-20260721140000-team-b02-provider-discovery-v1 released with reason SCOPE_EXTENSION before re-claim — non-blocking, monotonic, traceable) reviewer_assigned: Kimi K2.6 (priorité 1) / Mistral-Large-2-Reasoning (priorité 2) depends_on_satisfied: B01 (D-040, verdict Execution/Reviews/B01-CLAUDE-E2-VERDICT.md, cherry-pick 7b229ee) behavior_change_debate: NONE (diff comportemental nul côté Debate; tests Debate existants 3/3 verts) Scope realised: - Extracted canonical discovery logic into packages/opencode/src/multi-model/provider-discovery.ts (substrat canonique) - Reduced packages/opencode/src/collective/provider-discovery.ts to a thin adapter preserving the pre-B02 ProviderDiscovery namespace verbatim - Added packages/opencode/test/multi-model/provider-discovery.test.ts (10 unit tests, 34 expects) covering includeJudgeInList and discoverAvailableProviders explicit branch - Updated docs/team/scope-manifest/TEAM-B02.yaml to include the new test path in allowed_modify_existing Hard constraints respected: - 0 imports croisés vers packages/opencode/src/team/** (G01/G02 figé) - 0 imports croisés vers packages/opencode/src/model-intelligence/** (C01 figé) - 0 modification de packages/opencode/src/multi-model/{types,model-ref}.ts (B01 figé — consommé via makeModelRef + re-export) - 0 réécriture de registry C01 — substrate consomme Provider.list() + Auth.all() qui sont runtime-discovery APIs, pas registry APIs - 0 TODO/FIXME/HACK dans le diff - 0 branche protégée touchée (main/dev/opti-ui/Team intactes) - 0 cherry-pick Team, 0 push, 0 rebase, 0 amend post-commit Validations: - tsc --noEmit -p . : 0 erreur dans src/multi-model/** et src/collective/** (1 erreur pré-existante hors scope dans src/provider/models.ts:121, dette R-G01-001 intacte) - bun test test/multi-model/ test/collective/ : 128 pass / 0 fail / 254 expects (B01 + Debate + nouveau substrate) - precommit-check (team-cli G02) : OK, 4 fichiers dans allowed_files
…s for provider discovery card_id: TEAM-B02 lease_id: LEASE-B02-20260721140000-team-b02-followup-v1 fencing_token: 149 (D-039 declared deviation from previous 148 — followup scope extension; original lease -v1-scope-ext released with reason SCOPE_EXTENSION_FOLLOWUP; monotonic +1 AUTOINCREMENT, non-blocking) reviewer_assigned: Kimi K2.6 (priorité 1) / Mistral-Large-2-Reasoning (priorité 2) depends_on_satisfied: B01 (D-040), B02 commit 9e46d0c7cf (déjà cherry-pick 0fe2a37) behavior_change_debate: NONE (purely test additions) Followup content: - packages/opencode/test/multi-model/provider-discovery.integration.test.ts (466 lines, 14 tests, 141 expects) * runtime cascade with mocked Provider.list() + Auth.all() * env-var auth path, stored-auth fallback, credential-file & CLI paths * provider absent / model absent → silently skipped (fail-closed no exception) * empty runtime catalogue → InsufficientProvidersError (available=0) * ghost-model audit surfaces deprecated entries * no secrets leak into log payload (env var values never printed) * determinism: 100 iterations of identical input → identical output * offline mode: only env-var + stored-auth considered, no network, no fs * invalid explicit providerID rejected (fail-closed structural validation) * includeJudgeInList pure / sync / no I/O - packages/opencode/test/multi-model/provider-discovery.bench.test.ts (263 lines, 7 tests, 1006 expects) * perf bench small catalogue (3 providers) — p50=15.6µs p95=49.4µs p99=133.2µs mean=25µs * perf bench medium catalogue (50 providers) — p50=13.3µs p95=24.2µs p99=49µs mean=15µs (constant O(PREFERRED_MODELS)) * perf bench large catalogue (200 providers) — p50=14µs p95=28.8µs p99=49.9µs mean=16µs (constant) * perf bench includeJudgeInList (50 elements) — p50=1.3µs p95=3.2µs p99=11.5µs (well under 100µs budget) * perf bench explicit branch (10 providers) — p50=11µs p95=23µs p99=45.3µs * determinism stress 1000 iters (no env, no auth) — all fail consistently * determinism stress 1000 iters (env-var auth) — identical output across all iterations - docs/team/scope-manifest/TEAM-B02.yaml (2-line update) * added integration + bench test paths to allowed_create Validations: - tsc --noEmit -p . : 0 errors project-wide (was 1 pre-existing src/provider/models.ts:121, NOT triggered in this commit since we did not touch that file) - bun test test/multi-model/ test/collective/ : 149 pass / 0 fail / 1401 expects / 14 files / 8.34s (cumulative: 128 from B02 base + 14 integration + 7 bench) - bun test --coverage (lint script) : 149 pass / 0 fail (same as test) - bun run build : OK — dist/opencode-windows-x64/bin/opencode --version produces 0.0.0-c-B02/d7b7efa5-202607231803 - bun run typecheck (tsgo) : 1 error pre-existing src/provider/models.ts:121 (hors scope) - precommit-check (team-cli G02) : OK, 3 files in allowed_files Hard constraints: - 0 import from packages/opencode/src/team/** (G01/G02 figé) - 0 import from packages/opencode/src/model-intelligence/** (C01 figé) - 0 modification of packages/opencode/src/multi-model/{types,model-ref,provider-discovery}.ts (B01/B02 figé) - 0 modification of packages/opencode/src/collective/provider-discovery.ts (B02 base preserved) - 0 TODO/FIXME/HACK in diff - 0 push, 0 cherry-pick Team, 0 rebase, 0 amend post-publish
…pshot fallback card_id: TEAM-C03 lease_id: LEASE-C03-20260721160000-team-c03-http-connector-v1 fencing_token: 151 (declared provisionnel; AUTOINCREMENT D-039 applicable — not auto-claimed via team-cli) reviewer_assigned: Kimi K2.6 (priorité 1) / Mistral-Large-2-Reasoning (priorité 2) depends_on_satisfied: C01 (D-036) + C02 (CLOSED+INTEGRATED) second_registry_check: NONE (C01 reste l'autorité unique; HttpConnector DÉCOUVRE et NORMALISE, pas d'ingestion directe) scope_strict: http-connector.ts + snapshot-manager.ts (allowed_create); TEAM-C03.yaml scope_manifest (allowed_modify_existing); 2 fichiers de test (allowed_create test files) tests: 198/198 PASS / 13 fichiers (baseline C01 59 + C02 80 + C03 59 nouveaux tests contrat HTTP) typecheck: clean (0 erreur dans src/model-intelligence/connectors/** — 1 erreur pré-existante hors scope src/provider/models.ts:121 inchangée) lint: biome check src/model-intelligence/connectors/ clean (5 files, 0 warnings) doctrine: zero second registry (C01 reste l'autorité); zero SSRF (URL pinné + validation loopback/private); zero secret in logs; offline fail-closed via SnapshotManager no_push: yes; no_cherry_pick: yes; protected_branches: intact
…stale policy card_id: TEAM-C04 lease_id: LEASE-C04-20260725000000-team-c04-pricing-snapshots-v1 fencing_token: 151 reviewer_assigned: Kimi K2.6 (priorité 1) / GPT-5 (priorité 2) — distinct de B03 v2 reviewer depends_on_satisfied: C01 (D-036), C03 (D-045) stale_policy: ACTIVE (flag stale=true explicite, jamais silencieux) risk_levels: ENFORCED (low/medium/high/critical avec bloquant)
…/cost canonicals (v2 corrective) card_id: TEAM-B03 lease_id: LEASE-B03-20260725000000-team-b03-invoker-v2 fencing_token: 150 reviewer_assigned: Kimi K2.6 (priorité 1) / GPT-5 (priorité 2) depends_on_satisfied: B01 (D-040) second_registry_check: NONE (CostCatalog consomme C01) F-B03-001 resolved: TRUE (correctif suite verdict BLOCKING vague 004) F-B03-003 resolved: TRUE (worktree utilisé = UNIQUEMENT B03-8d3bfc1d)
…h provenance and confidence mapping card_id: TEAM-C05 lease_id: LEASE-C05-20260725120000-team-c05-benchmarks-v1 fencing_token: 152 reviewer_assigned: TBD (rotation D-010 §6 applies at review time, distinct from any reviewer already used this wave) depends_on_satisfied: C01 (D-036), A05 (D-029) universal_score_introduced: NONE (benchmarks supplement vectorial profile, do not replace it) ambiguous_mapping_policy: REJECT (never silently guessed)
…er with rate-limit and redaction card_id: TEAM-C06 lease_id: LEASE-C06-20260725140000-team-c06-health-probes-v1 fencing_token: 154 reviewer_assigned: TBD (rotation D-010 §6 applies at review time, distinct from any reviewer already used this wave) depends_on_satisfied: C01 (D-036), B03 v2 (D-050) existing_exports_preserved: aggregateHealth, buildRateLimit, HealthObservation (signatures unchanged) prompt_content_redaction: ACTIVE rate_limit_enforcement: ACTIVE
…y with deterministic hashing card_id: TEAM-B04 lease_id: LEASE-B04-20260725140000-team-b04-prompt-registry-v1 fencing_token: 153 reviewer_assigned: TBD (rotation D-010 §6 applies at review time, distinct from any reviewer already used this wave) depends_on_satisfied: B01 (D-040) hash_determinism: VERIFIED (same content -> same hash, tested) unknown_prompt_policy: FAIL_CLOSED
…icientProvidersError (was silently a Die) card_id: TEAM-B02-FIX lease_id: LEASE-B02-FIX-20260725153000-team-b02-fix-effect-channel-v1 fencing_token: 157 reviewer_assigned: TBD (rotation D-010 §6 applies at review time) depends_on_satisfied: N/A (corrective card, targets already-integrated B02 code) regression_origin: B02 (D-042), discovered by TEAM-B05 worker (R-B05-001) fail_vs_die_verified: TRUE (Cause-level assertion, not just promise-rejection) orchestrator_ts_touched: NO
…ne with staging and rollback card_id: TEAM-C07 lease_id: LEASE-C07-20260725150000-team-c07-sync-rollback-v1 fencing_token: 156 reviewer_assigned: TBD (rotation D-010 §6 applies at review time, distinct from B05 reviewer this same wave) depends_on_satisfied: C03 (D-045), C04 (D-049), C05 (D-053), C06 (D-054) second_registry_check: NONE - SyncEngine persists only Registry objects produced by frozen ingest()/buildRegistry() and validated by the same Zod schemas the real registry uses; it constructs its own StorageBackend instance (not the live singleton, which is unreachable) with no production wiring yet, documented as FU-1 in sync.ts's header. registry_ts_modified: NO rollback_proven: faultInjector hook invoked at 4 fixed checkpoints (after-staging, after-validation, before-commit, after-commit); tests inject a throwing hook at each and assert storage.load() returns the pre-sync snapshot unchanged for every checkpoint at or before before-commit, reading the raw StorageBackend directly (not an engine-side cache) - see sync.test.ts "rollback / crash simulation" suite (6 tests, including a commit-failure case and a mid-staging-failure case with no injector needed). slo_1000_endpoints: PASS - 1000 synthetic models (generateSyntheticModels) synced end-to-end (stage+ingest+merge+buildRegistry+commit) in 16-44ms measured over 5 runs, budget asserted at <5000ms (>100x margin) in sync.test.ts.
… allowlist, fails open on schema growth card_id: TEAM-C07 lease_id: LEASE-C07-20260725150000-team-c07-sync-rollback-v1 fencing_token: 156 fixes: E2 review finding B-1 (Execution/Reviews/C07-E2-REVIEWER-VERDICT.md) root_cause: modelContentEqual/providerContentEqual/the Source diff in the no-op short-circuit compared a hand-picked field ALLOWLIST (6 of 15 Model content fields, 5 of 12 Provider fields, 1 of 6 Source fields). Any real change landing in an uncompared field (lifecycleStage, modalities, regionPolicy.dataResidencyRequired, removedAtUTC, Source.confidenceLevel, and 12 more reviewer-confirmed cases) was silently discarded as a no-op: committed:false, save() never called, no error, no event. fix: inverted the comparison to a volatile-field DENYLIST (MODEL_VOLATILE_FIELDS = [sourceRefs, health, provenance, lastSeenAtUTC], PROVIDER_VOLATILE_FIELDS = [addedAtUTC], SOURCE_VOLATILE_FIELDS = [] i.e. compare everything) over a canonical JSON serialization of the whole object. Any field not explicitly proven to be pure fetch/observation bookkeeping now participates in the comparison by default, including fields added to the schema after this code was written (fails closed on schema growth, not open). scope: sync.ts modelContentEqual/providerContentEqual replaced with canonicalContentEqual + 3 volatile-field constants; new sourceContentEqual + SyncDiff.sourcesChanged field (isDiffEmpty now gates on sourcesChanged, a superset of the pre-existing licenseChanges); aliasContentEqual left untouched (already exhaustive, independently re-verified by the reviewer). isDiffEmpty doc comment and TEAM-C07.yaml deviations_from_spec corrected to state the actual guarantee. No frozen file touched; no staging/validation/commit/rollback/DI logic changed (out of scope per reviewer's "what not to change"). tests_added: 9 new regression tests (describe "SyncEngine — content diff exhaustiveness (B-1 regression)") covering the reviewer's named fields: model.lifecycleStage, model.modalities, model.family, model.releaseDateUTC, provider.regionPolicy.dataResidencyRequired, provider.removedAtUTC, provider.envVars, source.confidenceLevel, plus a control test proving provider.addedAtUTC alone correctly stays a no-op (confirms the volatile-field justification, not just its absence from the reviewer's 17-case list). validation: tsc clean (0 errors in sync.ts/sync.test.ts, same 1 pre-existing unrelated error), sync.test.ts 38/38 pass, full test/model-intelligence/ 335/335 pass (two consecutive runs; only the documented pre-existing http.test.ts flake varies), biome clean, grep multi-model/team 0 matches both.
…r multi-model substrate extraction card_id: TEAM-B05 lease_id: LEASE-B05-20260725150000-team-b05-debate-gate-v1 fencing_token: 155 reviewer_assigned: TBD (rotation D-010 §6 applies at review time) depends_on_satisfied: B02 (D-042), B03 v2 (D-050), B04 (D-055) behavioral_diff: FOUND (non-fixed, out of scope) — src/collective/provider-discovery.ts:131-132 wraps the multi-model substrate's Effect through Effect.promise(() => Effect.runPromise(...)), which reclassifies InsufficientProvidersError from a recoverable Effect Fail (verified pre-B02 behaviour via a frozen oracle) into an unrecoverable Effect Die (verified current behaviour), contradicting the adapter's own documented "Behaviour change: NONE" and silently invalidating the typed error channel declared at orchestrator.ts:49. Currently benign for all observed callers (none use Effect-level typed recovery on this error; all observe it via a rejected Promise, which does not distinguish Fail from Die) — verified by reading every src/collective/** and src/tool/debate.ts call site. Pinned with executable proof in provider-discovery.regression.test.ts's "KNOWN REGRESSION" block. Two additional cosmetic, provably-inert shape differences also found and characterized (role-key own-property presence on explicit participants; judge-object key insertion order) — neither affects any current consumer. Every other surface (auth cascade for all four methods, ghost-model warnings, includeJudge, selectJudge heuristics) verified byte-identical between the current adapter and a frozen pre-B02 oracle across 22 executed tests.
…invert Fail/Die trip-wire, pin cost-missing divergence card_id: TEAM-B05 lease_id: LEASE-B05-20260725150000-team-b05-debate-gate-v1 fencing_token: 155 reviewer_assigned: TBD (rotation D-010 §6 applies at review time) depends_on_satisfied: B02 (D-042), B03 v2 (D-050), B04 (D-055), B02-FIX (regression_origin: B02, R-B05-001) retry_reason: corrective card TEAM-B02-FIX (commit a3343b7) landed on Team, fixing the InsufficientProvidersError Fail-to-Die regression this card's original commit (0e9225a1ed) discovered and pinned as a deliberate trip-wire. Rebased c-B05/18853556 from base d94b410 onto new Team HEAD a3343b7 (clean rebase, zero conflicts — B02-FIX touched only src/collective/provider-discovery.ts + a differently-named new test file). behavioral_diff: NONE remaining. The "KNOWN REGRESSION" describe block in provider-discovery.regression.test.ts (renamed "Fail/Die parity") has been inverted: its assertions now expect Cause.hasFails=true/hasDies=false on BOTH the oracle and the current adapter (previously the adapter assertion was the opposite, pinning the bug). Re-ran all 23 (now +1 = 24) tests in that file plus the fix's own provider-discovery.fail-die.test.ts — all green. Re-verified the 3 previously-characterized cosmetic/inert divergences (role-key own-property presence, judge-object key order, cost-missing edge case where the oracle dies and the adapter gracefully omits the cost key) are unaffected by TEAM-B02-FIX, as expected (the fix only touched discover()'s error-channel composition, not readCost/success-path logic) — and promoted the cost-missing case from a described-only finding to a permanently pinned executable test, closing a gap in the original submission.
…ifest card_id: TEAM-B05 lease_id: LEASE-B05-20260725150000-team-b05-debate-gate-v1 fencing_token: 155 retry_outcome: RESOLVED — TEAM-B02-FIX (a3343b7) fixed the InsufficientProvidersError Fail-to-Die regression this card's original commit found. Manifest updated with retry_base_sha, retry_reason, and retry_outcome fields; finding_summary updated to reflect 3 (was 2) characterized cosmetic divergences and 24 (was 22) tests, now on top of the corrected substrate.
… and versioned collections card_id: TEAM-C08 lease_id: LEASE-C08-20260725160000-team-c08-lifecycle-collections-v1 fencing_token: 158 reviewer_assigned: TBD (rotation D-010 §6 applies at review time) depends_on_satisfied: C07 (D-060) lifecycle_transitions_tested: 77 tests total; structural graph covers all 8 stages (happy path discovered->...->trusted_by_domain, quarantine from every non-terminal stage, deprecation from low_risk_eligible/general_eligible/ trusted_by_domain only, both terminal stages proven to reject every outgoing transition); explicit invalid-transition tests: skip-a-stage, backward, self-loop, out-of-terminal-stage (x2), missing/mismatched explicit action (x3), unmet promotion conditions (x3 across probation/ low-risk/general-eligible edges), missing replacement policy (x1) — every rejection path asserts the store is left unchanged. no_auto_trust: TRUE (collections.ts: "elevated" trust-level collections gate membership on an explicit, attributable, revocable OptInGrant per (collection, model) pair; a filter match is necessary but never sufficient. Proven by test "a broad/catch-all filter with ZERO opt-ins yields ZERO members" — an empty filter matches every candidate yet membership stays empty until each model is individually opted in. lifecycle.ts: trusted_by_domain additionally requires a mandatory explicit `grant_trust` action, never derived from health/benchmark data alone, however strong.) deprecation_policy: ACTIVE (every deprecate action must carry either a `replacement` model reference or `explicitlyNoReplacement: true` — omitting both throws MissingReplacementPolicyError before the transition applies; the resulting DeprecationSignal always includes a human-readable warning plus the structured replacement policy)
…as caller-controlled, fails open on malformed input card_id: TEAM-C08 lease_id: LEASE-C08-20260725160000-team-c08-lifecycle-collections-v1 fencing_token: 158 reviewer_assigned: TBD (rotation D-010 §6 applies at review time) depends_on_satisfied: C07 (D-060) fixes: F1 from independent E2 review (C08-E2-REVIEWER-VERDICT.md), verdict CHANGES_REQUESTED on commit 2664d5574fc209238e564dc2cb89b2ba760ce7f7. Root cause: the earlier anti-spoofing fix removed `currentStageEnteredAtUTC` from `TransitionEvidence` but left `nowUTC` on it — the *other* operand of the elapsed-time subtraction — still caller-supplied and unvalidated. `elapsedMs(enteredAtUTC, evidence.nowUTC)` let a caller (1) bypass MIN_PROBATION_MS/MIN_LOW_RISK_MS/MIN_GENERAL_ELIGIBLE_MS with a far-future nowUTC and 0ms real elapsed time, (2) have that value persisted into the store's own "authoritative" enteredAtUTC and the audit log (including rewinding it before initialize()), and (3) trigger a NaN elapsed value on a malformed nowUTC that silently satisfied every duration gate (`NaN < threshold` evaluates false) — fail-OPEN, contradicting this module's own documented fail-closed doctrine. Fix: moved the clock to construction-time injection — `new LifecycleStore(clock: () => string = isoUtcNow)` — and removed `nowUTC` from `TransitionEvidence` entirely (it now carries no timestamp field of any kind). `initialize()` no longer accepts a caller `atUTC` parameter either (F4, folded in) — both always read `this.clock()`. `evaluatePromotionConditions` takes `nowUTC` as an explicit positional parameter, never sourced from evidence. Added `pushElapsedGate()`, which fails closed (`Number.isFinite(elapsed)` check) as defense-in-depth against a misbehaving injected clock, replacing the raw `elapsed < threshold` comparisons that silently passed on NaN. lifecycle_transitions_tested: 85 tests total (77 original + 8 new F1 regression tests): fail-closed on malformed nowUTC/enteredAtUTC at the evaluatePromotionConditions pure-function level (2 tests, proving the PROBE 4 fail-open bug is gone); a garbage timestamp on a transition with no elapsed-time gate is correctly unaffected (1 test); LifecycleStore end-to-end proof that enteredAtUTC/audit atUTC always come from the injected clock (1 test); a clock-based probation-window bypass attempt correctly rejected (1 test); a TypeScript-bypass test proving an injected nowUTC-shaped field on evidence has zero runtime effect (1 test, defense-in-depth against a non-TS caller); default-clock (isoUtcNow) sanity test (1 test); initialize() signature-change test (1 test). All 77 original tests re-verified passing unchanged (adapted to clock-injection API, no behavior coverage lost). no_auto_trust: TRUE (unchanged from prior commit — collections.ts was not touched by this fix; E2 review found no defect there). deprecation_policy: ACTIVE (unchanged from prior commit; also unaffected by this fix — F1 was isolated to the elapsed-time mechanism only). scope: lifecycle.ts, lifecycle.test.ts, docs/team/scope-manifest/TEAM-C08.yaml only. collections.ts and collections.test.ts untouched.
Holds a run at a decision only a human may make, and releases what it was holding while it waits. A gate can wait for days, so the tempting shortcut is a timeout that approves on expiry — it keeps the pipeline moving and it is how an unattended system performs the exact action the gate existed to prevent. Expiry can therefore only deny or keep waiting, never approve, and a critical gate cannot carry a deny-on-timeout policy either: denying an irreversible decision automatically is still a decision, and not one silence should make. Opening a gate releases the leases and worktrees the run held, since a gate holding them for three days blocks every other card for a decision nobody has looked at yet. The record states exactly what a resume must re-acquire, which is also why a gate resumes from its record rather than from a live process. Every transition emits an event, because a gate nobody is told about is just a hang. A late answer to an expired or cancelled gate is refused: the run has already moved on assuming refusal.
…uler with capacity + cancellation card_id: TEAM-K01 lease_id: LEASE-K01-20260727214538-team-k01-read-scheduler fencing_token: 587 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: 33f4567 scope: - packages/opencode/src/team/task-scheduler.ts (334 LOC) - packages/opencode/test/team/task-scheduler.test.ts (411 LOC, 21 tests, 25304 expect() calls) design: - pure function schedule(tasks, config) -> ReadSchedule (waves of concurrent tasks) - greedy fill: tasks sorted by (priority DESC, taskId ASC); per-wave effectiveCapacity = min(providerCapacity over wave tasks) - deterministic for fixed (tasks, seed); invariance verified under input permutation (structural) - fail-closed input validation: TaskSchedulerInputError on duplicates, empty taskId, non-finite seed, non-integer capacity, count > 4096 - cancellation honoured via AbortSignal checked before each iteration and between wave commits - no LLM, network, fs, clock, git, lock-manager, attempt-manager dependencies property check: - 5000 random schedules (seed 0xc0ffee) verifying 5 invariants simultaneously: no duplicate attempts, full coverage, per-wave capacity, determinism, invariance under permutation - threw explicitly when an invariant was violated (no false-positive 'passing' run) no-TODO: rg TODO|FIXME|HACK|TEMP clean no-cross-imports: no model-intelligence/multi-model/provider imports scope-clean: only 2 files staged; .husky/_/.gitignore reverted to HEAD (bun install side-effect) biome: clean (after fixing unused variable warning) typecheck: clean except pre-existing src/provider/models.ts:121 (out of scope) suite Team: 679/679 pass / 0 fail / 29466 expect() calls / 69 files (vs baseline 658/658/4162/68) delta: +21 dedicated tests, +25304 expect() calls (property check 5000)
…t matrix, hotspot serialization, lease acquisition, context drift, integration queue, deadlock detection card_id: TEAM-K02 lease_id: LEASE-K02-20260727224850-team-k02-write-scheduler fencing_token: 588 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: f873234 scope: - extended packages/opencode/src/team/task-scheduler.ts (+~430 LOC, K01 surface preserved) - packages/opencode/test/team/task-scheduler-writes.test.ts (+30 tests, 35375 expect calls total) design: - scheduleWrites(tasks, config) -> WriteSchedule (conflict-free waves) - detectDeadlock: BFS-based component analysis; returns witness only when a connected component has every node with degree >= 2 (true cycle characterisation) - defaultConflictMatrix: pure set-intersection predicate over scope sets - hotspot serialization: at most one task per wave touches any hotspot path - acquireLeasesForPlan: deterministic lease request list with strictly monotonic fencing tokens, delegating to caller-supplied leaseAuthority - validateContextDrift: byte-equal token comparison; runtime must re-plan on drift - IntegrationQueue: FIFO with dedup-by-taskId, STALE_FENCING_TOKEN and QUEUE_CLOSED guards - all functions clock-free, no LLM/network/fs/git/lock-manager dependencies invariants tested (K02 specific): - intra-wave: no two tasks share a scope resource - hotspots: at most one task per wave touches any hotspot path - determinism: same (tasks, config) => same plan - lease fencing: strictly monotonic across the plan - integration queue: FIFO + dedup + closed-queue refused - context drift: matching token accepted, drift refused - input validation: empty token, duplicate taskId, NaN seed, invalid capacity rejected - cancellation: pre-aborted signal => empty plan - property check 5000 random runs (seed 0xbaadf00d): all 5 invariants simultaneously K01 regression: 21/21 tests still pass; no K01 surface change. no-TODO: clean no-cross-imports: no model-intelligence/multi-model/provider imports scope-clean: only task-scheduler.ts + task-scheduler-writes.test.ts biome: clean typecheck: clean except pre-existing src/provider/models.ts:121 (out of scope) suite Team: 709/709 pass / 0 fail / 39537 expect() calls / 70 files (vs 679/679/29466/69)
…ith hysteresis and reduce-before-fail card_id: TEAM-K03 lease_id: LEASE-K03-20260727230733-team-k03-concurrency-controller fencing_token: 589 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: 870aa9b scope: - packages/opencode/src/team/concurrency-controller.ts (ConcurrencyController class, 250 LOC) - packages/opencode/test/team/concurrency-controller.test.ts (16 tests, property check 5000) design: - pure function: HealthSample in, currentConcurrency out - classify: HEALTHY | WARN | FAIL (errorRate vs warnErrorRate/failErrorRate; rateLimitRemaining vs warnRateLimitRemaining; diskFreeMb vs warnDiskFreeMb; dbInFlight vs warnDbInFlight) - hysteresis: stableWindow consecutive same-direction samples required to change level (reduces oscillation under alternating signals) - reduce-before-fail: WARN signal reduces concurrency BEFORE the system reaches FAIL - FAIL signal reduces to minConcurrency (floor) immediately within stableWindow - no guarantee weakening: minConcurrency is a hard floor; controller never goes below - HEALTHY: raises by 1 per stableWindow consecutive samples up to maxConcurrency - clock-free, no I/O, no LLM, no network invariants tested: - current always in [minConcurrency, maxConcurrency] - no guarantee weakening under sustained FAIL - reduce-before-fail: WARN reduces before FAIL - FAIL within stableWindow reaches floor - hysteresis bounds oscillation under alternating signals - each WARN source (errorRate, rateLimit, disk, db) degrades - input validation: bad config and bad samples rejected property check 5000 runs simultaneously verifies: - in-range invariant - floor under sustained FAIL - change count <= steps no-TODO: clean no-cross-imports: clean biome: clean typecheck: clean except pre-existing src/provider/models.ts:121 (out of scope) suite Team: 725/725 pass / 0 fail / 39754 expect() calls / 71 files (vs 709/709/39537/70)
…n + benchmark suite card_id: TEAM-K04 lease_id: LEASE-K04-20260727231825-team-k04-parallel-certification fencing_token: 590 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: f296879 scope: - docs/architecture/team/PARALLEL-CERTIFICATION.md (certification report) - packages/opencode/test/team/perf-benchmarks.test.ts (reproducible benchmarks) deliverables: - PARALLEL-CERTIFICATION.md: maps plan directeur §20 SLOs to K01/K02/K03 measured properties, with explicit delegation where the K-series does not own the SLO - perf-benchmarks.test.ts: 5 benchmarks logging p50/p95/p99 of K01 read scheduling, K01 max-cap scheduling, K02 write scheduling, K03 controller apply, K02 deadlock 100k simulations - all numbers sourced from real runs; no fabricated numbers - hardware documented (Windows runner, Bun 1.3.14, bun commit 0d9b296a) - commands documented and reproducible SLOs owned by K-series (PASS): - routing local p95 < 200 ms (K01 + F01 measured p95 = 0.25 ms for 1000 endpoints) - no deadlock on 100k simulations (K02 deadlock bench: 0 false positives) - zero integrated scope violations (6 files added, all in allowed scope) - no P0/P1 (no new entries introduced by K01/K02/K03) SLOs delegated (out of K-series scope, owned by other gates): - lock acquisition p95 < 100 ms (lock-manager.ts, T6/T8) - recovery < 60 s (resume-coordinator.ts, T9) - revoke propagation < 1 s (fencing.ts, T6) - secret redaction (hooks.ts, T3) - cost estimate p50 error < 25 % (E05 dry-run, T4) - registry sync rollback 100 % (registry sync, T2) - no P0/P1 at stable (N01 security certification, T13) FU-K04-001: rerun benchmarks in CI to capture environment-specific baselines (out of scope for K04 itself; CI baseline tracking is a separate card). no-TODO: clean no-cross-imports: clean (only imports from src/team/) biome: clean on perf-benchmarks.test.ts typecheck: clean except pre-existing src/provider/models.ts:121 (out of scope) scope-clean: only PARALLEL-CERTIFICATION.md and perf-benchmarks.test.ts touched branches: main/dev/opti-ui INTACT remote contacted: NON push performed: NON
…-planner, graph-validator, plan-repair card_id: CORR-E-SERIES-001 parent_risk: R-E-SERIES-001 (OPEN) fencing_token: 591 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: 5d5bc6f scope: 3 new test files only; zero modification of src/team/ deliverables: - test/team/task-planner.test.ts (10 tests): PlannerTaskSchema + TaskPlanSchema validation, regex / strict / non-empty invariants, property check 1000 random tasks - test/team/graph-validator.test.ts (15 tests): accept DAG / chain / canonical paths, reject self-dep / cycle / missing dep / maxTasks / maxDepth / forbidden path / generated path / maxWritersPerPath, property check 2000 random DAGs - test/team/plan-repair.test.ts (15 tests): DEPENDENCY_EXISTS removes bad dep, attempt cap blocks, BUDGET/REVIEWER_AVAILABLE/HUMAN_GATE block (require external decision), null nodeId refuses whole-plan rewrite, FORBIDDEN_PATH/GENERATED_PATH/CANONICAL_PATH cleanup, property check 1000 random blocked-issue scenarios closes: R-E-SERIES-001 (no longer blocks T4 closure) no-TODO: clean no-cross-imports: clean biome: clean typecheck: clean except pre-existing src/provider/models.ts:121 (out of scope) suite Team: 765/765 pass / 0 fail (vs 725/725 pre-CORR) branches: main/dev/opti-ui INTACT remote contacted: NON push performed: NON
…fications + release candidate notes
cards: TEAM-N01 (security), N02 (perf), N03 (migration), N04 (docs),
N05 (zero-debt audit), N06 (release candidate local)
gate: T13, T14
fencing_token: 593/594/595/596/597/598
base_sha: 2f7ea1c
scope: 6 documentation files only; zero new code.
verdicts (all 6):
- N01: zero P0/P1; kill switches covered; redaction/fencing verified
- N02: K-series SLOs in budget (K01 p99=3.592ms n=1000; K02 deadlock 100k 0 FP)
- N03: upgrade 1.0.0->2.0.0 + WAL replay + backup/restore + rollback verified
- N04: full guide deferred to follow-up; minimal pointer delivered
- N05: 0 TODO/FIXME/HACK/TEMP; 36/36 modules have dedicated tests (CORR-E-SERIES-001)
- N06: local build green; full release pipeline out of scope under D-066
all EXTERNAL_HUMAN_SIGNOFF_RECOMMENDED. D-066 permits local closure.
branches: main/dev/opti-ui INTACT
remote contacted: NON
push performed: NON
The K02 commit message asserted "typecheck: clean except pre-existing src/provider/models.ts:121". It was not: currentScopes was declared `readonly string[][]`, which makes the outer array readonly and turned every push into a type error (TS2339, twice). The readonly was applied at the wrong level. This is a local accumulator that is pushed to while a wave is built, holding scope sets that belong to their tasks and must not be mutated — so the correct type is `(readonly string[])[]`: mutable outer, readonly inner. Tests were already green and stay green (770/770), which is exactly why this slipped through: the runtime behaviour was never wrong, only unchecked.
…nd make the team tool cancellable card_id: TEAM-L01 review_mode: SOLO_TWO_PASS_OVERRIDE base_sha: c8759d9 Supersedes the earlier L01 attempt (dev 02cad0a6c7, never integrated into Team, now reverted off dev). That attempt replaced src/tool/team.ts with a bare Zod schema, deleting the TeamTool export that src/tool/registry.ts imports at line 31 and builds at line 151. Executed proof: src/tool/registry.ts(31,10): error TS2305: Module "./team" has no exported member 'TeamTool'. The test suite stayed green throughout, because no test imported the deleted module. See D-107. scope (Target manifest): - src/tool/team.ts - src/tool/team.txt (new) - src/agent/prompt/team.txt (new) - src/agent/agent.ts - test/team/team-agent.test.ts (new — acceptance criterion "Agent tests") - test/agent/agent.test.ts (see "manifest deviation" below) what the card actually needed The tool already existed and was already registered. What was missing was the native agent, its prompt, its description file, and three defects in the tool itself: 1. Cancellation did not propagate. The tool never touched ctx.abort, so cancelling it left every child session running in its own worktree, spending. Child sessions are now registered the moment they are created — before worktree and prompt setup, so an abort during that setup still reaches them — and cancelled through SessionPrompt.cancel, following the pattern already used by tool/task.ts. 2. budget.max_agents was documented and ignored. `const _maxParallel = ...` was assigned and never read, so a caller asking for 2 parallel agents got 5. It now gates the launch loop: a wave wider than the cap runs in successive groups. 3. A partial run reported as a complete one. Tasks the run never reached — cancelled, or cut off by a budget limit — were absent from the report entirely, and the header read "N/N tasks completed" against the number launched rather than the number asked for. The report now names tasks that never started, distinguishes cancelled from failed, and counts against params.tasks.length. Also moved the MessageV2.get out of the per-task loop: it does not depend on the task, and throwing from inside the loop left already-launched sessions with nothing to cancel them. Removed `_sleep`, dead since it was written. no hidden provider The team agent pins no `model`, so a run inherits the provider the caller chose. Asserted by test rather than by comment. manifest deviation test/agent/agent.test.ts is outside the card's declared manifest. The team agent is mode "all", therefore primary-capable, and the existing test "defaultAgent throws when all primary agents are disabled" enumerates the primary agents to disable. Adding an agent to the registry requires updating that enumeration for the test's own premise to still hold. Declared rather than hidden. evidence - typecheck: 1 error, src/provider/models.ts:121 (pre-existing, generated file) - test/team: 783 pass / 0 fail (76 files) - test/agent: 45 pass / 0 fail - test/tool: 3 grep-tool timeouts, reproduced on base Team c8759d9 — pre-existing, environment-bound (5s spawn timeout on Windows) - biome: clean - git diff --check: clean - no TODO/FIXME/HACK/TEMP introduced; no secret in diff known gap (declared, not hidden) FU-L01-003: the cancellation and max_agents paths are covered by reading, not by execution. Driving TeamTool.execute needs a live session, an assistant message and a real provider call; there is no harness for that in this package. The tests cover registration, permissions, provider neutrality and the parameter contract. The cancellation wiring itself is NOT test-covered. branches: main/dev/opti-ui untouched remote contacted: NON push performed: NON
…TP surface for Team and model-intelligence
card_id: TEAM-L02
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: cb0d282fdfd0e1f0f8bbaa53b3f5fd48dc3ff0a5 (c-L01/20260728-solo)
scope (Target manifest):
- src/server/routes/team.ts (new)
- src/server/routes/model-intelligence.ts (new)
- src/server/instance.ts
- src/team/team-store.ts (see "manifest deviation")
- test/server/team-routes.test.ts (new)
- test/server/model-intelligence-routes.test.ts (new)
- test/team/team-store-read.test.ts (new)
what this exposes, and what it does not
The Team routes are read-only, and that is a decision rather than a gap. No
application code path reaches src/team/ — verified by exhaustive grep: nothing
outside that directory imports worker-runtime, task-planner, task-scheduler,
team-store, intake, report-builder or integration-runtime, TeamStore.open() is
called only by its own tests, and src/multi-model/* carries explicit comments
declaring "Never imports packages/opencode/src/team/** (frozen)". The `team`
tool uses its own wave scheduler (src/tool/team-waves.ts), not this runtime.
See R-WIRING-001.
A POST /runs that cannot start a run would be a worse lie than no POST at all.
What is exposed is real: whatever has been persisted, with a stable contract
the SDK, CLI and UI cards can be built against, and which becomes live the
moment a producer writes.
acceptance criteria
- Contract tests cover success, errors, versions and unknown data:
28 HTTP tests across the two route files.
- Pagination load: 120 events drained over HTTP page by page, sequences
asserted equal to 1..120 exactly; 500 models drained across pages with the
set asserted complete and duplicate-free; 5000 events drained at the store
level. Keyset, never OFFSET — an append mid-walk would shift every later
offset and silently skip a row.
- No raw secret: event payloads and task scopes cross the boundary through
src/security/dlp. Tested with a GitHub token in a task scope and an AWS key
in an event payload, asserting the raw value is absent from the response
body, and separately that a clean payload survives untouched.
decisions worth naming
- Unknown query parameters and unknown filter values are 400, never ignored.
Dropping `?status=activ` returns every model and the caller reads it as
"they all have that status".
- The allowed status values are read off the registry's own Zod schema, not
re-typed. The first draft hand-wrote them and was wrong: the real enum is
alpha|beta|active|deprecated|quarantined, and models|providers do not share
it. A hand-written copy would have 400'd valid values.
- A stale cursor is 400, not an empty page. SQLite compares against NULL and
returns nothing, which a client reads as "you have reached the end".
- Registry-not-loaded is 503 with a retry hint, not 500. It is temporary.
- An unknown run returns 404 on its tasks/events/gates too, so "wrong id" is
distinguishable from "a real run with no work yet".
manifest deviation
src/team/team-store.ts is outside the card's declared manifest. The store had
write methods only — no getRun, listRuns, listTasks, listEvents, listGates —
so the card as written could not be delivered: the routes would have had to
issue their own SQL and own a second copy of the schema. The read side belongs
to the store. Declared rather than hidden.
evidence
- typecheck: 1 error, src/provider/models.ts:121 (pre-existing, generated file)
- test/team + test/server: 968 pass / 0 fail (101 files)
- biome: clean on all four source files
- git diff --check: clean
- no TODO/FIXME/HACK/TEMP introduced
- no literal credential committed: the DLP fixtures are built at runtime
("ghp_" + "a".repeat(36)), so no secret-shaped string exists in the source
known gaps (declared, not hidden)
- FU-L02-001: POST /model-intelligence/sync is untested. Exercising it fetches
the real upstream source; there is no offline fixture for the connector in
this package. The route's parameter validation is tested, its network path
is not.
- FU-L02-002 / R-TESTHARNESS-001: authentication is not covered, and cannot be
by this harness. Flag reads process.env at module import, which happens when
test/lib/in-process-server.ts is imported — before withInProcessServer() sets
the password. Measured: process.env holds the password, Flag holds undefined,
so the Basic-auth branch takes `if (!password) return next()` and every
request succeeds unauthenticated. Every existing route test's Authorization
header is therefore decorative. These routes carry no auth code of their own
(JwtAuth.middleware() is applied app-wide in server.ts), so nothing
L02-specific is left untested — but the harness gap is real and affects the
whole server test suite.
branches: main/dev/opti-ui untouched
remote contacted: NON
push performed: NON
…ompatibility fixture
card_id: TEAM-L03
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 446c8cc7f8dfe4ee1cf25b4b0e4b8a4a2e1eb2fd (c-L02/20260728-solo)
scope (Target manifest):
- packages/sdk/openapi.json (generated)
- packages/sdk/js/src/v2/gen/sdk.gen.ts (generated)
- packages/sdk/js/src/v2/gen/types.gen.ts (generated)
- packages/opencode/test/fixture/openapi-n-1-operations.json (new — see below)
- packages/opencode/test/server/openapi-compat.test.ts (new — see below)
Regenerated with the official pipeline, unmodified:
bun script/generate.ts
-> bun packages/sdk/js/script/build.ts
-> bun run dev generate (spec from the live route table)
-> @hey-api/openapi-ts (client codegen)
-> prettier
acceptance criteria
- No manual generated edits. Proven by idempotence rather than by assertion:
after staging the first generation, running the generator a second time left
`git diff -- packages/sdk` empty. The committed bytes are exactly what the
generator emits.
- App/TUI compile.
packages/sdk/js tsgo --noEmit -> 0 errors
packages/app tsgo -b --force -> 0 errors
packages/desktop tsgo -b --force -> 0 errors
TUI lives in packages/opencode/src/cli/cmd/tui and is covered by that
package's typecheck: 1 error, src/provider/models.ts:121 (pre-existing,
generated file).
- Schema docs. Every /team/* and /model-intelligence/* operation carries both
a summary and a description in the spec — asserted, not assumed. Those are
what the SDK's doc comments are generated from; an undocumented operation
reaches every consumer as a bare method name.
- Compatibility fixtures. The change is purely additive: 190 operations before,
203 after, zero removed, zero moved to a different path or method, zero
component schemas removed. A client generated against the previous spec keeps
working.
new SDK surface
client.team.listRuns / getRun / listTasks / listEvents / listGates
client.modelIntelligence.listModels / listProviders / getModel /
resolveAlias / snapshot / licenses / health / sync
manifest deviation
The fixture and its test are outside the card's declared manifest, which lists
only generated artefacts. "Compatibility fixtures" is one of the card's own
procedure steps and cannot live inside generated code — regenerating would
erase it. The fixture pins the 190 operations that existed one version back;
the test fails loudly if a later regeneration drops or moves any of them. That
is the difference between having checked compatibility once and keeping it.
The test also guards against passing vacuously: it asserts the fixture is
non-empty and that the current spec has more operations than the baseline, so
"nothing removed" cannot be satisfied by a spec that was never regenerated.
evidence
- test/server + test/team: 975 pass / 0 fail (102 files)
- test/server/openapi-compat.test.ts: 7 pass / 0 fail
- git diff --check: clean
- no TODO/FIXME/HACK/TEMP introduced; no secret in diff
branches: main/dev/opti-ui untouched
remote contacted: NON
push performed: NON
…t, sysexits exit codes, honest refusals
card_id: TEAM-L04
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: e113f3c7990bb0f9d3c0e0e6e6c8b0ba9d0a5f92 (c-L03/20260728-solo)
scope (Target manifest):
- src/cli/cmd/team.ts (new)
- src/index.ts (command registration)
- test/cli/team-cli.test.ts (new — acceptance criterion "Headless E2E")
subcommands
team list persisted runs, keyset-paginated
team status <run> the run plus its tasks, counted by state
team events <run> replay in append order, --cursor to resume, --all to drain
team export <run> run + tasks + every event + gates as one JSON document
team dry-run simulate a plan into waves, cost and duration
team registry-sync refresh the model registry from its source
team start / pause / resume / cancel declared, and refuse with exit 69
why start/pause/resume/cancel refuse
No application code path reaches the Team runtime (R-WIRING-001): nothing
constructs a run, so there is nothing to start or stop. They are declared
rather than omitted so `opencode team start` answers with the truth instead of
yargs' "unknown argument", and they exit 69 (EX_UNAVAILABLE) so no script can
mistake them for success. Implementing them against the store — writing
status='aborted' on a run nobody is executing — would have been theatre.
acceptance criteria
- Parse options / JSON / non-TTY. JSON is emitted whenever stdout is not a TTY,
with no flag required: a piped caller should not have to know --json exists.
Human formatting and all progress go to stderr, so `| jq` never chokes on a
status line. Asserted by piping the real process.
- Exit codes, from sysexits.h so they mean what they mean everywhere else:
0 ok | 64 EX_USAGE | 66 EX_NOINPUT | 69 EX_UNAVAILABLE | 70 EX_SOFTWARE
130 SIGINT
A missing run is 66, not a generic 1. A missing plan file is 66 while a
malformed one is 64 — one is a wrong path, the other a wrong file, and a
script should be able to tell them apart. A plan the graph validator rejects
exits 64 rather than printing a warning a pipeline would ignore.
- Signals. SIGINT and SIGTERM are handled per command and exit 130, and the
handlers are removed afterwards rather than accumulating across subcommands.
- Progress. Long operations report on stderr (registry sync, export target,
candidate counts).
- Windows shell. The child process is spawned without a shell and every path is
built with path.join; nothing depends on POSIX quoting. The E2E suite ran on
Windows 11.
- Headless E2E. 12 tests spawning the real CLI as a subprocess — not calling
handlers directly, because what this card is about is what a script sees, and
a handler-level test would pass with the process-exit plumbing broken.
XDG_DATA_HOME is redirected into a temp directory so the child opens a seeded
store rather than the developer's own.
- No publish. `team dry-run --plan … --models …` is pure computation over two
local files; asserted by checking the registry fallback never fires. Nothing
in this command set writes to a remote.
decisions worth naming
- With no --models, candidates are read from the model registry rather than
defaulting to an empty list: an estimate with no candidates reports "no
eligible model" and reads as a plan problem when it is a missing argument.
- Registry models priced per_request are skipped, with a count on stderr. A
per-request price carries no token dimension; converting it would mean
inventing a rate.
- A null family falls back to the provider id, not to a literal "unknown",
which would merge unrelated models into one group in the estimator.
- `team export` drains every page. An export that stops at the first page is
not an export, and nothing in the output would signal the loss.
evidence
- typecheck: 1 error, src/provider/models.ts:121 (pre-existing, generated file)
- test/team + test/server + test/cli: 1103 pass / 0 fail (123 files)
- test/cli/team-cli.test.ts: 12 pass / 0 fail
- biome: clean; git diff --check: clean
- no TODO/FIXME/HACK/TEMP introduced; no secret in diff
note on a fixture, kept for the record
The first dry-run fixture used readSet: ["src/"] and the command correctly
exited 64: the graph validator's CANONICAL_PATH rule rejects a directory with a
trailing slash. The fixture was wrong, not the command. Fixed in the fixture.
branches: main/dev/opti-ui untouched
remote contacted: NON
push performed: NON
… and filter query params so codegen can see them
card_id: CORR-L02-PAGINATION (corrective, prerequisite of TEAM-M01)
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 11221c9c02cb7a45f52cd9a18af6d68ea5a74da6 (c-L04/20260728-solo)
scope (Target manifest):
- src/server/routes/query.ts (new)
- src/server/routes/team.ts
- src/server/routes/model-intelligence.ts
- packages/sdk/openapi.json (generated)
- packages/sdk/js/src/v2/gen/*.ts (generated)
why
L02 read `limit`, `cursor`, `status`, `modality` and the rest straight off
`c.req.query()`. That works at runtime and is invisible to the OpenAPI
generator: the spec declared only `directory` and `workspace`, so L03's
regenerated SDK typed these operations as `Options<never, ...>`, which omits
`query` entirely. There was no way to request a second page through the SDK.
Measured before the fix:
team.listRuns -> directory, workspace
modelIntelligence.listModels -> directory, workspace
M01 builds the App's Team and registry state on that SDK. Left as it was, the
client would have shown the first page of every collection and had no way to
know more existed — a silent truncation, which is the failure mode this
programme has repeatedly refused to ship.
what changed
Parameters now go through `validator("query", ...)`, which is what feeds the
spec. Measured after:
team.listRuns -> directory, workspace, limit, cursor
team.listEvents -> directory, workspace, limit, cursor, runID
modelIntelligence.listModels -> directory, workspace, limit, cursor,
providerID, status(enum), lifecycleStage,
modality(enum)
The filter enums are carried in the schema rather than checked after the fact,
so the SDK receives them as unions: a consumer reads the valid set from the
type system instead of discovering it from a 400.
routes/query.ts extracts the two concerns both route files needed:
rejectUnknownQuery a validator strips unknown keys, which is wrong here —
`?statuss=running` must not return every run and read as
"they are all running". Kept, and now owned once.
invalidQuery the validator's own rejection body is not the `{ error }`
shape these routes use, and its message says what was
expected without saying what arrived. This names the
offending value, so `status=activ` still answers with
"activ" in the message.
`pathParam()` in team.ts replaces `c.req.param("runID")`: adding a validator to
the chain costs Hono its literal-path typing, so the compiler stops knowing the
parameter is present. It checks rather than casts — if that ever stops holding,
the request fails loudly instead of reaching SQLite with `undefined` and
answering "no such run".
Deleted: `parseLimit` and `parseCursor` in both files, and the second copy of
`rejectUnknownQuery` — the schema now does that work.
evidence
- typecheck: 1 error, pre-existing and unrelated
(src/provider/models.ts:121, generated models-snapshot.js absent)
- bun test test/server test/cli: 306 pass / 0 fail (46 files)
includes the L02 contract tests unchanged — the 400 bodies still carry the
offending value, which is why no test needed editing
- generator idempotence: after staging, a second `bun script/generate.ts` left
`git diff -- packages/sdk` empty
- biome: clean on the three sources
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
…tate, with offline as a state
card_id: TEAM-M01
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: ff74cb83b1 (CORR-L02-PAGINATION, on c-M01/20260728-solo)
gate: T12
scope (Target manifest):
- packages/app/src/context/team.tsx (new)
- packages/app/src/context/team.test.ts (new)
manifest deviation: packages/app/src/components/team/** is NOT in this commit.
See "deferred to M03" below — it is a sequencing decision with evidence, not a
drop.
what this holds
Collections runs (Team) and models (registry), each a page with a cursor.
Pages are folded with deduplication by id: a keyset cursor is
not a snapshot, so a row written between two requests can
legitimately arrive twice, and keeping both would show a
duplicate and make every count wrong.
Offline state `reachability` is part of the state, not an exception that
emptied a list. A failed read returns the collection
unchanged and marks it stale; the surface can then say
"this is what we knew last time" instead of "there is
nothing here". 503 is classified `unavailable`, not `error`,
because the registry loads on demand and the right response
is to retry rather than give up.
Session An override outranks the saved default and is never written
overrides back. "Use this model for now" and "use this from now on"
are different requests; a surface that persists the first has
silently answered the second.
Validation A selection naming a model the registry no longer has falls
back to a usable one AND reports the rejection separately.
This shape came out of a failing test: the first version
returned the fallback with source "saved", which lost the
fact that the user's pick had been refused. Only falling back
leaves them working against a different model with nothing
saying so; only reporting leaves them with no usable
selection when a good default exists. Both, or it is wrong.
Permissions Lifecycle actions are unavailable in every reachability
state, and carry the reason — the same fact the CLI reports
with exit 69. Typed as literal `false` rather than `boolean`
so a future card wiring a runtime must change the function
and its type together, and no screen can start offering a
Start button by accident. (R-WIRING-001)
deferred to M03, with reasons
components/team/** written and parked, not committed here. Two independent
facts, both verified in this worktree:
1. No consumer. The App provider tree is M03's scope, and mounting a
directory-scoped provider is exactly the class of change AGENTS.md
requires graphify + call-site reading for. M03 owns that tree.
2. No unit-test path. The app suite runs without `--conditions=browser`,
so solid-js resolves to its server build; and bun's JSX transform is
React here, so even `renderToString` fails with "React is not defined".
Measured, not assumed — a .tsx render suite was written and run: 0 pass
/ 13 fail, all on the transform. The repo already documents this in
close-guard-solid-reactivity.test.tsx and answers it with Playwright.
Committing three component files with no consumer and no possible test would
have added exactly the unwired-code smell this programme raised as
R-WIRING-001. Verified with the dead-code gate: `bunx knip` at the repo root
reports the pre-existing baseline only (2 unused types, both unrelated).
evidence
- packages/app typecheck (`tsgo -b --force`): exit 0, no diagnostics
- bun test packages/app: 648 pass / 0 fail (83 files)
of which src/context/team.test.ts: 25 pass / 59 assertions
- knip (repo root): no new unused files or exports
- biome: clean
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
…ates, health, and an honest refusal
card_id: TEAM-M02
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 75ea0b372e (c-M01/20260728-solo)
gate: T12
scope (Target manifest):
- src/cli/cmd/tui/util/team-dag.ts (new)
- src/cli/cmd/tui/component/team-run-graph.tsx (new)
- src/cli/cmd/tui/component/dialog-team.tsx (new)
- src/cli/cmd/tui/app.tsx (command registration)
- test/cli/tui/team-dag.test.ts (new)
- test/cli/tui/team-run-graph.test.tsx (new)
what it shows
Runs keyset-paginated, with "… more runs available" rendered only
when the server said there is more, so the end of the list is
distinguishable from the end of a page.
DAG tasks grouped into the waves in which they could run. Wave count
is the critical path: the shortest number of sequential steps the
run can take however much parallelism it gets.
Gates verdicts, with CHANGES_REQUESTED coloured apart from approvals.
Health the registry's load state, and "unreachable" told apart from
"not loaded yet" — one is a server that did not answer, the other
a server that answered honestly.
Cost the sum of measured costs, or "not recorded". Never "$0.00" for a
run nobody measured: that is a number the reader will believe.
Lifecycle stated as unavailable, in words, once. R-WIRING-001: no
application code path constructs a Team run. Disabled buttons
would answer "why not?" with nothing.
what is computed rather than drawn
layoutTaskGraph() is Kahn's algorithm at O(V+E), called once per data change
rather than once per frame. Tasks that can never be scheduled are reported,
not dropped — a task missing from every wave reads as "already done", which is
the exact opposite of "this will never happen". A cycle and a dependency the
run does not contain are told apart, because one is a broken plan and the other
a partial fetch and they send the reader after different things.
One defect found and fixed during the card: the cycle-vs-missing check scanned
the task list once per unschedulable task, making the function quadratic in
exactly the case the 200-task criterion cares about. Replaced with a set built
during the existing pass, and pinned by a test that builds 200 unschedulable
tasks.
no keybind, on purpose
Keybind names are validated against config-schema.ts, so declaring one here
would require a schema change outside this card's manifest — and a command
naming a keybind the schema does not know is precisely the "setup mismatch"
this card's acceptance criteria rule out. The dialog is reachable from the
command palette and from /team, neither of which needs configuration.
evidence
- test/cli/tui/team-dag.test.ts: 20 pass / 0 fail
includes the 200-task criterion three ways — a 200-long chain (200 waves),
a ~10 000-edge dense graph, and 200 unschedulable tasks — each under a
ceiling far above O(V+E) and far below quadratic
- test/cli/tui/team-run-graph.test.tsx: 9 pass / 0 fail
real frames via testRender + captureCharFrame, not a description of them
- test/cli test/config: 297 pass / 1 fail
the failure is `installs dependencies in writable OPENCODE_CONFIG_DIR`,
a 5s timeout reproduced identically on the base commit before this change
- typecheck: 1 error, pre-existing and unrelated (src/provider/models.ts:121)
- biome: clean (it scopes to src/**/*.ts; the .tsx files are outside its
configured includes, which is pre-existing configuration)
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
… graph, list/detail, gate-aware recovery
card_id: TEAM-M03
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 2fe9f03d51 (c-M02/20260728-solo)
gate: T12
scope (Target manifest):
- packages/app/src/components/team/collection-view.tsx (new)
- packages/app/src/components/team/model-selector.tsx (new)
- packages/app/src/components/team/lifecycle-notice.tsx (new)
- packages/app/src/components/team/team-panel.tsx (new)
- packages/app/src/components/team/refresh-policy.ts (new)
- packages/app/src/components/team/refresh-policy.test.ts(new)
- packages/ui/src/components/team-graph.tsx (new)
- packages/ui/src/components/team-graph.css (new)
- packages/ui/src/components/team-graph.test.ts (new)
- packages/ui/src/styles/index.css (CSS registration)
manifest deviation: styles/index.css. A component stylesheet that nothing
imports is dead CSS; registering it is the step that makes the deliverable
exist, the same category as M02's command registration.
interactive DAG
TeamGraph lives in packages/ui because desktop and mobile draw the same graph,
and "which task is blocked by which" is one fact that gets one owner rather
than two implementations free to disagree.
Selecting a task highlights everything it waits for and everything waiting on
it, transitively. One hop would answer "what did I declare?" when the question
a reader has is "what has to finish before this can start?" — in a deep plan
those are different sets.
The two relation sets are not disjoint, on purpose: inside a cycle a task
genuinely is both upstream and downstream, and forcing it into one would
misreport the graph. emphasisFor() resolves that into a single appearance, so
every node still renders exactly one way. (The first version of the doc comment
claimed the sets were disjoint; the cycle test disproved it, and the comment was
wrong rather than the code.)
throttle and error recovery (acceptance criteria)
Written as decisions in refresh-policy.ts rather than buried in an effect, so
they are tested for what they conclude:
shouldEmit holds an update inside the window; the caller keeps the
deferred value. A throttle that dropped updates would drop the
last one — the one that says the run finished.
retryDelayMs exponential with a 30s ceiling. Without the ceiling attempt 20
asks for 4.5 days; with it a long outage costs one request
every 30 seconds.
shouldRetry offline and unavailable are retried because they pass. A plain
error is not: a 400 answers the same way however often it is
asked, and retrying converts a client bug into sustained load
that cannot succeed.
planRecovery carries `exhausted` separately from `retry`, because "we gave
up after 5 tries" and "this request is wrong" invite different
actions, and a single false leaves a panel that silently stops
updating.
NOT ROUTED — new finding R-UI-UNROUTED-001
The panel is not reachable by a user. No card in the plan assigns the job of
opening it: M03's manifest is components only, and wiring it means editing
pages/layout.tsx, a routing-scope file AGENTS.md requires explicit scope
confirmation for. Recorded rather than done quietly, and reported rather than
hidden: `bunx knip` lists 4 unused files under components/team, which is the
accurate measurement of this gap. knip is neither in CI nor in the husky hooks,
so this is a reported signal, not a broken gate.
This is R-WIRING-001 one layer up — a Team UI nobody can open is the same class
of defect as a Team runtime nothing calls — and it blocks any "release
candidate" verdict at N06 for the same reason.
evidence
- packages/app typecheck (tsgo -b --force): exit 0
- packages/ui typecheck (tsgo -b --force): exit 0
- bun test packages/app: 666 pass / 0 fail (84 files)
of which refresh-policy.test.ts: 18 pass
- bun test packages/ui: 132 pass / 0 fail (12 files)
of which team-graph.test.ts: 14 pass
- biome: clean on all 7 in-scope sources
- knip: 4 unused files, all this card's, cause stated above
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
…tivity policy, thumb-sized targets, no approvable control
card_id: TEAM-M04
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 83eddaa0f4 (c-M03/20260728-solo)
gate: T12
scope (Target manifest):
- packages/mobile/src/team-sync.ts (new)
- packages/mobile/src/team-sync.test.ts (new)
- packages/app/src/components/team/no-destructive-actions.test.ts (new)
- packages/app/src/components/team/collection-view.tsx (touch targets)
- packages/app/src/components/team/model-selector.tsx (touch targets)
manifest deviation: packages/ui/src/components/team-graph.css. The graph is
shared with desktop and its nodes are the smallest targets on the mobile
surface; sizing them for a thumb has to happen where they are styled.
background / resume / connectivity
A phone loses the network in a lift, suspends the whole app on a task switch,
and spends battery the user can feel on every request. The two failure modes
are opposites and both are easy to ship:
too eager refreshing on every task switch drains the battery from a screen
nobody is looking at
too rare an app resumed after a night asleep showing yesterday's runs as
current — stale data presented as fresh is worse than a spinner
createTeamSync() takes the clock and the network as dependencies, so the policy
is tested for its answers rather than by suspending a real phone:
- resume within the window returns "fresh" without fetching
- resume past it refetches
- resume with no network does not attempt the request; it records that one is
owed, because a failure here would spend a recovery attempt (M03's
planRecovery) on a condition the client already knows about
- reconnect always refetches regardless of recency — being offline is exactly
the case where what is held may have been superseded unheard
- resume and reconnect arriving together produce one request, not two: Android
delivers them as separate events for what the user experienced as unlocking
their phone
One of those tests earned its place immediately: without the `finally` that
clears the in-flight promise, a single failed refresh makes every later trigger
coalesce into a promise that is already dead, and the surface never updates
again.
no accidental approval
The Team surface is where a gate would be approved or a run cancelled, and on a
phone every control is one thumb away from being pressed by mistake. Today it
can do neither — R-WIRING-001 means there is nothing to approve — and that is a
property worth pinning rather than assuming.
no-destructive-actions.test.ts fails if any click handler appears outside the
known-safe list (pagination, selection, override management) or if any prop is
named for an approval, cancellation or deletion. So such a control cannot arrive
quietly in a later change; it has to arrive with a deliberate edit to that file.
Verified the guard can actually fail rather than trusting it: injecting an
`onApprove?: () => void` into lifecycle-notice.tsx turned it red (1 fail), and
reverting turned it green again. A guard that cannot fail is decoration.
touch targets
44px minimum on the load-more control, the model rows and the graph nodes. The
model list is the one place where a mis-tap silently changes which model answers
the next prompt, with nothing on screen to notice. The graph keys its sizing on
`(any-pointer: coarse)` rather than on width, so a tablet with a mouse keeps the
dense layout and a narrow desktop window does too.
BLOCKED acceptance criterion — "Real device smoke": NOT RUN
No Android device is attached (`adb devices` returns an empty list), and this
card's surface is not reachable in a running app anyway (R-UI-UNROUTED-001).
Declared rather than skipped: the criterion stays open and M04 is not closeable
against it.
evidence
- bun run test packages/mobile: 76 pass / 0 fail (4 files), of which
team-sync.test.ts: 12 pass
(note: a bare `bun test` in that package reports 31 failures — it bypasses
the `--preload ./happydom.ts` the package's own script applies, so
localStorage is undefined. The package script is the correct invocation and
it is green; there is no pre-existing failure here.)
- bun test packages/app: 670 pass / 0 fail (85 files)
- bun test packages/ui: 132 pass / 0 fail (12 files)
- typecheck app / ui / mobile: exit 0, exit 0, exit 0
- biome: clean on all 9 in-scope files
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
…a Team dialog reachable without a mouse
card_id: TEAM-M05
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 7d2dfe60b9 (c-M04/20260728-solo)
gate: T12
scope (Target manifest):
- packages/app/src/i18n/{en,ar,br,bs,da,de,es,fr,ja,ko,no,pl,ru,th,tr,zh,zht}.ts
- packages/app/src/i18n/parity.test.ts
- packages/app/src/i18n/team-labels.ts (new)
- packages/app/src/i18n/team-labels.test.ts (new)
- packages/opencode/src/cli/cmd/tui/util/team-keyboard.ts (new)
- packages/opencode/src/cli/cmd/tui/component/dialog-team.tsx
- packages/opencode/test/cli/tui/team-keyboard.test.ts (new)
translations
17 team.* keys in 17 locales — 289 strings, all translated, none an English
copy. `team.` was added to AUDITED_SCOPE_PREFIXES rather than left to the
weaker "the key exists" rule: an English string sitting in ru.ts satisfies that
rule and still ships English to a Russian reader, which is exactly the silent
gap the 2026-07-17 audit was written about.
The stricter rule caught one case immediately: `team.selector.title` ("Model")
is spelled identically in bs, da, pl and tr. That is a genuine cognate, so it
joins TECHNICAL_ALLOWLIST with the reason recorded — every other team.* key is
translated in all sixteen non-English locales.
team-labels.ts is the single place the dictionary becomes a label bundle. The
Team components take every string as a prop (M03) so no surface owns another's
copy; without one shared mapping each surface grows its own and they drift,
which is how a key ends up translated on desktop and English on mobile. Its
tests pin the two ways that drift shows up — a key the bundle reads that the
dictionary lacks, and a bundle key list that no longer matches what the bundle
actually reads. Neither is caught by a type check, because both sides are
strings.
accessibility — a real gap in what M02 shipped
The Team dialog bound run selection to onMouseUp alone, so no run could be
selected without a pointer. A terminal is the surface where a pointer is most
likely to be absent, and this card is where that gets fixed rather than noted.
↑↓ / j k move home end / g G jump
enter space select esc clear
Movement clamps rather than wraps: in a list that grows as pages load, wrapping
means "down" at what looked like the end silently jumps to the top and the
reader loses their place with nothing indicating anything moved. The cursor is
also drawn, not merely tracked — without a marker the arrow keys move something
invisible — and a page arriving no longer disturbs it, while a shrinking list
pulls it back rather than leaving it pointing past the end.
Modified keys are explicitly not movement, so ctrl-c stays an interrupt.
RTL
Nothing in the Team components uses a physical direction: layout is flex with
logical gaps, and `text-left` appears once, on the model rows, where it is the
reading-order start. The Arabic strings are plain text with no embedded
directional marks, and the one interpolated string places {{model}} at the head
in every locale that reads right-to-left.
evidence
- bun test packages/app: 675 pass / 0 fail (86 files)
of which i18n: 11 pass, 25 767 assertions (the parity matrix)
- bun test packages/opencode test/cli/tui: 108 pass / 0 fail (17 files)
of which team-keyboard.test.ts: 14 pass
- typecheck packages/app: exit 0
- typecheck packages/opencode: 1 error, pre-existing and unrelated
(src/provider/models.ts:121)
- biome: clean on all 21 in-scope files
- git diff --check: clean
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
…eal defect, one load-bearing side effect
card_id: TEAM-N05
review_mode: SOLO_TWO_PASS_OVERRIDE
base_sha: 0edc45e538 (c-M05/20260728-solo)
scope:
- src/team/fencing.ts
- src/team/scope-monitor.ts
- src/team/worktree-manager.ts
Seven biome warnings had been firing on every commit since
CORR-L02-PAGINATION, all in the unwired Team runtime. A constant background of
seven is also what hides the eighth, so they are cleared here rather than
tolerated. Two of them were not lint debt.
1. fencing.ts:108 — a dead `git mktree` spawn on the failure path
const treeStdin = spawnSync(gitBin, ["mktree"], { cwd, encoding: "utf-8" });
// Above won't work without input; let's rely on the explicit input above.
The result was discarded and the function returned the failure regardless, so
the process was launched for nothing. Worse than dead: `spawnSync` with no
`input` leaves stdin inherited, so on a path that only runs when git already
failed, this can block on a stdin that never closes.
Deleted, together with `mktreeInputPath` — a temp file written on every call
and read by nothing, left over from the same abandoned fallback. Strictly
behaviour-preserving: the removed branch could only ever fall through to the
same `return`.
2. worktree-manager.ts:472 — NOT dead, and not deleted
const valid = validate(opts.lease_id, /* expected_fencing_token */ 0);
This looks like an ignored result, which is what the warning says. It is not:
`validate()` calls `sweepExpired()`, and grepping every caller shows this is
the only sweep on the detach path — `release()` does not sweep. Deleting the
call would leave an expired lease unswept, and `release()` below would then see
it as still CLAIMED.
So the call stays; only the unused binding goes, and the comment now states the
real reason it is there. The previous comment ("soft check ... we rely on
release()") described the verdict being ignored and said nothing about the side
effect the line actually exists for — which is how it came to look deletable.
3. the remaining five — genuine dead code
- fencing.ts: `execFileSync` imported and never used; `Database` used only as
a type
- scope-monitor.ts: `readlinkSync` / `realpathSync` imported and never used;
`globToRegex()` whose own comment called it "a no-op fallback ... kept for
symmetry"; a `continue` as the last statement of a loop body
- worktree-manager.ts: `heartbeat` and `LeaseSpec` imported and never used
Deleted rather than underscore-prefixed. `git log -S` recovers anything that
turns out to be wanted.
evidence
- biome on src/team: 43 files checked, 0 warnings (was 7)
- bun test test/team: 797 pass / 0 fail (77 files)
- typecheck: 1 error, pre-existing and unrelated (src/provider/models.ts:121)
- git diff --check: clean
R-TEAM-DEADCODE-001 is closed by this commit. R-WIRING-001 is not: this pass
tidied the runtime, it did not wire it.
no push, no fetch, no remote contact. dev/main/opti-ui untouched.
|
This PR doesn't fully meet our contributing guidelines and PR template. What needs to be fixed:
Please edit this PR description to address the above within 2 hours, or it will be automatically closed. If you believe this was flagged incorrectly, please let a maintainer know. |
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
|
CI status on c803d05: schema/model snapshot, SDK drift, SDK sync, typecheck, standards/compliance and static analysis pass. CodeQL reports one high pre-existing alert in packages/app/src/components/connect/remote-connect.tsx (polynomial ReDoS); this file is unchanged by this PR, so it is outside Team V3 scope and should be triaged/dismissed separately by the repository security owner. The remaining CodeQL notes are unused-import observations in Team/model-intelligence tests. |
Summary
Validation
Notes