Add Browser Collector Enrollment Primitive
tasks14/16
Created Updated openspec/changes/add-browser-collector-enrollment-primitive/tasks.mdView on GitHub →
1. Design (this lane)
- Confirm the gap and grounding facts in tree (enroll route hardcodes
local_device; intent route classifiesbrowser→browser_bound→unsupported; Amazon manifest declaresbindings.browser.required). - Decide
browser_collectoris a connector-instance source kind, a peer oflocal_device, and explicitly not the spineSourceKindunion. - Specify binding-aware enrollment gating (manifest-derived, contradiction rejection, no defaulting).
- Specify owner-mediated browser-bound initiation reaching a typed next step
with
connection_active: false. - Specify the proof precondition before any route flips Amazon off
unsupported. - State Core / Collection Profile / reference boundaries and defer the
browserbinding-name reconciliation to its design note. - Write proposal, design, and spec deltas.
2. Validation (this lane)
-
pnpm exec openspec validate add-browser-collector-enrollment-primitive --strict(valid) -
pnpm exec openspec validate --all --strict(39 passed, 0 failed) -
git diff --check(clean, exit 0)
3. Implementation
- Add
browser_collectorto the connector-instance source-kind type and the enroll handler'ssourceBindingconstruction. (Widened theconnector_instances.source_kindCHECK in sqlitedb.js+ postgrespostgres-storage.js, with forward migrations for already-migrated DBs, and the store-levelVALID_SOURCE_KINDSguard. Enroll handler now writes the manifest-derived kind for bothsourceKindandsourceBinding.kind.) - Add a manifest-derived source-kind resolver shared by the enrollment-code
and enroll routes; reuse the intent classifier's binding precedence.
(
server/routes/connector-source-kind.ts;filesystemwins overbrowser, matchingclassifyConnectorIntentModality.) - Reject contradicting / unresolvable source kinds with typed errors; unit
coverage for filesystem→
local_device, browser→browser_collector, contradiction→reject, no-binding→reject. (Unit tests intest/connector-source-kind.test.js; route-level enroll tests intest/device-exporter-routes.test.js. The enrollment-code route rejects a contradicting/unresolvable kind before minting a code.) - Surface the already-shipped manual Amazon browser_collector enrollment
primitive in the operator console without flipping the proof-gated
owner-agent intent route. (
/dashboard/recordsnow lists Amazon under a manual browser-collector setup path,/dashboard/device-exportersaccepts?connector=amazonas a supported prefill, and the enrollment form generates monorepo browser-collector enroll/run commands while preserving the "not one-click" proof boundary.) - Land an Amazon end-to-end proof test that drives enrollment → browser
session → device-exporter ingest, plus a scrubbed Amazon fixture.
(STILL OWNER/LIVE-GATED. The deterministic, no-human HALF landed in lane
ri-browser-collector-proof-harness-v1:reference-implementation/test/browser-collector-ingest-proof.test.jsdrives the real enroll → heartbeat →ingest-batches→ records-persisted path for abrowser_collectorAmazon instance, ingesting records the real Amazon connector parsers produce (committed fixturereference-implementation/test/fixtures/amazon-browser-collector-proof-records.json, generated + drift-locked against the live parsers over the scrubbed DOM fixturefixtures/amazon/scrubbed/pilot-real-shape/dom/orders-list-2026.htmlbypackages/polyfill-connectors/connectors/amazon/proof-ingest-records.test.ts), and proves multi-account isolation. The monorepo runner now registers theamazonlocal-device profile (src/local-device-runtime.ts) so the live run is executable. What remains is the LIVE half — a real, owner-logged-in Amazon browser session producing those records, scrubbed into a committedfixtures/amazon/scrubbed/<runId>/fixture — which cannot be produced honestly in a no-human worktree. The owner-run procedure and the exact closing artifact are documented indocs/operator/browser-collector-proof-runbook.md. Laneri-browser-collector-proof-reduction-v2further reduced the live gate's surface without faking support: (a) the runbook's source-kind verification step is now backed by a deterministic test (reference-implementation/test/owner-connections-list.test.js→ "owner-agent bearer sees source_kind=browser_collector …") that enrolls Amazon through the real binding-aware path and assertsGET /v1/owner/connectionshonestly reportssource_kind: browser_collector— so the owner can verify the source-kind half of the live run through the owner-agent API, with no SQL; (b) the runbook was corrected to be copy-paste executable: it now exports both the distinctconnector_instance_id(verification filter) andsource_instance_id(run --connection-id) ids the enroll response returns, and the Step 2 source-kind check no longer queriessource-instances— which does NOT carrysource_kind— but the owner-agent listing that does.) - Flip the
browser_boundintent branch to returnenroll_browser_collectoronly after the proof lands; addadd-owner-agent-control-surfacetasks 5.3 / 8.5 Amazon second-account acceptance coverage. (INTENTIONALLY UNFLIPPED. Left as honestunsupporteduntil the LIVE proof above lands and is committed — the spec (local-device-exporter-collection: "Browser-bound connectors SHALL NOT advertise a real next step without committed proof") forbids advertising the next step without committed proof, and requires the flip and the proof to be reviewable as one unit. The flip steps are pre-written in the runbook §7. Laneri-browser-collector-live-proof-tail-v3removed one no-human prerequisite the flip would otherwise have to carry: the published contract now RESERVESenroll_browser_collectorin thePOST /v1/owner/connections/intentsnext_step.kindenum (packages/reference-contract/src/reference/index.tsOwnerConnectionIntentNextStepSchema, regenerated intoreference-implementation/openapi/reference-full.openapi.json), alongside the other reserved-but-unemitted kinds (open_url,complete_browser_assistance,upload_file) — exactly as design Decision 3 states. Reserving the value does NOT advertise the flow: no route emits it, and the runtimebrowser_boundbranch still returnsunsupported(pinned bytest/owner-connection-intent.test.js→ "owner-agent initiating a browser-bound connector (Amazon) gets a typed unsupported"). A new contract test ("owner-agent intent contract reserves enroll_browser_collector without emitting it") pins the reservation against the generated OpenAPI so a regen/edit can't silently drop it (negative control verified). Net effect: the post-proof flip is now a single reviewable unit — flip the branch + its tests — with no hidden contract-widening step.)
Acceptance checks
Reproducible from the worktree root:
pnpm exec openspec validate add-browser-collector-enrollment-primitive --strictexits 0.pnpm exec openspec validate --all --strictexits 0.git diff --checkreports no whitespace errors.- The design names:
browser_collector(distinct fromlocal_deviceand from the spineSourceKind), binding-aware enrollment, the owner-mediated next-step-without-active-connection rule, and the proof-before-flip gate.