Skip to content

Latest commit

 

History

History
99 lines (80 loc) · 6.59 KB

File metadata and controls

99 lines (80 loc) · 6.59 KB

Architecture Overview

The non-negotiable reality of Previred

Internalize before writing code. All first-hand observed (2026-08-10), not assumed.

  1. One host, one front controller. Every program is dispatched through https://www.previred.com/wPortal/CtrlFce?reqName=<programa>. There is no per-area host and no public API. The portal is the source of truth; any program name found in devtools is internal and may change without notice.

  2. The URL tells you nothing about your session. An unauthenticated request returns HTTP 200, unredirected, at the same URL, rendering "SU SESIÓN HA EXPIRADO". Authentication is therefore a content judgement, and it is made fail-closed: a positive marker means authenticated, anything unrecognised means not authenticated (ADR-003). This is the single biggest structural difference from the sii project.

  3. This portal moves money. Previred exists to declare and pay cotizaciones previsionales. A read and a payment differ by one string in one query parameter, so the core enforces an allowlist of dispatchable programs at runtime, pinned by test (ADR-004).

  4. The login form must be driven, not synthesised. #token is regenerated per page load and the page's own JS derives web_rut/web_dv. A hand-built POST is both fragile and pointless (ADR-005).

  5. Production-only. Previred URLs are constants in core/config; never hard-code one elsewhere. There is no env switch.

Package layout

previred/
├── packages/
│   ├── core/  (@dostertags/previred-core)  # All domain logic. A Node library.
│   └── cli/   (@dostertags/previred-cli)   # The `previred` command.
└── docs/                                          # ADRs + observed wire contracts

An MCP server is planned but deliberately absent: with only auth tools it would give an assistant nothing to do, and a headed browser launched from a background stdio server is awkward. It arrives with the first real read surface.

The core — a Node library with injectable seams

previred-core is a normal Node library that MAY use Node APIs. What it keeps is a small set of injectable seams so unit tests never touch the real portal, the real filesystem, or the wall clock (ADR-002):

Seam Purpose Node default
PortalDriver / PortalSession Drive the JS-heavy portal Playwright/Chromium
PortalDriver.requestPublic Session-less public fetch (ADR-010) Node fetch — no browser
KeyValueStore Persist the cookies-only session ~/.previred/*.json (ACL-hardened, ADR-007)
AuditSink Append-only JSONL receipt ~/.previred/audit.jsonl
Clock Time + the login poll interval System clock

PortalSession.snapshot() is the seam that makes ADR-003 work: it returns { url, title, text } as plain data, so the classifier is a pure function and the entire auth decision is unit-testable with no browser.

Where the guardrails live

human (CLI)  ──▶  tasks/  ──▶  auth/  ──▶  guard/readOnly(session)  ──▶  PortalDriver
                    │                            │
                    │                            └── reqName allowlist (ADR-004)
                    └── audit receipt, session lifecycle
  • Surfaces call tasks only. Reaching past the task layer bypasses the audit receipt and the session lifecycle.
  • readOnly() is applied centrally — by login on a fresh session and by withSession on a restored one — so no facade can obtain an unguarded session, not even by accident. Because it lives in the core rather than the adapter, the fake session in tests is guarded identically.
  • Only login mints a session. Domain tasks consume one via withSession.

Module map

Module Purpose Status
rut RUT parse / canonicalise / Mod-11 (in-house) Done
config Prod URLs + settings Done
config/reqnames The read-only allowlist + write-token denylist (ADR-004) Done
seams PortalDriver / PortalSession / KeyValueStore / AuditSink / Clock Done
guard readOnly() session decorator + assertNavigable Done
auth/markers Pure fail-closed page classifier (ADR-003) Done — authenticated marker unpinned
auth Headed login (polling), logout, local + live status Done
capture Redacted structural capture (ADR-006) Done
audit Append-only JSONL receipt, secret keys dropped Done
tasks/auth What the CLI calls Done
html/tables In-house HTML table reader (pure, tested) Done
portal/indicadores Public, session-less indicadores previsionales (ADR-010) Done
portal/pagos Historial de pagos / planillas pagadas Planned
portal/nomina Trabajadores — blocked on ADR-008 Blocked
portal/certificados PDF downloads (needs a requestBinary seam) Planned