feat: Inbox-Intelligence epic — digest read layer, trust gates, UI (#484)#488
Merged
Conversation
…matrix (spec 07, #480) Normalize any RawSignal (email/calendar/filesystem/voice) into a channel-agnostic SignalText so commitment/deadline/security/cluster/entity capabilities are source-agnostic. Extends AuthoringTier with authored_originated/received_shared; adds a tested capability×source coverage matrix. Foundation for #475/#476/#479. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…spec 10, #483) - LOCKED: new users default to trust_tier 'observer' (users.ts) — matches DB default + CLAUDE.md; resolves the 3-way conflict that forced 'suggest'. - provisionNewUser: eager empty twin profile + conservative autonomy defaults (no spend caps, so the built-in NO_SPEND_WITHOUT_LIMIT gate blocks spend until the user sets a budget — safe by construction). - seedUpsert/buildUpsertSql: shared, tested idempotent upsert helper for re-runnable seeds (used by spec 09). Existing seed.ts already idempotent. Part C (promotion soak-floor hoursInCurrentTier + tier-ladder intro) still TODO. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(spec 10 Part C, #483) Daily promotion-eligibility job now populates hoursInCurrentTier (from last tier change or account creation), so the engine actually enforces minDurationInTierHours (24h observer->suggest, etc). Closes the documented gap where the floor was skipped in the auto path. Fail-safe 0 keeps a promotion blocked when time can't be derived. Tier-ladder intro UI folds into spec 08. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
extractDeadline parses absolute/relative dates (chrono-node) from any text-bearing signal (SignalText-compatible) and returns the earliest credible FUTURE deadline. situation-interpreter.enrichDeadline stamps rawEvent.deadline when the connector didn't, so the existing assessUrgency consumer finally gets fed. Rejects past dates + no-match. v1 leaves per-user-timezone resolution to spec 12. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pec 02, #475) extractCommitments surfaces the user's own stated obligations ("I'll send the draft tomorrow" -> "Send the draft tomorrow") from authored SignalText. Gated to authoredByUser + the commitments source allowlist (safety invariant #8: never from inbound content). Rule extractor handles modal forms, excludes questions/past/third-party/hypotheticals, dedups, and emits a deadlineHint for spec 03. CommitmentStrategy seam left for an LLM path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ly (spec 06, #479) Adds SituationType.SECURITY_ALERT (enums.ts). classifySituation matches inbound account-security markers FIRST (precedence over finance/email), urgency=high, domain=security. The candidate generator emits ONLY a human-review escalation that says "open the provider directly" with link-free parameters — never an auto-executable action, never a URL from the untrusted body (safety invariant #8). Provenance stays untrusted_external regardless of claimed sender. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#477) clusterSignals groups awareness signals into life-domain topic clusters for the Topics section. Anchors to known domains (beats the reference product's mis-filing), guarantees complete + non-overlapping partition, caps cluster count with overflow merged into "More updates" (logged via onMerge). Deterministic fallback ships; ClusterStrategy seam for an LLM path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (spec 13, #487) computeCoverage evaluates the capability x source matrix against a user's connected accounts -> per-capability available/partial/unavailable + the sources that would unlock each, plus a coldStart flag (zero sources, distinct from connected-but-quiet). Excludes mock sources. Drives "connect X to unlock Y" transparency; UI affordances render in spec 08. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(spec 11, #485) Scope gate (policy-engine): requiredWriteScope/hasWriteScope/applyScopeGate. Wired into DecisionMaker.generateCandidates — when grantedScopes is supplied, un-granted write candidates (send/calendar) downgrade to a human-review "grant access" item. Fail-safe NOT granted (safety invariant #8). Visibility filter (decision-engine): isHidden/filterVisible — the single hide predicate the digest routes input through (briefing-generator wiring lands with spec 01). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… 12, #486) Migration 063 adds users.language + users.timezone. userRepository.getLocale + resolveLanguage/resolveTimezone/isNonEnglish helpers (safe fallbacks: en / UTC with a logged-default flag). Briefing prose locale now reads the user profile instead of hardcoded 'en'. isNonEnglish is the LLM-vs-rule routing signal for the extractors (degraded-marker wiring is a follow-up on 02/03/06). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
) assertDemoSafe (3-gate invariant #0): explicit-only, prod hard-blocked + non-local needs override, identity isolation via is_demo (migration 064). Never wired into bin/skytwin-dev/auto-seed — can't run for a real or new user. demo-fixture.ts guards then upserts the reserved demo user + ingests a synthetic source-varied corpus (email/calendar/file/voice) through /api/events/ingest; --reset deletes is_demo rows only. `pnpm demo:fixture`. Guard fully unit-tested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#474) buildDigest partitions items into action-required to-dos (urgency-ordered, capped) vs domain-clustered topics, with no overlap. Composes the epic: filters hidden content first (spec 11), clusters topics (spec 04), carries sourceType+deadline for the UI (spec 07/03). New briefing-prose v2 prompt emits the two-section structured payload (todos + topics). The structured_payload column + repo read + render land with spec 08 (UI). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…pec 05, #478) extractEntities pulls people (emails) + orgs (suffix-tagged) from SignalText. resolveEntities links mentions to stable entityIds — exact email key for people (never fuzzy), token-overlap floor for orgs, conservative mint-on-doubt so a false merge can't corrupt the graph. linkEntitiesAcrossSignals aggregates "every signal touching X". Persistence reuses MemoryPort.recordEntity; the getSignalsForEntity port method is the remaining integration seam. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…481) twin-briefing.js renders the structured digest: To-dos above Topics, each row with a source-type chip (email/calendar/file/voice) + citation chips that open the in-app signal detail (never an external URL — safety #8). Reuses the existing singleton-delegator + hash-gate + data-action conventions (new open-signal action). Falls back to prose when structured is null (back-compat). API /latest passes through structured (nullable, forward-compatible). CSS reuses card/badge tokens. Mobile BriefingScreen mirror is the remaining part of this spec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR implements major pieces of the “Inbox-Intelligence” epic across the decision engine, policy/trust gates, DB/bootstrap/demo tooling, and web digest UI, aiming to produce a source-agnostic, access-faithful digest with safety constraints (hide/scope gates) and launch fixtures.
Changes:
- Added source-agnostic signal normalization (
SignalText), capability×source coverage modeling, and new extractors/assemblers (deadlines, commitments, clustering, entity linking, digest builder). - Introduced trust/safety gates: inbound
SECURITY_ALERTsituation handling, write-scope gating utilities, and hidden-content filtering utilities. - Added locale/timezone plumbing, demo-fixture guard + fixture seed tooling, and a structured digest render in the web briefing UI.
Reviewed changes
Copilot reviewed 51 out of 52 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new workspace + dependency additions (connectors + chrono-node). |
| packages/shared-types/src/enums.ts | Adds SituationType.SECURITY_ALERT. |
| packages/policy-prompts/prompts/briefing-prose/v2.md | New v2 briefing prompt schema supporting structured todos/topics. |
| packages/policy-engine/src/scope-gate.ts | Implements write-scope requirement mapping + candidate downgrading. |
| packages/policy-engine/src/index.ts | Re-exports scope-gate utilities. |
| packages/policy-engine/src/tests/scope-gate.test.ts | Unit tests for scope gating behavior. |
| packages/decision-engine/src/visibility-filter.ts | Adds shared hidden-content predicate + filter helper. |
| packages/decision-engine/src/topic-clusterer.ts | Adds deterministic domain-based topic clustering with overflow merge. |
| packages/decision-engine/src/source-coverage.ts | Computes capability availability vs connected sources (cold-start + missing sources). |
| packages/decision-engine/src/situation-interpreter.ts | Adds security-alert classification, deadline enrichment, and summary text for security alerts. |
| packages/decision-engine/src/signal-text.ts | Introduces toSignalText normalization across sources + authored-by-user derivation. |
| packages/decision-engine/src/locale.ts | Adds language/timezone resolution helpers + non-English detection. |
| packages/decision-engine/src/index.ts | Exports new digest/intelligence modules from decision-engine. |
| packages/decision-engine/src/entity-linking.ts | Adds entity extraction/resolution utilities for cross-signal linking. |
| packages/decision-engine/src/digest.ts | Adds structured digest builder (todos vs topics) with visibility filtering. |
| packages/decision-engine/src/decision-maker.ts | Adds scope-gate hook and escalate-only candidates for security alerts. |
| packages/decision-engine/src/deadline-extractor.ts | Adds chrono-node powered deadline extraction. |
| packages/decision-engine/src/commitment-extractor.ts | Adds authored-only, matrix-gated commitment extraction (rule-based). |
| packages/decision-engine/src/capability-source-matrix.ts | Adds tested allowlist for capability×source coverage. |
| packages/decision-engine/src/tests/visibility-filter.test.ts | Tests for hidden predicate + filtering behavior. |
| packages/decision-engine/src/tests/topic-clusterer.test.ts | Tests clustering invariants and overflow merge. |
| packages/decision-engine/src/tests/source-coverage.test.ts | Tests coverage computation across connection configs. |
| packages/decision-engine/src/tests/signal-text.test.ts | Tests per-source mapping + fail-safe behavior. |
| packages/decision-engine/src/tests/security-alert.test.ts | Tests security-alert classification + escalate-only candidates. |
| packages/decision-engine/src/tests/locale.test.ts | Tests language/timezone resolution + non-English detection. |
| packages/decision-engine/src/tests/entity-linking.test.ts | Tests conservative entity extraction/resolution behavior. |
| packages/decision-engine/src/tests/digest.test.ts | Tests digest partitioning, urgency ordering, and visibility filtering. |
| packages/decision-engine/src/tests/deadline-extractor.test.ts | Tests deadline parsing behaviors and edge cases. |
| packages/decision-engine/src/tests/commitment-extractor.test.ts | Tests authored-only commitment extraction + negative cases. |
| packages/decision-engine/src/tests/capability-source-matrix.test.ts | Tests allowlist behavior and mock/real parity. |
| packages/decision-engine/package.json | Adds @skytwin/connectors and chrono-node deps. |
| packages/db/src/seeds/upsert.ts | Adds shared idempotent upsert SQL builder + executor for seeds. |
| packages/db/src/seeds/demo-guard.ts | Adds 3-gate demo fixture guard + identity isolation helper. |
| packages/db/src/seeds/demo-fixtures/signals.ts | Adds synthetic multi-source demo signal corpus. |
| packages/db/src/seeds/demo-fixture.ts | Adds opt-in demo fixture command with guard + ingestion loop. |
| packages/db/src/repositories/user-repository.ts | Adds getLocale() for language/timezone reads. |
| packages/db/src/repositories/trust-tier-audit-repository.ts | Adds hoursInCurrentTier() for soak-floor enforcement. |
| packages/db/src/migrations/064-users-is-demo.sql | Adds is_demo column for demo identity isolation. |
| packages/db/src/migrations/063-user-locale-timezone.sql | Adds nullable language and timezone columns. |
| packages/db/src/index.ts | Exposes new seed/upsert and demo-guard utilities. |
| packages/db/src/tests/upsert.test.ts | Tests for upsert SQL builder/executor behavior. |
| packages/db/src/tests/demo-guard.test.ts | Tests demo guard gates + identity isolation. |
| packages/db/package.json | Adds demo:fixture script entry. |
| packages/connectors/src/authoring-tier.ts | Extends AuthoringTier with authored_originated and received_shared. |
| package.json | Adds root demo:fixture script. |
| apps/worker/src/jobs/promotion-eligibility-check.ts | Wires hoursInCurrentTier into trust-tier engine evaluation. |
| apps/worker/src/tests/promotion-eligibility-check.test.ts | Tests soak-floor wiring into promotion evaluation. |
| apps/worker/src/jobs/briefing-generator.ts | Wires briefing language from user profile (no longer hardcoded en). |
| apps/web/public/js/pages/twin-briefing.js | Adds structured digest rendering + citation chips + source chips. |
| apps/web/public/css/styles.css | Adds styles for digest sections, source chips, and citation chips. |
| apps/api/src/routes/users.ts | Sets new-user trust tier to observer and provisions profile/settings. |
| apps/api/src/routes/twin-briefings.ts | Exposes structured_payload as briefing.structured (nullable/additive). |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Correctness: - deadline urgency: stale (past-relative-to-now) deadlines no longer read as critical; far-out deadlines no longer DOWNGRADE a type's default urgency (#1/#2) - security markers curated to specific phrases — kill false positives on shipping notices / "welcome back" / articles (#3); marker check also applied on the LLM path so escalate-only holds regardless of classifier (safety defense-in-depth) - digest emits signalRefs[] so citation chips actually render (#4) - scope gate now covers calendar RSVP/invite write actions (#5) - commitment extractor: clause-level negation (keep real commitments sharing a sentence with "if I…") (#6); "by <person>" no longer a deadline hint (#7) - entity resolver compares full normalized string, not the truncated slug (#10) Hardening/robustness: - demo-guard isLocalDbTarget: exact host match, not substring (#8) - provisionNewUser is genuinely best-effort (try/catch) — never 500s after the user row exists - briefing-generator pinned to prompt v1 until it consumes v2 structured output (avoids requesting+discarding todos/topics); v2 deterministic_fallback fixed - briefing test mock provides userRepository.getLocale so the LLM-prose path is actually exercised (#13) Regression tests added for each. Full suite green (70/70 tasks). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…design-review) Showing the structured two-bucket digest AND the full prose was the same briefing twice. When structured is present, the prose moves under a "Full briefing" <details> as the long-form view; falls back to inline prose when there's no structured payload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One digest, two depths. Default stays the clean view (non-technical users
unaffected); a discoverable header "Power view" toggle (persisted) + per-item
"Details" expander reveal the depth SkyTwin already computes — provenance,
confidence %, urgency reason, why-it-didn't-auto-run (scope/tier/policy), real
source refs, and the explanation — plus a coverage panel ("what I can see,
connect X to unlock Y"). Not buried in settings.
buildDigestItemDetail is the pure view-model (raw codes -> human strings), unit
tested. UI follows the singleton-delegator/hash-gate/data-action conventions.
Digest payload carries optional per-item detail + coverage (generator populates).
Verified rendering via a headless-browser screenshot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…(DESIGN.md) Source of truth grounded in a full element-and-state inventory of the digest surfaces. Cool-neutral base (refines existing #0f1117 tokens; rejected the warm/brown direction), iris #7C72E8 as the SINGLE accent meaning "needs you / act", Fraunces voice + Geist + Geist Mono, action-vs-awareness hierarchy. Catalogs every element + EVERY state including the gaps never rendered before: cold-start, scope-blocked grant-access, loading, error, prose-fallback, distinct security treatment, provenance in default view. CLAUDE.md now points UI + /qa + /design-review at it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ates (spec 15)
Wires the locked design system into the real digest UI:
- Load Fraunces (twin voice) + Geist + Geist Mono (index.html)
- Iris #7C72E8 as the single accent = "needs you / act"; killed the CAPS
source-chip soup -> one neutral source mark + a single "·N sources" citation;
provenance as a dot (neutral, never accent)
- Action zone (to-dos: checkbox + inline Draft/Snooze/Verify/Grant, hover-reveal,
always-on for security + touch) vs awareness zone (topics: lighter, no edge)
- Twin voice (Fraunces) + value line ("✓ N handled · M need you · K to catch up")
- Power view detail panel + coverage panel restyled to the system
- GAP STATES now designed: loading skeleton, empty-quiet, cold-start ("connect a
source"), prose-fallback disclosure, distinct security treatment, scope-blocked
"Grant access". Verified via headless-browser render of the real CSS.
Row-action wiring (draft/snooze/verify) routes/acknowledges until the act layer
lands. App-wide token adoption (vs digest-scoped iris) is a follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rror (pre-existing) app.js:856 registered a non-async DOMContentLoaded handler, but the pairToken branch (line ~904) uses `await fetch(...)` → "Unexpected reserved word" at parse time, which aborts ALL app initialization. Every page rendered as an empty #page-content shell. Present on origin/main; web JS has no type-check or tests, so it shipped silently. One-word fix (() => → async () =>); verified by booting the seeded app and touring dashboard/decisions/approvals/settings. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The digest existed as tested modules but never rendered in the running app: the briefing generator produces no structured payload, so /latest returned null and the UI fell back to "No briefing content yet". This closes that seam so the AI-inbox parity (to-dos vs topics, multi-source) actually shows. - live-digest.ts: compute the structured digest from a user's recent decisions — read each decision's RawSignal through toSignalText (spec 07) for real, source-agnostic titles, partition via buildDigest (spec 01/04), attach power-view detail (spec 14) and coverage (spec 13). - twin-briefings /latest: when no structured_payload is stored, compute the digest live (best-effort; degrades to prose on error) and synthesize a briefing envelope so the page renders parity today. Forward-compatible: a stored payload still wins once the worker writes one. - dashboard: Home leads with a read-only digest hero (action zone first, DESIGN.md) linking to the full interactive /briefing; stop showing the "connect Google" nag once the twin has produced decisions. - index.html: first-class "Briefing" nav link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The decisions log mapped auto_executed=false to "You OK'd", which mislabels a decision still awaiting approval (notably an escalated security alert) as already approved. Surface the outcome's requires_approval through the API and add a distinct "Needs you" state so the log matches the Approvals page. - decision-repository.getOutcomesForDecisions: also select requires_approval. - decisions route: return requiresApproval per decision. - decisions.js: Auto / Needs you / You OK'd / Pending, in that order. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A structured preference value (e.g. a brand-preference object) fell through to String(value) and rendered as "[object Object]" in the dashboard "What I've learned" summaries. Render arrays/objects readably instead. Adds a regression test covering objects, nested objects, arrays, booleans, strings, numbers, and null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The live digest (no stored row) carries the sentinel id 'live'; its "Mark as read" button POSTed to /briefings/live/read and 400'd on the UUID check. Gate the New badge + Mark-as-read on a persisted briefing so the control only shows when there's a real row to mark. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The power-view detail panel rendered noise: "URGENCY: Default for security",
"REFS: email: 77538186" (an internal id slice), "WHY: Account notice" (just the
title again), and no confidence at all. Feed it real technical depth instead:
- confidence: pull decision_outcomes.confidence -> a real percentage.
- source ref: the actual sender/organizer/file ("email: no-reply@accounts.example"),
not an opaque decision-id slice.
- urgencyReason: a real driver ("Security alert — always sent to you", "New
invite — awaiting your RSVP", "Routine — no deadline detected") via a new
optional urgencyReason override on buildDigestItemDetail, instead of the
generic "Default for <domain>".
- drop the redundant explanation (it duplicated the title).
- honest whyNotAutoExecuted: use the engine's real escalation_reason, and only
fall back to the trust-tier gate when the item genuinely required approval —
no fabricated "trust_tier:observer" on escalate-only items.
- normalizeUrgency: map the DB default 'normal' to 'medium', not 'low'
(silent demotion).
- name the recent-decisions window; drop the redundant maxTodos override.
Adds a DB-mocked buildLiveDigest suite (cold start, to-do mapping + detail,
malformed raw_event, provenance fail-safe, handledCount) plus normalizeUrgency
and urgencyReasonFor helper tests. Fixes the sections-fold test's @skytwin/db
mock to define query so the live-digest path resolves cleanly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Gating the Connect-Google/Connect-Gmail heroes on `hasAnyData` hid the onboarding CTA for users who have decisions but haven't connected Gmail (the "Calendar connected, Gmail not yet" segment) — the heroes already self-suppress when actually connected, so the extra gate only hurt real users. Revert to gating on tourMode only. Also drop a dead `t.kind === 'security'` branch in the Home digest hero (buildDigest never sets kind). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The digest told you a title and a pile of system metadata (origin, confidence,
"why it escalated") but not the two things that actually matter: what the item
says and what to do about it. Surface both, sourced from data we already had:
- body: the real content (email snippet, event description, file excerpt,
transcript) via toSignalText, rendered as a one-line preview under each title
— visible by default, not buried in the power view.
- suggestedAction: the twin's recommended next step, taken from the pipeline's
selected candidate action ("Accept this calendar invitation", "Review this
security alert in the provider's official app — don't click links in the
message"), with sensible fallbacks for escalate-only situations.
UI: the to-do/topic rows now lead with title -> what it says; the power-view
detail leads with the actionable "suggested" step, and the trust metadata
(origin/confidence/refs) drops below it. The Home hero shows the content line
plus an iris "→ next step" so it's actionable without opening anything.
Carries body through DigestItem/DigestTodo/DigestTopicItem + buildDigest, and
adds suggestedAction to DigestItemDetail. Tests cover body extraction, the
pipeline-selected action, and the security/RSVP fallbacks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two gaps from the last pass: some suggestions were the rule-based engine's raw
internal text ("Apply appropriate labels to this email", "Escalate to user:
Decision needed regarding: transcript"), and the suggestion only showed in the
power-view detail — so in the default view most items had no visible next step.
- suggestedActionFor now maps the structured selected action TYPE to plain
English ("Accept the invite, or decline / propose another time", "Nothing
needed — I'll file it", "Take a look and tell me what to do"), with a
security-specific instruction and situation fallbacks. Every item gets a
clean, user-facing step — no engine internals leak through.
- The "→ next step" now renders in the row itself for every item (to-do and
topic), visible without the power view. The power-view detail drops back to
the trust/technical metadata (origin, confidence, refs) it's meant for.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail panel was accurate but spoke the way the system names things, not the way a person asks. A non-technical user can't parse "ORIGIN: Inbound — untrusted", "REFS", "NOT AUTO-RUN", a bare "CONFIDENCE: 80%", or "From your twin" — and "untrusted" reads as a threat rather than "you didn't write this". Rephrase everything user-facing: - provenance: "Inbound — untrusted" -> "From someone else"; "From your twin" -> "From your assistant"; fail-safe stays "someone else". - block reasons: "trust level (observer) asks me to check" -> "You've asked me to check with you before I act"; "From untrusted content" -> "It came from someone else, so I want your OK first". No internal codes leak. - detail labels: origin/confidence/urgency/not-auto-run/refs become "where it's from / written by / how sure I am / why now / why I'm asking you". - source ref: a real sender or a friendly "your calendar"/"a voice note", not an id slice or a filename echo. Default view was already plain; this brings the power view to the same bar so "advanced" doesn't mean "fluent in our nouns". Tests updated to assert the plain wording and that no jargon leaks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the detail expansion uniformly useful and cut the filler:
- add "when" (relative time) — was missing entirely.
- "why now" is explanatory for FYI items too ("Not time-sensitive — just so
you're aware") instead of the meaningless "Normal priority".
- confidence gets a word: "fairly sure (80%)", "very sure (100%)".
- drop the redundant "written by: someone else" (the sender already shows it);
keep "written by: you" only when you authored it (genuinely notable).
- friendly source when there's no sender ("a voice note", "your files").
Also rename the page "Twin Briefing" → "Your briefing" with a plain subtitle,
matching the Home hero — "twin" is our metaphor, not a word a first-timer maps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The critical-urgency reason changed to "Urgent — needs your attention now"; update the assertion from /critical/i to /urgent/i. (Caught by the full test run after the per-file runs passed — the prior commit shipped this red.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
saveRiskAssessment runs `UPDATE candidate_actions ... WHERE id = ?`, but
saveCandidates (the INSERT) ran AFTER it — so the UPDATE hit zero rows, the full
RiskAssessment (overallTier/dimensions) was lost, and only the thin
`{reasoning}` placeholder survived. At approve time the execute-preflight
(getRiskAssessment → parseRiskAssessmentFromRow, which requires overallTier)
then returned null → `risk_assessment_missing`, blocking the ENTIRE
approve→execute path (no action could ever be executed).
Move saveCandidates ahead of the risk-assessment loop so the rows exist when the
UPDATEs land. Adds a regression test asserting saveCandidates is invoked before
the first saveRiskAssessment (via vi.fn invocationCallOrder).
Found via a safe end-to-end execution-stack test (mock adapter + isolated
tokenless user + fake email); verified fixed: fresh fake email → approve →
execution completed via the (mock) adapter, no risk_assessment_missing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…est docs bin/skytwin-test-execution-stack: a repeatable, no-real-side-effects test of the full execution path (ingest → decide → policy/spend/risk gate → approval → execution router → adapter → result). Two safety layers: an isolated TOKENLESS test user (Direct handlers throw at resolveAccessToken before any Google fetch) + USE_MOCK_IRONCLAW (simulated adapter). Spins up its own mock-mode API on a test port; re-runnable; asserts the stack executed and recorded a result. docs/testing-openclaw.md: how to exercise the OpenClaw adapter safely against local Ollama via the openclaw-bridge (verified working: Ollama installed, bridge completes a fake action end-to-end, simulated, nothing real touched). Notes the router trust-ranking caveat (direct outranks openclaw, so isolate it to see OpenClaw execute) and the OPENCLAW_API_URL config. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…reachable The Connect (#/setup) page showed "Not fully synced to IronClaw" + a "Sync to IronClaw" button even when no IronClaw is configured/reachable (the common case), so clicking it failed with a connection error. Gate the sync lookup on ironclawSync.reachable: when IronClaw isn't reachable (no IronClaw, the local mock, or a remote that's down) the sync affordance is hidden entirely — it's an advanced feature that only applies to a real, reachable IronClaw. The execution adapter row still shows its true state (Running / Registered-but-unreachable / Not detected) via renderAdapterStatus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… offline") The Credential Vault page (#/credential-vault) showed "Unable to load vault status. The API may be offline." on every load: the route's getUserId read only req.user?.id (unset under the localhost dev-auth bypass), with none of the req.query['userId'] fallback every other route has — so /credential-vault/status 400'd with "userId is required". Add the standard session→query→body userId fallback (ownership still gated by requireOwnership when a real session exists), and pass userId on the web's init/rotate/lock/unlock POST bodies so those work under bypass too. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ailed An optional, unconfigured execution adapter (IronClaw / OpenClaw) rendered as "Not detected" in the setup page's Live status — which reads like something is broken. For optional engines, that's not a failure: most users never run them (the always-available Direct adapter handles actions). renderAdapterStatus now takes an `optional` flag; an optional adapter that isn't registered shows "Optional — not connected" (calm, muted) instead of "Not detected". Direct still shows "Not detected" if it ever went missing (a real problem). This is the proper fix — correct whether or not a mock IronClaw is running, so no demo crutch is needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the Inbox-Intelligence epic (#484) — 13 specs building the digest read
layer, source-agnostic extractors, the cross-cutting trust layer, UI, and launch
fixtures. Every commit builds + tests green; full suite green (70/70 turbo tasks).
What's implemented (tested logic per commit)
Integration seams left (documented in commits)
Tests
New: SignalText, capability-matrix, deadline, commitment, security-alert,
clustering, coverage, scope-gate, visibility, locale, digest, entity-linking,
seedUpsert, demo-guard, soak-floor. Full monorepo suite green.
Closes #480 #474 #476 #475 #479 #477 #478 #487 #485 #486 #483 #482 #481
🤖 Generated with Claude Code