Skip to content

release: v1.0.0-alpha.20 - #46

Merged
cativo23 merged 86 commits into
mainfrom
release/v1.0.0-alpha.20
Sep 28, 2026
Merged

cativo23 merged 86 commits into
mainfrom
release/v1.0.0-alpha.20

Conversation

@cativo23

Copy link
Copy Markdown
Owner

[1.0.0-alpha.20] - 2026-09-28

Features

  • Structured clause-level prompts v2.1 for Basic, Premium, and Forensic tiers, with Forensic-only numeric coverage fields (nota_upgrade, error, coverage percentage)
  • Analysis results UI: category section index with click-to-scroll navigation and keyboard/reduced-motion support, always-visible quoted clause with accessible collapse toggle, per-finding certainty pill, and a combined 0-10 risk score panel with category breakdown bars

Security

  • Suspended users are rejected at both /api/upload and worker job pickup (ADMIN-04), closing the v1.1 audit gap
  • Removed prescriptive legal directives from the Forensic tier's example JSON prompt

Changed

  • Migrated the 3-tier analysis strategy to gpt-6-luna/sol/astra, including the pricing migration
  • Fixed a 1000x unit error in the admin cost dashboard
  • Funnel Stage 2 now sources from a dedicated RPC (get_email_verified_users_in_range) with a warn-on-fallback path
  • Revenue package breakdown now maps from credits_purchased instead of amount-range inference, decoupling it from Stripe price changes
  • Added a proprietary license

Fixed

  • Aligned the Basic tier's risk algorithm with Premium/Forensic for the single-red-finding case
  • Eliminated overlapping puntaje_riesgo boundary values at 3 and 6
  • Worker now detects DOCUMENT_TOO_LARGE explicitly and no longer falls back to a dead nivel_riesgo path

