Files
waggle-os/docs/redesign-warm-hive/pr4-recon/GROUNDING-2026-06-16.md
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

8.9 KiB

PR4 Grounding — recon verified against live code (2026-06-16)

5-reader workflow wf_19393d8b-c2d (~669k tokens). Verdicts: Slice1 minor-drift · Slice2 confirmed · Slice3 confirmed · Slice4 MAJOR-drift · Slice5 minor-drift. Net: the build plan is sound; the recon DOCS had 2 material errors that change Phases C/D execution (not the architecture).

Material drifts (these change HOW a phase is built)

D-A (Slice 4, MAJOR) — Phase D binds to CapabilityRequestCard, NOT the approval gate

Recon doc 04-inline-in-chat.md pointed Phase D at ApprovalGate/InlineApprovalCard (the SSE approval_required singleton). Wrong. The live inline-install card is chat-blocks/CapabilityRequestCard.tsx, parsed from agent TEXT by capability-request-parser.segmentText (marker <!--waggle:capability_request {json}--> or legacy phrasing) and rendered by TextBlock.tsx:25-27. The build plan §2/§5 already names CapabilityRequestCard correctly — so the PLAN is right, only recon-04's framing was misleading. Phase D = (1) widen CapabilityRequest.kind 'skill'|'marketplace'+'connector'|'mcp' (parser already passes kind through verbatim — parser.ts:22 — no parser change to ACCEPT, only to RENDER/DISPATCH); (2) add connector/mcp dispatch branches in CapabilityRequestCard.handleInstall routing through the shared store; (3) connector approve is FE-direct adapter.connectConnector(id,{token}) — token NEVER transits the boolean approval wire (PendingApproval.resolve(approved:boolean) — index.ts:168). Do NOT build a 3rd ConnectorOfferCard. Marker regex (\{[^}]+\}) forbids nested } — fine, a connector offer is flat {name,source,kind,reason}.

D-B (Slice 5, minor but load-bearing) — searchCapabilities has NO connector/mcp lane

searchCapabilities() only ever EMITS type ∈ {'native','skill','marketplace'} — never 'connector'|'mcp'|'plugin' (truth table below). ALL marketplace results collapse to type:'marketplace' because the searchMarketplace closure (index.ts:646) drops waggle_install_type. Connectors aren't even an input (they live in connectorRegistry, not marketplace.db). So §09's literal "connector + skill + tool" three-up box CANNOT be produced by pickOnePerKind(candidate.type).PHASE-C DECISION (founder-gate when I reach C): either (a) honest scope — group on what the engine really emits: native-tool + skill + marketplace-pkg, each with real matchReason "why" + installAction (delivers a true three-up box, no connector lane); or (b) add a connector lane — keyword-match connectorRegistry.getDefinitions() in the route and inject connector candidates + carry waggle_install_type through MarketplaceCandidate so mcp/skill packages bucket correctly (scope+). Recommend (a) for v1; flag (b) as the "literal §09" upgrade.

Verified contracts (verbatim — bind to these)

Phase A — shared install store (UNAFFECTED by drifts; fully grounded, build now)

  • No FE store exists (grep InstallProvider/useInstallStore/InstallStore → none). Providers pattern = React context (ServiceProvider/ShellContext/ThemeProvider), no Redux/Zustand. useService() exposes {connecting}; gate authed hydrate on connect-settled (MarketplaceApp.tsx:101).
  • Value type + normalizers REUSE apps/web/src/lib/extension-catalog.ts: Extension{id,name,description,type,source,installed,lifecycle:'available'|'installed',installable,kind:'package'|'pack'|'federated',packageId?,scanStatus?,trust?,category?,openIn?}. ID scheme keys the store: pkg:<n>|pack:<slug|id|name>|connector:<id>|agent:<id>|model:<id>|template:<id>|mcp:<id>. installable is hard-coded FALSE today for connector/mcp/pack → Phase B flips connector/mcp to true once the store can install them.
  • Install verbs (adapter, REUSE): skill installSkill(id, source:'starter'|'pack'|'marketplace', packageId?) | starter installPack(skillId):void | marketplace pkg installMarketplacePackage(packageId:number):Promise<Response> (RAW — inspect .status/.json; 403 TIER fires waggle:tier-insufficient, 403 {blocked}, 422 fail) | connector connectConnector(id,{token?|apiKey?...}):void (UNGATED) | mcp installMcp(mcpId,opts?):{installed,server?,status?,requiresApproval?} (PRO).
  • Hydrate reads: GET /api/skills {skills:[{name,...}],count} · GET /api/connectors {connectors:ConnectorDefinition[]} (status derived live: connected/disconnected/expired/error) · GET /api/mcps {mcps:McpListItem[],total,installed} (per-row installed/status/scope) · GET /api/marketplace/installed {installations,total} + /search per-row installed flag (the ONLY queryable installed-set; skills/connectors/mcp derive at read).
  • Reconcile hazard: install can 403(TIER)/403(SecurityGate)/422 AFTER an optimistic flip → store rolls back + routes to upgrade(waggle:tier-insufficient)/approval; count bar must never show a gate-rejected item.
  • Count bar (D1 ratified): FE-derived GLOBAL count (install_audit has NO workspace_id, is append-only + OSS-excluded; getInstalledCount() is global). Label honestly "installed", not "in this workspace".

