Assemble the v1 code layout #35
Notifications
Due Date
No due date set.
Blocks
Depends on
#36 Define the v1 component system and visual identity
christian/helmdocs-proposal-system
#14 Specify the two-party data release rules
christian/helmdocs-proposal-system
Reference: christian/helmdocs-proposal-system#35
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Question
The destination of this map. Assemble the v1 code layout in this repo: the directory and module
structure, the domain model as real schema files, the permission boundaries expressed as code, and
stubs at the seams where undecided parts will land. Not a running product.
Structural requirements accumulated by earlier resolutions, all of which the layout must satisfy:
path must run inside a transaction that has issued
SET LOCAL app.actor_id / app.org_id / app.capability. From the tenancy decision.recompute and the eligibility read model. From the vault and entitlement decisions.
may newly undertake and never what a retailer has come to rely on. Its call sites are the
paid-feature list. From entitlement and pricing.
propagation. From the eligibility decision.
From the app framework.
and vendor document to answer set. From the app framework.
Also decide what a seam looks like concretely — an interface with a throwing stub, an empty module
with a doc comment, or a typed boundary with no implementation — since six tickets will land in
these and the answer determines whether they land cleanly.
Blocked on data release: the permission boundaries are half of what this layout exists to express,
and RLS policies cannot be stubbed convincingly without them.
Parent: #1
christian referenced this issue2026-08-03 16:47:07 +00:00
Resolution
Committed:
da74362, 46 files onmaster. The map has reached its destination.Two decisions, then the build, then three things the build itself found.
1. A seam is real types plus a throwing stub that names its ticket
The signature is the handoff. A ticket lands by replacing a body, not by
designing an interface — which matters because six tickets land in adjacent
seams and would otherwise invent incompatible shapes.
Why not types-only. Nothing could be wired up or run, so the layout could
never be sanity-checked, and #37's prototype would have nothing to mock against.
Why not empty modules with doc comments. The hard part of a handoff is the
signature, and that is exactly what it defers.
yarn seamsgreps the todo list out of the code.docs/seams.mdalso records what is deliberately not a seam — the database entry point, the
privileged-path list, the release matrix, the routing table. Changing those means
reopening a closed ticket rather than filling in a blank.
2. Ownership splits on what Drizzle can diff
pgPolicybeside the tableNothing generated is ever hand-edited. Policies sit in the same file as the
table they guard, because a new table shipping with no policy is the failure
that matters and RLS default-deny only helps if it was enabled on that table.
The matrix is seeded by a
--custommigration because it is versioned, neverupdated — a rule change is an INSERT at a new version.
What was built
Every one of the four outstanding structural requirements is now satisfied and
pointed at from the README: the single database entry point (#11), the
scheduler entry point (#13), the entitlement assertion helper (#20, #34),
and the enumerated privileged-path list (#33).
What the build found
1. An ordering cycle between Drizzle's policies and our functions. Drizzle
emits a policy in the same migration as its table, so
class_releasedmustalready exist — but the functions read
solicitation,participationandrelease_matrix, which that same migration creates.Resolved by making the functions plpgsql, which defers body validation to
first call, and giving them
textsignatures rather than therelease_levelenum, which does not exist yet either. Functions are migration
0000, schema andpolicies
0001, the matrix seed0002.The cost is real and belongs to #21: a typo inside a function body is not
caught at migration time. It surfaces the first time a policy runs — and
because policy failures present as empty results, that is precisely the failure
mode #11 flagged as hard to diagnose. Database-level tests over these
functions are not optional.
2. drizzle-kit below 0.31 cannot resolve
.jsextensions in a TS schema.0.30.6 fails with MODULE_NOT_FOUND on the ESM import style the rest of the stack
uses; 0.31.10 works. Pinned, and worth knowing before anyone downgrades.
3.
pgPolicyworks. It was the flagged risk on decision 2 and it holds:generation produces RLS on 18 tables and 6 policies calling the
release-level functions, with the
sqltemplate passing class names throughuntouched.
Verification
tsc --noEmitclean.drizzle-kit generateproduces0001_initial.sqlwith RLS enabled on 18tables and 6 policies.
are plpgsql, so their bodies are unvalidated until first call — see finding 1.
A migration dry-run against a real database is the obvious next check and is
the kind of thing #30's corpus work will force anyway.
Consequences
throwing stub, so components are built against live signatures rather than
guesses.
landing in a seam, v1 content filling a vocabulary, or the demo chain. Nothing
left on the map redesigns the skeleton.