Skip to content

feat(observability): grouped error Issues (local, OSS) — Phase 1 - #138

Merged
prisis merged 7 commits into
alphafrom
feat/observability-issues
Jul 13, 2026
Merged

feat(observability): grouped error Issues (local, OSS) — Phase 1#138
prisis merged 7 commits into
alphafrom
feat/observability-issues

Conversation

@prisis

@prisis prisis commented Jul 11, 2026

Copy link
Copy Markdown
Member

Phase 1 — Fingerprint + local Issues + container-crash grouping

First slice of the observability plan: grouped error triage in local dev and any self-hosted worker, shipping in the framework (pure OSS, no cloud dependency). Draws the error-grouping model from superloglabs/superlog (Apache-2.0). Phases 2–4 (OTLP transport, cloud ingest/store/hosted views, alerts+AI) stack later on the cloud branch (PR #85).

What lands

@lunora/fingerprint (new package) — zero-dependency error-grouping core vendored from superlog. fingerprintError({ functionPath, message, code? }) → { hash, title, culprit }. The canonical hash is over functionPath :: bucket(message) only (code is metadata, never hashed), so a live-sink error event and a persisted request-log row for the same error collapse onto one stable hash — the same key a future cloud Incident groups on. The message bucketer strips per-request noise (URLs, UUIDs, IPs, timestamps, hex, long ids, paths) so a route-scanner sweep folds into a single Issue. Portable SHA-256 backend (workerd/browser-safe), proven byte-for-byte against upstream's node:crypto vectors. superlog copyright + NOTICE retained per Apache-2.0 §4; ships under the repo's FSL-1.1-Apache-2.0.

@lunora/do — new __lunora_admin__:getIssues admin RPC. readErrorIssues is a pure read-side aggregation over the bounded __lunora_reqlog__ readout (no new storage, no transport): it groups recent error-outcome rows by fingerprint into { hash, title, culprit, count, firstSeen, lastSeen, sampleMessage }, most-recently-active first. Wired through readAdminOp with an ADMIN_WILDCARD table dep so it serves both the one-shot POST and live WS subscriptions under the existing admin auth. Container lifecycle events now carry the instance id and parsed exit code onto LogEntry, and an error-level container event also appends an error request-log row — so a container crash and a Worker throw fold into the same Issues list under one fingerprint.

@lunora/studio — new Issues observability panel (live over the admin WS), threaded through the compile-checked route table. Containers moved from the Functions nav group into Observability, and the container fold is now keyed per (name, instance) with Instance + Exit columns so crash-looping instances are distinguishable.

Verification

  • @lunora/fingerprint: 19 golden-vector tests (bot-sweep collapse, live-sink↔reqlog hash equality, container-crash grouping) ✓ · tsc ✓ · eslint ✓
  • @lunora/do: full suite 1042 passed + 1 todo (incl. new readErrorIssues + container-crash-fold tests) ✓ · tsc ✓ · eslint ✓
  • @lunora/studio: tsc ✓ · eslint ✓ · fold-container-instances unit suite 12 passed ✓
  • Prettier clean across all changed/new files.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added an Issues view in Studio to live-monitor and group recurring errors with filtering.
    • Introduced stable error fingerprinting to consistently aggregate identical issues across environments.
    • Enhanced container lifecycle monitoring with per-instance details and stop exitCode (including correlation instance).
  • Bug Fixes
    • Improved issue coverage by recording crash-related lifecycle events and flushing durable request-log updates promptly.
  • Documentation
    • Added documentation for error fingerprinting and populated license/notice materials.

@netlify

netlify Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploy Preview for lunorash ready!

Name Link
🔨 Latest commit 9a2d879
🔍 Latest deploy log https://app.netlify.com/projects/lunorash/deploys/6a54c29cf814ef00087eb5b2
😎 Deploy Preview https://deploy-preview-138--lunorash.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for following the naming conventions! 🙏

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a cross-runtime fingerprint package, persists and groups errors into Issues, exposes a new admin RPC, and adds Studio navigation and panels for grouped issues and per-instance container lifecycle data.

Changes

Issues observability

Layer / File(s) Summary
Fingerprinting engine
packages/fingerprint/src/*
Adds portable SHA-256 hashing, message normalization, stack-aware fingerprints, and the fingerprintError grouping adapter.
Fingerprint package delivery
packages/fingerprint/package.json, packages/fingerprint/*
Adds package metadata, exports, build/test/lint configuration, release settings, documentation, and licensing notices.
Request-log issue aggregation
packages/do/src/request-log.ts, packages/do/src/shard-do.ts, packages/do/src/introspect.ts
Groups error request-log entries by fingerprint and exposes them through the getIssues admin operation with correlation filters.
Container lifecycle correlation
packages/do/src/shard-do.ts, packages/do/src/log-buffer.ts, packages/studio/src/features/containers/*
Parses instance and exit-code metadata, persists error lifecycle events, and displays container state per instance.
Studio Issues UI
packages/studio/src/features/issues/*, packages/studio/src/app/studio.tsx, packages/studio/src/lib/admin.ts, packages/studio/src/locales/en.ts
Adds the Issues panel, admin wire types, live querying, navigation, routing, and localized observability labels.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Studio
  participant AdminRPC
  participant RequestLog
  participant Fingerprint
  Studio->>AdminRPC: request getIssues
  AdminRPC->>RequestLog: read error entries
  RequestLog->>Fingerprint: fingerprint functionPath and message
  Fingerprint-->>RequestLog: grouping hash
  RequestLog-->>AdminRPC: grouped issues
  AdminRPC-->>Studio: issues payload
Loading

Possibly related PRs

  • anolilab/lunora#97: Extends the Containers observability UI and typing with per-instance and exit-code handling.

Suggested labels: package: do, package: studio

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change, but it misses the required Summary, Linked issues, Test plan, Checklist, Notes, and CLA sections. Rewrite the PR description to follow the template with the required headings and fill in the missing linked issues, test plan, checklist, notes, and CLA text.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: grouped error issues for observability in phase 1.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/observability-issues

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Thank you for confirming the Contributor License Agreement! 🙏

@github-actions

github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 9a2d879.

Comment thread packages/fingerprint/src/superlog.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/do/src/shard-do.ts`:
- Around line 4966-4990: After the successful appendRequestLogEntry call in
handleRecordContainerEvent, mark the request-log table as changed and flush the
pending changes so live getIssues/getLogs subscriptions refresh immediately.
Keep this notification inside the try block, ensuring failed best-effort
persistence does not trigger a flush.

In `@packages/studio/src/locales/en.ts`:
- Around line 1104-1105: Update the two locale strings and their matching call
sites in containers-panel.tsx and studio.tsx to describe lifecycle state and
observations per instance rather than per container, while preserving the
existing message IDs and meaning.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 606607cb-c036-4fa8-88f5-0c90b8287634

📥 Commits

Reviewing files that changed from the base of the PR and between 81e9df8 and 07bc411.

⛔ Files ignored due to path filters (8)
  • labeler-config.yml is excluded by none and included by none
  • packages/do/__tests__/request-log.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/fingerprint/__assets__/package-og.svg is excluded by !**/*.svg and included by packages/**
  • packages/fingerprint/__tests__/fingerprint.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • packages/studio/__tests__/features/containers/fold-container-instances.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
  • pnpm-workspace.yaml is excluded by none and included by none
📒 Files selected for processing (26)
  • packages/do/package.json
  • packages/do/src/introspect.ts
  • packages/do/src/log-buffer.ts
  • packages/do/src/request-log.ts
  • packages/do/src/shard-do.ts
  • packages/fingerprint/.releaserc.json
  • packages/fingerprint/LICENSE.md
  • packages/fingerprint/NOTICE
  • packages/fingerprint/README.md
  • packages/fingerprint/eslint.config.js
  • packages/fingerprint/package.json
  • packages/fingerprint/packem.config.ts
  • packages/fingerprint/prettier.config.js
  • packages/fingerprint/project.json
  • packages/fingerprint/src/index.ts
  • packages/fingerprint/src/lunora.ts
  • packages/fingerprint/src/sha256.ts
  • packages/fingerprint/src/superlog.ts
  • packages/fingerprint/tsconfig.json
  • packages/fingerprint/vitest.config.ts
  • packages/studio/src/app/studio.tsx
  • packages/studio/src/features/containers/containers-panel.tsx
  • packages/studio/src/features/containers/fold-container-instances.ts
  • packages/studio/src/features/issues/issues-panel.tsx
  • packages/studio/src/lib/admin.ts
  • packages/studio/src/locales/en.ts

Comment thread packages/do/src/shard-do.ts Outdated
Comment thread packages/studio/src/locales/en.ts Outdated
Comment on lines 1104 to 1105
"Live Cloudflare Containers — current lifecycle state per container from the log stream.",
"Cloudflare Containers are observed from their lifecycle log stream. This shows the current state per container — ports and health checks aren't carried in that stream.",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Describe the view as per-instance.

The new panel renders separate rows for concurrent instances, but these strings still say “per container,” obscuring the feature this PR adds. Update the matching call sites in containers-panel.tsx and studio.tsx too, since they use these message IDs.

Proposed fix
- "Live Cloudflare Containers — current lifecycle state per container from the log stream.",
- "Cloudflare Containers are observed from their lifecycle log stream. This shows the current state per container — ports and health checks aren't carried in that stream.",
+ "Live Cloudflare Containers — current lifecycle state per instance from the log stream.",
+ "Cloudflare Containers are observed from their lifecycle log stream. This shows the current state per instance — ports and health checks aren't carried in that stream.",
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"Live Cloudflare Containers — current lifecycle state per container from the log stream.",
"Cloudflare Containers are observed from their lifecycle log stream. This shows the current state per container — ports and health checks aren't carried in that stream.",
"Live Cloudflare Containers — current lifecycle state per instance from the log stream.",
"Cloudflare Containers are observed from their lifecycle log stream. This shows the current state per instance — ports and health checks aren't carried in that stream.",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/studio/src/locales/en.ts` around lines 1104 - 1105, Update the two
locale strings and their matching call sites in containers-panel.tsx and
studio.tsx to describe lifecycle state and observations per instance rather than
per container, while preserving the existing message IDs and meaning.

@codspeed-hq

codspeed-hq Bot commented Jul 11, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 10.14%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 159 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
baseline (Object.keys + toInternal + path spread per field) 71.5 µs 64.9 µs +10.14%

Tip

Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.


Comparing feat/observability-issues (9a2d879) with alpha (32ee8a6)1

Open in CodSpeed

Footnotes

  1. No successful run was found on alpha (0d0c299) during the generation of this report, so 32ee8a6 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

prisis added a commit that referenced this pull request Jul 12, 2026
- fingerprint: replace regex stack-frame parser with linear index scan and
  clamp bucketer input length, removing the polynomial-ReDoS sinks (CodeQL #25);
  inline the firstNonEmpty helper
- do: treat a container stop with a non-zero exit code as a crash so it folds
  into Issues; extract a shared persistRequestLog seam so container crashes also
  reach Logpush; flush the reqlog table on the RPC error and container paths so
  live getIssues/getLogs refresh in real time; project only the 3 needed columns
  in readErrorIssues instead of the full 14-column hydrate
- studio: drop the NUL-delimiter container key for a JSON-encoded pair (file was
  a binary blob), remove the redundant useMemo, format Issue timestamps via
  formatTimestamp, reword container copy to per-instance
- docs: add the @lunora/fingerprint package row

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@prisis

prisis commented Jul 12, 2026

Copy link
Copy Markdown
Member Author

Addressed the review + bot findings in a284d9d (plus a /thermos audit that surfaced two more):

Bots

  • CodeQL ci: rebuild test + lint pipelines on the visulima model (vis, no build job) #25 (polynomial ReDoS, superlog.ts) — replaced the regex stack-frame parser with a linear index-based scanner, and clamped the message bucketer's input length before its regexes run. The genuinely-reachable path in this PR was actually messageBucketFor (run per error row in readErrorIssues), not just the stack parser CodeQL pointed at — both are now defanged.
  • CodeRabbit (container flush) — a container crash now marks the reqlog table changed and flushes, so live getIssues/getLogs refresh immediately. Same fix applied to the RPC error path, which had the same gap.
  • CodeRabbit ("per container" copy) — reworded to "per instance" in en.ts, studio.tsx, and containers-panel.tsx.
  • react-doctor L54 / L104 / L107 — removed the redundant useMemo; Issue timestamps now format via the shared formatTimestamp helper instead of new Date().toLocaleString() in render.

From the audit

  • fold-container-instances.ts had literal NUL bytes in the fold key, making git treat it as a binary blob (unreviewable diff). Replaced the hand-rolled delimiter with a JSON-encoded [name, instance] key.
  • A container stop with a non-zero exit code never became an Issue (it arrives as level: "info"), contradicting the handler's docstring — now treated as a crash. Added tests for the non-zero-exit and clean-exit (exit 0) cases.
  • Container crashes were persisted but not emitted to Logpush like every other error; extracted a shared persistRequestLog seam so the two writers can't drift.
  • readErrorIssues now projects only the 3 columns it needs instead of the full 14-column hydrate + per-row JSON.parse, since the subscription re-runs on every write-flush.
  • Added the @lunora/fingerprint row to the package table in AGENTS.md.

@lunora/do (1044 pass), @lunora/fingerprint (19), and the studio container/issues suites are green; tsc + eslint + prettier clean across the changed files.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/do/src/request-log.ts (1)

574-598: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Filter/limit-building logic duplicates readRequestLog.

The functionPathPrefix/userId/shardKey conjunct-building (Lines 582-595) and the limit clamp (Line 577, Math.max(1, Math.min(options.limit ?? REQUEST_LOG_RETENTION, 10_000))) are copy-pasted from readRequestLog (Lines 468, 473-486). A shared helper (e.g. buildCommonLogFilters(options) / clampLimit(limit)) would remove the duplication and prevent the two readers from silently drifting apart if a filter is added to one but not the other.

♻️ Extract shared filter/limit helpers
+const clampLimit = (limit: number | undefined): number => Math.max(1, Math.min(limit ?? REQUEST_LOG_RETENTION, 10_000));
+
+const buildCommonLogFilters = (options: {
+    functionPathPrefix?: string;
+    shardKey?: string;
+    userId?: string;
+}): { conjuncts: string[]; parameters: unknown[] } => {
+    const conjuncts: string[] = [];
+    const parameters: unknown[] = [];
+
+    if (options.functionPathPrefix !== undefined && options.functionPathPrefix !== "") {
+        conjuncts.push(String.raw`function_path LIKE ? ESCAPE '\'`);
+        parameters.push(`${escapeLike(options.functionPathPrefix)}%`);
+    }
+
+    if (options.userId !== undefined && options.userId !== "") {
+        conjuncts.push("user_id = ?");
+        parameters.push(options.userId);
+    }
+
+    if (options.shardKey !== undefined && options.shardKey !== "") {
+        conjuncts.push("shard_key = ?");
+        parameters.push(options.shardKey);
+    }
+
+    return { conjuncts, parameters };
+};

Then both readRequestLog and readErrorIssues call buildCommonLogFilters(options) and append their own extra conjuncts (seq > ?, outcome = 'error', tableTouched, etc.) plus clampLimit(options.limit).

As per path instructions, "Follow DRY principles" for packages/**/*.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/do/src/request-log.ts` around lines 574 - 598, Extract the
duplicated limit clamping and functionPathPrefix/userId/shardKey conjunct
construction from readRequestLog and readErrorIssues into shared helpers such as
clampLimit and buildCommonLogFilters. Update both readers to reuse these helpers
while preserving their reader-specific predicates and parameter ordering,
including readErrorIssues’ outcome filter and limit parameter.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/do/src/request-log.ts`:
- Around line 564-573: Update the issue-folding logic around the newest-first
row iteration to select representative title, sampleMessage, and culprit fields
by maximum ts rather than first seq order. Track the timestamp of the current
representative, refresh those fields whenever a row has a newer ts, and keep
lastSeen/count/firstSeen aggregation behavior unchanged.

---

Nitpick comments:
In `@packages/do/src/request-log.ts`:
- Around line 574-598: Extract the duplicated limit clamping and
functionPathPrefix/userId/shardKey conjunct construction from readRequestLog and
readErrorIssues into shared helpers such as clampLimit and
buildCommonLogFilters. Update both readers to reuse these helpers while
preserving their reader-specific predicates and parameter ordering, including
readErrorIssues’ outcome filter and limit parameter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a6020c7-1661-4eb2-af44-9403039f1fa3

📥 Commits

Reviewing files that changed from the base of the PR and between 8457de9 and a284d9d.

⛔ Files ignored due to path filters (2)
  • AGENTS.md is excluded by none and included by none
  • packages/do/__tests__/shard-do.admin.test.ts is excluded by !**/__tests__/**, !**/*.test.ts and included by packages/**
📒 Files selected for processing (9)
  • packages/do/src/request-log.ts
  • packages/do/src/shard-do.ts
  • packages/fingerprint/src/lunora.ts
  • packages/fingerprint/src/superlog.ts
  • packages/studio/src/app/studio.tsx
  • packages/studio/src/features/containers/containers-panel.tsx
  • packages/studio/src/features/containers/fold-container-instances.ts
  • packages/studio/src/features/issues/issues-panel.tsx
  • packages/studio/src/locales/en.ts
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/studio/src/features/containers/fold-container-instances.ts
  • packages/studio/src/features/issues/issues-panel.tsx
  • packages/fingerprint/src/lunora.ts
  • packages/studio/src/features/containers/containers-panel.tsx
  • packages/studio/src/app/studio.tsx
  • packages/studio/src/locales/en.ts
  • packages/do/src/shard-do.ts
  • packages/fingerprint/src/superlog.ts

Comment thread packages/do/src/request-log.ts Outdated
prisis and others added 6 commits July 13, 2026 12:23
Add @lunora/fingerprint, a zero-dependency error-grouping core vendored
from superloglabs/superlog (Apache-2.0). It exposes `fingerprintError({
functionPath, message, code? }) -> { hash, title, culprit }`, whose
canonical hash is computed over `functionPath :: bucket(message)` only —
code is returned as metadata, never hashed — so a live-sink error event
and a persisted request-log row for the same error collapse onto one
stable hash. The message bucketer strips per-request noise (URLs, UUIDs,
IPs, timestamps, hex, long ids, paths) so a route-scanner sweep folds
into a single Issue. Ships a portable SHA-256 backend (workerd/browser
safe) proven byte-for-byte against upstream's node:crypto vectors.

superlog's copyright and a NOTICE are retained per Apache-2.0 §4; the
package ships under the repo's FSL-1.1-Apache-2.0 convention.

Registered in pnpm-workspace overrides and the labeler config.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm
Add a `__lunora_admin__:getIssues` admin RPC that folds the recent
`error`-outcome rows of the bounded request-log readout into grouped
Issues by fingerprint (`@lunora/fingerprint`). `readErrorIssues` is a
pure read-side aggregation — no new storage, no transport — returning
`{ hash, title, culprit, count, firstSeen, lastSeen, sampleMessage }`
per group, most-recently-active first. It is wired through `readAdminOp`
with an `ADMIN_WILDCARD` table dep so it serves both the one-shot POST
and live WebSocket subscriptions, gated by the existing admin auth.

Carry the container instance id and process exit code through to
`LogEntry`: `parseRecordContainerEventArgs` now reads `envelope.instance`
and parses `(exit <n>)` out of the lifecycle message instead of
collapsing to `container:<name>` only. Error-level container events also
append an `error`-outcome request-log row, so a container crash and a
Worker throw fold into the same Issues list under one fingerprint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm
Add an Issues observability panel that reads the new
`__lunora_admin__:getIssues` RPC live over the admin WebSocket, showing
grouped error triage — each row a fingerprinted Issue (title, culprit,
event count, first/last-seen) folding Worker throws and container
crashes together. Threaded through the compile-checked route table
(tab union, nav group, lazy import, icon, label, description, panel).

Relocate Containers from the Functions nav group into Observability, and
key the container fold per `(name, instance)` now that the instance id
survives — adding Instance and Exit columns so crash-looping instances
are distinguishable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018sRFb1136YE8KDmDbFMYmm
- fingerprint: replace regex stack-frame parser with linear index scan and
  clamp bucketer input length, removing the polynomial-ReDoS sinks (CodeQL #25);
  inline the firstNonEmpty helper
- do: treat a container stop with a non-zero exit code as a crash so it folds
  into Issues; extract a shared persistRequestLog seam so container crashes also
  reach Logpush; flush the reqlog table on the RPC error and container paths so
  live getIssues/getLogs refresh in real time; project only the 3 needed columns
  in readErrorIssues instead of the full 14-column hydrate
- studio: drop the NUL-delimiter container key for a JSON-encoded pair (file was
  a binary blob), remove the redundant useMemo, format Issue timestamps via
  formatTimestamp, reword container copy to per-instance
- docs: add the @lunora/fingerprint package row

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The @lunora/fingerprint row broke prettier's markdown table alignment,
failing the Lint (prettier) check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A container lifecycle row carries the caller's envelope `ts`, not `Date.now()`,
so an out-of-order or clock-skewed push can write an older-`ts` row at a higher
`seq`. readErrorIssues seeded title/sampleMessage from the first row in `seq`
order while lastSeen took `max(ts)` — so the sample could describe a different
occurrence than the one lastSeen pointed at. Track the representative by max ts.

culprit needs no such tracking: it is `functionPath`, an input to the hash, so
it is invariant across a group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@prisis
prisis force-pushed the feat/observability-issues branch from 3d29a07 to 51a2c77 Compare July 13, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants