Overview
Kelly Insure Data is a Busabase Cloud App-in-Skill. Its canonical product surface is the AirApp in Busabase, not a separate local-data product. The same Hono source supports an explicitly requested local preview with OAuth connection bootstrap. Use this skill as Kelly's insurance data-entry and data-governance cockpit: it reads one Busabase Drive node for the file drive, one Base for insurance QA pairs, two Bases for featured information and insurer notices, and one Base for user feedback — surfacing metadata completeness, missing fields, and review status before data becomes trusted insurance knowledge.
Default behavior is AirApp-first. Unless the user explicitly asks only for explanation, give the user the clickable AirApp URL. Start localhost only when local preview/debugging is explicitly requested; it uses the same Busabase resources and never offers another data provider. Use chat-only mode only when the user says "纯聊天", "chat only", "不要打开 UI", or similar.
Mandatory Dependencies
- Read and follow
$kelly-app-skill-creatorfor product behavior, visual quality, responsive layout, and the complete canonicalcontent/kelly-insure-data-app/artifact. - Read and follow
$busabasefor connection, target Space, node discovery, ChangeRequests, review, and merge behavior. - Read and follow
$busabase-app-creatorfor resource modeling, AirApp runtime limits, security, validation, and deployment.
If a dependency is unavailable, preserve this skill's local artifact and product contracts, stop before the unavailable Busabase operation, and report the exact missing dependency. Do not invent a second data backend.
Architectural note: an operator-provisioned workspace, not an app-owned one
Every Base and the Drive node here belong to an existing production insurance dataset — they are not a Folder+Bases tree this AirApp creates or owns the way most other Kelly App-in-Skills do. There is nothing for a read-only reader to safely auto-create in someone else's canonical dataset, so:
content/kelly-insure-data-app/app/js/config.jsdeclares the Drive node and the four Bases (by slug) for lookup only; page size is the reader's own concern, not a per-Base declaration, so each Base is read to exhaustion. It does not go throughresource-provisioning.js's create-if-missing/ownership-metadata flow used by every other converted skill in this batch — that flow is the wrong model for "connect to an existing external workspace."content/kelly-insure-data-app/app/js/providers/busabase-provider.jsresolves the Drive node and each Base by slug (viacontent/kelly-insure-data-app/app/js/insure-client.js, a raw-fetch client — see below) and degrades a missing resource to asnapshot.warningsentry, exactly like the retiredlib/data-provider/busabase-provider.tsdid. It never shows an "Initialize workspace" setup screen and never creates a Folder, Drive, Base, or record.- An operator provisions and repairs the Drive node and Bases out-of-band
with the trusted scripts in the skill-root
scripts/directory (export_busabase_snapshot.mjs,restore_busabase_snapshot.mjs,backfill_pdf_metadata.mjs), documented below.
Architectural note: Drive/Asset reads bypass busabase-sdk
busabase-sdk (the vendored package every other converted skill uses for
Bases/records reads) only wraps /api/v1/nodes, /api/v1/bases, and
/api/v1/records. It has no equivalent for the Drive/Asset REST surface
(/api/v1/drives/*, /api/v1/assets/*) this skill's file drive needs.
content/kelly-insure-data-app/app/js/insure-client.js is therefore a small browser module that talks
straight to the same-origin /api/v1/* proxy in content/kelly-insure-data-app/server.js with plain
fetch — a port of the read paths (resolveDrive, resolveBase,
listDriveFiles, listRecords) from the retired
lib/data-provider/busabase-client.ts, with two endpoint paths corrected
against a live busabase@0.11.0 server during porting: records listing is
GET /records (not /records/paged, verified against busabase-sdk's own
recordContract.list route), and a Drive's file listing/metadata come from
GET /nodes/{nodeId} (not /drives/{nodeId} or /drives/{nodeId}/files,
both 404). See the header comment in insure-client.js for details.
scripts/lib/busabase-client.mjs is the same corrected client ported for
the trusted skill-root scripts, using the operator's own
BUSABASE_BASE_URL / BUSABASE_API_KEY / BUSABASE_SPACE_ID credentials
(never the AirApp's ambient OAuth session), since those scripts also need
the write/upload endpoints (assets/upload-urls, assets/confirmations,
node/drive/record ChangeRequests) that
busabase-sdk does not cover either.
App UI Screenshots
Boundary
- The AirApp reads the Busabase Drive node and the four Bases only; it is
entirely read-only and must NEVER create, update, or delete a Busabase
node, Base, or record (
readOnly: true, nowriteProcedures). - Never expose API keys, tokens, or cookies in UI state, logs, screenshots, or chat.
- Treat insurance data quality as high-stakes: the trusted scripts preserve source attribution, dates, jurisdiction, carrier/product names, and original wording where possible.
- If the user asks for data entry or cleanup, propose it as a reviewable
change (a manifest edit plus
restore_busabase_snapshot.mjs --apply, or a direct Busabase ChangeRequest the user reviews) — never silently mutate the canonical workspace.
Busabase Resources
Declared in content/kelly-insure-data-app/app/js/config.js and the generated template sidecars under content/, resolved by
slug (IDs are never required):
drive(kelly-insure-data-files): the file drive — insurance PDFs/docs with governance metadata (policy_type,carrier,region,effective_date,status, ...).featured(kelly-insure-data-featured) andnotices(kelly-insure-data-notices, legacy aliasnews): combined in the#/newsroute, each item taggedfeaturedornotice.qa(kelly-insure-data-qa): canonical insurance question/answer pairs.feedback(kelly-insure-data-feedback): user feedback records.prompts(insurance-prompts, preset prompts): the preset prompt library read by the insure miniapp, not by this AirApp. It is not declared incontent/kelly-insure-data-app/app/js/config.jsand never appears in the snapshot, but it lives in the same workspace folder as the four Bases above, so an operator provisioning or repairing this workspace must keep it — with the field contract inreferences/insure-data-schema.md.
See references/insure-data-schema.md for the exact normalized snapshot
shape and references/restore-manifest-schema.md for the backup/restore
manifest shape.
Views
#/overview: counts, data quality score, metadata field coverage, and records needing governance.#/files("File Drive"): Drive-node files with metadata fields and missing-field badges.#/qa("Q&A"): QA Base records with question/answer, source, and completeness.#/news("Featured Information / Insurer Notices"): Featured Information and Insurer Notices combined, each taggedfeaturedornotice.#/feedback("User Feedback"): Feedback Base records with content, source, rating, and status.#/settings: sanitized Busabase target slugs and Base field schemas. Never exposes tokens.
Backup / Restore / PDF Text Backfill
These are trusted skill-root Node scripts (own scripts/lib/busabase-client.mjs,
raw fetch, the operator's own BUSABASE_BASE_URL/BUSABASE_API_KEY/BUSABASE_SPACE_ID).
They read local PDF bytes from disk, which a browser AirApp cannot do.
cd skills/kelly-insure-data
npm run busabase:export -- --output content/kelly-insure-data-app/.data/busabase_restore_manifest.json
npm run busabase:restore -- --manifest content/kelly-insure-data-app/.data/busabase_restore_manifest.json --files-root /path/to/local/pdf-backup --dry-run
npm run busabase:backfill-pdf-text -- --drive-node-id <node-id> --files-root /path/to/local/pdf-backup --limit 5
busabase:exportwrites a portable restore manifest (folder/Drive/Base shape, Drive file paths, sanitized asset metadata, Base records). It never embeds PDF bytes.busabase:restorepreviews restoration from that manifest plus a local PDF backup directory; add--applyonly when the user explicitly asks to recreate a missing folder, Drive files, Bases, or records.busabase:backfill-pdf-textparses local PDFs and previews the Asset text slot write and generated governance metadata. Add--applyto write the text slot (PUT /api/v1/assets/{assetId}/text) and sanitized metadata (neverparsed_text) back to Busabase. The oldbusabase:backfill-pdf-metadatacommand remains available as an alias.
Demo Mode
?demo=1(or?demo=overview) opens a deterministic offline dataset: 4 files, 4 QA pairs, 3 news items (2 featured, 1 notice), and 2 feedback items with varying governance completeness.?demo=files,?demo=qa,?demo=news,?demo=feedback,?demo=settingsselect named scenes.lang=enorlang=zhforces UI chrome language for screenshots.- Demo mode never reads Busabase, tokens, or local production data.
Local App
Default behavior is AirApp-first — give the user the clickable AirApp URL.
Start pnpm --dir content/kelly-insure-data-app dev only when local preview/debugging is explicitly
requested. UI language supports Chinese (primary) and English chrome with an
Auto default.
Deployed AirApp node: hk-insurance-workbench
The workspace already contains a deployed AirApp node, hk-insurance-workbench
(Hong Kong Insurance Materials Workbench), sitting at the space root beside the Hong Kong Insurance Company Materials Library folder. It
is not a deployment of this skill's content/kelly-insure-data-app/ artifact — it is an older
hand-written five-file viewer (package.json, server.js, index.html,
app.js, styles.css) that predates the AirApp migration. Treat it as a second,
much smaller surface on the same dataset: a read-only tile row of record counts
per Base plus the five most recently updated records.
Runtime contract, and the reason this node was broken for months:
- Busabase starts an AirApp with
npm installthen literallynpm run dev. Apackage.jsonwith only astartscript never boots — the runtime reportsMissing script: "dev". Busabase now rejects such a write (AIRAPP_NOT_RUNNABLE), but nodes created before that check can still be in this state. - A bundler dev server (
vite,next,webpack, …) cannot boot in this runtime at all, so renaming a Vite scaffold's script todevis not a fix. Serve from a plain Node server. - A running AirApp is proxied same-origin under
/api/airapp-preview/{nodeId}/, so browser code reaches Busabase with an absolute/api/v1/...path, using the session of the user who clicked Run. There is no API-key handling and no bridge prefix in front of/api/v1. - Reads use
GET /api/v1/records?baseId=…&limit=…(cursor paging vianextCursor) orGET /api/v1/records/page(numbered pages). Record values live underheadCommit.payload, keyed by field slug. - The cloud engine is Nodepod, an in-browser Node runtime, and two habits that
are fine under real Node kill the dev server there. This node hit both:
fileURLToPath(new URL(".", import.meta.url))throws, becauseimport.meta.urlis not guaranteed to be afile:URL — read static files relative to cwd (./index.html) instead; andlisten(port, function () { this.address().port })throwsthis.address is not a function, because the listen callback'sthisis not bound to the server — log a capturedportvariable instead. Both throw afternpm run devstarts, so the symptom is[dev server exited with code 1]and a run stuck before "ready". Busabase's ownPURE_HTML_SERVER_JSdemo still uses the second shape, so it is not a safe template to copy verbatim.
Fixed in this node (version 0.3.2): the missing dev script; a request to a
non-existent /__busabase_api__/api/v1/records/paged prefix and route; reading
headCommit.fields instead of headCommit.payload; the two Nodepod crashes
above; and a hardcoded three-Base id list that had gone stale. Bases are now resolved by slug from
GET /api/v1/bases, and a slug that no longer resolves degrades to a — tile
plus a warning instead of failing the page.
Open question for the operator, deliberately left open: whether to keep this
node as a thin viewer or replace its contents with this skill's content/kelly-insure-data-app/ artifact.
Keeping both means two divergent readers of one dataset; replacing means
uploading the full content/kelly-insure-data-app/ tree, including the vendored SDK, into the node.
File Contract
Read references/insure-data-schema.md before editing the app or
content/kelly-insure-data-app/app/js/config.js, and references/restore-manifest-schema.md before
changing the trusted export/restore scripts.
Safety Defaults
- Never create, update, or delete a Busabase node, Base, or record from the
AirApp. Only the trusted skill-root scripts write, and only with the
operator's own credentials and an explicit
--apply. - Keep real tokens in environment variables only; never commit
config.local.json-style files, local PDF backups, or anything undercontent/kelly-insure-data-app/.data/. - A missing Drive node or Base degrades to a visible warning, never a blocked or broken view.