papeete-actor-message 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. papeete_actor_message-0.1.0/.github/workflows/ci.yml +31 -0
  2. papeete_actor_message-0.1.0/.github/workflows/release.yml +30 -0
  3. papeete_actor_message-0.1.0/.gitignore +6 -0
  4. papeete_actor_message-0.1.0/CLAUDE.md +88 -0
  5. papeete_actor_message-0.1.0/LICENSE +21 -0
  6. papeete_actor_message-0.1.0/PKG-INFO +137 -0
  7. papeete_actor_message-0.1.0/README.md +119 -0
  8. papeete_actor_message-0.1.0/adr/ADR-PAM-0001-data-and-message-catalogs.md +133 -0
  9. papeete_actor_message-0.1.0/adr/README.md +20 -0
  10. papeete_actor_message-0.1.0/adr/template.md +18 -0
  11. papeete_actor_message-0.1.0/examples/waiter/actor-data.yaml +19 -0
  12. papeete_actor_message-0.1.0/examples/waiter/actor-message.yaml +14 -0
  13. papeete_actor_message-0.1.0/pyproject.toml +34 -0
  14. papeete_actor_message-0.1.0/src/papeete_actor_message/__init__.py +6 -0
  15. papeete_actor_message-0.1.0/src/papeete_actor_message/cli.py +69 -0
  16. papeete_actor_message-0.1.0/src/papeete_actor_message/data.py +86 -0
  17. papeete_actor_message-0.1.0/src/papeete_actor_message/messages.py +86 -0
  18. papeete_actor_message-0.1.0/src/papeete_actor_message/report.py +42 -0
  19. papeete_actor_message-0.1.0/src/papeete_actor_message/schemas/papeete-actor-data.schema.yaml +46 -0
  20. papeete_actor_message-0.1.0/src/papeete_actor_message/schemas/papeete-actor-message.schema.yaml +43 -0
  21. papeete_actor_message-0.1.0/src/papeete_actor_message/schemas.py +25 -0
  22. papeete_actor_message-0.1.0/tests/unit/test_cli_contracts.py +49 -0
  23. papeete_actor_message-0.1.0/tests/unit/test_data.py +62 -0
  24. papeete_actor_message-0.1.0/tests/unit/test_messages.py +47 -0
  25. papeete_actor_message-0.1.0/tests/unit/test_referential_integrity.py +74 -0
  26. papeete_actor_message-0.1.0/uv.lock +139 -0
