@phosra/cli 0.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.
Files changed (58) hide show
  1. package/README.md +42 -0
  2. package/dist/bin.d.ts +19 -0
  3. package/dist/bin.d.ts.map +1 -0
  4. package/dist/bin.js +159 -0
  5. package/dist/bin.js.map +1 -0
  6. package/dist/commands/caps.d.ts +16 -0
  7. package/dist/commands/caps.d.ts.map +1 -0
  8. package/dist/commands/caps.js +125 -0
  9. package/dist/commands/caps.js.map +1 -0
  10. package/dist/commands/doctor.d.ts +34 -0
  11. package/dist/commands/doctor.d.ts.map +1 -0
  12. package/dist/commands/doctor.js +321 -0
  13. package/dist/commands/doctor.js.map +1 -0
  14. package/dist/commands/gk-check.d.ts +29 -0
  15. package/dist/commands/gk-check.d.ts.map +1 -0
  16. package/dist/commands/gk-check.js +134 -0
  17. package/dist/commands/gk-check.js.map +1 -0
  18. package/dist/commands/init.d.ts +18 -0
  19. package/dist/commands/init.d.ts.map +1 -0
  20. package/dist/commands/init.js +90 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/link-write.d.ts +26 -0
  23. package/dist/commands/link-write.d.ts.map +1 -0
  24. package/dist/commands/link-write.js +129 -0
  25. package/dist/commands/link-write.js.map +1 -0
  26. package/dist/commands/register.d.ts +63 -0
  27. package/dist/commands/register.d.ts.map +1 -0
  28. package/dist/commands/register.js +167 -0
  29. package/dist/commands/register.js.map +1 -0
  30. package/dist/config.d.ts +72 -0
  31. package/dist/config.d.ts.map +1 -0
  32. package/dist/config.js +94 -0
  33. package/dist/config.js.map +1 -0
  34. package/dist/envelope.d.ts +32 -0
  35. package/dist/envelope.d.ts.map +1 -0
  36. package/dist/envelope.js +53 -0
  37. package/dist/envelope.js.map +1 -0
  38. package/dist/keygen.d.ts +29 -0
  39. package/dist/keygen.d.ts.map +1 -0
  40. package/dist/keygen.js +38 -0
  41. package/dist/keygen.js.map +1 -0
  42. package/dist/out.d.ts +33 -0
  43. package/dist/out.d.ts.map +1 -0
  44. package/dist/out.js +76 -0
  45. package/dist/out.js.map +1 -0
  46. package/dist/round-trip.d.ts +51 -0
  47. package/dist/round-trip.d.ts.map +1 -0
  48. package/dist/round-trip.js +115 -0
  49. package/dist/round-trip.js.map +1 -0
  50. package/dist/sender-key.d.ts +26 -0
  51. package/dist/sender-key.d.ts.map +1 -0
  52. package/dist/sender-key.js +35 -0
  53. package/dist/sender-key.js.map +1 -0
  54. package/dist/transport.d.ts +30 -0
  55. package/dist/transport.d.ts.map +1 -0
  56. package/dist/transport.js +62 -0
  57. package/dist/transport.js.map +1 -0
  58. package/package.json +31 -0
