This commit is contained in:
40
docs/redesign-warm-hive/pr4-recon/GROUNDING-2026-06-16.md
Normal file
40
docs/redesign-warm-hive/pr4-recon/GROUNDING-2026-06-16.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user