Deploy

  • First production deploy: docker-compose.prod.yml is aligned to the polaris2 Traefik conventions (entrypoints web/websecure, resolver letsencryptresolver), references the CI-built image with a CLARIFY_IMAGE_TAG rollback seam, and adds the full NUXT_-prefixed runtime env contract so the image's baked-in build-time defaults no longer apply in production
  • Fixed a pre-existing production blocker found while proving the deploy locally: pdf-parse's @napi-rs/canvas DOMMatrix polyfill was silently dropped by Nitro's build tracer, crashing every container on boot — pinned as an explicit dependency and copied into the runtime image directly
  • CI Docker actions upgraded for the Node 24 GitHub Actions runner (lands through this release's merge to main)

Deploy notes: This is the first production deploy of Clarify to polaris2. Merging this release into main triggers auto-release.yml (tag + prerelease with these notes) and then ci-cd.yml (build, push to Docker Hub, SSH deploy to polaris2).

cativo23 added 30 commits May 6, 2026 18:27
Phase 10 closes 4 audit gaps (ADMIN-04, DEPLOY-01, ADMIN-01, ADMIN-02).
Context locks decisions per audit evidence; planner runs next.
Phase 10 closes 4 v1.1 audit gaps (ADMIN-04, ADMIN-01, DEPLOY-01, ADMIN-02).
Plans verified by gsd-plan-checker; all 4 require IDs covered.
- New migration creates SECURITY DEFINER SQL function joining
  public.users to auth.users on email_confirmed_at within a
  timestamptz range, matching funnel.get.ts call signature
- Parameterized inputs only (no dynamic SQL); search_path pinned
  to public, auth; EXECUTE granted to authenticated + service_role
- Closes ADMIN-01 funnel Stage 2 fabricated 100% verification rate
- console.warn fires when get_email_verified_users_in_range RPC
  is missing, with actionable migration ID (20260506000001) so
  operators can self-diagnose the fabricated 100% rate symptom
- Fallback path preserved for resilience (Stage 2 == Stage 1
  is now a known/logged degradation, not silent failure)
- 6 tests covering: false→pass, true→403, constant export, auto-resolve userId, no-user noop, fail-open on lookup error
- RED phase for ADMIN-04 suspension enforcement
…tant

- Throws 403 with data.code='ACCOUNT_SUSPENDED' when users.is_suspended=true
- Auto-resolves userId from session when omitted; returns silently for unauth
- Fails open on infra error (matches admin_emails defensive pattern)
- Stub createError global in test so thrown errors retain statusCode/data
- ADMIN-04 (BLOCKER) groundwork — Task 1/3
- Mocks @supabase/supabase-js with chainable thenable stubs to
  drive funnel.get.ts through both happy-path and degraded-path
- Asserts Stage 1 == 10, Stage 2 == 5, Stage 2 < Stage 1 with
  mixed seed (proves ADMIN-01 fabricated 100% rate is closed)
- Asserts console.warn fires with migration ID 20260506000001
  when RPC errors so operators can self-diagnose
- Test placed under tests/unit/server/ to match vitest project
  glob (tests/unit/**) — plan path tests/server/ would not be
  discovered by the runner
- Test 5: suspended user gets 403 ACCOUNT_SUSPENDED
- Test 6: non-suspended user passes the gate
- Test 7: gate runs before readMultipartFormData (no file parsed)
- RED phase for Task 2 of ADMIN-04
Records ADMIN-01 closure: migration file, handler warn, smoke
test. Notes [BLOCKING] db:migrate and vitest run deferred to
post-merge operator steps (worktree env constraints).
- Suspended users get 403 ACCOUNT_SUSPENDED before any file work
- Gate runs after auth, before readMultipartFormData (verified by Test 7)
- Defense-in-depth: stops upload even if BullMQ enqueue is reached later
- All 9 suspension tests pass (Tests 1-7 + 4b + fail-open)
- Logs pre-existing typecheck errors as out-of-scope deferred items
- Tests 8a/8b/8c: isUserSuspended helper behavior (true/false/error)
- Test 9: worker source wires gate before downloadContractFile
- Test 10: worker emits status=failed/account_suspended + early return
- RED phase for Task 3 of ADMIN-04
…N-04)

- Adds isUserSuspended() helper to worker-supabase scoped client
- Worker checks at job pickup (not enqueue) — covers pre-suspension queued jobs
- Failure mode: status=failed, error_message='account_suspended', no credit debit
- Early return BEFORE downloadContractFile / analyzeContract — no work done
- Helper fails open on infra error to avoid orphaning legitimate analyses
- All 14 suspension tests pass
- 3 tasks, 6 commits (3 RED + 3 GREEN), 14 tests
- ADMIN-04 (BLOCKER) closed: helper + upload gate + worker gate
- Documents 3 Rule-3 deviations (test path, runner cwd, createError stub)
- Add ADMIN_EMAIL (admin gate, dashboards inaccessible if missing)
- Add STRIPE_PRICE_ID_{5,10,25}_CREDITS (prevent wrong-product billing fallback)
- Add NODE_ENV, ALLOWED_REDIRECT_ORIGINS (production hardening)
- Add DISABLE_WORKER (worker topology toggle)
- Each key documents consequence-if-missing; placeholders kept obviously fake

Closes DEPLOY-01 contract gap (runtime fallback removal deferred).
Add STRIPE_PRICE_ID_{5,10,25}_CREDITS, ALLOWED_REDIRECT_ORIGINS,
DISABLE_WORKER, NODE_ENV to the required-env list and point readers
to .env.example for per-key consequences.
Capture verification results, decisions, and DEPLOY-01 closure for
the v1.1 env-key contract update.
Replace amount-band inference (4.49–5.49 → 5 credits, etc.) in
admin/revenue.get.ts with a deterministic mapping on
credit_transactions.credits_purchased. Stripe price changes in
stripe-client.ts can no longer silently shift the by_package buckets.
Legacy rows with NULL credits_purchased bucket as "other".

Closes ADMIN-02 (WARNING) per v1.1-MILESTONE-AUDIT.md.
5 vitest cases pin the credits_purchased-based mapping in
admin/revenue.get.ts. Test 2 is load-bearing: identical
credits_purchased with mutated amounts (99/199/299) produces an
identical by_package shape — proving Stripe price changes cannot
silently drift the breakdown again.

Path: tests/unit/server/ (vitest config restricts discovery to
tests/unit/** and tests/integration/**; plan-suggested tests/server/
would not be discovered — Rule 3 deviation).
ADMIN-02 closed: by_package now derives from credits_purchased; 5/5
vitest invariance tests pin the fix.
- Phase 06 UAT: all 16 tests passed (admin analytics + user management)
- Phase 03 VERIFICATION: browser-verified date filter, limpiar filtros, cross-user denial
- Phase 07: production tests formally deferred (requires live clarify.cativo.dev)
- STATE.md: deferred items documented, Phase 10 stats added
- MILESTONES.md: Phase 10 accomplishments appended to v1.1 entry
- ROADMAP.md: Phase 10 added to v1.1 details section
- v1.1-admin-deploy-ROADMAP.md: Phase 10 appended as post-ship closure
- RETROSPECTIVE.md: created with v1.0 + v1.1 entries and cross-milestone trends
Milestone v2.0 focus: clause navigation (sidebar, expandable cards,
AI-quoted source text), confidence signals (risk score, per-clause
certainty, coverage %), and prompt improvements to emit structured
clause-level data. Comparison mode deferred to v2.1.
Renders hallazgo.confianza as an inline "Confianza Alta/Media/Baja"
pill in the card title row, dot-less per AnalysisStatusBadge
precedent. Covers CONF-02/D-07.
Moves cita_textual out of the collapsible details section so the
AI-quoted source clause is verifiable without a click (RESULT-03,
D-05). Details toggle now carries aria-expanded and type="button";
quote-only findings no longer show an empty toggle (D-06).
Moves the findings grid out of the 879-line report page into
FindingsSection, giving every card a stable hallazgo-{index} anchor
(the scroll-target contract plan 12-02's section index consumes) and
rendering the approved zero-hallazgos empty state.
- groupFindingsByCategory + CategoryIndexEntry group findings by categoria_riesgo in first-appearance order
- ReportSidebar renders Secciones nav, scrolls to each category's first finding card
- FindingsSection hosts the sidebar in a sticky two-column layout beside the cards
… index

- scrollToEntry respects prefers-reduced-motion and focuses the target card with preventScroll
- card wrappers are programmatic focus targets (tabindex=-1, no outline jump)
- integration test proves an index click reaches the real anchored FindingsSection card
- composables/useRiskScore.ts: getScoreRisk bands puntaje_riesgo 0-10 into Riesgo Bajo/Medio/Alto (clamped, rounded)
- components/analysis/RiskScorePanel.vue: score + risk label + Críticos/Alertas/Seguros pills + Cobertura, reusing the old dark card chrome
- pages/analyze/[id].vue: old inline Métricas card replaced by RiskScorePanel bound to real summary fields
- buildBreakdownBars sorts desglose_riesgo entries by count, sizes fills relative to the max with a 6% floor
- RiskScorePanel renders one bar per category between the score header and Cobertura, accent fill on slate-800 track
- pages/analyze/[id].vue passes summary.desglose_riesgo through to the panel
…xecution

Records the goal-backward verification of phase 12 (6/6 requirements
confirmed against real code, not just SUMMARY claims) and the branching
decision needed to unblock execution: this project has no phase-12
feature branch, so plans committed straight to develop as prior phase-12
commits already did.
…x bug

gpt-5 and gpt-5-mini retire from the OpenAI API on 2026-12-11, so the
premium/forensic tiers needed a replacement before then; basic tier moved
too since gpt-6-luna is both cheaper and newer than gpt-4o-mini. Picked
gpt-6-astra for forensic over sharing gpt-6-sol with premium to keep the
tier meaningfully differentiated, after checking real margins stay
healthy either way (~70% vs ~94% at worst-case token volume).

Along the way found GPT-6 rejects the API call outright if temperature is
sent while reasoning is on (gpt-5 merely ignored it) — fixed the reasoning-
model branch to omit it entirely rather than defaulting it.

Also fixed an unrelated but real bug noticed while updating pricing: the
admin cost dashboard divided token counts by 1000 before applying
pricing_tables' rates, but those rates are already real per-token USD
values — silently under-reporting AI cost by 1000x. Extracted the
calculation into cost-calculator.ts so it's actually unit-testable.
- DNS: clarify.cativo.dev and cativo.dev both resolve to 167.235.52.161
- Ports: 52222 open, 22 closed/timeout on cativo.dev
- Server: traefik healthy, space-server_web network present, entrypoint/resolver names confirmed, no clarify containers yet, 24G free
- Created /home/cativo23/deploy/clarify-deploy (mode 700) with .env.example template copied in (mode 644)
- Recorded GitHub secret names/dates and authorized-key fingerprints for Carlos's key check in Task 2
…survives Nitro tracing

RED baseline (Task 1, pre-existing production blocker discovered during
research): the published cativo23/clarify:latest (v1.0.0-alpha.19) crashes
on boot in both app and worker containers with
`ReferenceError: DOMMatrix is not defined` at pdfjs-dist/legacy/build/pdf.mjs.

Root cause: server/utils/pdf-parser.ts statically imports pdf-parse, which
imports pdfjs-dist, which wraps `require("@napi-rs/canvas")` in a try/catch
to polyfill DOMMatrix/ImageData/Path2D in Node. pdf-parse@2.4.5 already
depends on @napi-rs/canvas@0.1.80 as a regular (non-optional) dependency,
and `npm ci` on the Alpine deps stage correctly installs both the -gnu and
-musl native binaries. The break is downstream: Nitro's node-file-trace
does not follow that try/catch-wrapped require() when building `.output`,
so `@napi-rs/canvas` is silently absent from the runner image even though
it installs fine in the deps/builder stages, and the runner stage only
copies `.output` + package.json (no node_modules).

Fix (verified against a locally rebuilt image, not the broken published
tag): pin @napi-rs/canvas as an explicit top-level dependency (keeps it
from disappearing if pdf-parse's own dependency tree ever changes), and
explicitly COPY the resolved @napi-rs/canvas + @napi-rs/canvas-linux-x64-musl
packages from the builder stage into .output/server/node_modules in the
Dockerfile runner stage, bypassing the tracing gap.

package-lock.json also picks up a pre-existing, unrelated top-level
"version" field correction (1.0.0-alpha.10.2 -> 1.0.0-alpha.18) as an
unavoidable side effect of running npm install to add the pin — npm
always re-syncs that field to package.json's version on any install.

RED (published v1.0.0-alpha.19 image, plain REDIS_HOST):
  Warning: Cannot load "@napi-rs/canvas" package: "Error: Cannot find module '@napi-rs/canvas'"
  ReferenceError: DOMMatrix is not defined
    at pdfjs-dist/legacy/build/pdf.mjs:15620:22

GREEN (local rebuild with this fix):
  DOMMatrix type: function, ImageData type: function
  Container stays up; no crash on boot.
…ime env

Three root causes, each confirmed against the host or the code this session:

1. Routers never attached: the polaris2 Traefik v3.6 static config defines
   entrypoints web/websecure and resolver letsencryptresolver — the compose
   labels referenced entrypoints=http/https and certresolver=letsencrypt,
   none of which exist there, so the router would never match. The generic
   https-redirect middleware and the separate clarify-http router are also
   removed: the web entrypoint already redirects globally to websecure, and
   a generically-named middleware defined via Docker labels can collide
   with another project on the shared Traefik instance.

2. No image to pull: app/worker only had a build: block. The deploy job
   copies just docker-compose.prod.yml to the server, so `docker compose
   pull` had nothing to pull. Added `image: cativo23/clarify:${CLARIFY_IMAGE_TAG:-latest}`
   to both services (build: kept for the documented manual --build path),
   giving production a rollback seam via CLARIFY_IMAGE_TAG.

3. Runtime env ignored: the image is built without env, so nuxt.config.ts's
   process.env defaults (redisHost "localhost", openaiApiKey "", etc.) are
   frozen at build time. Nuxt only overrides runtimeConfig at runtime via
   NUXT_-prefixed vars. Added the full NUXT_ block (Redis, OpenAI, Supabase
   service key, Stripe, admin email, public base URL/publishable key/Supabase
   public url+key) to both app and worker, mapped from the same plain names
   the server .env file already uses, plus plain pass-throughs the code
   reads via process.env that the compose file never forwarded (ADMIN_EMAIL,
   ALLOWED_REDIRECT_ORIGINS, STRIPE_PRICE_ID_*).

RED baseline (published v1.0.0-alpha.19 image + this compose file's OLD
runtime env contract, plain REDIS_HOST only — recorded against a locally
rebuilt image carrying the @napi-rs/canvas fix from the prior commit, since
the published tag cannot boot at all):
  /api/health hangs / never reports "redis":"connected" — runtimeConfig.redisHost
  is still the build-time "localhost" default; ioredis retries against
  127.0.0.1:6379 inside the container.

GREEN (this compose file, `docker compose -f docker-compose.prod.yml up -d
--no-build`, fake prodcheck Supabase values, CI-built image with the
@napi-rs/canvas fix):
  {"status":"ok","services":{"database":"unknown","redis":"connected","ai":"active"}, ...}
  worker log: "[Worker] Analysis worker plugin initialized"
  worker log: zero occurrences of 127.0.0.1:6379

.afm probe (informational, for plan 13-05): 14 .afm files found under
.output in the rebuilt image — pdfkit's font metrics survived Nitro tracing.
…unbook

auto-release.yml parses GitHub Release notes by an exact CHANGELOG.md
version-heading match, and the previous top entry never accounted for
Phase 10-12 work or the deploy fixes in this release — without this
section the release would ship with placeholder "Release $TAG" notes.

DEPLOY.md was also wrong about the SSH port (every documented command used
the default port 22, which is closed on polaris2 — only 52222 works) and
said nothing about the NUXT_ runtime-env contract or the CLARIFY_IMAGE_TAG
rollback seam this release's compose fix introduces, both of which the next
person deploying this needs to know.

Version continues the existing v1.0.0-alpha.N tag sequence per
13-CONTEXT.md's Claude's Discretion note (git tag -l shows v1.0.0-alpha.19
as the latest published tag; no v1.1/v2.0 tag was ever cut despite the
milestone names).
- All 7 required GitHub secrets confirmed present: DOCKER_USERNAME, DOCKER_PASSWORD, RELEASE_PAT, SSH_HOST, SSH_PORT, SSH_USERNAME, SSH_PRIVATE_KEY
- SSH_HOST=cativo.dev, SSH_PORT=52222 set (and SSH_PRIVATE_KEY rotated) as part of the orchestrator/Carlos key-rotation handoff
- Server deploy dir holds exactly one non-empty mode-600 file (.env, 1542 bytes) — verified by metadata only, contents never read
- All Wave 0 deploy prerequisites (RESEARCH P1, P2, P3, P6) now resolved
Deploy prerequisites resolved: SSH_HOST/SSH_PORT set, deploy key
rotated, server deploy directory scaffolded with production .env in
place, and Supabase Auth URLs pointed at clarify.cativo.dev.
@cativo23
cativo23 merged commit e248cc7 into main Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant