The destination of the wayfinder map: directory and module structure, the domain model as real schema files, permission boundaries expressed as code, and typed seams where the undecided parts land. Satisfies the four structural requirements earlier resolutions handed to a layout that did not exist: - a single database entry point (#11) — the pool is not exported - a scheduler entry point with actor context and no request (#13) - an entitlement assertion helper (#20, #34) — its call sites are the paid-feature list - an enumerated list of privileged RLS bypasses (#33) — three of them Seams carry real types and throw with the ticket that owns them, so the skeleton wires up and fails only where a decision is genuinely missing. Verified: tsc --noEmit clean; drizzle-kit generate produces RLS on 18 tables and 6 policies calling the release-level functions. Resolves #35 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94 lines
4.4 KiB
Markdown
94 lines
4.4 KiB
Markdown
# 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.
|