@@ -0,0 +1,321 @@
1
+ /**
2
+ * phosra doctor — end-to-end setup verification.
3
+ *
4
+ * Runs 5 checks in order, reporting PASS / FAIL / WARN for each.
5
+ * Exits 0 if all checks pass (warns are non-fatal). Exits 1 on any FAIL.
6
+ *
7
+ * Checks:
8
+ * 1. census_reachable GET /health → 200
9
+ * 2. trust_list_verified GET /.well-known/ocss/trust-list + Ed25519 root-sig verify
10
+ * 3. caps_verified GET /.well-known/ocss/capabilities + root-sig verify (WARN if 404)
11
+ * 4. version_negotiates negotiate(SUPPORTED_VERSIONS, caps.version_range.supported)
12
+ * 5. sandbox_round_trip Full §8.3.2 + §8.3.1 write cycle via runProviderRoundTrip:
13
+ * trust-list fetch → router JWK extract →
14
+ * consent_attestation ingest (201) → rule write (201)
15
+ *
16
+ * Check 5 uses the zero-provisioning sandbox: household-acme against the STAGING
17
+ * shared sandbox trust-list + Mia child/policy — no DB, no per-org provisioning,
18
+ * no extra backend.
19
+ *
20
+ * §12.3: census is the safety authority. The CLI verifies; it never decides.
21
+ * Fail-closed: any FAIL exits non-zero, never a false green.
22
+ */
23
+ import { verifyDocument, verifyCapabilitiesDocument, negotiate, SUPPORTED_VERSIONS, fromVerifiedDocument, b64urlRaw, } from "@openchildsafety/ocss";
24
+ import { HOUSEHOLD_ACME_KEY_ID } from "../config.js";
25
+ import { senderKeyFromSeedB64url, publicXFromSenderKey } from "../sender-key.js";
26
+ import { runProviderRoundTrip } from "../round-trip.js";
27
+ import { printCheck, printJson, heading, summary, } from "../out.js";
28
+ /**
29
+ * Run all doctor checks.
30
+ * Returns the array of CheckResults; the caller (bin.ts) sets the exit code.
31
+ *
32
+ * fetchImpl is injectable for unit tests — real commands pass global fetch.
33
+ */
34
+ export async function runDoctor(cfg, json, fetchImpl = fetch) {
35
+ heading("Phosra doctor", json);
36
+ const results = [];
37
+ // ── 1: census reachable ──────────────────────────────────────────────────
38
+ const c1 = await checkCensusReachable(cfg.censusUrl, fetchImpl);
39
+ results.push(c1);
40
+ printCheck(c1, json);
41
+ if (c1.status === "fail") {
42
+ const skips = [
43
+ ["trust_list_verified", "skipped — census unreachable"],
44
+ ["caps_verified", "skipped — census unreachable"],
45
+ ["version_negotiates", "skipped — no caps doc"],
46
+ ["sandbox_round_trip", "skipped — census unreachable"],
47
+ ];
48
+ for (const [name, msg] of skips) {
49
+ const r = skip(name, msg);
50
+ results.push(r);
51
+ printCheck(r, json);
52
+ }
53
+ finish(results, json);
54
+ return results;
55
+ }
56
+ // ── 2: trust-list verified ──────────────────────────────────────────────
57
+ const c2 = await checkTrustListVerified(cfg.censusUrl, cfg.trustRootX, fetchImpl);
58
+ results.push(c2);
59
+ printCheck(c2, json);
60
+ let resolver;
61
+ if (c2.status === "pass" && c2.data) {
62
+ const tlDoc = c2.data.doc;
63
+ try {
64
+ resolver = fromVerifiedDocument(tlDoc);
65
+ }
66
+ catch { /* resolver stays undefined */ }
67
+ }
68
+ // ── 3: capabilities verified ────────────────────────────────────────────
69
+ const c3 = await checkCapsVerified(cfg.censusUrl, cfg.trustRootX, fetchImpl);
70
+ results.push(c3);
71
+ printCheck(c3, json);
72
+ // ── 4: version negotiates ───────────────────────────────────────────────
73
+ const serverVersions = c3.status === "pass" && c3.data
74
+ ? c3.data.supported
75
+ : undefined;
76
+ const c4 = checkVersionNegotiates(serverVersions);
77
+ results.push(c4);
78
+ printCheck(c4, json);
79
+ // ── 5: sandbox round-trip ───────────────────────────────────────────────
80
+ const c5 = await checkSandboxRoundTrip(cfg, resolver, fetchImpl);
81
+ results.push(c5);
82
+ printCheck(c5, json);
83
+ finish(results, json);
84
+ return results;
85
+ }
86
+ // ── output helpers ────────────────────────────────────────────────────────────
87
+ function skip(name, message) {
88
+ return { name, status: "skip", message };
89
+ }
90
+ function finish(results, json) {
91
+ if (json) {
92
+ printJson({
93
+ ok: results.every((r) => r.status !== "fail"),
94
+ checks: results.map((r) => ({
95
+ name: r.name,
96
+ status: r.status,
97
+ message: r.message,
98
+ ...(r.data !== undefined ? { data: r.data } : {}),
99
+ })),
100
+ });
101
+ }
102
+ else {
103
+ summary(results, json);
104
+ }
105
+ }
106
+ // ── individual check implementations ─────────────────────────────────────────
107
+ async function checkCensusReachable(censusUrl, fetchImpl) {
108
+ try {
109
+ const res = await fetchImpl(`${censusUrl}/health`, { method: "GET" });
110
+ if (res.status === 200) {
111
+ return { name: "census_reachable", status: "pass", message: censusUrl };
112
+ }
113
+ return {
114
+ name: "census_reachable",
115
+ status: "fail",
116
+ message: `GET /health → HTTP ${res.status}`,
117
+ hint: `Check PHOSRA_CENSUS_URL (currently: ${censusUrl})`,
118
+ };
119
+ }
120
+ catch (err) {
121
+ return {
122
+ name: "census_reachable",
123
+ status: "fail",
124
+ message: `network error: ${err.message}`,
125
+ hint: `Check PHOSRA_CENSUS_URL and network connectivity. Current: ${censusUrl}`,
126
+ };
127
+ }
128
+ }
129
+ async function checkTrustListVerified(censusUrl, trustRootX, fetchImpl) {
130
+ try {
131
+ const res = await fetchImpl(`${censusUrl}/.well-known/ocss/trust-list`, {
132
+ method: "GET",
133
+ });
134
+ if (!res.ok) {
135
+ return {
136
+ name: "trust_list_verified",
137
+ status: "fail",
138
+ message: `GET /.well-known/ocss/trust-list → HTTP ${res.status}`,
139
+ hint: "The census may not have the Trust List endpoint enabled.",
140
+ };
141
+ }
142
+ const signed = (await res.json());
143
+ const doc = verifyDocument(signed, trustRootX);
144
+ const entryCount = doc.entries?.length ?? 0;
145
+ const serial = doc.issue?.serial ?? "?";
146
+ return {
147
+ name: "trust_list_verified",
148
+ status: "pass",
149
+ message: `${entryCount} entr${entryCount === 1 ? "y" : "ies"}, serial ${serial}`,
150
+ data: { doc, entry_count: entryCount },
151
+ };
152
+ }
153
+ catch (err) {
154
+ const msg = err.message;
155
+ const isSignatureErr = msg.includes("signature") || msg.includes("verify");
156
+ return {
157
+ name: "trust_list_verified",
158
+ status: "fail",
159
+ message: msg,
160
+ hint: isSignatureErr
161
+ ? `PHOSRA_TRUST_ROOT_X may not match the census root key. Current: ${trustRootX}`
162
+ : "Inspect the raw Trust List response for malformed JSON.",
163
+ };
164
+ }
165
+ }
166
+ async function checkCapsVerified(censusUrl, trustRootX, fetchImpl) {
167
+ try {
168
+ const res = await fetchImpl(`${censusUrl}/.well-known/ocss/capabilities`, {
169
+ method: "GET",
170
+ });
171
+ if (res.status === 404) {
172
+ return {
173
+ name: "caps_verified",
174
+ status: "warn",
175
+ message: "404 — capabilities doc not yet published (P1 feature)",
176
+ hint: "Expected for pre-P1 censuses. `phosra doctor` still passes overall.",
177
+ };
178
+ }
179
+ if (!res.ok) {
180
+ return {
181
+ name: "caps_verified",
182
+ status: "warn",
183
+ message: `HTTP ${res.status}`,
184
+ hint: "Caps endpoint error; skipping version-range check.",
185
+ };
186
+ }
187
+ const signed = (await res.json());
188
+ const doc = verifyCapabilitiesDocument(signed, trustRootX);
189
+ return {
190
+ name: "caps_verified",
191
+ status: "pass",
192
+ message: `ocss_version ${doc.ocss_version}, ${doc.rule_categories?.length ?? 0} rules, serial ${doc.serial}`,
193
+ data: {
194
+ ocss_version: doc.ocss_version,
195
+ supported: doc.version_range?.supported ?? [],
196
+ rule_categories: doc.rule_categories?.length ?? 0,
197
+ },
198
+ };
199
+ }
200
+ catch (err) {
201
+ return {
202
+ name: "caps_verified",
203
+ status: "warn",
204
+ message: err.message,
205
+ hint: "Caps doc failed verification (non-fatal — continuing without it).",
206
+ };
207
+ }
208
+ }
209
+ function checkVersionNegotiates(serverVersions) {
210
+ if (!serverVersions) {
211
+ return {
212
+ name: "version_negotiates",
213
+ status: "skip",
214
+ message: "no caps doc — version negotiation check skipped",
215
+ };
216
+ }
217
+ const agreed = negotiate(SUPPORTED_VERSIONS, serverVersions);
218
+ if (agreed === null) {
219
+ return {
220
+ name: "version_negotiates",
221
+ status: "fail",
222
+ message: `no common version client: [${SUPPORTED_VERSIONS.join(", ")}] server: [${serverVersions.join(", ")}]`,
223
+ hint: "Upgrade @phosra/cli or the census to share a common OCSS spec version.",
224
+ };
225
+ }
226
+ return {
227
+ name: "version_negotiates",
228
+ status: "pass",
229
+ message: `agreed ${agreed}`,
230
+ data: { agreed, client: SUPPORTED_VERSIONS, server: serverVersions },
231
+ };
232
+ }
233
+ /**
234
+ * Check 5: full §8.3.2 + §8.3.1 sandbox provider round-trip.
235
+ *
236
+ * Calls runProviderRoundTrip (round-trip.ts) which:
237
+ * 1. Fetches + verifies the trust list from the sandbox census
238
+ * 2. Extracts the router P-256 payload key
239
+ * 3. Seals a consent_attestation envelope to the router
240
+ * 4. POSTs to /api/v1/webhooks/inbound/app-store → expects 201/200
241
+ * 5. Writes addictive_pattern_block citing the attestation as standing
242
+ * 6. Returns ok = true iff both POST calls succeeded
243
+ *
244
+ * Also cross-checks the key material: derives the household-acme public key X
245
+ * from the config seed and (optionally) verifies it against the trust-list entry.
246
+ */
247
+ async function checkSandboxRoundTrip(cfg, resolver, fetchImpl) {
248
+ // ── build SenderKey from seed ──────────────────────────────────────────
249
+ let key;
250
+ try {
251
+ key = senderKeyFromSeedB64url(cfg.householdSeed, HOUSEHOLD_ACME_KEY_ID);
252
+ }
253
+ catch (err) {
254
+ return {
255
+ name: "sandbox_round_trip",
256
+ status: "fail",
257
+ message: `seed decode failed: ${err.message}`,
258
+ hint: "Check PHOSRA_HOUSEHOLD_SEED — must be a base64url-encoded 32-byte Ed25519 seed.",
259
+ };
260
+ }
261
+ // ── optional key-material cross-check against the trust-list ──────────
262
+ let keyNote = "";
263
+ if (resolver) {
264
+ try {
265
+ const derivedX = publicXFromSenderKey(key);
266
+ const tlRaw = resolver.signingKey(HOUSEHOLD_ACME_KEY_ID);
267
+ const tlX = b64urlRaw(tlRaw);
268
+ if (derivedX !== tlX) {
269
+ return {
270
+ name: "sandbox_round_trip",
271
+ status: "fail",
272
+ message: `key mismatch: seed derives ${derivedX.slice(0, 10)}… ` +
273
+ `but trust-list has ${tlX.slice(0, 10)}…`,
274
+ hint: `PHOSRA_HOUSEHOLD_SEED does not match the trust-list entry ` +
275
+ `for ${HOUSEHOLD_ACME_KEY_ID}.`,
276
+ };
277
+ }
278
+ keyNote = " — key verified vs trust-list";
279
+ }
280
+ catch {
281
+ keyNote = " — household-acme not in trust-list (sandbox may differ)";
282
+ }
283
+ }
284
+ // ── full §8.3.2 + §8.3.1 round-trip ────────────────────────────────
285
+ const rt = await runProviderRoundTrip(key, {
286
+ censusUrl: cfg.censusUrl,
287
+ trustRootX: cfg.trustRootX,
288
+ miaPolicyId: cfg.miaPolicyId,
289
+ miaChildId: cfg.miaChildId,
290
+ fetchImpl,
291
+ });
292
+ if (!rt.ok) {
293
+ return {
294
+ name: "sandbox_round_trip",
295
+ status: "fail",
296
+ message: rt.error ?? `consent=${rt.consentStatus} rule=${rt.ruleStatus}`,
297
+ hint: (rt.error?.includes("trust-list"))
298
+ ? "Trust-list fetch failed — check census connectivity and PHOSRA_TRUST_ROOT_X."
299
+ : rt.error?.includes("401")
300
+ ? "The census rejected the RFC-9421 signature. Check PHOSRA_HOUSEHOLD_SEED."
301
+ : "Check census connectivity and that the sandbox Mia policy is seeded.",
302
+ data: {
303
+ trust_list_entries: rt.trustListEntries,
304
+ consent_status: rt.consentStatus,
305
+ rule_status: rt.ruleStatus,
306
+ },
307
+ };
308
+ }
309
+ return {
310
+ name: "sandbox_round_trip",
311
+ status: "pass",
312
+ message: `consent ${rt.consentStatus}, rule ${rt.ruleStatus}, ` +
313
+ `${rt.trustListEntries} trust-list entries${keyNote}`,
314
+ data: {
315
+ trust_list_entries: rt.trustListEntries,
316
+ consent_status: rt.consentStatus,
317
+ rule_status: rt.ruleStatus,
318
+ },
319
+ };
320
+ }
321
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * phosra gatekeeper check — platform isAllowed() against live census.
3
+ *
4
+ * Drives the @phosra/gatekeeper SDK to:
5
+ * 1. Refresh the trust list
6
+ * 2. Refresh the enforcement profile for the configured endpoint
7
+ * 3. Call isAllowed({ category }) → Verdict (allow / warn / block)
8
+ * 4. Print the verdict + optionally confirm enforcement via --confirm
9
+ *
10
+ * Required config:
11
+ * PHOSRA_ENDPOINT_ID §9.3(b) bound-resolver label (from connect ceremony)
12
+ * PHOSRA_TRUST_ROOT_X Ed25519 root public key X
13
+ * PHOSRA_CENSUS_URL Census base URL
14
+ *
15
+ * @phosra/gatekeeper is a peer / optional dependency. If not installed, the
16
+ * command will prompt for it.
17
+ *
18
+ * §12.3: the CLI surfaces the verdict; the census is the safety authority.
19
+ * The CLI must NOT make an offline enforcement decision.
20
+ */
21
+ import type { PhosraConfig } from "../config.js";
22
+ export interface GkCheckOpts {
23
+ category: string;
24
+ endpointId?: string;
25
+ confirm?: "applied" | "degraded" | "refused";
26
+ json: boolean;
27
+ }
28
+ export declare function runGkCheck(cfg: PhosraConfig, opts: GkCheckOpts): Promise<void>;
29
+ //# sourceMappingURL=gk-check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gk-check.d.ts","sourceRoot":"","sources":["../../src/commands/gk-check.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAIjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC7C,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,IAAI,CAAC,CAuHf"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * phosra gatekeeper check — platform isAllowed() against live census.
3
+ *
4
+ * Drives the @phosra/gatekeeper SDK to:
5
+ * 1. Refresh the trust list
6
+ * 2. Refresh the enforcement profile for the configured endpoint
7
+ * 3. Call isAllowed({ category }) → Verdict (allow / warn / block)
8
+ * 4. Print the verdict + optionally confirm enforcement via --confirm
9
+ *
10
+ * Required config:
11
+ * PHOSRA_ENDPOINT_ID §9.3(b) bound-resolver label (from connect ceremony)
12
+ * PHOSRA_TRUST_ROOT_X Ed25519 root public key X
13
+ * PHOSRA_CENSUS_URL Census base URL
14
+ *
15
+ * @phosra/gatekeeper is a peer / optional dependency. If not installed, the
16
+ * command will prompt for it.
17
+ *
18
+ * §12.3: the CLI surfaces the verdict; the census is the safety authority.
19
+ * The CLI must NOT make an offline enforcement decision.
20
+ */
21
+ import { printJson } from "../out.js";
22
+ import { senderKeyFromSeedB64url } from "../sender-key.js";
23
+ import { HOUSEHOLD_ACME_KEY_ID } from "../config.js";
24
+ export async function runGkCheck(cfg, opts) {
25
+ const endpointId = opts.endpointId ?? cfg.endpointId;
26
+ if (!endpointId) {
27
+ const msg = "PHOSRA_ENDPOINT_ID is required for `phosra gatekeeper check`.\n\n" +
28
+ " It is the §9.3(b) bound-resolver label returned by the connect ceremony.\n" +
29
+ " Set it in .phosra.env:\n" +
30
+ " PHOSRA_ENDPOINT_ID=<label from connect ceremony>\n";
31
+ if (opts.json) {
32
+ printJson({ ok: false, error: "missing_endpoint_id", hint: msg });
33
+ }
34
+ else {
35
+ console.error(" ✘ " + msg);
36
+ }
37
+ process.exit(1);
38
+ }
39
+ // ── dynamic import of @phosra/gatekeeper ────────────────────────────────
40
+ let gkModule;
41
+ try {
42
+ gkModule = await import("@phosra/gatekeeper");
43
+ }
44
+ catch {
45
+ const msg = "`phosra gatekeeper check` requires @phosra/gatekeeper.\n\n" +
46
+ " Install it:\n" +
47
+ " npm install @phosra/gatekeeper\n";
48
+ if (opts.json) {
49
+ printJson({ ok: false, error: "missing_peer_dep", package: "@phosra/gatekeeper", hint: msg });
50
+ }
51
+ else {
52
+ console.error(" ✘ " + msg);
53
+ }
54
+ process.exit(1);
55
+ }
56
+ const { createGatekeeper } = gkModule;
57
+ // Build a platform signing key from the household-acme seed (sandbox demo mode).
58
+ // In production, the platform has its own Ed25519 key registered in the trust-list.
59
+ const signingKey = senderKeyFromSeedB64url(cfg.householdSeed, HOUSEHOLD_ACME_KEY_ID);
60
+ const gkCfg = {
61
+ platformDid: "did:ocss:household-acme",
62
+ platformKeyId: HOUSEHOLD_ACME_KEY_ID,
63
+ gatekeeperSigningKey: signingKey,
64
+ censusBaseUrl: cfg.censusUrl,
65
+ trustRootXB64Url: cfg.trustRootX,
66
+ endpointId,
67
+ ratingMappings: [], // no rating-mapping needed for categorical checks
68
+ };
69
+ let gk;
70
+ try {
71
+ gk = await createGatekeeper(gkCfg);
72
+ await gk.refreshTrustList();
73
+ await gk.refreshProfile(endpointId);
74
+ }
75
+ catch (err) {
76
+ if (opts.json) {
77
+ printJson({ ok: false, error: "gatekeeper_init_failed", message: err.message });
78
+ }
79
+ else {
80
+ console.error(` ✘ gatekeeper init failed: ${err.message}`);
81
+ }
82
+ process.exit(1);
83
+ }
84
+ const verdict = gk.isAllowed({ endpointId, category: opts.category });
85
+ if (opts.json && !opts.confirm) {
86
+ printJson({
87
+ ok: true,
88
+ verdict: {
89
+ decision: verdict.decision,
90
+ fail_mode: verdict.failMode,
91
+ rule_slug: verdict.ruleSlug,
92
+ rule_ref: verdict.ruleRef,
93
+ },
94
+ });
95
+ gk.destroy();
96
+ return;
97
+ }
98
+ if (!opts.json) {
99
+ const icon = verdict.decision === "allow" ? "✔" :
100
+ verdict.decision === "warn" ? "⚠" : "✘";
101
+ console.log(`\n ${icon} ${opts.category} → ${verdict.decision.toUpperCase()}` +
102
+ ` (fail-mode: ${verdict.failMode})\n`);
103
+ if (verdict.ruleRef)
104
+ console.log(` rule_ref: ${verdict.ruleRef}`);
105
+ }
106
+ // ── optional §8.3.8 confirmation ─────────────────────────────────────────
107
+ if (opts.confirm) {
108
+ try {
109
+ const receipt = await verdict.confirm(opts.confirm);
110
+ if (opts.json) {
111
+ printJson({
112
+ ok: true,
113
+ verdict: { decision: verdict.decision },
114
+ confirmation: { state: opts.confirm, receipt },
115
+ });
116
+ }
117
+ else {
118
+ console.log(` ✔ Confirmation sent (${opts.confirm}) receipt: ${receipt ? JSON.stringify(receipt).slice(0, 80) + "…" : "(none)"}\n`);
119
+ }
120
+ }
121
+ catch (err) {
122
+ if (opts.json) {
123
+ printJson({ ok: false, error: "confirm_failed", message: err.message });
124
+ }
125
+ else {
126
+ console.error(` ✘ confirm failed: ${err.message}`);
127
+ }
128
+ gk.destroy();
129
+ process.exit(1);
130
+ }
131
+ }
132
+ gk.destroy();
133
+ }
134
+ //# sourceMappingURL=gk-check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gk-check.js","sourceRoot":"","sources":["../../src/commands/gk-check.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * phosra init — scaffold a .phosra.env for a partner integration.
3
+ *
4
+ * Writes a working .phosra.env in the current directory wired to the EXISTING
5
+ * shared sandbox DID (household-acme, 16 seeded trust-list entries) against the
6
+ * STAGING sandbox census — ZERO new backend required.
7
+ *
8
+ * §12.3 invariant: the scaffolded file contains only the household-acme SEED
9
+ * (a deterministic test key, not production key material). It never contains
10
+ * production signing keys; those are Jake-gated and set only in Railway env vars.
11
+ */
12
+ export declare function runInit(opts: {
13
+ role: "provider" | "platform";
14
+ force: boolean;
15
+ json: boolean;
16
+ cwd?: string;
17
+ }): Promise<void>;
18
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAyEH,wBAAsB,OAAO,CAAC,IAAI,EAAE;IAClC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhB"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * phosra init — scaffold a .phosra.env for a partner integration.
3
+ *
4
+ * Writes a working .phosra.env in the current directory wired to the EXISTING
5
+ * shared sandbox DID (household-acme, 16 seeded trust-list entries) against the
6
+ * STAGING sandbox census — ZERO new backend required.
7
+ *
8
+ * §12.3 invariant: the scaffolded file contains only the household-acme SEED
9
+ * (a deterministic test key, not production key material). It never contains
10
+ * production signing keys; those are Jake-gated and set only in Railway env vars.
11
+ */
12
+ import { writeFileSync, existsSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { SANDBOX_DEFAULTS, HOUSEHOLD_ACME_KEY_ID } from "../config.js";
15
+ import { printJson } from "../out.js";
16
+ const IS_TTY = Boolean(process.stdout.isTTY);
17
+ const bold = (s) => IS_TTY ? `\x1b[1m${s}\x1b[0m` : s;
18
+ const green = (s) => IS_TTY ? `\x1b[32m${s}\x1b[0m` : s;
19
+ const red = (s) => IS_TTY ? `\x1b[31m${s}\x1b[0m` : s;
20
+ const dim = (s) => IS_TTY ? `\x1b[2m${s}\x1b[0m` : s;
21
+ const ENV_FILE = ".phosra.env";
22
+ function buildEnvContent(role) {
23
+ const lines = [
24
+ "# Phosra partner integration config",
25
+ "# Generated by: phosra init",
26
+ "# Wired to the STAGING shared sandbox — run `phosra doctor` to verify",
27
+ "#",
28
+ "# ⚠ This file uses deterministic test keys (sandbox only).",
29
+ "# NEVER commit to git. Add .phosra.env to .gitignore.",
30
+ "",
31
+ "# Census URL — OCSS census API (no trailing slash)",
32
+ `PHOSRA_CENSUS_URL=${SANDBOX_DEFAULTS.CENSUS_URL}`,
33
+ "",
34
+ "# Trust root — Ed25519 root public key X (base64url-raw) for the sandbox",
35
+ `PHOSRA_TRUST_ROOT_X=${SANDBOX_DEFAULTS.TRUST_ROOT_X}`,
36
+ "",
37
+ "# Household-acme parent persona — the shared sandbox DID (16 trust-list entries)",
38
+ `PHOSRA_HOUSEHOLD_SEED=${SANDBOX_DEFAULTS.HOUSEHOLD_SEED}`,
39
+ `# Key ID: ${HOUSEHOLD_ACME_KEY_ID}`,
40
+ "",
41
+ "# Sandbox round-trip targets (seeded in the staging sandbox DB)",
42
+ `SANDBOX_MIA_POLICY_ID=${SANDBOX_DEFAULTS.MIA_POLICY_ID}`,
43
+ `SANDBOX_MIA_CHILD_ID=${SANDBOX_DEFAULTS.MIA_CHILD_ID}`,
44
+ "",
45
+ "# Allow deterministic test-key derivation (sandbox only — never production)",
46
+ "PHOSRA_TEST_KEYS=1",
47
+ "",
48
+ `# Integration role: ${role}`,
49
+ `PHOSRA_ROLE=${role}`,
50
+ "",
51
+ ];
52
+ if (role === "provider") {
53
+ lines.push("# Provider (parental-controls vendor): @phosra/link — connect → directive → confirm", "# PHOSRA_DATABASE_URL=postgresql://... (Postgres for grant store; required for link write)", "");
54
+ }
55
+ else {
56
+ lines.push("# Platform: @phosra/gatekeeper — refreshProfile → isAllowed → confirm", "# PHOSRA_ENDPOINT_ID=... (§9.3(b) bound-resolver label; returned by link connect ceremony)", "");
57
+ }
58
+ lines.push("# --- Production upgrade path ---", "# When ready for production, replace the values above with per-org material:", "# PHOSRA_CENSUS_URL=https://phosra-api-sandbox-production.up.railway.app (or self-hosted)", "# PHOSRA_TRUST_ROOT_X=<prod root X>", "# PHOSRA_HOUSEHOLD_SEED=<your org seed from phosra onboarding>", "# PHOSRA_TEST_KEYS=0", "");
59
+ return lines.join("\n");
60
+ }
61
+ export async function runInit(opts) {
62
+ const cwd = opts.cwd ?? process.cwd();
63
+ const dest = join(cwd, ENV_FILE);
64
+ if (existsSync(dest) && !opts.force) {
65
+ if (opts.json) {
66
+ printJson({ ok: false, error: "file_exists", path: dest });
67
+ }
68
+ else {
69
+ console.error(red(` ✘ ${ENV_FILE} already exists. Use --force to overwrite.`));
70
+ }
71
+ process.exit(1);
72
+ }
73
+ const content = buildEnvContent(opts.role);
74
+ writeFileSync(dest, content, "utf-8");
75
+ if (opts.json) {
76
+ printJson({
77
+ ok: true,
78
+ path: dest,
79
+ role: opts.role,
80
+ census_url: SANDBOX_DEFAULTS.CENSUS_URL,
81
+ trust_root_x: SANDBOX_DEFAULTS.TRUST_ROOT_X,
82
+ });
83
+ }
84
+ else {
85
+ console.log(bold("\n Phosra — init\n"));
86
+ console.log(` ${green("✔")} Wrote ${bold(ENV_FILE)} (role: ${opts.role})`);
87
+ console.log(dim(`\n Next: run ${bold("phosra doctor")} to verify the setup end-to-end.\n`));
88
+ }
89
+ }
90
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * phosra link write <category> — provider rule-write via the @phosra/link SDK.
3
+ *
4
+ * This is the "metered binding leg": the provider signs a rule on behalf of a
5
+ * parent's signed consent, writing it to the census. Requires:
6
+ * - PHOSRA_DATABASE_URL — Postgres connection string for the @phosra/link grant store
7
+ * - PHOSRA_GRANT_ID — active grant ID produced by the connect ceremony
8
+ * - PHOSRA_CHILD_REF — target child ref (e.g. "child:<uuid>")
9
+ *
10
+ * §12.3: the census is the safety authority. This command drives the signed
11
+ * census write; it does NOT accept a rule, sign a trust-list, or hold billing
12
+ * authority.
13
+ *
14
+ * @phosra/link is a peer / optional dependency — the CLI core only requires
15
+ * @openchildsafety/ocss. If @phosra/link is not installed this command will prompt for it.
16
+ */
17
+ import type { PhosraConfig } from "../config.js";
18
+ export interface LinkWriteOpts {
19
+ category: string;
20
+ decision: "block" | "warn" | "allow";
21
+ grantId: string;
22
+ childRef: string;
23
+ json: boolean;
24
+ }
25
+ export declare function runLinkWrite(cfg: PhosraConfig, opts: LinkWriteOpts): Promise<void>;
26
+ //# sourceMappingURL=link-write.d.ts.map