Commit Graph

7 Commits

Author SHA1 Message Date
11da9abed5 feat: AI trail tab — who did what, which tools, what it reasoned
The console showed the agent's OUTPUT but never showed that an agent produced
it. On a screen full of numbers a viewer cannot tell which came from a model,
which from a rule, and which from a person. This makes it visible.

Second tab on the file: "AI trail", beside "Evidence & decision".

  * WHO DID WHAT — the real audit trail from /view/audit, one row per
    completed step, each attributed. "Credit Assessor AI" appears there
    because the agent submitted the activity under its OWN JWT and the
    platform recorded it, not because this component says so. Agent rows get
    a bot glyph and a violet chip; human rows get a person and no chip, plus
    a count: "1 of 6 by an agent".

  * TOOLS IT CALLS — the three deterministic SQL tools, named, each with the
    figures it returns for this file shown against the threshold it was
    tested on. Spread beyond tolerance goes amber.

  * WHAT IT REASONED — the narrative, citations and deviation flag verbatim,
    violet as everywhere else, closing with the explicit statement that it
    computed no ratio and chose no outcome.

Deliberately NOT shown: the per-step trace (individual model calls, raw tool
payloads, durations, tokens, cost). It exists in
aiemployee.tbl_ai_trace_events and is served by
/ai-employee/monitor/instances/:id/trace, but that endpoint requires an
ORG-SCOPED token and the app JWT carries no org claim — it answers 403 "no
organization scope on this account" for every user of this console. A footnote
says so and points at Studio. Rendering a plausible waterfall from guesses
would be the one dishonest thing on a screen whose whole purpose is showing
what actually happened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 19:51:28 +05:30
8e846d6df3 fix: send datetime fields as RFC3339, not the picker's local string
Release Disbursal failed with:

    validation failed for activity hdfc-act-disburse: disbursed_at(type)

An HTML datetime-local input yields "2026-08-17T18:31" — minute precision,
no seconds, no timezone. The workflow validates the field as a timestamp and
rejects that shape. The API smoke test passed because it sent RFC3339
("2026-08-17T11:20:00Z"), so the gap only ever showed through the UI.

Converts local wall-clock to RFC3339 UTC on the way out and back again on the
way in. Storing the ISO string and slicing it for display would have shown the
UTC time in the picker — 13:01 for an 18:31 selection — which reads as the
control losing the input.

An unparseable value is handed back untouched rather than blanked, so a value
the server sent stays visible even if we misread it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:33:35 +05:30
046c4951db fix: resolve roles from the server, and fail open when they are unknown
Every action button vanished on every file. The header read "No role" for a
user who has one.

CAUSE: the JWT carries no roles claim — only user_id / name / email / sub /
exp / iat. Roles live in the login RESPONSE BODY and at GET /usr/me, nowhere
else. The provider restored a session by decoding the token, so any page
refresh produced a user with an empty role list, and actionsFor() filtered
every action away.

Two fixes, and the second matters more than the first:

  * ZinoProvider now fetches /usr/me whenever it has a token but no roles, so
    a restored session recovers the real permission set. A fresh login already
    has them from the login response, so this costs no extra round trip there.

  * actionsFor() FAILS OPEN. With no roles known it returns every action the
    stage allows and lets the server refuse what it must. There is a real
    window where the console cannot know permissions — a restored session
    before /usr/me answers, or that call failing — and filtering on an empty
    list in that window hid every button and made a working app look broken.
    The server's refusal is safe and legible ("user 29533 does not have
    permission for activity ..."), and the form shows it verbatim. Hiding a
    button the user needs is the worse failure, because nothing on screen
    explains it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:09:47 +05:30
0b4c5576e4 feat: OCR-driven capture and document upload 2026-08-17 17:27:54 +05:30
602b570064 fix(brand): the logo, the typeface and the palette are HDFC's actual ones
The console was wearing an approximation of the bank. Everything here was
read off HDFC Bank's own production assets instead.

