Repository navigation
release: v1.0.0-alpha.20 - #46
Merged
Merged
Conversation
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.
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.
[1.0.0-alpha.20] - 2026-09-28
Features
nota_upgrade,error, coverage percentage)Security
/api/uploadand worker job pickup (ADMIN-04), closing the v1.1 audit gapChanged
get_email_verified_users_in_range) with a warn-on-fallback pathcredits_purchasedinstead of amount-range inference, decoupling it from Stripe price changesFixed
puntaje_riesgoboundary values at 3 and 6DOCUMENT_TOO_LARGEexplicitly and no longer falls back to a deadnivel_riesgopathDeploy
docker-compose.prod.ymlis aligned to the polaris2 Traefik conventions (entrypointsweb/websecure, resolverletsencryptresolver), references the CI-built image with aCLARIFY_IMAGE_TAGrollback seam, and adds the fullNUXT_-prefixed runtime env contract so the image's baked-in build-time defaults no longer apply in productionpdf-parse's@napi-rs/canvasDOMMatrix 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 directlymain)Deploy notes: This is the first production deploy of Clarify to polaris2. Merging this release into
maintriggersauto-release.yml(tag + prerelease with these notes) and thenci-cd.yml(build, push to Docker Hub, SSH deploy to polaris2).