Task: Pin copied facts with composite keys
Table of Contents
This page documents a task in the Teach the code generator the trade shape story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
Let an instrument copy its trade's type and party, and a booking its netting set's counterparty and party, without the copies being able to drift.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Teach the code generator the trade shape |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-03 |
3. Acceptance
- A model can declare named unique keys over several columns.
- A foreign key can name several columns and the target columns they match.
- A test shows a row rejected when its copied value disagrees with the target's.
4. Plan
- Named unique keys already exist: a row of the SQL
** Indexestable withuniquetrue and no predicate is a key PostgreSQL accepts as a foreign key target. No new declaration is needed. - Composite keys go in a new
** Pinned keystable under* SQL, not in* Foreign keys. Each* Foreign keysentry is keyed by one column and feeds C++ consumers (test seeding, list-by queries, group prefixes); a pin is a SQL-only rule. - The source decides the check: a constraint for an immutable source, a trigger check on the current row for a temporal one.
5. Measurements
Every number this task claims about a tree, with the command that produced it
and the commit it was measured at. A number without both is not a measurement,
because a clean-up record describes a tree and trunk moves under a branch: a
consumer count, a gate's component count and a suite's size all change with no
code changing, and the reader of a bare number cannot tell which tree it came
from. Where the raw output matters, put it in an evidence/ file beside the
task and name that file in the Command column.
| What | Value | Command | Commit |
|---|---|---|---|
6. Notes
7. Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario)
that verify this task. Link
new ones here as they're created; the scenario doc itself links back
via its "Verifies task" field.
| Scenario | State | Notes |
|---|---|---|
8. PRs
| PR | Title |
|---|---|
| #2531 | [codegen] Pin copied facts with composite keys |
9. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | Message not checked for one % | core.py | Accept | Refused unless it has exactly one %, for every pin, since a source can change mode. |
| 2 | Columns not checked to exist | core.py | Accept | Both column lists are checked against the two models. |
| 3 | Duplicate pin names | core.py | Accept | Refused. |
| 4 | Source table quoted in one path only | template | Decline | The trigger form matches the existing copy validations; generated table names never need quoting. |
| 5 | Notes on the insert-only rule and nulls | meta-model page | Accept | Both stated. |
| 6 | Tenant column in the missing-key message | core.py | Decline | The message already lists tenant_id among the columns it needs. |
10. Result
- A
** Pinned keystable under* SQLdeclares a pin: name, copied columns, source table, source columns and error message. - Against an immutable source the pin renders a constraint,
<table>_<name>_pin, that includestenant_idwhen the row is tenant-scoped. The generator refuses the pin unless the source declares the referenced columns as its primary key or as a unique index with no predicate. - Against a temporal source the insert trigger checks the source's current row. The check holds at insert only, so a pin copies only facts the source never changes.
- The generator refuses an unknown source, column lists of different
lengths, columns either model does not declare, repeated pin names, a
message without exactly one
%, tenant scopes that differ, a mutable current-state source and a constraint name over 63 characters. - Nine tests in
projects/ores.codegen/tests/test_pinned_key.py. - In Postgres, in transactions that rolled back, a copy whose kind differed
from its source was refused with 23503: by
ores_testcomp_copier_records_source_pinagainst an immutable source, and by the pin's own trigger message against a temporal source. A matching copy was accepted in both. - The SQL meta-model page documents pinned keys, and the variability index
lists
pinned_keys.