@terpjs/spec 0.19.0 → 0.20.0

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.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 0.19.0
1
+ 0.20.0
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "backend/emitted_events_are_declared",
3
+ "surface": "backend",
4
+ "title": "A module emits only the events its manifest declares",
5
+ "intent": "The manifest's emits list is the module's published contract: it is what the control plane validates, what an operator reads to know what a module produces, and what another team subscribes against. An emit the manifest never declared makes that contract quietly untrue — the event really does go out, so nothing fails, while the document everyone reasons from says it cannot happen. The rule compares every event constant an emit call or lifecycle event map names inside a module package against that module's declared emits.",
6
+ "layer": "static-bespoke",
7
+ "enforcement": [
8
+ {
9
+ "kind": "build-time",
10
+ "tool": "terp.arch",
11
+ "ref": "check_emitted_events_are_declared"
12
+ }
13
+ ],
14
+ "reference": "ModuleSpec(emits=[...]) in modules/<name>/module.py, compared against emit(event=...) and LifecycleEventMap(created=/updated=/deleted=...) references in the same module package.",
15
+ "opt_out": "# arch-allow-emitted-events-are-declared: <reason>",
16
+ "runtime": {
17
+ "applicability": "not-applicable",
18
+ "rationale": "emit() carries no module identity at the call site, so the running system cannot attribute an emit to the manifest that should have declared it; the association exists only in the source layout (which module package the call lives in). Adding a module handle to emit() to make it runtime-checkable would put the answer in the caller's hands, which is exactly what the rule is verifying."
19
+ },
20
+ "guide_topic": "events",
21
+ "corpus": true
22
+ }
@@ -0,0 +1,13 @@
1
+ from control_plane.events import NOTE_CREATED, NOTE_DELETED
2
+ from terp.core import ModuleSpec, Policy, Roles, emit
3
+
4
+ spec = ModuleSpec(
5
+ name="notes",
6
+ router=router,
7
+ policy=Policy(read=Roles.VIEWER, write=Roles.EDITOR),
8
+ emits=[NOTE_CREATED, NOTE_DELETED],
9
+ )
10
+
11
+
12
+ def archive(session, note):
13
+ emit(session, event=NOTE_DELETED, payload={"id": str(note.id)})
@@ -0,0 +1,14 @@
1
+ from control_plane.events import NOTE_CREATED, NOTE_DELETED
2
+ from terp.core import ModuleSpec, Policy, Roles, emit
3
+
4
+ spec = ModuleSpec(
5
+ name="notes",
6
+ router=router,
7
+ policy=Policy(read=Roles.VIEWER, write=Roles.EDITOR),
8
+ emits=[NOTE_CREATED],
9
+ )
10
+
11
+
12
+ def archive(session, note):
13
+ # Declared nowhere: the manifest above says this module emits only NOTE_CREATED.
14
+ emit(session, event=NOTE_DELETED, payload={"id": str(note.id)})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@terpjs/spec",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "The Terp Standard — stack-neutral rule catalog, violation corpus, finding format, and refused-surface declaration (ADRs 0080/0081; packaged per ADR 0082, published per ADR 0086). Data only: consumers resolve the spec root via require.resolve('@terpjs/spec/package.json').",
5
5
  "files": [
6
6
  "VERSION",