diff --git a/.pql/changelog/ticket_history/2026-10.sql b/.pql/changelog/ticket_history/2026-10.sql index 3389808..6efebbe 100644 --- a/.pql/changelog/ticket_history/2026-10.sql +++ b/.pql/changelog/ticket_history/2026-10.sql @@ -41,3 +41,14 @@ INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, chang **Add an owner (D-73).** Book records need an `owner` — the tatlock tenant id from the token''s signed `tatlock_user` claim (production `jpmschweitzer`), forwarded by the Office — and every query scoped by it. Not in migration 005 as written; add it while amending 005. The Scribe''s tables (workspace T-176) carry the same owner on addresses. **Isolation (D-73).** Every Book query and write filters on the owner tenant; a test proves an `llm_tester` caller cannot read, change, link to or close a `jpmschweitzer` record (and the reverse) — the error is `not_found`, so another tenant''s ids are not even confirmed to exist.', NULL, '2026-10-19 10:44:56', '2026-10-19 10:44:56.770', '2026-10-19 10:44:56.770', NULL, '5a9e8eb56b5634bdb34d36f9b5a6cdbf', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN7T48Z610867H891MZCVGA4', 'description', '`require_caller()` (T-1) learns who the Office''s caller is beyond a declared identity: the **consumer** (the Authentik client the request was signed in through, e.g. `office-claude`) and the **tenant** (the signed `tatlock_user` claim, e.g. `jpmschweitzer`, `llm_tester`). The Office has already verified the token; core-api trusts these only alongside the Office''s service key, as it does the identity. Driven by workspace T-185; design in workspace D-73.', '`require_caller()` (T-1) learns who the Office''s caller is beyond a declared identity: the **consumer** (the Authentik client the request was signed in through, e.g. `office-claude`) and the **tenant** (the signed `tatlock_user` claim, e.g. `jpmschweitzer`, `llm_tester`). The Office has already verified the token; core-api trusts these only alongside the Office''s service key, as it does the identity. Driven by workspace T-185; design in workspace D-73. + +**Code done 2026-10-19** (round core-api-T-4-001): `0453d08`, `ce2ec61` — `Caller` carries consumer and tenant; the Office must send `X-Office-Consumer` and `X-Office-Tenant` on every call (400 `consumer_required`/`_invalid`, `tenant_required`/`_invalid`); other kinds ignore both headers; a JWT user''s tenant comes only from its own `tatlock_user` claim; the consumer lands in `book_events.client`. `make test` 430 → 451; mutation-checked (tenant requirement, header spoofing). Held — deploys with core-api T-1 at the milestone.', NULL, '2026-10-19 11:46:47', '2026-10-19 11:46:47.566', '2026-10-19 11:46:47.566', NULL, '1c452fa8a2ac5f5c326e9bf08e7981b6', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN7T48Z610867H891MZCVGA4', 'status', 'backlog', 'done', NULL, '2026-10-19 11:46:47', '2026-10-19 11:46:47.688', '2026-10-19 11:46:47.688', NULL, '877161504860711bfc61a86a6ed44e46', 2) ON CONFLICT(hash) DO NOTHING; +INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN6XMYNQF0VP25QMY7YTB41W', 'description', '`alembic/env.py` always takes its target from `get_settings().database_url`, composed from `.env`''s `POSTGRES_*` — which point at **production** `core_api`. `get_settings()` is `lru_cache`d and is warmed by anything that imports `src.main`, so a test that needs to migrate `core_api_test` has to run alembic in a subprocess with every `POSTGRES_*` overridden. T-1''s tests do exactly that, correctly — but one missed override migrates production. + +Fix: honour `-x db_url=…` (or a `TEST_DATABASE_URL`-style variable) in `env.py` before falling back to settings, and have the migration tests use it, so the target is named rather than inherited. Found by the T-1 round''s friction report.', '`alembic/env.py` always takes its target from `get_settings().database_url`, composed from `.env`''s `POSTGRES_*` — which point at **production** `core_api`. `get_settings()` is `lru_cache`d and is warmed by anything that imports `src.main`, so a test that needs to migrate `core_api_test` has to run alembic in a subprocess with every `POSTGRES_*` overridden. T-1''s tests do exactly that, correctly — but one missed override migrates production. + +Fix: honour `-x db_url=…` (or a `TEST_DATABASE_URL`-style variable) in `env.py` before falling back to settings, and have the migration tests use it, so the target is named rather than inherited. Found by the T-1 round''s friction report. + +Related, from the T-4 round: there is no make target to start a dev server against `core_api_test` — the round built the `POSTGRES_*` overrides from `TEST_DATABASE_URL` by hand, carefully unprinted. A `make dev-test-server` that does it would remove a recurring hand-built step that one slip turns into a run against production.', NULL, '2026-10-19 11:46:47', '2026-10-19 11:46:47.827', '2026-10-19 11:46:47.827', NULL, '1cf3bed3f8970e8f70a0b78845c75c35', 2) ON CONFLICT(hash) DO NOTHING; diff --git a/.pql/changelog/tickets/2026-10.sql b/.pql/changelog/tickets/2026-10.sql index 43e4857..09b5c7a 100644 --- a/.pql/changelog/tickets/2026-10.sql +++ b/.pql/changelog/tickets/2026-10.sql @@ -39,3 +39,14 @@ INSERT INTO tickets (record_id, type, parent_record_id, title, description, stat **Isolation (D-73).** Every Book query and write filters on the owner tenant; a test proves an `llm_tester` caller cannot read, change, link to or close a `jpmschweitzer` record (and the reverse) — the error is `not_found`, so another tenant''s ids are not even confirmed to exist.', 'backlog', 'high', NULL, NULL, NULL, '2026-10-19 09:20:45.104', '2026-10-19 10:44:56.770', NULL, 'd930f0dbc1197dff3600619b31a6b0c6', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN7T48Z610867H891MZCVGA4', 'task', NULL, 'The caller carries consumer and tenant from the Office', '`require_caller()` (T-1) learns who the Office''s caller is beyond a declared identity: the **consumer** (the Authentik client the request was signed in through, e.g. `office-claude`) and the **tenant** (the signed `tatlock_user` claim, e.g. `jpmschweitzer`, `llm_tester`). The Office has already verified the token; core-api trusts these only alongside the Office''s service key, as it does the identity. Driven by workspace T-185; design in workspace D-73.', 'backlog', 'high', NULL, NULL, NULL, '2026-10-19 11:25:10.777', '2026-10-19 11:25:10.777', NULL, 'e73c46c5e77917c049e92ff1f627f447', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN7T48Z610867H891MZCVGA4', 'task', NULL, 'The caller carries consumer and tenant from the Office', '`require_caller()` (T-1) learns who the Office''s caller is beyond a declared identity: the **consumer** (the Authentik client the request was signed in through, e.g. `office-claude`) and the **tenant** (the signed `tatlock_user` claim, e.g. `jpmschweitzer`, `llm_tester`). The Office has already verified the token; core-api trusts these only alongside the Office''s service key, as it does the identity. Driven by workspace T-185; design in workspace D-73. + +**Code done 2026-10-19** (round core-api-T-4-001): `0453d08`, `ce2ec61` — `Caller` carries consumer and tenant; the Office must send `X-Office-Consumer` and `X-Office-Tenant` on every call (400 `consumer_required`/`_invalid`, `tenant_required`/`_invalid`); other kinds ignore both headers; a JWT user''s tenant comes only from its own `tatlock_user` claim; the consumer lands in `book_events.client`. `make test` 430 → 451; mutation-checked (tenant requirement, header spoofing). Held — deploys with core-api T-1 at the milestone.', 'backlog', 'high', NULL, NULL, NULL, '2026-10-19 11:25:10.777', '2026-10-19 11:46:47.566', NULL, 'ed07dd9dcd17801b51cbbf8cb3145451', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN7T48Z610867H891MZCVGA4', 'task', NULL, 'The caller carries consumer and tenant from the Office', '`require_caller()` (T-1) learns who the Office''s caller is beyond a declared identity: the **consumer** (the Authentik client the request was signed in through, e.g. `office-claude`) and the **tenant** (the signed `tatlock_user` claim, e.g. `jpmschweitzer`, `llm_tester`). The Office has already verified the token; core-api trusts these only alongside the Office''s service key, as it does the identity. Driven by workspace T-185; design in workspace D-73. + +**Code done 2026-10-19** (round core-api-T-4-001): `0453d08`, `ce2ec61` — `Caller` carries consumer and tenant; the Office must send `X-Office-Consumer` and `X-Office-Tenant` on every call (400 `consumer_required`/`_invalid`, `tenant_required`/`_invalid`); other kinds ignore both headers; a JWT user''s tenant comes only from its own `tatlock_user` claim; the consumer lands in `book_events.client`. `make test` 430 → 451; mutation-checked (tenant requirement, header spoofing). Held — deploys with core-api T-1 at the milestone.', 'done', 'high', NULL, NULL, NULL, '2026-10-19 11:25:10.777', '2026-10-19 11:46:47.688', NULL, '4fbb627f0d1f48350ebd4d97fc64fd94', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at; +INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06GN6XMYNQF0VP25QMY7YTB41W', 'task', NULL, 'Alembic takes its target from production settings; name it instead', '`alembic/env.py` always takes its target from `get_settings().database_url`, composed from `.env`''s `POSTGRES_*` — which point at **production** `core_api`. `get_settings()` is `lru_cache`d and is warmed by anything that imports `src.main`, so a test that needs to migrate `core_api_test` has to run alembic in a subprocess with every `POSTGRES_*` overridden. T-1''s tests do exactly that, correctly — but one missed override migrates production. + +Fix: honour `-x db_url=…` (or a `TEST_DATABASE_URL`-style variable) in `env.py` before falling back to settings, and have the migration tests use it, so the target is named rather than inherited. Found by the T-1 round''s friction report. + +Related, from the T-4 round: there is no make target to start a dev server against `core_api_test` — the round built the `POSTGRES_*` overrides from `TEST_DATABASE_URL` by hand, carefully unprinted. A `make dev-test-server` that does it would remove a recurring hand-built step that one slip turns into a run against production.', 'backlog', 'medium', NULL, NULL, NULL, '2026-10-19 09:20:45.229', '2026-10-19 11:46:47.827', NULL, '3d53cf5d73b33f32d80232456e23f5ad', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;