Phase B — Variation A grid

  • ExtensionCard props {ext, installing?, onInstall?(ext), onUninstall?(ext), onOpenIn?(appId)}; testids root extension-card, install extension-install-${ext.id}. Today: generic Install/Remove/Open-in, no type-aware verbs, no micro-states. Phase B adds Add/Connect/Enable + idle→in-progress→done from the store.
  • MarketplaceApp exports (named) installRiskFor/buildRemoveRequest/buildInstallRequest + default. Facets today = ['all',...EXTENSION_TYPES] = 7. D2 collapse → 4 (All/Skills/Connectors/MCP). Count today = visible.length → replace with store count bar.
  • Tests to rewrite = 10 it() blocks (recon said 11 — DRIFT) in apps/web/src/test/phase4b-marketplace-extend.test.tsx; adapter mocked via vi.hoisted. Pinned testids: extension-facets, federated-note, extension-card, extension-install-pkg:7/:9, approval-modal(-approve); call shapes getMarketplace({type:'skill',limit:30})/({type:'mcp',limit:30}).
  • Path correction: ExtensionCard lives at components/os/apps/extend/ExtensionCard.tsx (nested under apps/), NOT os/extend/.

Phase C — POST /api/marketplace/agent-search (route under packages/server/src/local/routes/, NOT src/routes/)

  • Engine searchCapabilities(input:SearchCapabilitiesInput):AcquisitionProposal exported from @waggle/agent (index.ts:326). Input {need, installedSkills:[{name,content}], starterSkillsDir, nativeToolNames?, marketplaceCandidates?} — engine does NO marketplace IO (candidates passed in).
  • CapabilityCandidate{name,type,availability,description,source,matchScore,matchReason,installAction,trust?}. Emitted-type truth table: native→type:'native',availability:'active',installAction:null; active skill→'skill','active',null; starter→'skill','installable','install_capability'; marketplace→'marketplace','installable','install_capability'. recommendation = SINGLE candidate; candidates capped at 8; summary is markdown for the model — return STRUCTURED candidates/recommendation, NOT summary (skill-tools.ts:476 discards it).
  • Deps to lift (index.ts:620-657): nativeToolNames = union of mind/system/plan/git/document tool names (closure-local; OMITS search/browser/cli/cron/connector — so web_search can't score even today); getInstalledSkills = server.agentState.skills ?? loadSkills; searchMarketplace = marketplaceDb.search({query,limit:10}).packages → MarketplaceCandidate (drops waggle_install_type + score). Ride-along audit fastify.auditStore?.record({...action:'proposed',initiator:'agent'}).

Phase D — inline (see D-A above for the corrected target)

  • Boolean approval channel CONFIRMED: PendingApproval.resolve:(approved:boolean) (index.ts:168); POST /api/approval/:requestId body {approved,always?,reason?,sourceWorkspaceId?} — NO token. adapter.respondApproval(requestId,approved,opts).
  • Connect route POST /api/connectors/:id/connect body {token?|apiKey?,refreshToken?,expiresAt?,scopes?,email?} → vault setConnectorCredential; 404/400/503; authType = registry.get(id)?.authType ?? 'bearer'. Card must branch on authType — token-paste fits bearer/api_key only; OAuth (oauth2) needs redirect → Hub fallback (D3). Google family connector-id ≠ provider key (OAUTH_PROVIDER_FOR_CONNECTOR).
  • Gate a new agent offer/connect tool: add exact name to ALWAYS_CONFIRM (confirmation.ts:16) OR name it connector_<id>_<writeverb>_* (CONNECTOR_WRITE_PATTERNS).

install_audit (do not touch as state)

Append-only INSERT-only, NO workspace_id, action vocab = proposed|approved|installed|rejected|failed|blocked|uninstalled (NO 'synced'/'connected' — adding one is a CHECK migration). OSS-EXCLUDED (interleaved in hive-mind-core schema.ts/db.ts; curated out by hand) → install_audit-only changes have nowhere to land on the mirror. NEVER the source of installed state.