@pellux/goodvibes-contracts 0.38.0 → 1.1.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.
@@ -0,0 +1,103 @@
1
+ /**
2
+ * core-verbs.ts — the canonical operator-method verb vocabulary (see CHANGELOG 1.0.0).
3
+ *
4
+ * WHY THIS EXISTS: OPERATOR_METHOD_IDS (generated/operator-method-ids.ts) is a
5
+ * flat list of dotted ids (`<namespace...>.<verb>`). Before this file, nothing
6
+ * enumerated or constrained the verb vocabulary — every method-catalog author
7
+ * picked whatever word felt right, which is how an audit found three
8
+ * worst-class collisions on the word "schedule", a redundant lifecycle pair
9
+ * (`enable`/`disable` duplicated by `pause`/`resume`), and an update-verb split
10
+ * (`patch` vs `update`). This module is the forcing function that keeps that
11
+ * from recurring: CORE_VERBS is the closed vocabulary for generic lifecycle
12
+ * operations, BANNED_VERBS are verbs that were retired and must never
13
+ * reappear, and EXEMPT_VERB_CATEGORIES documents the (large, expected) set of
14
+ * domain-specific verbs that aren't generic CRUD/lifecycle words — things like
15
+ * `voice.stt`, `homeassistant.homeGraph.askHomeGraph`, or `telemetry.otlp.logs`
16
+ * are real, single-purpose operations, not a coherence bug.
17
+ *
18
+ * NAMESPACE RULE: a resource family name is the plural noun the family
19
+ * manages (`tasks`, `sessions`, `schedules`, `watchers`); verbs attach
20
+ * directly to it (`tasks.list`, `tasks.get`). The ONE exception is a
21
+ * documented, reusable pattern — not ad hoc: when a family already has both a
22
+ * per-item action surface AND a collection surface, and giving both the same
23
+ * plural name would be ambiguous about which one an action targets, the
24
+ * per-item family takes the SINGULAR form and the collection keeps the
25
+ * PLURAL. `knowledge.schedule.get/save/delete/enable` (singular — acts on one
26
+ * schedule) alongside `knowledge.schedules.list` (plural — the collection) is
27
+ * the canonical example. Do not introduce this split defensively "for
28
+ * symmetry" on a family that only ever has one shape — `automation.schedules.*`
29
+ * has no singular sibling because nothing needs one yet.
30
+ *
31
+ * CONFORMANCE: see test/core-verbs-conformance.test.ts, which lints every id
32
+ * in OPERATOR_METHOD_IDS against this file: each verb tail must be in
33
+ * CORE_VERBS, in one of EXEMPT_VERB_CATEGORIES, or the test fails and names
34
+ * the offending id — a new ad hoc verb cannot land silently. BANNED_VERBS are
35
+ * asserted absent outright, so a retired verb can never come back under the
36
+ * same tail.
37
+ */
38
+ /**
39
+ * The canonical generic-lifecycle verb vocabulary. Every operator method
40
+ * whose action is a generic CRUD/lifecycle operation (not a bespoke,
41
+ * domain-specific action) must use one of these exact words as its id's final
42
+ * dotted segment.
43
+ */
44
+ export declare const CORE_VERBS: readonly ["list", "get", "search", "snapshot", "status", "create", "update", "delete", "upsert", "set", "enable", "disable", "close", "reopen", "cancel", "run", "retry", "invoke", "stream", "register"];
45
+ export type CoreVerb = typeof CORE_VERBS[number];
46
+ /**
47
+ * Verbs that were retired by the core-verb ruling (see CHANGELOG 1.0.0) and
48
+ * must never reappear as a method id's final dotted segment. A verb lands
49
+ * here (instead of just being deleted from history) so the conformance test
50
+ * keeps banning it even if someone re-adds it later without knowing why it
51
+ * was removed.
52
+ *
53
+ * - `patch` — retired in favor of `update` (automation.jobs.patch ->
54
+ * automation.jobs.update, routes.bindings.patch -> routes.bindings.update,
55
+ * watchers.patch -> watchers.update). `update` is the one
56
+ * canonical partial-mutation verb; `patch` mirrored the HTTP-verb name
57
+ * (PATCH) instead of the operator-method vocabulary in these three places —
58
+ * the HTTP method on the descriptor is unaffected, only the id's verb tail
59
+ * changed.
60
+ * - `pause` / `resume` — retired as a byte-identical redundant lifecycle pair
61
+ * with `enable`/`disable` (automation.jobs.pause/resume -> deleted;
62
+ * same `{id, enabled}` output shape, same semantics). A caller-facing
63
+ * "pause"/"resume" user verb should map onto `disable`/`enable` at the wire.
64
+ */
65
+ export declare const BANNED_VERBS: readonly ["patch", "pause", "resume"];
66
+ export type BannedVerb = typeof BANNED_VERBS[number];
67
+ /**
68
+ * Domain-specific verbs that are NOT part of the generic lifecycle vocabulary
69
+ * but are legitimate, real operations — not a coherence bug. Grouped by
70
+ * category with a one-line reason each, rather than one entry per id, because
71
+ * the catalog has ~300 methods across a dozen unrelated domains (calendar,
72
+ * channels, home automation, knowledge, media, telemetry, voice, ...) and
73
+ * requiring an individual justification per verb would make the exemption
74
+ * list an unmaintainable copy of the catalog itself. The categorization is
75
+ * still a real constraint: a verb tail that matches none of CORE_VERBS and
76
+ * none of these categories' listed verbs fails the conformance test — a
77
+ * genuinely new ad hoc verb has to either fit an existing category, join
78
+ * CORE_VERBS with a decision record, or get its own category here.
79
+ */
80
+ export declare const EXEMPT_VERB_CATEGORIES: Readonly<Record<string, readonly string[]>>;
81
+ /** Flattened set of every exempt (non-core, non-banned) verb, for fast lookup. */
82
+ export declare const EXEMPT_VERBS: ReadonlySet<string>;
83
+ /**
84
+ * Classify a single operator-method id's verb tail (its final dotted
85
+ * segment) against the vocabulary.
86
+ */
87
+ export type VerbClassification = {
88
+ readonly kind: 'core';
89
+ readonly verb: CoreVerb;
90
+ } | {
91
+ readonly kind: 'exempt';
92
+ readonly verb: string;
93
+ readonly category: string;
94
+ } | {
95
+ readonly kind: 'banned';
96
+ readonly verb: BannedVerb;
97
+ } | {
98
+ readonly kind: 'unclassified';
99
+ readonly verb: string;
100
+ };
101
+ export declare function verbTailOf(methodId: string): string;
102
+ export declare function classifyVerb(methodId: string): VerbClassification;
103
+ //# sourceMappingURL=core-verbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core-verbs.d.ts","sourceRoot":"","sources":["../src/core-verbs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,UAAU,2MAyBb,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,uCAAwC,CAAC;AAElE,MAAM,MAAM,UAAU,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAsFrE,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,YAAY,EAAE,WAAW,CAAC,MAAM,CAE5C,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC7E;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,GACtD;IAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE7D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAcjE"}
@@ -0,0 +1,210 @@
1
+ /**
2
+ * core-verbs.ts — the canonical operator-method verb vocabulary (see CHANGELOG 1.0.0).
3
+ *
4
+ * WHY THIS EXISTS: OPERATOR_METHOD_IDS (generated/operator-method-ids.ts) is a
5
+ * flat list of dotted ids (`<namespace...>.<verb>`). Before this file, nothing
6
+ * enumerated or constrained the verb vocabulary — every method-catalog author
7
+ * picked whatever word felt right, which is how an audit found three
8
+ * worst-class collisions on the word "schedule", a redundant lifecycle pair
9
+ * (`enable`/`disable` duplicated by `pause`/`resume`), and an update-verb split
10
+ * (`patch` vs `update`). This module is the forcing function that keeps that
11
+ * from recurring: CORE_VERBS is the closed vocabulary for generic lifecycle
12
+ * operations, BANNED_VERBS are verbs that were retired and must never
13
+ * reappear, and EXEMPT_VERB_CATEGORIES documents the (large, expected) set of
14
+ * domain-specific verbs that aren't generic CRUD/lifecycle words — things like
15
+ * `voice.stt`, `homeassistant.homeGraph.askHomeGraph`, or `telemetry.otlp.logs`
16
+ * are real, single-purpose operations, not a coherence bug.
17
+ *
18
+ * NAMESPACE RULE: a resource family name is the plural noun the family
19
+ * manages (`tasks`, `sessions`, `schedules`, `watchers`); verbs attach
20
+ * directly to it (`tasks.list`, `tasks.get`). The ONE exception is a
21
+ * documented, reusable pattern — not ad hoc: when a family already has both a
22
+ * per-item action surface AND a collection surface, and giving both the same
23
+ * plural name would be ambiguous about which one an action targets, the
24
+ * per-item family takes the SINGULAR form and the collection keeps the
25
+ * PLURAL. `knowledge.schedule.get/save/delete/enable` (singular — acts on one
26
+ * schedule) alongside `knowledge.schedules.list` (plural — the collection) is
27
+ * the canonical example. Do not introduce this split defensively "for
28
+ * symmetry" on a family that only ever has one shape — `automation.schedules.*`
29
+ * has no singular sibling because nothing needs one yet.
30
+ *
31
+ * CONFORMANCE: see test/core-verbs-conformance.test.ts, which lints every id
32
+ * in OPERATOR_METHOD_IDS against this file: each verb tail must be in
33
+ * CORE_VERBS, in one of EXEMPT_VERB_CATEGORIES, or the test fails and names
34
+ * the offending id — a new ad hoc verb cannot land silently. BANNED_VERBS are
35
+ * asserted absent outright, so a retired verb can never come back under the
36
+ * same tail.
37
+ */
38
+ /**
39
+ * The canonical generic-lifecycle verb vocabulary. Every operator method
40
+ * whose action is a generic CRUD/lifecycle operation (not a bespoke,
41
+ * domain-specific action) must use one of these exact words as its id's final
42
+ * dotted segment.
43
+ */
44
+ export const CORE_VERBS = [
45
+ // Collection / item reads
46
+ 'list',
47
+ 'get',
48
+ 'search',
49
+ 'snapshot',
50
+ 'status',
51
+ // Writes
52
+ 'create',
53
+ 'update',
54
+ 'delete',
55
+ 'upsert',
56
+ 'set',
57
+ // Lifecycle
58
+ 'enable',
59
+ 'disable',
60
+ 'close',
61
+ 'reopen',
62
+ 'cancel',
63
+ 'run',
64
+ 'retry',
65
+ // Cross-cutting / transport
66
+ 'invoke',
67
+ 'stream',
68
+ 'register',
69
+ ];
70
+ /**
71
+ * Verbs that were retired by the core-verb ruling (see CHANGELOG 1.0.0) and
72
+ * must never reappear as a method id's final dotted segment. A verb lands
73
+ * here (instead of just being deleted from history) so the conformance test
74
+ * keeps banning it even if someone re-adds it later without knowing why it
75
+ * was removed.
76
+ *
77
+ * - `patch` — retired in favor of `update` (automation.jobs.patch ->
78
+ * automation.jobs.update, routes.bindings.patch -> routes.bindings.update,
79
+ * watchers.patch -> watchers.update). `update` is the one
80
+ * canonical partial-mutation verb; `patch` mirrored the HTTP-verb name
81
+ * (PATCH) instead of the operator-method vocabulary in these three places —
82
+ * the HTTP method on the descriptor is unaffected, only the id's verb tail
83
+ * changed.
84
+ * - `pause` / `resume` — retired as a byte-identical redundant lifecycle pair
85
+ * with `enable`/`disable` (automation.jobs.pause/resume -> deleted;
86
+ * same `{id, enabled}` output shape, same semantics). A caller-facing
87
+ * "pause"/"resume" user verb should map onto `disable`/`enable` at the wire.
88
+ */
89
+ export const BANNED_VERBS = ['patch', 'pause', 'resume'];
90
+ /**
91
+ * Domain-specific verbs that are NOT part of the generic lifecycle vocabulary
92
+ * but are legitimate, real operations — not a coherence bug. Grouped by
93
+ * category with a one-line reason each, rather than one entry per id, because
94
+ * the catalog has ~300 methods across a dozen unrelated domains (calendar,
95
+ * channels, home automation, knowledge, media, telemetry, voice, ...) and
96
+ * requiring an individual justification per verb would make the exemption
97
+ * list an unmaintainable copy of the catalog itself. The categorization is
98
+ * still a real constraint: a verb tail that matches none of CORE_VERBS and
99
+ * none of these categories' listed verbs fails the conformance test — a
100
+ * genuinely new ad hoc verb has to either fit an existing category, join
101
+ * CORE_VERBS with a decision record, or get its own category here.
102
+ */
103
+ export const EXEMPT_VERB_CATEGORIES = {
104
+ 'external-api-mirror': [
105
+ // Verbs that mirror an external vendor's own API vocabulary (Home
106
+ // Assistant's homeGraph.* actions, calendar ICS import/export). Renaming
107
+ // these to the core vocabulary would obscure the 1:1 mapping to the
108
+ // vendor operation they wrap.
109
+ 'askHomeGraph', 'browse', 'export', 'generateHomeGraphPacket', 'generateRoomPage',
110
+ 'import', 'ingestHomeGraphArtifact', 'ingestHomeGraphNote', 'ingestHomeGraphUrl',
111
+ 'linkHomeGraphKnowledge', 'listHomeGraphIssues', 'map', 'refreshDevicePassport',
112
+ 'reset', 'reviewHomeGraphFact', 'syncHomeGraph', 'unlinkHomeGraphKnowledge',
113
+ ],
114
+ 'media-and-voice-io': [
115
+ // Single-purpose media/voice operations named for what they produce, not
116
+ // a generic CRUD shape.
117
+ 'analyze', 'generate', 'transform', 'writeback', 'stt', 'tts', 'session',
118
+ ],
119
+ 'maintenance-and-indexing': [
120
+ // Index/derived-state maintenance actions (knowledge and home-graph
121
+ // reindexing, memory vector rebuilds) — a maintenance operation on
122
+ // derived state, not a CRUD action on the record itself.
123
+ 'reindex', 'rebuild',
124
+ ],
125
+ 'transport-and-protocol': [
126
+ // Endpoints whose name is a protocol/transport concept, not a resource
127
+ // verb (auth, OTLP ingestion, contract introspection, GraphQL execution).
128
+ 'login', 'current', 'contract', 'schema', 'execute', 'logs', 'metrics', 'traces', 'web',
129
+ ],
130
+ 'ingest-and-content': [
131
+ // Content-shaped verbs describing what is being brought in or produced,
132
+ // not a generic lifecycle action.
133
+ 'artifact', 'bookmarks', 'browserHistory', 'connector', 'url', 'urls', 'packet',
134
+ 'lint', 'materialize', 'render', 'query', 'ask', 'decide', 'review',
135
+ ],
136
+ 'approval-and-routing': [
137
+ // Domain verbs specific to the approvals/routing/session-target model.
138
+ 'approve', 'claim', 'deny', 'default', 'named', 'assign', 'resolve', 'authorize',
139
+ 'audit', 'edit', 'diff', 'restore', 'revoke', 'rotate', 'disconnect',
140
+ ],
141
+ 'session-and-work-lifecycle': [
142
+ // Session/task-graph-specific state transitions that are not the generic
143
+ // enable/disable/cancel vocabulary (they carry session semantics:
144
+ // steering a live turn, delivering an out-of-band input, etc).
145
+ 'detach', 'followUp', 'deliver', 'steer', 'reorder', 'clearCompleted', 'record',
146
+ 'evaluate', 'send', 'read', 'save',
147
+ ],
148
+ 'reporting-and-diagnostics': [
149
+ // Read-shaped diagnostic/reporting endpoints named for their specific
150
+ // report, not a generic "get"/"list".
151
+ 'doctor', 'stats', 'capacity', 'settings', 'catalog', 'reject', 'review-queue',
152
+ ],
153
+ 'memory-record-store': [
154
+ // The daemon-owned canonical memory store mirrors the MemoryStore engine's
155
+ // own long-standing API verbs rather than the generic CRUD words. `add`
156
+ // (MemoryStore.add stamps a new record at the recall-confidence floor — the
157
+ // recall-honesty contract is written in terms of `add`, not `create`) and
158
+ // `update-review` (mutates ONLY a record's review signal —
159
+ // reviewState/confidence/reviewer/staleReason — a narrower, honesty-load-
160
+ // bearing operation than a generic `update` that would also touch content).
161
+ 'add', 'update-review',
162
+ ],
163
+ 'push-delivery': [
164
+ // Browser-push delivery action: `verify` sends a live test notification to
165
+ // a stored subscription and returns an honest delivery receipt (proving the
166
+ // encryption + endpoint round trip). It is a single-purpose delivery probe,
167
+ // not a generic read/lifecycle word — the subscription lifecycle itself uses
168
+ // core verbs (push.subscriptions.create/list/delete, push.vapid.get).
169
+ 'verify',
170
+ ],
171
+ 'process-control': [
172
+ // OS/service process lifecycle verbs (distinct domain from
173
+ // enable/disable, which toggle a *record's* activation state) plus
174
+ // reload (re-read a live config from disk — a process action, not a
175
+ // record lifecycle transition).
176
+ 'install', 'restart', 'start', 'stop', 'uninstall', 'open', 'reload',
177
+ ],
178
+ 'legacy-verb-aliases': [
179
+ // KNOWN, OUT-OF-SCOPE minor inconsistency (not one of the ranked
180
+ // worst-class collisions): mcp.servers.remove means exactly what `delete`
181
+ // means everywhere else in the catalog. Flagged here rather than fixed,
182
+ // per the scope discipline of fixing only the ranked worst-class
183
+ // items (schedule/memory/tasks/session-orphan/sessions-visibility) —
184
+ // renaming every already-consistent-but-differently-worded single
185
+ // outlier was explicitly out of scope for this pass. A
186
+ // future pass can fold this into `delete`.
187
+ 'remove',
188
+ ],
189
+ };
190
+ /** Flattened set of every exempt (non-core, non-banned) verb, for fast lookup. */
191
+ export const EXEMPT_VERBS = new Set(Object.values(EXEMPT_VERB_CATEGORIES).flat());
192
+ export function verbTailOf(methodId) {
193
+ const segments = methodId.split('.');
194
+ return segments[segments.length - 1] ?? methodId;
195
+ }
196
+ export function classifyVerb(methodId) {
197
+ const verb = verbTailOf(methodId);
198
+ if (BANNED_VERBS.includes(verb)) {
199
+ return { kind: 'banned', verb: verb };
200
+ }
201
+ if (CORE_VERBS.includes(verb)) {
202
+ return { kind: 'core', verb: verb };
203
+ }
204
+ for (const [category, verbs] of Object.entries(EXEMPT_VERB_CATEGORIES)) {
205
+ if (verbs.includes(verb)) {
206
+ return { kind: 'exempt', verb, category };
207
+ }
208
+ }
209
+ return { kind: 'unclassified', verb };
210
+ }