Croissant Le Doux fdb0aeac5d Close #29: the v1 rate structure types
Price is a rate function, not a scalar (#22), and #15 bars any model from
evaluating one — so the shapes have to be closed AND computable. This replaces
the src/pricing seam with a composition rather than an enumeration: one base
schedule deciding unit price, plus closed modifiers, plus an optional floor.
Three vendors on one small RFP already produced five cells of the alternative
cross-product, and two of the three are the same object wearing different
decoration.

The scenario vocabulary is closed too, and published with the RFP, so every bid
in a field is priced against identical assumptions. A structure reading a
variable the RFP never declared is rejected at submission, while the vendor can
still fix it.

Abstention is typed. `input_missing` is separated from `shape_unsupported`
because Chesapeake's shape is fully supported and its unit price simply is not
in the document — that is recoverable by asking, and collapsing the two throws
away the only actionable fact. Null is never zero, never the vendor's stated
figure, and never a partial sum.

The derivation is the record. A bare scalar can only assert; a protest asks why
a bid ranked where it did. Lines accumulate as exact rationals in bigint and
round once, half-up. A scenario amendment voids derived costs rather than
recomputing them.

Exceptions carry a closed consequence union. Anacostia's EX-3 says the vendor
cannot bid produce at all if declined — a nullable price delta reads that as "no
cost impact", the inverse of the truth, on the most consequential of the three.

Two defects the corpus check caught that review would not have:

  - The band ladder priced two ways depending on a flag. `to` is inclusive, and
    subtracting those bounds in the marginal path lost the unit between bands —
    one in 21,400, invisible in the total and wrong in the record.

  - The published scenario was quantizing the price before the evaluator saw it.
    Twelve basis-point shares moved the seasonal factor by 3e-5, about $19 on a
    $624,000 line. Weights, not shares: a school calendar publishes instructional
    days, an exact integer, and the ratio is taken last.

  npm run check:pricing

Potomac computes $589,570.00 and not the stated $589,970.00; Chesapeake abstains
naming base.unitPrice; Anacostia is $649,792.00 with the calendar and unpriceable
without it; a 3-of-4-line bid withholds its total.

Migrations generated in two passes so drizzle-kit never needed the interactive
rename prompt. No SQL hand-edited.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-04 02:39:20 -04:00
2026-08-03 17:20:47 -04:00
2026-08-03 17:20:47 -04:00
2026-08-03 17:20:47 -04:00
2026-08-03 17:20:47 -04:00
2026-08-03 17:20:47 -04:00
2026-08-03 17:20:47 -04:00

Retail RFP transmission system — v1 layout

A distribution and evaluation layer for retail sourcing. Large retailers issue RFPs and evaluate bids for free; vendors pay for the workspace that helps them win. This repository is the v1 code layout: directory and module structure, the domain model as real schema files, permission boundaries expressed as code, and typed seams where the undecided parts will land.

It is not a running product. It is the skeleton a build can start from without being redesigned.

Decisions live on the issue tracker, indexed by the map. Nearly every file here carries a comment naming the ticket that fixed it — if a choice looks arbitrary, the ticket says why, and what it cost.

The four structural requirements this layout exists to satisfy

Four resolutions handed requirements to a layout that did not exist yet. They are the reason this repository is shaped the way it is.

Requirement Where it lives From
A single database entry point that makes bypassing actor context awkward src/db/index.ts — the pool is not exported #11
A scheduled-job entry point establishing actor context with no request src/scheduler/ #13
An entitlement assertion helper carrying the convenience-not-correctness principle src/auth/entitlement.ts #20, #34
An explicit, reviewable list of privileged RLS bypasses src/db/privileged.ts — three of them #33

Layout

src/
  db/
    index.ts         THE database entry point. withActor() is the only way in.
    actor.ts         ActorContext; SET LOCAL discipline
    privileged.ts    the enumerated RLS bypasses — keep this short
    schema/          the domain model, with RLS policies beside their tables
  auth/
    entitlement.ts   assert per feature; its call sites ARE the paid-feature list
  release/
    matrix.ts        the release matrix, mirrored from SQL so screen and policy agree
  scoring/
    routing.ts       what a model may decide. Two lanes of six.
    rank.ts          ranking over committed scores only
  extraction/
    index.ts         ONE module used twice — RFP and response
    completeness.ts  the second, INDEPENDENT pass. Must share nothing with the above.
  scheduler/         jobs, with actor context and no request
  http/
    context.ts       request -> actor context; entitlement resolved once
    routes/          two capability-scoped nav trees, plus a public pre-auth surface
  pricing/           rate function evaluation — deterministic, no model fallback
  audit/ integration/ helmdocs/ kickoff/ claiming/     seams
migrations/
  0000_*             SQL functions, hand-authored — must precede the policies
  0001_*             generated schema, with RLS policies beside their tables
  0002_*             the release matrix seed, hand-authored
docs/
  seams.md           what is open, and what is deliberately closed
  migrations.md      why policies sit beside tables and the matrix is a data migration

Three invariants worth knowing before you touch anything

Permissions never resolve against an org alone. Always (org, capability, resource). An org may hold both the issuer and vendor capability, and those are two authorization contexts that cannot see each other — a dual-role company's vendor vault is invisible to its own issuing side. This is enforced in Postgres, not in application code, because the invariant that a vendor's bid is never reachable by another vendor is the one failure that would destroy the product's credibility.

Entitlement is not row visibility. RLS answers whose row is this; a lapsed vendor still owns every row they owned yesterday. Policies must not reference entitlement at all. Entitlement gates what a vendor may newly undertake, never what a retailer has already come to rely on.

Bids are sealed until the deadline, then the whole field opens at once. Bid shopping is structurally impossible rather than discouraged. A surprising amount depends on this: it is also what makes mid-flight amendments safe, since the retailer has read nothing and cannot tailor one to advantage a response they have already seen.

Working on a seam

yarn seams

Every seam throws with the ticket that owns it. Replace the body; the signature is already the handoff. If you find yourself changing a seam's types, check the ticket first — the shape usually came out of a resolution rather than out of convenience.

Description
No description provided
Readme 3.7 MiB
Languages
TypeScript 88.9%
Python 7.9%
PLpgSQL 3.2%