# DECISIONS One entry per decision that is not already obvious from the specs. Newest phase last. Markers used in the code: - `// SPEC-GAP:` the specs did not settle this and a choice was made here. - `// TODO-TAX-VERIFY:` a tax rule that RULES.md does not state. Never invented, always flagged. --- ## Blocking gaps in the source material ### RULES.md was missing during phase 0, supplied for phase 1 `packages/rules` shipped phase 0 with only `RULES_VERSION` and the phase 0 seed stopped short of profiles, because the RUC check digit and the deadline digit are RULES.md algorithms. Both are implemented in phase 1 and the seed can now be completed. **Resolved.** ### `boneyard` and `canvas-ui` are not the packages the prompt means Both names resolve on npm to unrelated projects: `boneyard@0.1.4` is a 2015 Backbone "architectural toolkit", `canvas-ui@0.2.3` is a Mesosphere Bootstrap theme. Neither does skeleton loading or canvas effects. Neither is needed before phase 7. Plan unless corrected: keep the *behaviour* the prompt specifies (skeletons on every content load and never a spinner; canvas effects in exactly two places, degrading gracefully) behind a single `` component and a single effect component, so swapping in the real library later is a one file change. **Please confirm the intended packages before phase 7.** ### Neither named skill is installed `ponytail` and `ui-ux-pro-max-skill` are not available in this environment. Their stated intent was applied by hand: nothing speculative, no unused configuration, and FLOWS.md section 1 as the design constraint. --- ## Phase 0 ### Migrations: better-auth generates its own four tables, we own the rest `src/db/migrator.ts` runs two ordered steps: better-auth's `getMigrations()` creates and updates `user`, `session`, `account` and `verification`, then the Kysely migrator applies `src/db/migrations`. Delegating the auth tables keeps them in step with the installed better-auth version and emits correct DDL for both dialects, so no hand written dialect SQL was needed for them. Upgrading better-auth in a way that adds a column means adding a migration that calls the same generator again. ### The whole schema ships in migration `001_core`, not phase by phase Every table in SPEC.md section 5 is created now. The schema is fully specified and stable; splitting it across phases would produce a pile of migration files and no benefit before release. Later phases add modules on top, not tables. ### Timestamps and money are portable by construction Timestamps are ISO-8601 text and dates are `YYYY-MM-DD` text in both dialects: they sort chronologically as strings, so no dialect specific date type or comparison is needed anywhere. Money is `bigint`, because guaranies pass int4 at about Gs. 2.100.000.000, and `apps/api/src/db/postgres.ts` registers an int8 parser that returns a number and throws outside the safe integer range. ### `/api` is proxied by a route handler, not a Next rewrite SPEC-GAP against SPEC.md section 2, which specifies Next rewrites. Next bakes rewrite destinations into the build manifest, so `API_INTERNAL_URL` would become a build time value and one image could not serve both compose and k8s. `apps/web/app/api/[...path]/route.ts` forwards at request time instead. The single origin model is unchanged: the browser only ever sees the web origin, cookies stay first party, and there is still no CORS anywhere. ### Workspace packages ship TypeScript source with no build step `packages/*` have no `dist`. The web app lists them in `transpilePackages` and tsup bundles them into the API. Their relative imports are extensionless, because Turbopack does not rewrite a `.js` specifier onto a `.ts` source file. ### The es catalog is split from the strings COPY.md does not define `catalogs/es.ts` is generated from COPY.md and is verbatim; `copy-parity.test.ts` re-derives it from `docs/COPY.md` on every run and fails on any drift, in either direction. Strings the product needs that COPY.md does not list (the seven error envelope messages, three sign in labels, the language switcher) live in `catalogs/es.extra.ts` and follow the tone rules in COPY.md section 0. `es` is the merge of the two. ### `decl.approve` is both a message and a namespace COPY.md defines `decl.approve` (the button) alongside `decl.approve.confirmTitle`. A nested message tree cannot hold both, and next-intl walks a nested tree. `unflatten` moves such a message to a reserved `_` child and `resolveKey` maps the key for callers, so components still address messages by their COPY.md key. `apps/web/src/i18n/t.ts` is the wrapper; it is computed from the catalog, so a future collision is handled without another change. ### Locale negotiation on `/` next-intl's default detection is left on: a browser asking for English lands on `/en`, anything else falls back to `es`. The en catalog exists for expats and international users (COPY.md section 0-EN), which is exactly the population whose browser is in English. An explicit choice through the switcher always wins and is in the URL. ### SQLite refuses `JOBS_INLINE=false` Implemented literally as SPEC.md section 15 instructs, which is narrower than the mode matrix in the same section: that table allows "SQLite, 1 dedicated worker" under Split small. Two pollers cannot be made safe against a single writer, so the boot check wins and SQLite stays single process. Worth reconciling in SPEC.md. ### Deferred to the phase that needs them, deliberately - `RateLimiter`: SPEC.md section 6 specifies a token bucket, but the first endpoint with a stated limit is `GET /lookup/ruc/:number` in phase 2. better-auth's own rate limiting covers the auth routes until then. - DTO schemas in `packages/contracts`: enums, the error envelope, the client and `ProfileDto` exist because phase 0 uses them. The rest arrive with their endpoints. - Storage in `/readyz`: the check covers the database and pending migrations. The storage driver probe is added in phase 3 with the driver. ### Smaller choices - TypeScript 5.9, not 7.x: `typescript-eslint@8` declares `typescript <6.1.0`. - `better-sqlite3` is kept out of `onlyBuiltDependencies`: it ships prebuilt binaries, so letting pnpm run the implicit `node-gyp rebuild` would compile it for nothing and force a toolchain into the image. - The language switcher is a native `