The mark was inside out. It had been drawn as a blue square with a red
square inset; HDFC's is the reverse — a red ground, a white channel cross
splitting it into four corner blocks, and a small blue square at the
centre. Every coordinate now comes verbatim from the SVG the bank serves
in its own site header, including the "HDFC BANK" wordmark outlines, so
the lockup is the bank's rather than a redrawing of it. The favicon in
index.html carried the same inversion and is fixed with the same numbers.

Open Sans replaces the Segoe UI stack. hdfcbank.com preloads OpenSans
Regular / SemiBold / Bold / ExtraBold and sets its whole site in it. It is
self-hosted through @fontsource-variable/open-sans rather than pulled from
a CDN, for the reason the API URL is read at runtime: this build gets
promoted between environments and dropped behind bank proxies, and a font
that sometimes fails to arrive is a product that sometimes looks like
someone else's.

The header stops being a navy band. HDFC puts the full lockup on a light
ground on every one of its own digital surfaces, and a navy header made
the real logo unusable — the lockup's own blue bar would have dissolved
into it. The blue now does its work as the rule under the header, the
active queue and the primary button, which is how the bank uses it. The
lockup's 3px white keyline is the bank's own, so it sits correctly on the
panel in dark mode too and no reversed variant is needed.

Palette, grounded rather than invented: the page canvas is #F0F6FB, the
tint hdfcbank.com uses behind content; the good/ tokens sit on the hue of
the bank's #00B947 instead of a generic emerald, darkened to #00713D
because #00B947 is 2.6:1 on white and unreadable as a label. The two brand
hexes were already right and are unchanged.

Also closes three AA failures the palette claimed it did not have —
--color-faint in light, and --color-faint / --color-brand in dark, each
below 4.5:1 on --color-panel3. White on --color-signal (4.31:1) is left
alone knowingly: darkening HDFC red for the Decline button would have made
it a different red from the logo beside it.
2026-08-17 16:55:50 +05:30
8949a1b0d7 chore: trigger first frontgen build 2026-08-17 15:59:41 +05:30
2e8cc580ac feat: HDFC Loan Desk operator console
Custom frontend for the HDFC loan-origination demo (dev, org 83 / app 524,
workflow hdfc_wf_loan). MSME and personal loan files: agents assemble the
evidence, rules compute capacity, credit decides.

Stack and platform contract follow the Flight-Disruption-Management console,
which runs against this same cluster:

  * API client carries its predecessor's hard-won notes — instance_id goes
    over the wire as a NUMBER (a quoted id fails the int64 decode), org_id as
    a STRING, record views answer under `data` OR `records`, and audit rows
    need the TRIGGER_ prefix filtered or every activity appears three times.
  * VITE_ZINO_API_URL is read at RUNTIME from the config.js the server writes
    at placement, never compiled in, so one artifact is promoted between
    environments unchanged. Missing config fails loudly.
  * base: './' plus a router basename from <base href>, so one build serves
    any mount path.
  * Forms are read from the LIVE activity schema — no field definitions in
    this repo. Add a field in Studio, redeploy, it appears.

Written for this app:

  * EvidencePanel, the centrepiece. Three independent income sources side by
    side with the widest pair marked; every computed ratio shown against the
    threshold it was tested on; and a visible line between what a rule
    computed and what a model wrote (rules are never violet).
  * Queues are the one record view filtered server-side on
    current_state_name — the sidebar is the pipeline. Income variance is
    surfaced in the list, not only on the file.
  * Application 360 with the evidence panel above the offer and the sanction,
    so the screen reads in the order the decision was made.
  * HDFC palette where red is never decoration: the logo block and declines
    only. The referred queue's amber is the only amber in the pipeline.

Known gaps, documented in README rather than hidden: OCR uploads render as a
visible pending row instead of a control that pretends to work, and Credit
Assessment is still performed by a human pending the agent wiring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 15:42:55 +05:30