Conversation
…c granularity Two rounds of improvements packaged as tier0-sdk-v0.4.0 for platform testing. Corrections and unification: - uns/create.md: topicType is derived from the type folder, not required (legacy explicit param accepted with a warning); repair duplicated example sections; align type-casing guidance with browse.md - Translate all remaining Chinese references to English (uns/*, flow/*, react, vue, info, whoami, reload); unify frontmatter descriptions New guidance: - mq/quickstart.md: reconnect/reliability semantics verified against src/mq/client.ts (auto-reconnect, auto-resubscribe, QoS 1 subscribe, clean-session offline loss) + history backfill example - scaffolds/monoapptemplate.md: long-lived subscriber singleton pattern (globalThis guard, lazy start, graceful shutdown) and realtime-data-to-UI guidance (server subscribe -> DB -> polling) - openapi/react.md: polling reads for dashboards (useQuery + refetchInterval) - SKILL.md: troubleshooting index (error message -> reference file) Integration manifest and granularity (v0.4.0): - New core/integration-manifest.md: uns-manifest.yaml declaring every recurring publish/consume; declare-before-implement; consumer discover->pin->read workflow; observation-not-authority staleness rule; scope boundary exempting one-off scripts/diagnostics - core/data-integration.md: Topic Granularity section — one topic = one schema = one subscription decision; merge/split tests; anti-patterns (per-record, per-scalar, mega-topic, DB mirror); advisory count signal - SKILL.md: "Every Integration Is Declared" doctrine, guardrail, final checklist item 5 Evals: - skill-evals/tier0-sdk/eval-battery.md (outside npm files): 11 pressure scenarios incl. negative controls against manifest over-triggering Design doc archived locally (Develop/docs/superpowers/specs/ 2026-07-10-uns-integration-manifest-design.md). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
# Conflicts: # skills/tier0-sdk/references/core/data-integration.md # skills/tier0-sdk/references/mq/quickstart.md # skills/tier0-sdk/references/openapi/uns/create.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c3179afaab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| client.on('connect', async () => { | ||
| // Runs on first connect AND every reconnect: fill the gap since the last processed event. | ||
| const res = await unsApi.openapiv1unshistory({ |
There was a problem hiding this comment.
Catch reconnect backfill failures
When the history request fails during a reconnect (for example a transient HTTP error or expired key), this async connect listener rejects, but Tier0MQClient.emit does not await or catch event-listener promises; only subscription message-handler exceptions are caught. In a long-lived subscriber this can surface as an unhandled rejection and terminate or destabilize the worker, so the recommended backfill pattern should wrap the body in try/catch and log/emit the error.
Useful? React with 👍 / 👎.
| client.subscribe(TOPIC, (_topic, payload) => { | ||
| const evt = JSON.parse(payload); | ||
| handleEvent(evt); // idempotent: dedupes by evt.requestId | ||
| lastProcessedAt = Math.max(lastProcessedAt, evt.updatedAt ?? Date.now()); |
There was a problem hiding this comment.
Use the history clock for the backfill cursor
This cursor is later used as history.start_time, but the history endpoint filters by the UNS/VQT timeStamp, not by an arbitrary business field inside the payload. If producers omit WriteItem.timeStamp, or if evt.updatedAt is delayed/ahead due to clock skew, the next reconnect can skip missed records or replay the wrong window; persist and advance the cursor from the UNS history record timeStamp (or explicitly require writers to set timeStamp from the same event clock) instead of deriving it from evt.updatedAt alone.
Useful? React with 👍 / 👎.
| topics: [TOPIC], | ||
| start_time: new Date(lastProcessedAt).toISOString(), | ||
| end_time: new Date().toISOString(), | ||
| size: 500, |
There was a problem hiding this comment.
Page through reconnect history backfills
When the client has been offline long enough for more than 500 events, this example requests a single history page and then processes only results[0].result.values; the history response is paginated (page, size, and total), so records beyond this first page are never replayed. Because this section is the recommended pattern for consumers that must not miss events, it should loop through pages until the gap is exhausted instead of fixing one size: 500 request.
Useful? React with 👍 / 👎.
| | Subscribe QoS | Fixed at QoS 1 (at-least-once delivery **while connected**; duplicates possible — dedupe by business key) | | ||
| | Publish QoS | Default 0; pass `{ qos: 1 }` or `{ qos: 2 }` per publish when delivery matters | | ||
| | Offline gap | **Messages published while the client is disconnected are lost** (clean session — the broker does not queue for offline clients). Reconnect restores the subscription, not the missed messages | | ||
| | Handler errors | Exceptions thrown inside a handler are caught and emitted as `error` events; they do not kill the connection | |
There was a problem hiding this comment.
Clarify that async handler rejections are not caught
This guarantee only holds for synchronous throws: Tier0MQClient invokes sub.handler(topic, payloadStr) inside a try/catch but does not await a returned promise, so an async handler that rejects after an await will still become an unhandled rejection unless it catches its own errors. Since subscribers commonly persist events asynchronously, the table should either limit this statement to synchronous handlers or tell users to wrap async handlers in their own try/catch.
Useful? React with 👍 / 👎.
…ct hooks guide - scaffolds/monoapptemplate.md: publishDeviceMessage example created and disconnected a client per publish — a full WebSocket handshake per message, contradicting the subscriber singleton on the same page. Now a globalThis-guarded shared client, with the one-off-script exception and the HTTP-write transport default spelled out (audit K3) - openapi/react.md: scope banner — these hooks are for standalone SPAs; MonoApp scaffold pages go through app API routes with the scaffold's shared request hooks (audit K4) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 540ab07447
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| lastProcessedAt = Math.max(lastProcessedAt, evt.updatedAt ?? Date.now()); | ||
| }); | ||
|
|
||
| client.on('connect', async () => { |
There was a problem hiding this comment.
Avoid backfilling before subscriptions are restored
For reconnects, this connect listener starts the history query before the SDK restores the broker subscription: src/mq/client.ts lines 133-138 emit connect and only then call resubscribeAll(). A message published after this sample's end_time is captured but before the resubscribe completes can be outside the replay window and not delivered live, which breaks the must-not-miss backfill pattern; either backfill after confirmed resubscribe or intentionally overlap/re-run the history window.
Useful? React with 👍 / 👎.
| if (!g[MQ_CLIENT_KEY]) { | ||
| const { Tier0MQClient } = await loadTier0Mq(); | ||
| g[MQ_CLIENT_KEY] = new Tier0MQClient(); |
There was a problem hiding this comment.
Memoize the shared MQ client initialization
When two requests publish during cold start, both can enter this branch before loadTier0Mq() resolves, so each creates a Tier0MQClient; the later assignment overwrites the first and leaves an extra untracked WebSocket connection alive. That defeats the promised one-client-per-process pattern under concurrent first use, so cache an initialization promise or assign a placeholder before the await.
Useful? React with 👍 / 👎.
| Every `topic` value must satisfy the naming contract | ||
| (`^.+/(Metric|Action|State)/[^/]+$`, wildcards allowed in consume patterns). |
There was a problem hiding this comment.
Permit documented broad wildcard subscriptions
When a consumer uses the wildcard subscription pattern documented in references/mq/quickstart.md (Plant/Line1/#) to receive multiple type folders under a branch, this regex rejects the manifest entry because there is no literal Metric|Action|State segment before the leaf. That leaves no compliant way to declare a registered subscription the SDK supports, so agents will either omit it from uns-manifest.yaml or narrow the subscription and miss events; allow broad # consume patterns or remove that subscription pattern from the docs.
Useful? React with 👍 / 👎.
Platform-tested skill iteration (uploaded and verified as a test batch before this PR). Contains main's schema-first rules (merged in via c182390) — no content on main is lost.
Corrections
uns/create.md:topicTypedocumented as derived from the type folder, not required (explicit param accepted with a deprecation warning); repaired duplicated example sections; alignedtypecasing guidance withbrowse.md.New guidance
references/core/integration-manifest.md):uns-manifest.yamldeclares every recurring publish/consume; declare-before-implement; consumer discover→pin→read workflow; observation-not-authority staleness rule; scope boundary exempting one-off scripts/diagnostics. New SKILL.md doctrine "Every Integration Is Declared" + final-checklist item.data-integration.md): one topic = one schema = one subscription decision; merge/split criteria as judgment tests; anti-patterns (per-record, per-scalar, mega-topic, DB mirror); advisory count signal (review trigger, not a quota).mq/quickstart.md): reconnect/resubscribe semantics verified againstsrc/mq/client.ts, clean-session offline loss, history backfill example.scaffolds/monoapptemplate.md): long-lived subscriber singleton pattern (globalThis guard, lazy start, graceful shutdown), realtime-data-to-UI guidance.openapi/react.md): polling reads for dashboards (useQuery+refetchInterval) with transport-selection boundaries.Evals
skill-evals/tier0-sdk/eval-battery.md(outside the npmfilesset): 11 pressure scenarios including negative controls against manifest over-triggering.Design doc archived in the local workspace (docs/superpowers/specs/2026-07-10-uns-integration-manifest-design.md).
🤖 Generated with Claude Code