@ikenga/contract 0.17.1 → 0.18.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/src/ngwa.ts ADDED
@@ -0,0 +1,262 @@
1
+ // Ngwa — the unified "installed thing" item, frozen as gate G-NGWA-ITEM
2
+ // (`plans/shell-ux-rearchitecture/drafts/ngwa-item.md` §2, Round 11).
3
+ //
4
+ // One `NgwaItem` joins five subsystems that no command joins today: the pkg
5
+ // kernel (`InstalledSummary`), the Ọba Claude-asset store (`ClaudeStoreEntry`),
6
+ // the engine-config scan (`claude_config.rs`), the `engine_assets` registry,
7
+ // and trust (`pkg/trust.rs` + `commands/pkg_trust.rs`). None of the five is a
8
+ // superset of another, so this type is a join, not a rename of any one of them.
9
+ //
10
+ // Producer: the Rust `ngwa_snapshot` command (WP-14). Consumers: WP-15
11
+ // installed+store, WP-16 scopes+health, WP-17 item detail, WP-18 trust sheet.
12
+ //
13
+ // KEY CASING IS `snake_case`, deliberately. The producer is a Rust command
14
+ // whose neighbouring struct (`InstalledSummary`) serializes with serde defaults
15
+ // and no renames; Ọba's camelCase renames are the exception in this codebase,
16
+ // not the rule, and matching serde defaults keeps `ngwa_snapshot` free of a
17
+ // rename attribute per field.
18
+ //
19
+ // The draft is FROZEN: any change to the shape below needs a new round in
20
+ // `04-discussion.md`, not an edit here.
21
+
22
+ import { z } from 'zod';
23
+
24
+ // ── Kind ──────────────────────────────────────────────────────────────────
25
+
26
+ /** Eleven kinds — two more than D-02's locked Kind facet. `bundle` and
27
+ * `sidecar` are real in the code (`Kind::Bundle`, `SidecarSpec`) and reachable
28
+ * through *More filters*; `workflow` has no producer in Phase 2 (Phase 4 /
29
+ * G-MANIFEST-V5) but stays in the union so adding one is not a contract break.
30
+ * `project` and `artifact` from the older `ngwa-surface.html` are NOT here —
31
+ * neither is in the locked facet list and neither has a producer. */
32
+ export const NGWA_KINDS = [
33
+ 'app', 'engine', 'tool', 'sidecar', // from the pkg kernel
34
+ 'skill', 'agent', 'command', 'hook', // from Ọba + the engine-config scan
35
+ 'bundle', // Ọba bundle, or a pkg that only pulls requires[]
36
+ 'schedule', // manifest cron[]
37
+ 'workflow', // Phase 4 — no producer in Phase 2
38
+ ] as const;
39
+ export const NgwaKindSchema = z.enum(NGWA_KINDS);
40
+ export type NgwaKind = (typeof NGWA_KINDS)[number];
41
+
42
+ // ── Scope ─────────────────────────────────────────────────────────────────
43
+
44
+ /** `personal` is the kernel's `project_id: null` and Ọba's `workspace`. */
45
+ export const NgwaScopeSchema = z.discriminatedUnion('kind', [
46
+ z.object({ kind: z.literal('personal') }),
47
+ z.object({ kind: z.literal('project'), project_id: z.string() }),
48
+ ]);
49
+ export type NgwaScope = z.infer<typeof NgwaScopeSchema>;
50
+
51
+ // ── Source (provenance) ───────────────────────────────────────────────────
52
+
53
+ /** The union of the kernel's `InstallSource` (`builtin`/`registry`/`local`/
54
+ * `dev`) and Ọba's `ProvenanceSource` (`local`/`git`/`npx`/`catalog`). */
55
+ export const NGWA_SOURCES = [
56
+ 'builtin', 'registry', 'git', 'npx', 'local', 'dev',
57
+ 'catalog', // deprecated Ọba back-compat only (claude_store.rs:94-109)
58
+ ] as const;
59
+ export const NgwaSourceSchema = z.enum(NGWA_SOURCES);
60
+ export type NgwaSource = z.infer<typeof NgwaSourceSchema>;
61
+
62
+ /** Provenance. The plan's single `source` string could not hold the url / ref /
63
+ * resolved version / publisher both provenance records carry, so it became an
64
+ * object; `origin.source` is the facet value. */
65
+ export const NgwaOriginSchema = z.object({
66
+ source: NgwaSourceSchema,
67
+ /** registry url | git remote | npm spec */
68
+ url: z.string().nullable(),
69
+ /** git ref */
70
+ ref: z.string().nullable(),
71
+ /** git SHA | npm version */
72
+ resolved_version: z.string().nullable(),
73
+ /** `InstallSource::Registry.publisher_key`, or catalog publisher */
74
+ publisher: z.string().nullable(),
75
+ /** vault-owned and deletable (Ọba `managed`) */
76
+ managed: z.boolean(),
77
+ auto_update: z.boolean(),
78
+ installed_at_ms: z.number().nullable(),
79
+ updated_at_ms: z.number().nullable(),
80
+ });
81
+ export type NgwaOrigin = z.infer<typeof NgwaOriginSchema>;
82
+
83
+ // ── State ─────────────────────────────────────────────────────────────────
84
+
85
+ export const NGWA_STATES = [
86
+ 'enabled', 'disabled', 'available', 'update', 'orphaned', 'broken',
87
+ ] as const;
88
+ export const NgwaStateSchema = z.enum(NGWA_STATES);
89
+ export type NgwaState = z.infer<typeof NgwaStateSchema>;
90
+
91
+ /** Live supervisor state. Only long-lived sidecars / MCP servers have one.
92
+ * Read from `sidecar_supervisor` directly — `PkgRowV2.state` never does, which
93
+ * is drift §10.5 that this type fixes for the new surface. */
94
+ export const NgwaRuntimeSchema = z.object({
95
+ state: z.enum([
96
+ 'spawning', 'running', 'crashed', 'blocked', 'parked', 'stopped', 'shuttingdown',
97
+ ]),
98
+ pid: z.number().nullable(),
99
+ uptime_s: z.number().nullable(),
100
+ restarts: z.number(),
101
+ last_err: z.string().nullable(),
102
+ last_crash_ms: z.number().nullable(),
103
+ });
104
+ export type NgwaRuntime = z.infer<typeof NgwaRuntimeSchema>;
105
+
106
+ // ── Trust ─────────────────────────────────────────────────────────────────
107
+
108
+ /** The four sorted permission lists the trust snapshot hash is taken over
109
+ * (`trust.rs:61-67`, `159-182`). All-or-nothing: no per-permission row exists
110
+ * anywhere, which is the constraint WP-18 inherits. */
111
+ export const NgwaPermsSummarySchema = z.object({
112
+ shell_execute: z.array(z.string()),
113
+ fs_write_outside_sandbox: z.array(z.string()),
114
+ net: z.array(z.string()),
115
+ vault_keys: z.array(z.string()),
116
+ });
117
+ export type NgwaPermsSummary = z.infer<typeof NgwaPermsSummarySchema>;
118
+
119
+ /** Two distinct trust systems exist and must not be conflated: `pkg/trust.rs`
120
+ * is the per-call sensitive-permission gate (`state`), `commands/pkg_trust.rs`
121
+ * is the separate boot-time capability-diff review (`review_pending`). The
122
+ * D-02 facet values are derived, not read off one enum:
123
+ * `builtin` ← `auto_trusted`; `signed` ← `signed`; `unsigned` ← `!signed &&
124
+ * state !== 'needs_approval'`; `review` ← `state === 'needs_approval' ||
125
+ * review_pending`. Ọba primitives get `state: 'not_applicable'`, `perms: null`
126
+ * — a skill declares intent and is never granted anything. */
127
+ export const NgwaTrustSchema = z.object({
128
+ /** Mirrors PkgTrustState; 'not_applicable' for everything that is not a pkg. */
129
+ state: z.enum([
130
+ 'auto_trusted', 'auto_granted', 'granted', 'needs_approval', 'not_applicable',
131
+ ]),
132
+ /** manifest `signature` present, or minisign-verified catalog entry */
133
+ signed: z.boolean(),
134
+ /** provenance-trusted (builtin / dev) */
135
+ auto_trusted: z.boolean(),
136
+ /** a TrustReview row is waiting (pkg_trust.rs) */
137
+ review_pending: z.boolean(),
138
+ /** null when state is 'not_applicable' */
139
+ perms: NgwaPermsSummarySchema.nullable(),
140
+ last_granted_at_ms: z.number().nullable(),
141
+ });
142
+ export type NgwaTrust = z.infer<typeof NgwaTrustSchema>;
143
+
144
+ // ── Placement ─────────────────────────────────────────────────────────────
145
+
146
+ /** The real per-instance placement record, sourced primarily from the
147
+ * `claude_config` scan; `engine_assets` is a second contributor that marks a
148
+ * placement `managed_by: 'pkg'`. `present === false` on an `enabled` item is
149
+ * the orphan case WP-16 Health reports. */
150
+ export const NgwaPlacementSchema = z.object({
151
+ /** 'claude' | 'gemini' | 'codex' | ... */
152
+ engine: z.string(),
153
+ scope: NgwaScopeSchema,
154
+ path: z.string(),
155
+ mechanism: z.enum(['symlink-dir', 'file', 'settings-key']),
156
+ /** the target actually exists on disk */
157
+ present: z.boolean(),
158
+ link_target: z.string().nullable(),
159
+ /** the link resolves into the Ọba store */
160
+ in_store: z.boolean(),
161
+ managed_by: z.enum(['oba', 'pkg', 'user']),
162
+ /** path of the placement that shadows this one */
163
+ overridden_by: z.string().nullable(),
164
+ format: z.enum(['md-yaml', 'toml', 'json-embedded']).nullable(),
165
+ status: z.enum(['active', 'deprecated']),
166
+ });
167
+ export type NgwaPlacement = z.infer<typeof NgwaPlacementSchema>;
168
+
169
+ // ── Usage ─────────────────────────────────────────────────────────────────
170
+
171
+ /** null on the item means "never measured" and MUST render as "—", never as 0
172
+ * (designs/frame-workbench-v4.html:2679 — "anything not measured reads —").
173
+ * Phase 2 sources this from transcript JSONL (DEC-24); `'hooks'` stays a legal
174
+ * value so a later increment can add it — principally for `hook` rows, which
175
+ * the transcript cannot see at all — without reopening the gate. */
176
+ export const NgwaUsageSchema = z.object({
177
+ source: z.enum(['hooks', 'transcript']),
178
+ last_used_ms: z.number().nullable(),
179
+ count_7d: z.number().nullable(),
180
+ count_30d: z.number().nullable(),
181
+ /** null when the chosen source carries no tokens */
182
+ tokens_30d: z.number().nullable(),
183
+ /** Earliest moment the source can see. Anything before it is unknown, not zero. */
184
+ window_start_ms: z.number(),
185
+ });
186
+ export type NgwaUsage = z.infer<typeof NgwaUsageSchema>;
187
+
188
+ // ── Dependency edges ──────────────────────────────────────────────────────
189
+
190
+ export const NgwaRefSchema = z.object({
191
+ /** open, mirroring RequiresEntry.kind (manifest.rs:177-192) */
192
+ kind: z.union([NgwaKindSchema, z.string()]),
193
+ name: z.string(),
194
+ /** resolved NgwaItem.id, or null if unresolved */
195
+ item_id: z.string().nullable(),
196
+ source: NgwaSourceSchema.nullable(),
197
+ ref: z.string().nullable(),
198
+ });
199
+ export type NgwaRef = z.infer<typeof NgwaRefSchema>;
200
+
201
+ // ── The item ──────────────────────────────────────────────────────────────
202
+
203
+ export const NgwaItemSchema = z.object({
204
+ /** Stable across a refresh. Pkg-backed: the manifest id. Everything else:
205
+ * `${kind}:${scope_key}:${name}` where scope_key is 'personal' | `project:<id>`. */
206
+ id: z.string(),
207
+ kind: NgwaKindSchema,
208
+ /** the on-disk / manifest name (stable, used for joins) */
209
+ name: z.string(),
210
+ /** manifest `name`, else `name` */
211
+ display_name: z.string(),
212
+ description: z.string().nullable(),
213
+ /** null for Ọba primitives with no resolved version */
214
+ version: z.string().nullable(),
215
+ /** registry index only; null otherwise */
216
+ latest_version: z.string().nullable(),
217
+ scope: NgwaScopeSchema,
218
+ origin: NgwaOriginSchema,
219
+ state: NgwaStateSchema,
220
+ runtime: NgwaRuntimeSchema.nullable(),
221
+ trust: NgwaTrustSchema,
222
+ /** An item with no placements is not an error: a pkg app has none. */
223
+ placements: z.array(NgwaPlacementSchema),
224
+ /** null = unmeasured. A measured zero is `{ count_7d: 0, … }`; the UI must
225
+ * distinguish them. */
226
+ usage: NgwaUsageSchema.nullable(),
227
+ requires: z.array(NgwaRefSchema),
228
+ /** Computed per snapshot by inverting `requires[]`, never stored — Ọba
229
+ * deliberately does not persist the reverse graph (claude_store.rs:120-122). */
230
+ required_by: z.array(NgwaRefSchema),
231
+ /** The pkg that contributed this item, when it is not itself a pkg. */
232
+ owner_pkg_id: z.string().nullable(),
233
+ install_path: z.string().nullable(),
234
+ /** Engines this item is placed in, derived from placements[]. Facet source. */
235
+ engines: z.array(z.string()),
236
+ });
237
+ export type NgwaItem = z.infer<typeof NgwaItemSchema>;
238
+
239
+ /** Per-source health entry, so the UI can say "Ọba unreadable" instead of
240
+ * silently rendering "no skills". */
241
+ export const NgwaSourceHealthSchema = z.object({
242
+ ok: z.boolean(),
243
+ error: z.string().nullable(),
244
+ count: z.number(),
245
+ });
246
+ export type NgwaSourceHealth = z.infer<typeof NgwaSourceHealthSchema>;
247
+
248
+ export const NgwaSnapshotSchema = z.object({
249
+ /** `id` is unique within a snapshot; WP-14 asserts uniqueness. */
250
+ items: z.array(NgwaItemSchema),
251
+ as_of_ms: z.number(),
252
+ /** Per-source health, so the UI can say "Ọba unreadable" instead of "no skills". */
253
+ sources: z.object({
254
+ kernel: NgwaSourceHealthSchema,
255
+ oba: NgwaSourceHealthSchema,
256
+ engine_config: NgwaSourceHealthSchema,
257
+ engine_assets: NgwaSourceHealthSchema,
258
+ trust: NgwaSourceHealthSchema,
259
+ usage: NgwaSourceHealthSchema,
260
+ }),
261
+ });
262
+ export type NgwaSnapshot = z.infer<typeof NgwaSnapshotSchema>;