|
1 | 1 | # OSDS — Event Schema, Adapter Interface & Entitlements |
2 | 2 |
|
3 | 3 | **Open Source Directory Site** |
4 | | -**Status:** Draft v0.4 · **License:** Apache-2.0 · **Steward:** Origin Development & IT, Inc. |
| 4 | +**Status:** Draft v0.5 · **License:** Apache-2.0 · **Steward:** Origin Development & IT, Inc. |
5 | 5 | **Audience:** core maintainers, adapter authors |
6 | 6 |
|
7 | 7 | This document defines the contract between the OSDS core and everything outside it. The core is a multi-tenant directory engine. It knows nothing about email providers, CRMs, payment gateways, or messaging platforms. It emits facts and accepts commands. Adapters translate. |
8 | 8 |
|
9 | 9 | If you are writing an adapter, sections 3, 7 and 8 are the ones you need. |
10 | 10 | If you are implementing the paid tiers, section 5 is the whole job. |
11 | 11 |
|
12 | | -### Changes from v0.3 |
| 12 | +### Changes from v0.4 |
13 | 13 |
|
14 | | -- **§3.3 added: the complete event catalogue.** v0.3 restructured section 3 and in doing so lost the per-group event tables for `claim.*`, `billing.*`, `lead.*`, `call.*`, `moderation.*`, `compliance.*`, `agent.*`, `tenant.*`, `import.*` and `postal.*`, along with six of the ten `listing.*` events. §3.3 is now the single canonical list of every event type OSDS emits. Detailed payloads remain in their behavioural sections; §3.3 is the index and the source of truth for the type union. |
15 | | -- `listing.owner_assigned` restored to the catalogue. |
16 | | -- `listing.claimed` is **not** an event. It appeared only in the v0.2 envelope example and was never in a catalogue. The real sequence is `claim.approved` followed by `listing.owner_assigned`. |
17 | | -- Section numbering below §3 shifted by one. Core entities moved 3→4, |
18 | | - reviews 4→5, entitlements 5→6, commands 6→7, adapter interface 7→8, |
19 | | - claim verification 8→9. The data-source position is now §4.1.1. |
| 14 | +- **§7.1 added.** `listing.upsert` payload, match key, partial-state semantics, rejected fields. |
| 15 | +- **§9.5 added.** Verification code lifetime per method, tenant-configurable within core-enforced bounds. §9.2's flat 21 days becomes the postcard default. |
| 16 | +- **§4.3 added**, plus the `user.*` namespace in §3.2 and §3.3. There is no `user.create` command; a row is minted inside the command that needs one and `user.created` is emitted. |
| 17 | +- **§11.2 expanded.** The command log is written outside the command transaction, and `command_log`'s nullable `tenant_id` is stated as a bounded exception rather than a violation. |
20 | 18 |
|
21 | 19 | --- |
22 | 20 |
|
@@ -1111,6 +1109,26 @@ The envelope is small and free of personal data, so keeping it indefinitely is a |
1111 | 1109 |
|
1112 | 1110 | The command log exists because a rejected command otherwise leaves no trace. "The agent attempted to delete a listing and was blocked" is precisely the record worth having. |
1113 | 1111 |
|
| 1112 | +#### The command log is written outside the command transaction |
| 1113 | + |
| 1114 | +The attempt is recorded **before** the command transaction opens, and concluded **after** it settles, each in its own independently-committed transaction. |
| 1115 | + |
| 1116 | +A log written inside the transaction it is logging disappears when that transaction rolls back — which is exactly the case the log exists for. A command that throws mid-apply leaves a row with a null outcome. That is the record, not a gap. |
| 1117 | + |
| 1118 | +A concluded row is never rewritten. An audit trail the application can edit is not one. |
| 1119 | + |
| 1120 | +#### Nullable `tenant_id` |
| 1121 | + |
| 1122 | +Design rule 4 makes every event tenant-scoped, and every table carries `tenant_id`. **`command_log` is the exception:** a command may be malformed, or name a tenant that does not exist, and that attempt still has to leave a trace. A row that cannot be written because its tenant could not be resolved is the specific failure the log is for. |
| 1123 | + |
| 1124 | +The exception is narrow, and a table qualifies for it only when all three hold: |
| 1125 | + |
| 1126 | +1. It records an **attempt**, not a state change. Attempts can fail before a tenant is resolvable; state changes cannot. |
| 1127 | +2. Rows with a null `tenant_id` are **unreadable by the application role**. The tenant-scoped policy matches no null row, so `osds_app` never sees another party's malformed payload. |
| 1128 | +3. Those rows are written **already concluded**, so the application never needs to read one back to update it. `UPDATE ... WHERE id` still consults the SELECT policy; a row the session cannot read is a row it cannot update. |
| 1129 | + |
| 1130 | +**A new exception is a maintainer decision, recorded in `docs/decisions.md`.** The rule is general enough to be argued into, which is why claiming it is not self-service. Today `command_log` is the only table that holds it. |
| 1131 | + |
1114 | 1132 | --- |
1115 | 1133 |
|
1116 | 1134 | ## 12. Search, sitemaps and SEO |
|
0 commit comments