foundry-implementation-actor 0.2.0__tar.gz → 0.2.1__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.
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/CLAUDE.md +9 -1
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/PKG-INFO +11 -1
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/README.md +10 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/pyproject.toml +1 -1
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/__init__.py +2 -1
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/cli.py +11 -1
- foundry_implementation_actor-0.2.1/src/foundry_implementation_actor/conformance.py +119 -0
- foundry_implementation_actor-0.2.1/tests/fixtures/valid/actor-data.yaml +38 -0
- foundry_implementation_actor-0.2.1/tests/fixtures/valid/actor-message.yaml +17 -0
- foundry_implementation_actor-0.2.1/tests/fixtures/valid/actor-synchronous-messaging.yaml +21 -0
- foundry_implementation_actor-0.2.1/tests/fixtures/valid/actor.yaml +8 -0
- foundry_implementation_actor-0.2.1/tests/test_conformance.py +111 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/uv.lock +1 -1
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/.github/workflows/ci.yml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/.github/workflows/release.yml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/.gitignore +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/adr/ADR-FIA-0001-the-machinery-leaves-the-capability.md +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/adr/README.md +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/adr/template.md +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/scripts/probe_grounding.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/cards/actor-data.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/cards/actor-message.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/cards/actor-synchronous-messaging.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/cards/actor.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/config.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/correlation.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/engine.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/grounding.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/handler.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/src/foundry_implementation_actor/schemas/agentic-context.schema.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/conftest.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/fixtures/broken/actor-agentic-context.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/fixtures/valid/actor-agentic-context.yaml +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_cards.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_cli.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_config.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_engine.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_grounding.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_handler.py +0 -0
- {foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_portability.py +0 -0
|
@@ -29,7 +29,7 @@ There is no separate lint/format command configured in this repo.
|
|
|
29
29
|
|
|
30
30
|
## Architecture
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Seven modules under `src/foundry_implementation_actor/`:
|
|
33
33
|
|
|
34
34
|
- **`config.py`** — `CapabilityConfig`. The heart. Loads the sidecar and derives **every**
|
|
35
35
|
rendering of the capability id from two declared fields (`capability`, `source_repo`). Also
|
|
@@ -44,6 +44,10 @@ Six modules under `src/foundry_implementation_actor/`:
|
|
|
44
44
|
opens a pull request.
|
|
45
45
|
- **`correlation.py`** — the two ids and the step vocabulary. Moved **verbatim** from the repo this
|
|
46
46
|
was extracted from; its code below the docstring is byte-identical.
|
|
47
|
+
- **`conformance.py`** — `check(folder)`. Compares a use's cards against the definition's, on
|
|
48
|
+
the derived wire contract only (door ids, each door's request/completion schema and engine).
|
|
49
|
+
Prose and `actor.yaml`'s `name:` are deliberately not compared — a use should name its own
|
|
50
|
+
capability. `lint` runs it beside the sidecar gate.
|
|
47
51
|
- **`cli.py`** — argparse wiring only, no logic of its own.
|
|
48
52
|
|
|
49
53
|
Beside them, two folders of committed contract, both shipped in the wheel:
|
|
@@ -56,6 +60,10 @@ Beside them, two folders of committed contract, both shipped in the wheel:
|
|
|
56
60
|
|
|
57
61
|
## Core invariants that any change must preserve
|
|
58
62
|
|
|
63
|
+
- **A use's cards are a hand copy, and hand copies drift.** Both folders pass `lint-card`
|
|
64
|
+
independently, and neither gate looks at the other — so a definition that gains a field leaves
|
|
65
|
+
every un-updated use linting green and refusing callers at runtime. `conformance.check` is the
|
|
66
|
+
only thing that catches it. Compare the DERIVED contract, never the prose.
|
|
59
67
|
- **The `-actor` suffix is a claim, and it is checked.** `ADR-ECO-0022`: a package ending in
|
|
60
68
|
`-actor` asserts a `papeete-actor` underneath, and one that ships no conformant card is
|
|
61
69
|
misnamed. `tests/test_cards.py` runs `lint-card` on `cards/` in the suite; CI and the release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: foundry-implementation-actor
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Runs a headless Claude Code implementation session against one capability's own repo — a papeete-actor for one use, with the capability supplied by a sidecar.
|
|
5
5
|
Project-URL: Homepage, https://github.com/papeete-hub/foundry-implementation-actor
|
|
6
6
|
Author-email: Papeete Consulting <yoann.remy@outlook.com>
|
|
@@ -97,6 +97,16 @@ papeete-actor-synchronous-messaging lint-card \
|
|
|
97
97
|
"$(python -c 'from foundry_implementation_actor import cards_path; print(cards_path())')"
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
A use's copy is made by hand, so it can drift: both folders pass `lint-card` independently, and
|
|
101
|
+
neither gate has an opinion about the other. `foundry-implementation-actor lint` therefore runs a
|
|
102
|
+
second check — `conformance.check` — comparing the use's cards against the definition's on the
|
|
103
|
+
**derived wire contract**: the set of doors, and each door's `request_schema`, `completion_schema`
|
|
104
|
+
and `engine`. Those derivations already fold in the data dictionary and the message catalog, so a
|
|
105
|
+
renamed item or a changed reference lands in the payload a caller is validated against.
|
|
106
|
+
|
|
107
|
+
Prose is not compared, on purpose: a use *should* name its real capability and its real peers, and
|
|
108
|
+
`actor.yaml`'s `name:` is its own identity and is required to differ.
|
|
109
|
+
|
|
100
110
|
Because the cards sit under `src/`, `tests/test_portability.py` greps them too — a capability id or
|
|
101
111
|
a knowledge tool name written into the actor's own definition fails the build exactly as it would
|
|
102
112
|
in the code.
|
|
@@ -77,6 +77,16 @@ papeete-actor-synchronous-messaging lint-card \
|
|
|
77
77
|
"$(python -c 'from foundry_implementation_actor import cards_path; print(cards_path())')"
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
A use's copy is made by hand, so it can drift: both folders pass `lint-card` independently, and
|
|
81
|
+
neither gate has an opinion about the other. `foundry-implementation-actor lint` therefore runs a
|
|
82
|
+
second check — `conformance.check` — comparing the use's cards against the definition's on the
|
|
83
|
+
**derived wire contract**: the set of doors, and each door's `request_schema`, `completion_schema`
|
|
84
|
+
and `engine`. Those derivations already fold in the data dictionary and the message catalog, so a
|
|
85
|
+
renamed item or a changed reference lands in the payload a caller is validated against.
|
|
86
|
+
|
|
87
|
+
Prose is not compared, on purpose: a use *should* name its real capability and its real peers, and
|
|
88
|
+
`actor.yaml`'s `name:` is its own identity and is required to differ.
|
|
89
|
+
|
|
80
90
|
Because the cards sit under `src/`, `tests/test_portability.py` greps them too — a capability id or
|
|
81
91
|
a knowledge tool name written into the actor's own definition fails the build exactly as it would
|
|
82
92
|
in the code.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "foundry-implementation-actor"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "Runs a headless Claude Code implementation session against one capability's own repo — a papeete-actor for one use, with the capability supplied by a sidecar."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -22,7 +22,7 @@ from .config import (CapabilityConfig, Component, ConfigError, Grounding, Report
|
|
|
22
22
|
cards_path, lint)
|
|
23
23
|
from .engine import ClaudeCodeEngine
|
|
24
24
|
from .handler import HandlerError, make_implement_task
|
|
25
|
-
from . import correlation, grounding
|
|
25
|
+
from . import conformance, correlation, grounding
|
|
26
26
|
|
|
27
27
|
__all__ = [
|
|
28
28
|
"CapabilityConfig",
|
|
@@ -33,6 +33,7 @@ __all__ = [
|
|
|
33
33
|
"HandlerError",
|
|
34
34
|
"Report",
|
|
35
35
|
"cards_path",
|
|
36
|
+
"conformance",
|
|
36
37
|
"correlation",
|
|
37
38
|
"grounding",
|
|
38
39
|
"lint",
|
|
@@ -13,13 +13,22 @@ import argparse
|
|
|
13
13
|
import sys
|
|
14
14
|
from pathlib import Path
|
|
15
15
|
|
|
16
|
+
from . import conformance
|
|
16
17
|
from .config import CapabilityConfig, ConfigError, lint
|
|
17
18
|
|
|
18
19
|
_REGISTRY_PLACEHOLDER = "<registry>"
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _cmd_lint(args: argparse.Namespace) -> int:
|
|
23
|
+
# Two gates, one command. The sidecar says which capability this use serves; the cards say
|
|
24
|
+
# which actor it claims to be. A use can be wrong about either independently, and the second
|
|
25
|
+
# check is the only thing standing between a hand-copied card set and a caller being refused
|
|
26
|
+
# at a door — see `conformance.py`.
|
|
22
27
|
report = lint(Path(args.folder))
|
|
28
|
+
conformance_report = conformance.check(Path(args.folder))
|
|
29
|
+
report.oks.extend(conformance_report.oks)
|
|
30
|
+
report.warns.extend(conformance_report.warns)
|
|
31
|
+
report.errors.extend(conformance_report.errors)
|
|
23
32
|
for warning in report.warns:
|
|
24
33
|
print(f" ! {warning}")
|
|
25
34
|
for error in report.errors:
|
|
@@ -29,7 +38,8 @@ def _cmd_lint(args: argparse.Namespace) -> int:
|
|
|
29
38
|
return 1
|
|
30
39
|
for line in report.oks:
|
|
31
40
|
print(f" ok {line}")
|
|
32
|
-
|
|
41
|
+
checked_cards = any((Path(args.folder) / name).exists() for name in conformance.CARD_FILES)
|
|
42
|
+
print("✓ sidecar and cards conform" if checked_cards else "✓ sidecar conforms")
|
|
33
43
|
return 0
|
|
34
44
|
|
|
35
45
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Does this use still answer the same doors as the actor it claims to be?
|
|
2
|
+
|
|
3
|
+
WHY THIS EXISTS. This package ships the actor's DEFINITION — four cards under `cards/` saying what
|
|
4
|
+
a foundry implementation actor is. A USE is one capability's own folder, carrying its own copy of
|
|
5
|
+
those four, named for the capability it serves. The copy is made by hand, because a use is a static
|
|
6
|
+
repository today rather than something spawned from a capability id.
|
|
7
|
+
|
|
8
|
+
A hand copy drifts. Both folders pass `lint-card` independently — each is a conformant actor — and
|
|
9
|
+
neither gate has any opinion about the other. So the day the definition gains a field, renames a
|
|
10
|
+
message, or changes a door's completion set, a use that was not updated keeps linting green and
|
|
11
|
+
starts refusing callers at runtime, with the rejection surfacing at the door rather than here.
|
|
12
|
+
|
|
13
|
+
WHAT IS COMPARED, AND WHAT IS NOT. Only what a caller can observe: the set of doors, and for each
|
|
14
|
+
one its derived `request_schema`, its `completion_schema`, and the `engine` key it resolves
|
|
15
|
+
through. Those derivations already fold in the data dictionary and the message catalog — a renamed
|
|
16
|
+
data item, a changed type, a reference added to a message, all of it lands in the schema a caller is
|
|
17
|
+
validated against — so comparing them separately would be comparing the same fact twice.
|
|
18
|
+
|
|
19
|
+
PROSE IS NOT COMPARED, ON PURPOSE. A use's `means:` should name its real capability and its real
|
|
20
|
+
peer actors; the definition's cannot, because it serves no capability and its cards are grepped for
|
|
21
|
+
exactly such literals. The use's wording is the better one for anyone reading `describe`, and
|
|
22
|
+
flattening it to the definition's would delete information. Nor is `actor.yaml`'s `name:`: that is
|
|
23
|
+
the use's own identity, and it is REQUIRED to differ.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
from papeete_actor_synchronous_messaging import card as pas_card
|
|
30
|
+
|
|
31
|
+
from .config import Report, cards_path
|
|
32
|
+
|
|
33
|
+
CARD_FILES = ("actor.yaml", "actor-data.yaml", "actor-message.yaml",
|
|
34
|
+
"actor-synchronous-messaging.yaml")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def check(folder: str | Path = ".") -> Report:
|
|
38
|
+
"""Compare one use's cards against the definition this package ships."""
|
|
39
|
+
use = Path(folder)
|
|
40
|
+
report = Report(oks=[], warns=[], errors=[])
|
|
41
|
+
|
|
42
|
+
present = [name for name in CARD_FILES if (use / name).exists()]
|
|
43
|
+
if not present:
|
|
44
|
+
# Not an error. `lint` is also run against a sidecar on its own — in this package's own CI,
|
|
45
|
+
# among other places — and a folder with no cards is not a malformed use, just not a
|
|
46
|
+
# complete one.
|
|
47
|
+
report.warns.append(
|
|
48
|
+
f"{use}: no cards here, so nothing to compare — a use carries its own copy of the "
|
|
49
|
+
f"actor's four cards beside its sidecar"
|
|
50
|
+
)
|
|
51
|
+
return report
|
|
52
|
+
if len(present) != len(CARD_FILES):
|
|
53
|
+
missing = [name for name in CARD_FILES if name not in present]
|
|
54
|
+
report.errors.append(
|
|
55
|
+
f"{use}: an incomplete card set — missing {', '.join(missing)}. `Actor.from_card` "
|
|
56
|
+
f"opens exactly these four and never globs, so a use missing one does not boot."
|
|
57
|
+
)
|
|
58
|
+
return report
|
|
59
|
+
|
|
60
|
+
definition = cards_path()
|
|
61
|
+
if any(not (definition / name).exists() for name in CARD_FILES):
|
|
62
|
+
# A wheel that lost its cards. `lint-card` in CI and in the release workflow exists to stop
|
|
63
|
+
# that reaching PyPI; this turns the leftover case into a report rather than a traceback
|
|
64
|
+
# from inside a gate a consumer is running.
|
|
65
|
+
report.errors.append(
|
|
66
|
+
f"{definition}: this package's own cards are missing, so there is nothing to compare "
|
|
67
|
+
f"against. The build shipped without them — report it against the release."
|
|
68
|
+
)
|
|
69
|
+
return report
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
theirs = pas_card.load(use)
|
|
73
|
+
except ValueError as e:
|
|
74
|
+
report.errors.append(f"{use}: its own cards do not pass the restriction, so they cannot "
|
|
75
|
+
f"be compared: {e}")
|
|
76
|
+
return report
|
|
77
|
+
ours = pas_card.load(definition)
|
|
78
|
+
|
|
79
|
+
report.errors.extend(_compare(ours, theirs, "actions", use))
|
|
80
|
+
report.errors.extend(_compare(ours, theirs, "queries", use))
|
|
81
|
+
if not report.errors:
|
|
82
|
+
doors = ", ".join(sorted(theirs.actions) + sorted(theirs.queries))
|
|
83
|
+
report.oks.append(f"{use} answers the definition's doors, unchanged: {doors}")
|
|
84
|
+
return report
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _compare(ours, theirs, kind: str, use: Path) -> list[str]:
|
|
88
|
+
"""The differences in one door family, as messages. Empty means they agree."""
|
|
89
|
+
defined, used = getattr(ours, kind), getattr(theirs, kind)
|
|
90
|
+
errors = []
|
|
91
|
+
|
|
92
|
+
for extra in sorted(set(used) - set(defined)):
|
|
93
|
+
errors.append(f"{use}: {kind[:-1]} '{extra}' is not a door the definition declares — a use "
|
|
94
|
+
f"answers the actor's doors, it does not add its own")
|
|
95
|
+
for absent in sorted(set(defined) - set(used)):
|
|
96
|
+
errors.append(f"{use}: {kind[:-1]} '{absent}' is missing — the definition declares it, so a "
|
|
97
|
+
f"caller addressing this actor may send it")
|
|
98
|
+
|
|
99
|
+
for door in sorted(set(defined) & set(used)):
|
|
100
|
+
mine, yours = defined[door], used[door]
|
|
101
|
+
if mine.request_schema != yours.request_schema:
|
|
102
|
+
errors.append(
|
|
103
|
+
f"{use}: {kind[:-1]} '{door}' accepts a different payload than the definition. "
|
|
104
|
+
f"Its `door_schema` message and the data items that message references are what "
|
|
105
|
+
f"derive this, so one of the two drifted:\n"
|
|
106
|
+
f" definition {mine.request_schema}\n"
|
|
107
|
+
f" this use {yours.request_schema}")
|
|
108
|
+
if mine.completion_schema != yours.completion_schema:
|
|
109
|
+
errors.append(
|
|
110
|
+
f"{use}: {kind[:-1]} '{door}' replies against a different completion set than the "
|
|
111
|
+
f"definition:\n"
|
|
112
|
+
f" definition {mine.completion_schema}\n"
|
|
113
|
+
f" this use {yours.completion_schema}")
|
|
114
|
+
if mine.engine != yours.engine:
|
|
115
|
+
errors.append(
|
|
116
|
+
f"{use}: {kind[:-1]} '{door}' resolves through engine '{yours.engine}', the "
|
|
117
|
+
f"definition through '{mine.engine}' — the entrypoint registers the engine under "
|
|
118
|
+
f"the sidecar's `engine:` key, so these three must agree")
|
|
119
|
+
return errors
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# WHAT DATA EXISTS. `papeete-actor-data/v0`, owned by `papeete-actor-message` — a named, minimally
|
|
2
|
+
# typed dictionary. Every field here belongs to the ACTOR, not to any capability: a task id, the
|
|
3
|
+
# caller's statement of the task, and what came back. Nothing names a capability, a component or a
|
|
4
|
+
# knowledge tool, because none of those are known until a sidecar supplies them.
|
|
5
|
+
data: papeete-actor-data/v0
|
|
6
|
+
items:
|
|
7
|
+
- name: task_id
|
|
8
|
+
type: string
|
|
9
|
+
description: the TASK-NNN card id this actor was asked about (e.g. "TASK-009")
|
|
10
|
+
- name: title
|
|
11
|
+
type: string
|
|
12
|
+
description: the task's short title/summary, supplied by the caller — no card lookup is performed here
|
|
13
|
+
- name: context
|
|
14
|
+
type: string
|
|
15
|
+
description: free-text elaboration beyond the title — background, constraints, links; optional
|
|
16
|
+
- name: definition_of_done
|
|
17
|
+
type: list
|
|
18
|
+
description: the acceptance criteria this task must satisfy, as the caller states them
|
|
19
|
+
- name: remediation_context
|
|
20
|
+
type: string
|
|
21
|
+
description: >-
|
|
22
|
+
on a retry, the prior attempt's failing test criteria — what the implementing session should
|
|
23
|
+
fix this time; optional, absent on a first attempt
|
|
24
|
+
- name: accepted
|
|
25
|
+
type: boolean
|
|
26
|
+
description: whether the implement-task request was accepted, pushed, and its images published
|
|
27
|
+
- name: because
|
|
28
|
+
type: string
|
|
29
|
+
description: why a request was refused (containment violation, nothing staged, ...)
|
|
30
|
+
- name: branch
|
|
31
|
+
type: string
|
|
32
|
+
description: the branch the implementation was pushed to (impl/TASK-NNN)
|
|
33
|
+
- name: images
|
|
34
|
+
type: list
|
|
35
|
+
description: >-
|
|
36
|
+
the full name:version ref of each touched component's published image (e.g.
|
|
37
|
+
"acme.parts.cap.sup.007.wid-backend:0.1.0-TASK-011-a1b2c3d"), by convention — no other actor
|
|
38
|
+
is ever told these tags explicitly
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# WHAT MESSAGES EXIST. `papeete-actor-message/v1`, owned by `papeete-actor-message` — named
|
|
2
|
+
# messages, each a pure regrouping of references into the data dictionary, under an intent.
|
|
3
|
+
# Wire-agnostic by that package's own design: WHAT a message is, never HOW it is carried.
|
|
4
|
+
message: papeete-actor-message/v1
|
|
5
|
+
messages:
|
|
6
|
+
- name: implement-task-cmd
|
|
7
|
+
intent: ask this actor to implement a TASK-NNN card for the capability it serves
|
|
8
|
+
references: [task_id, title, definition_of_done, context, remediation_context]
|
|
9
|
+
optional: [context, remediation_context]
|
|
10
|
+
|
|
11
|
+
- name: task-implemented-result
|
|
12
|
+
intent: report that the task was implemented, pushed, and its touched components' images published
|
|
13
|
+
references: [accepted, branch, images]
|
|
14
|
+
|
|
15
|
+
- name: task-refused-result
|
|
16
|
+
intent: report that the task was not implemented, and why
|
|
17
|
+
references: [accepted, because]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# THE DOORS. `synchronous-messaging-doors/v1` — one non-deterministic door, and no queries.
|
|
2
|
+
#
|
|
3
|
+
# `engine: claude-code` is this actor kind's declared engine. A use's sidecar repeats it in its own
|
|
4
|
+
# `engine:` field, and the entrypoint registers the engine under exactly that key — so a use whose
|
|
5
|
+
# sidecar names a different engine is caught by its own card rather than at the first request.
|
|
6
|
+
manifest: synchronous-messaging-doors/v1
|
|
7
|
+
actions:
|
|
8
|
+
- id: implement-task
|
|
9
|
+
means: >-
|
|
10
|
+
the door for "implement TASK-NNN for the capability I serve". Send it here with the task id,
|
|
11
|
+
title, definition of done, and (optionally) context and remediation_context — the caller
|
|
12
|
+
supplies everything, no task card is looked up here. I clone my capability's repository into
|
|
13
|
+
my own private copy, ground myself in that capability's own standing context, judge how to
|
|
14
|
+
implement the task (writing only under whichever of my declared components it touches), then
|
|
15
|
+
commit and push to impl/TASK-NNN, and build each touched component's image in the cluster's
|
|
16
|
+
shared buildkit and push it to the registry, named and versioned by convention. I never open
|
|
17
|
+
a pull request — an orchestrating actor does, once a testing actor also confirms.
|
|
18
|
+
completion: whether I accepted, pushed, and published images (with the branch and image refs), or refused, and why.
|
|
19
|
+
door_schema: implement-task-cmd
|
|
20
|
+
completion_schema: [task-implemented-result, task-refused-result]
|
|
21
|
+
engine: claude-code
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# A USE's identity card. The other three are copied verbatim from the definition this package
|
|
2
|
+
# ships, which is exactly what `conformance.check` asserts — only this file is a use's own, and
|
|
3
|
+
# only its `name:` and `description:` may differ.
|
|
4
|
+
manifest: papeete-actor-manifest/v0
|
|
5
|
+
name: ACME.PARTS.CAP.SUP.007.WID-implementation
|
|
6
|
+
description: >-
|
|
7
|
+
Implements TASK-NNN cards for the fictional capability ACME.PARTS.CAP.SUP.007.WID. Exists so the
|
|
8
|
+
gates that run against the installed wheel have a complete use to check, not a sidecar alone.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""A use answers the actor's doors, or it is not a use of that actor.
|
|
2
|
+
|
|
3
|
+
The definition and a use are two folders, each independently conformant, and neither gate has any
|
|
4
|
+
opinion about the other. `conformance.check` is the one that does — see `conformance.py` for what
|
|
5
|
+
it compares and, just as deliberately, what it does not.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import shutil
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
import yaml
|
|
13
|
+
|
|
14
|
+
from foundry_implementation_actor import cards_path, conformance
|
|
15
|
+
|
|
16
|
+
FIXTURE = "tests/fixtures/valid"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _use(tmp_path, source=None):
|
|
20
|
+
"""A complete use folder, copied so a test can bend one thing in it."""
|
|
21
|
+
folder = tmp_path / "use"
|
|
22
|
+
folder.mkdir()
|
|
23
|
+
for name in conformance.CARD_FILES:
|
|
24
|
+
shutil.copy((source or cards_path()) / name, folder / name)
|
|
25
|
+
return folder
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_a_verbatim_copy_conforms(tmp_path):
|
|
29
|
+
assert conformance.check(_use(tmp_path)).ok
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_the_committed_fixture_conforms():
|
|
33
|
+
"""The fixture the wheel gates run against is a real use, not a sidecar on its own."""
|
|
34
|
+
report = conformance.check(FIXTURE)
|
|
35
|
+
assert report.ok, report.errors
|
|
36
|
+
assert any("answers the definition's doors" in line for line in report.oks)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_no_cards_warns_rather_than_fails(tmp_path):
|
|
40
|
+
"""`lint` is also run against a bare sidecar. That is incomplete, not malformed."""
|
|
41
|
+
(tmp_path / "empty").mkdir()
|
|
42
|
+
report = conformance.check(tmp_path / "empty")
|
|
43
|
+
assert report.ok
|
|
44
|
+
assert any("no cards here" in w for w in report.warns)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_an_incomplete_card_set_fails(tmp_path):
|
|
48
|
+
folder = _use(tmp_path)
|
|
49
|
+
(folder / "actor-message.yaml").unlink()
|
|
50
|
+
report = conformance.check(folder)
|
|
51
|
+
assert not report.ok
|
|
52
|
+
assert "actor-message.yaml" in report.errors[0]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_a_drifted_payload_is_caught(tmp_path):
|
|
56
|
+
"""The whole point: the use still lints, and still answers a different door.
|
|
57
|
+
|
|
58
|
+
A reference dropped from the door's own message is the cheapest realistic drift — the card set
|
|
59
|
+
remains internally valid, so every existing gate stays green.
|
|
60
|
+
"""
|
|
61
|
+
folder = _use(tmp_path)
|
|
62
|
+
path = folder / "actor-message.yaml"
|
|
63
|
+
doc = yaml.safe_load(path.read_text())
|
|
64
|
+
doc["messages"][0]["references"].remove("title")
|
|
65
|
+
path.write_text(yaml.safe_dump(doc, sort_keys=False))
|
|
66
|
+
|
|
67
|
+
report = conformance.check(folder)
|
|
68
|
+
assert not report.ok
|
|
69
|
+
assert any("accepts a different payload" in e for e in report.errors)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_a_dropped_completion_outcome_is_caught(tmp_path):
|
|
73
|
+
folder = _use(tmp_path)
|
|
74
|
+
path = folder / "actor-synchronous-messaging.yaml"
|
|
75
|
+
doc = yaml.safe_load(path.read_text())
|
|
76
|
+
doc["actions"][0]["completion_schema"] = ["task-implemented-result"]
|
|
77
|
+
path.write_text(yaml.safe_dump(doc, sort_keys=False))
|
|
78
|
+
|
|
79
|
+
report = conformance.check(folder)
|
|
80
|
+
assert not report.ok
|
|
81
|
+
assert any("different completion set" in e for e in report.errors)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_a_different_engine_is_caught(tmp_path):
|
|
85
|
+
folder = _use(tmp_path)
|
|
86
|
+
path = folder / "actor-synchronous-messaging.yaml"
|
|
87
|
+
doc = yaml.safe_load(path.read_text())
|
|
88
|
+
doc["actions"][0]["engine"] = "some-other-engine"
|
|
89
|
+
path.write_text(yaml.safe_dump(doc, sort_keys=False))
|
|
90
|
+
|
|
91
|
+
report = conformance.check(folder)
|
|
92
|
+
assert not report.ok
|
|
93
|
+
assert any("resolves through engine" in e for e in report.errors)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_prose_and_identity_are_not_compared(tmp_path):
|
|
97
|
+
"""A use SHOULD name its own capability and its own peers. That is not drift."""
|
|
98
|
+
folder = _use(tmp_path)
|
|
99
|
+
|
|
100
|
+
identity = folder / "actor.yaml"
|
|
101
|
+
doc = yaml.safe_load(identity.read_text())
|
|
102
|
+
doc["name"] = "ACME.PARTS.CAP.SUP.007.WID-implementation"
|
|
103
|
+
doc["description"] = "Implements TASK-NNN cards for ACME.PARTS.CAP.SUP.007.WID."
|
|
104
|
+
identity.write_text(yaml.safe_dump(doc, sort_keys=False))
|
|
105
|
+
|
|
106
|
+
doors = folder / "actor-synchronous-messaging.yaml"
|
|
107
|
+
doc = yaml.safe_load(doors.read_text())
|
|
108
|
+
doc["actions"][0]["means"] = "the door for 'implement TASK-NNN for ACME.PARTS.CAP.SUP.007.WID'."
|
|
109
|
+
doors.write_text(yaml.safe_dump(doc, sort_keys=False))
|
|
110
|
+
|
|
111
|
+
assert conformance.check(folder).ok
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/.github/workflows/ci.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/scripts/probe_grounding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_cards.py
RENAMED
|
File without changes
|
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_config.py
RENAMED
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_engine.py
RENAMED
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_grounding.py
RENAMED
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_handler.py
RENAMED
|
File without changes
|
{foundry_implementation_actor-0.2.0 → foundry_implementation_actor-0.2.1}/tests/test_portability.py
RENAMED
|
File without changes
|