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.
- papeete_actor_message-0.1.0/.github/workflows/ci.yml +31 -0
- papeete_actor_message-0.1.0/.github/workflows/release.yml +30 -0
- papeete_actor_message-0.1.0/.gitignore +6 -0
- papeete_actor_message-0.1.0/CLAUDE.md +88 -0
- papeete_actor_message-0.1.0/LICENSE +21 -0
- papeete_actor_message-0.1.0/PKG-INFO +137 -0
- papeete_actor_message-0.1.0/README.md +119 -0
- papeete_actor_message-0.1.0/adr/ADR-PAM-0001-data-and-message-catalogs.md +133 -0
- papeete_actor_message-0.1.0/adr/README.md +20 -0
- papeete_actor_message-0.1.0/adr/template.md +18 -0
- papeete_actor_message-0.1.0/examples/waiter/actor-data.yaml +19 -0
- papeete_actor_message-0.1.0/examples/waiter/actor-message.yaml +14 -0
- papeete_actor_message-0.1.0/pyproject.toml +34 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/__init__.py +6 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/cli.py +69 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/data.py +86 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/messages.py +86 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/report.py +42 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/schemas/papeete-actor-data.schema.yaml +46 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/schemas/papeete-actor-message.schema.yaml +43 -0
- papeete_actor_message-0.1.0/src/papeete_actor_message/schemas.py +25 -0
- papeete_actor_message-0.1.0/tests/unit/test_cli_contracts.py +49 -0
- papeete_actor_message-0.1.0/tests/unit/test_data.py +62 -0
- papeete_actor_message-0.1.0/tests/unit/test_messages.py +47 -0
- papeete_actor_message-0.1.0/tests/unit/test_referential_integrity.py +74 -0
- 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,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,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
|