@@ -0,0 +1,31 @@
1
+ name: ci
2
+ on: [push, pull_request]
3
+
4
+ # NO CREDENTIAL. This pipeline reaches nothing outside its own checkout — both contracts are
5
+ # committed source here, not something fetched from a private sibling.
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: astral-sh/setup-uv@v5
13
+ - name: the gate suite
14
+ # FIRST, because everything below it asserts that the package ARRIVED, and nothing below
15
+ # it asserts that the package is RIGHT.
16
+ run: uv run --extra dev pytest -q
17
+
18
+ - name: build
19
+ run: uv build
20
+ - name: the wheel must carry both contracts
21
+ # A wheel that installs cleanly and enforces nothing is the failure mode worth a gate.
22
+ run: |
23
+ uv venv /tmp/probe
24
+ uv pip install --python /tmp/probe/bin/python -q dist/*.whl
25
+ /tmp/probe/bin/papeete-actor-message contracts
26
+ - name: the gates must run
27
+ # The package validates its own worked example with its own contracts — the smallest
28
+ # end-to-end proof that both schemas shipped and both gates can read them.
29
+ run: |
30
+ /tmp/probe/bin/papeete-actor-message lint-data examples/waiter/actor-data.yaml
31
+ /tmp/probe/bin/papeete-actor-message lint-messages examples/waiter/actor-message.yaml
@@ -0,0 +1,30 @@
1
+ name: release
2
+ on:
3
+ push:
4
+ tags: ["v*"]
5
+
6
+ # PyPI Trusted Publishing (OIDC) — no token is stored anywhere. The one-time setup is a pending
7
+ # publisher on pypi.org naming this repo and this workflow; after the first release it becomes a
8
+ # normal publisher. See README.
9
+ permissions:
10
+ id-token: write
11
+ contents: read
12
+
13
+ jobs:
14
+ publish:
15
+ runs-on: ubuntu-latest
16
+ environment: pypi
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: astral-sh/setup-uv@v5
20
+ - name: build
21
+ # No fetch step and no external token: both contracts are committed here. A release
22
+ # depends on nothing but this checkout and PyPI.
23
+ run: uv build
24
+ - name: verify the wheel carries both contracts
25
+ run: |
26
+ uv venv /tmp/probe
27
+ uv pip install --python /tmp/probe/bin/python -q dist/*.whl
28
+ /tmp/probe/bin/papeete-actor-message contracts
29
+ - name: publish to PyPI
30
+ run: uv publish --trusted-publishing always
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .pytest_cache/
4
+ .venv/
5
+ dist/
6
+ *.egg-info/
@@ -0,0 +1,88 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this repo is
6
+
7
+ `papeete-actor-message` ships two small, standalone contracts for the Papeete ecosystem:
8
+ `papeete-actor-data/v0` (a per-actor dictionary of named, minimally-typed data items) and
9
+ `papeete-actor-message/v0` (a per-actor catalog of named messages, each a pure regrouping of
10
+ references into that dictionary, under an intent). It carries no wire protocol and no runtime —
11
+ see `adr/ADR-PAM-0001-data-and-message-catalogs.md` for why, and for the reasoning behind every
12
+ other non-obvious choice below.
13
+
14
+ ## Commands
15
+
16
+ Dependency management and running are via `uv`.
17
+
18
+ ```bash
19
+ uv run --extra dev pytest -q # full test suite
20
+ uv run --extra dev pytest -q tests/unit/test_messages.py # one file
21
+ uv run --extra dev pytest -q tests/unit/test_messages.py::test_lints_the_worked_example_clean # one test
22
+
23
+ uv build # build the wheel/sdist, no network needed
24
+ uv run papeete-actor-message lint-data examples/waiter/actor-data.yaml
25
+ uv run papeete-actor-message lint-messages examples/waiter/actor-message.yaml
26
+ uv run papeete-actor-message contracts
27
+ ```
28
+
29
+ There is no linter/formatter configured in `pyproject.toml` — don't invent one.
30
+
31
+ ## Architecture
32
+
33
+ Five small modules, each with one job:
34
+
35
+ - **`data.py`** — the `papeete-actor-data/v0` gate. Loads `actor-data.yaml`, checks `items[]`
36
+ structurally (unique names, `type` in the closed vocabulary, `values:` present iff
37
+ `type: enum`, a non-empty `description`). A `data:` key naming an unrecognized version is
38
+ **UNMIGRATED**, reported as a warning, and not checked further — never treated as
39
+ non-conformant. Also exposes `names()`, the set of declared item names, used by `messages.py`.
40
+ - **`messages.py`** — the `papeete-actor-message/v0` gate. Loads `actor-message.yaml`, checks
41
+ structure (`name`, `intent`, non-empty `references`), then resolves every `references` entry
42
+ against `data.names()` for the sibling `actor-data.yaml` (or an explicit `--data` path). **This
43
+ cross-file referential-integrity check is the one genuinely new mechanic this repo owns** — an
44
+ unresolved reference is an error; a missing data file is a warning, not a hard failure.
45
+ - **`schemas.py`** — loads the two committed schema YAML files from
46
+ `src/papeete_actor_message/schemas/` as package data. **The package IS the contract** — no
47
+ fetch, no network call, in a source checkout or an installed wheel alike.
48
+ - **`report.py`** — the shared `Report` dataclass (`oks`/`notes`/`warns`/`errors`) both gates
49
+ return. `errors` fail a run; `warns`/`notes` never do.
50
+ - **`cli.py`** — argparse wiring for three subcommands: `lint-data`, `lint-messages` (with an
51
+ optional `--data` override), `contracts`.
52
+
53
+ ### The two contracts are deliberately separate files
54
+
55
+ Splitting data definitions from message groupings is what makes "the same data item can be
56
+ referenced by several messages within the same actor" a structural guarantee rather than a
57
+ discipline someone maintains by hand — see ADR-PAM-0001 for the full rationale, including why a
58
+ message may only *reference* a data item (never redefine, retype, or alias one) and why neither
59
+ file carries a sync/async tag.
60
+
61
+ ### No dependency on `papeete-actor`
62
+
63
+ This package never reads an actor's `name`/`description` — the coupling to "which actor" a pair
64
+ of files belongs to is purely positional (same folder), not a Python import. `pyproject.toml`
65
+ carries exactly one dependency, `pyyaml`.
66
+
67
+ ### The worked example lives at the repo root, not under tests/
68
+
69
+ `examples/waiter/actor-data.yaml` and `examples/waiter/actor-message.yaml` are real files the
70
+ README and both gate's tests point at directly (`tests/unit/test_data.py`,
71
+ `tests/unit/test_messages.py`, `tests/unit/test_cli_contracts.py`) — not disposable fixtures.
72
+ They reuse the `waiter` cast from `papeete-actor-synchronous-messaging`'s own worked example for
73
+ continuity across the ecosystem's docs; there is no functional dependency between the two repos.
74
+
75
+ ## Relationship to other repos
76
+
77
+ Nothing here is imported from or into `papeete-actor` or `papeete-actor-synchronous-messaging`.
78
+ This repo's contracts are a content-modeling layer meant to sit *above* whichever wire protocol
79
+ eventually carries a message (today: `simple-actor-protocol/v0`'s `request`/`ack`/`query`/
80
+ `answer`, owned entirely by `papeete-actor-synchronous-messaging`) — mapping a message name to a
81
+ verb is a downstream binding's job, not this repo's.
82
+
83
+ ## ADRs
84
+
85
+ Design decisions live in `adr/` as one file per decision (`ADR-PAM-00NN-*.md`, `template.md` for
86
+ the format). `adr/README.md` states the ownership line: decisions about either schema's shape or
87
+ this repo's scope belong here; decisions about the wire protocol or actor identity belong
88
+ upstream in the sibling repos' own logs.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Papeete Consulting
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.5
2
+ Name: papeete-actor-message
3
+ Version: 0.1.0
4
+ Summary: A per-actor data dictionary and message catalog contract for the Papeete ecosystem — papeete-actor-data/v0 and papeete-actor-message/v0.
5
+ Author-email: Papeete Consulting <yoann.remy@outlook.com>
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: actor-model,agents,contract,data,message,modeling
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Topic :: Software Development :: Quality Assurance
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: pyyaml>=6.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # papeete-actor-message
20
+
21
+ A per-actor data dictionary and message catalog for the [Papeete](https://github.com/papeete-hub)
22
+ ecosystem: `papeete-actor-data/v0` and `papeete-actor-message/v0`.
23
+
24
+ ```
25
+ papeete-actor-message lint-data ACTOR-DATA.YAML papeete-actor-data/v0
26
+ papeete-actor-message lint-messages ACTOR-MESSAGE.YAML [--data DATA.YAML] papeete-actor-message/v0
27
+ papeete-actor-message contracts which contracts this build enforces
28
+ ```
29
+
30
+ ```bash
31
+ pip install papeete-actor-message
32
+ ```
33
+
34
+ ## What it enforces
35
+
36
+ An actor's data dictionary — `actor-data.yaml` — declares a flat list of named, minimally-typed
37
+ items, and nothing else:
38
+
39
+ | Field | Says |
40
+ |---|---|
41
+ | `data` | names this contract: `papeete-actor-data/v0` |
42
+ | `items[].name` | the item's name, unique within this file |
43
+ | `items[].type` | one of `string`, `integer`, `number`, `boolean`, `enum`, `list` |
44
+ | `items[].values` | required when `type: enum`, disallowed otherwise |
45
+ | `items[].description` | free prose — what this datum is |
46
+
47
+ An actor's message catalog — `actor-message.yaml` — declares named messages that **reference**
48
+ data items only, grouped under an intent:
49
+
50
+ | Field | Says |
51
+ |---|---|
52
+ | `message` | names this contract: `papeete-actor-message/v0` |
53
+ | `messages[].name` | the message's name, unique within this file |
54
+ | `messages[].intent` | free prose — what this message is for |
55
+ | `messages[].references` | a list of data-item names, resolved against the sibling `actor-data.yaml` |
56
+
57
+ A reference that resolves nowhere in the actor's own `actor-data.yaml` fails `lint-messages` —
58
+ the one referential-integrity rule this repo owns. The same data item may be referenced by any
59
+ number of messages within the same actor; a message never redefines, retypes, or inlines a
60
+ datum of its own.
61
+
62
+ `data:` and `message:` each name their own contract version, the same role `manifest:` plays on
63
+ `papeete-actor`'s `actor.yaml` — so each lineage can migrate (v0 → v1, warn-not-fail) on its own,
64
+ and a file declaring some other value is read and warned as UNMIGRATED rather than failed.
65
+
66
+ ## Worked example
67
+
68
+ ```yaml
69
+ # actor-data.yaml
70
+ data: papeete-actor-data/v0
71
+ items:
72
+ - name: table_number
73
+ type: integer
74
+ description: The table where the order was placed.
75
+ - name: dish
76
+ type: string
77
+ description: The name of the dish being ordered.
78
+ - name: order_id
79
+ type: string
80
+ description: The unique identifier assigned to a placed order.
81
+ - name: order_state
82
+ type: enum
83
+ values: [resolved, rejected, pending, unknown]
84
+ description: The current lifecycle state of an order.
85
+ ```
86
+
87
+ ```yaml
88
+ # actor-message.yaml
89
+ message: papeete-actor-message/v0
90
+ messages:
91
+ - name: take-order
92
+ intent: Register what a customer wants to order, and where.
93
+ references: [table_number, dish]
94
+ - name: order-status
95
+ intent: Ask for and report the current state of a previously placed order.
96
+ references: [order_id, order_state]
97
+ ```
98
+
99
+ See [`examples/waiter`](./examples/waiter) — the same cast `papeete-actor-synchronous-messaging`'s
100
+ own worked example uses, for continuity across the ecosystem's docs, not a functional dependency.
101
+
102
+ ```bash
103
+ papeete-actor-message lint-data examples/waiter/actor-data.yaml
104
+ papeete-actor-message lint-messages examples/waiter/actor-message.yaml
105
+ ```
106
+
107
+ ## What this repo deliberately does not do
108
+
109
+ **It carries no wire protocol.** Whether `order-status` above ends up riding a synchronous
110
+ `query`/`answer` exchange (today's only runnable case, owned by
111
+ [`papeete-actor-synchronous-messaging`](https://github.com/papeete-hub/papeete-actor-synchronous-messaging))
112
+ or a future asynchronous publication is a downstream binding's decision — never this catalog's.
113
+ A message here is exactly a name, an intent, and a list of data references; nothing about how it
114
+ is carried.
115
+
116
+ **It ships no runtime.** No actor class, no engine, no mailbox. `v0` is the contract alone —
117
+ schema files and the two lint gates that check them — the same sequencing `papeete-actor` itself
118
+ followed (identity shipped alone, before anything ran it).
119
+
120
+ **Data is scoped to one actor's own folder.** An external RDF/SHACL ontology governing data
121
+ items across actors is out of scope for now — the door is not closed to it later, but nothing
122
+ here anticipates its shape. See [ADR-PAM-0001](./adr/ADR-PAM-0001-data-and-message-catalogs.md)
123
+ for the full reasoning behind every choice above.
124
+
125
+ ## The contract is in this repo
126
+
127
+ [`src/papeete_actor_message/schemas/`](./src/papeete_actor_message/schemas/) — ordinary
128
+ committed source. **The package IS the contract**, not a gate that goes looking for it, so a
129
+ build needs no network and no credential for the schemas themselves.
130
+
131
+ ```bash
132
+ uv build # no network, no token, no fetch step for either contract
133
+ ```
134
+
135
+ ## Licence
136
+
137
+ MIT.
@@ -0,0 +1,119 @@
1
+ # papeete-actor-message
2
+
3
+ A per-actor data dictionary and message catalog for the [Papeete](https://github.com/papeete-hub)
4
+ ecosystem: `papeete-actor-data/v0` and `papeete-actor-message/v0`.
5
+
6
+ ```
7
+ papeete-actor-message lint-data ACTOR-DATA.YAML papeete-actor-data/v0
8
+ papeete-actor-message lint-messages ACTOR-MESSAGE.YAML [--data DATA.YAML] papeete-actor-message/v0
9
+ papeete-actor-message contracts which contracts this build enforces
10
+ ```
11
+
12
+ ```bash
13
+ pip install papeete-actor-message
14
+ ```
15
+
16
+ ## What it enforces
17
+
18
+ An actor's data dictionary — `actor-data.yaml` — declares a flat list of named, minimally-typed
19
+ items, and nothing else:
20
+
21
+ | Field | Says |
22
+ |---|---|
23
+ | `data` | names this contract: `papeete-actor-data/v0` |
24
+ | `items[].name` | the item's name, unique within this file |
25
+ | `items[].type` | one of `string`, `integer`, `number`, `boolean`, `enum`, `list` |
26
+ | `items[].values` | required when `type: enum`, disallowed otherwise |
27
+ | `items[].description` | free prose — what this datum is |
28
+
29
+ An actor's message catalog — `actor-message.yaml` — declares named messages that **reference**
30
+ data items only, grouped under an intent:
31
+
32
+ | Field | Says |
33
+ |---|---|
34
+ | `message` | names this contract: `papeete-actor-message/v0` |
35
+ | `messages[].name` | the message's name, unique within this file |
36
+ | `messages[].intent` | free prose — what this message is for |
37
+ | `messages[].references` | a list of data-item names, resolved against the sibling `actor-data.yaml` |
38
+
39
+ A reference that resolves nowhere in the actor's own `actor-data.yaml` fails `lint-messages` —
40
+ the one referential-integrity rule this repo owns. The same data item may be referenced by any
41
+ number of messages within the same actor; a message never redefines, retypes, or inlines a
42
+ datum of its own.
43
+
44
+ `data:` and `message:` each name their own contract version, the same role `manifest:` plays on
45
+ `papeete-actor`'s `actor.yaml` — so each lineage can migrate (v0 → v1, warn-not-fail) on its own,
46
+ and a file declaring some other value is read and warned as UNMIGRATED rather than failed.
47
+
48
+ ## Worked example
49
+
50
+ ```yaml
51
+ # actor-data.yaml
52
+ data: papeete-actor-data/v0
53
+ items:
54
+ - name: table_number
55
+ type: integer
56
+ description: The table where the order was placed.
57
+ - name: dish
58
+ type: string
59
+ description: The name of the dish being ordered.
60
+ - name: order_id
61
+ type: string
62
+ description: The unique identifier assigned to a placed order.
63
+ - name: order_state
64
+ type: enum
65
+ values: [resolved, rejected, pending, unknown]
66
+ description: The current lifecycle state of an order.
67
+ ```
68
+
69
+ ```yaml
70
+ # actor-message.yaml
71
+ message: papeete-actor-message/v0
72
+ messages:
73
+ - name: take-order
74
+ intent: Register what a customer wants to order, and where.
75
+ references: [table_number, dish]
76
+ - name: order-status
77
+ intent: Ask for and report the current state of a previously placed order.
78
+ references: [order_id, order_state]
79
+ ```
80
+
81
+ See [`examples/waiter`](./examples/waiter) — the same cast `papeete-actor-synchronous-messaging`'s
82
+ own worked example uses, for continuity across the ecosystem's docs, not a functional dependency.
83
+
84
+ ```bash
85
+ papeete-actor-message lint-data examples/waiter/actor-data.yaml
86
+ papeete-actor-message lint-messages examples/waiter/actor-message.yaml
87
+ ```
88
+
89
+ ## What this repo deliberately does not do
90
+
91
+ **It carries no wire protocol.** Whether `order-status` above ends up riding a synchronous
92
+ `query`/`answer` exchange (today's only runnable case, owned by
93
+ [`papeete-actor-synchronous-messaging`](https://github.com/papeete-hub/papeete-actor-synchronous-messaging))
94
+ or a future asynchronous publication is a downstream binding's decision — never this catalog's.
95
+ A message here is exactly a name, an intent, and a list of data references; nothing about how it
96
+ is carried.
97
+
98
+ **It ships no runtime.** No actor class, no engine, no mailbox. `v0` is the contract alone —
99
+ schema files and the two lint gates that check them — the same sequencing `papeete-actor` itself
100
+ followed (identity shipped alone, before anything ran it).
101
+
102
+ **Data is scoped to one actor's own folder.** An external RDF/SHACL ontology governing data
103
+ items across actors is out of scope for now — the door is not closed to it later, but nothing
104
+ here anticipates its shape. See [ADR-PAM-0001](./adr/ADR-PAM-0001-data-and-message-catalogs.md)
105
+ for the full reasoning behind every choice above.
106
+
107
+ ## The contract is in this repo
108
+
109
+ [`src/papeete_actor_message/schemas/`](./src/papeete_actor_message/schemas/) — ordinary
110
+ committed source. **The package IS the contract**, not a gate that goes looking for it, so a
111
+ build needs no network and no credential for the schemas themselves.
112
+
113
+ ```bash
114
+ uv build # no network, no token, no fetch step for either contract
115
+ ```
116
+
117
+ ## Licence
118
+
119
+ MIT.
@@ -0,0 +1,133 @@
1
+ ---
2
+ id: ADR-PAM-0001
3
+ title: "Two files, reference-only messages, and why this stays wire-agnostic and standalone"
4
+ status: Accepted
5
+ date: 2026-08-23
6
+ supersedes: []
7
+ references:
8
+ - ../src/papeete_actor_message/schemas/papeete-actor-data.schema.yaml
9
+ - ../src/papeete_actor_message/schemas/papeete-actor-message.schema.yaml
10
+ - ../src/papeete_actor_message/data.py
11
+ - ../src/papeete_actor_message/messages.py
12
+ ---
13
+
14
+ # ADR-PAM-0001 — Two files, reference-only messages, and why this stays wire-agnostic and standalone
15
+
16
+ ## Context
17
+
18
+ `papeete-actor-synchronous-messaging` already owns a wire protocol —
19
+ `simple-actor-protocol/v0` — with an envelope and four payload kinds (`request`/`ack`/
20
+ `query`/`answer`), each field tagged `judged:` or left deterministic. That repo's own README
21
+ flags those four kinds as "candidates for a future `inter-agent-message/v0`" contract, and its
22
+ `adr/README.md` is explicit that decisions about the protocol itself belong there, not here.
23
+
24
+ What's missing across the ecosystem is a place to declare, per actor, **what data exists** and
25
+ **how it groups into an intent** — independent of which verb eventually carries it, and
26
+ independent of whether that verb is synchronous (an action or a query, today's only runnable
27
+ case) or asynchronous (publications/subscriptions, deliberately excluded from the sync-messaging
28
+ repo, but not from this one). This repo is that place.
29
+
30
+ ## Decision
31
+
32
+ **Two files per actor, each a small, self-declaring contract:**
33
+
34
+ `actor-data.yaml` — `papeete-actor-data/v0` — a dictionary of named, minimally-typed items:
35
+
36
+ ```yaml
37
+ data: papeete-actor-data/v0
38
+ items:
39
+ - name: order_id
40
+ type: string
41
+ description: The unique identifier assigned to a placed order.
42
+ ```
43
+
44
+ `actor-message.yaml` — `papeete-actor-message/v0` — a catalog of named messages, each a pure
45
+ regrouping of references into that dictionary:
46
+
47
+ ```yaml
48
+ message: papeete-actor-message/v0
49
+ messages:
50
+ - name: order-status
51
+ intent: Ask for and report the current state of a previously placed order.
52
+ references: [order_id, order_state]
53
+ ```
54
+
55
+ **A message references data only.** No inline data, no per-reference metadata (no
56
+ `required`/`optional` flag, no renaming or aliasing of a datum for one message's use). The same
57
+ data item may be referenced by any number of messages within the same actor — that's the entire
58
+ point of separating the two files.
59
+
60
+ **Data typing is minimal but real: `string | integer | number | boolean | enum | list`, plus a
61
+ `description`.** Not bare name-only placeholders (a datum should say what it is), and not an RDF/
62
+ SHACL ontology (deliberately out of scope for this version — see below). `values:` is required
63
+ exactly when `type: enum`, and disallowed otherwise.
64
+
65
+ **A message carries no sync/async tag.** Whether `order-status` above ends up riding a `query`/
66
+ `answer` pair, a future publication, or something else entirely is a downstream binding's
67
+ decision (e.g. a card's `offers`), never this catalog's. This keeps the catalog useful to both
68
+ messaging styles without hard-coding a guess about either's eventual shape.
69
+
70
+ **Referential integrity is enforced across files, not within either schema alone.** A single
71
+ YAML file's JSON Schema has nothing to compare a reference against; `messages.py` loads the
72
+ sibling `actor-data.yaml` and resolves every name in `references` against it. An unresolved
73
+ reference is an error (fails the run); a missing sibling data file is a warning (references are
74
+ left unresolved, not silently assumed correct).
75
+
76
+ **Filenames are singular, mirroring the repo's own name** — `actor-data.yaml` /
77
+ `actor-message.yaml` — the same mechanical rule already in use elsewhere in the ecosystem
78
+ (`papeete-actor` → `actor.yaml`, `papeete-actor-synchronous-messaging` →
79
+ `actor-synchronous-messaging.yaml`: repo name minus the `papeete-` prefix, unpluralized).
80
+
81
+ **No dependency on `papeete-actor`.** This package never needs an actor's `name`/`description` to
82
+ validate its own two files or the references between them — the coupling to "which actor" is
83
+ purely positional (both files live in the same folder), not a Python import. Unlike
84
+ `papeete-actor-synchronous-messaging`'s `SimpleActor`, this repo ships no runtime actor class in
85
+ v0.
86
+
87
+ **Data is scoped to one actor's own folder, for now.** An external RDF/SHACL ontology governing
88
+ data items across actors, or a shared vocabulary, is explicitly out of scope for `v0` — the door
89
+ is left open, nothing here forecloses it, but nothing here anticipates its shape either.
90
+
91
+ ## Rationale
92
+
93
+ **Why two files rather than one.** A single file mixing data definitions and message groupings
94
+ would let the same datum drift into slightly different shapes each time a message redeclares it
95
+ inline. Splitting them is what makes "the same data can be referenced several times by different
96
+ messages" a structural guarantee rather than a discipline someone has to maintain by hand.
97
+
98
+ **Why reference-only, with no per-reference metadata.** The instant a message can rename or
99
+ retype a referenced datum for its own purposes, the dictionary stops being a single source of
100
+ truth — a reader would have to check every message to know what a datum "really" is. Keeping
101
+ `references` a flat list of names is the smallest contract that still lets a message mean
102
+ something: *these facts, gathered under this intent.*
103
+
104
+ **Why no sync/async tag here.** Baking a transport assumption into the catalog would recreate
105
+ exactly the coupling `papeete-actor-synchronous-messaging` went out of its way to avoid between
106
+ its protocol and its card contract. A message catalog that outlives any one binding is the
107
+ reason this repo can serve both synchronous and (future) asynchronous messaging without a
108
+ migration.
109
+
110
+ **Why minimal-but-real typing, not bare placeholders and not RDF/SHACL.** A closed, tiny
111
+ vocabulary is enough to catch real mistakes (an enum with no values, a duplicate name) without
112
+ inventing a general type system this repo would then have to maintain. It also leaves room to
113
+ grow it (`v0 → v1`, warn-not-fail, the same migration discipline `papeete-actor`'s `manifest:`
114
+ key already established) rather than guessing ahead at a richness (RDF/SHACL) nobody has asked
115
+ this repo to provide yet.
116
+
117
+ **Why no dependency on `papeete-actor`.** Every check this repo performs is answerable from its
118
+ own two files. Adding a dependency an actual gate never reads would be coupling for its own sake
119
+ — the opposite of this ecosystem's demonstrated discipline of each package depending on exactly
120
+ what it uses and nothing else.
121
+
122
+ ## Consequences
123
+
124
+ - **A data/message catalog nobody consumes yet, on purpose.** Exactly like `papeete-actor`'s
125
+ manifest was consumable-but-unconsumed before any runtime read it, this repo ships the
126
+ contract alone in `v0`. A later repo or ADR is expected to wire a message name to a wire verb
127
+ (sync or async) — that wiring is deliberately not built here.
128
+ - **`references` has no way to mark a reference optional or required for a given message.** If a
129
+ real need for that surfaces, it is a new, explicit field to design and version — not something
130
+ to retrofit silently into a flat string list.
131
+ - **A missing `actor-data.yaml` degrades `lint-messages` to a warning, not a hard failure**, so a
132
+ message file can still be authored before its data file exists — but no reference is
133
+ considered resolved until both files are present together.
@@ -0,0 +1,20 @@
1
+ # Decision log (`ADR-PAM-*`)
2
+
3
+ Decisions owned by **this repo**: the shape of `papeete-actor-data/v0` and
4
+ `papeete-actor-message/v0`, what a message is allowed to do with a data item, and this
5
+ package's own boundary and scope.
6
+
7
+ **What belongs here.** A change to either schema's fields; what a message is and is not allowed
8
+ to declare; the referential-integrity rule between the two files; what stays deliberately out of
9
+ scope (typing richness, an external ontology, wire-protocol/sync-async concerns) and why.
10
+
11
+ **What does not.** Anything about the wire protocol itself — the envelope, verbs, or the
12
+ judged/deterministic split belongs to `papeete-hub/papeete-actor-synchronous-messaging`'s own
13
+ `ADR-PAS-*` log. Anything about actor identity belongs to `papeete-hub/papeete-actor`'s
14
+ `ADR-PA-*` log. This repo consumes neither directly (see ADR-PAM-0001) and re-authors neither.
15
+
16
+ ## The log
17
+
18
+ | ID | Title | Status |
19
+ |----|-------|--------|
20
+ | [ADR-PAM-0001](./ADR-PAM-0001-data-and-message-catalogs.md) | Two files, reference-only messages, and why this stays wire-agnostic and standalone | Accepted |
@@ -0,0 +1,18 @@
1
+ ---
2
+ id: ADR-PAM-NNNN
3
+ title: ""
4
+ status: Proposed
5
+ date: YYYY-MM-DD
6
+ supersedes: []
7
+ references: []
8
+ ---
9
+
10
+ # ADR-PAM-NNNN — <title>
11
+
12
+ ## Context
13
+
14
+ ## Decision
15
+
16
+ ## Rationale
17
+
18
+ ## Consequences
@@ -0,0 +1,19 @@
1
+ data: papeete-actor-data/v0
2
+
3
+ items:
4
+ - name: table_number
5
+ type: integer
6
+ description: The table where the order was placed.
7
+
8
+ - name: dish
9
+ type: string
10
+ description: The name of the dish being ordered.
11
+
12
+ - name: order_id
13
+ type: string
14
+ description: The unique identifier assigned to a placed order.
15
+
16
+ - name: order_state
17
+ type: enum
18
+ values: [resolved, rejected, pending, unknown]
19
+ description: The current lifecycle state of an order.
@@ -0,0 +1,14 @@
1
+ message: papeete-actor-message/v0
2
+
3
+ messages:
4
+ - name: take-order
5
+ intent: Register what a customer wants to order, and where.
6
+ references:
7
+ - table_number
8
+ - dish
9
+
10
+ - name: order-status
11
+ intent: Ask for and report the current state of a previously placed order.
12
+ references:
13
+ - order_id
14
+ - order_state