# 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](https://gitea.stephenmann.io/christian/helmdocs-proposal-system/issues/1).
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.
