@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.
- package/README.md +42 -0
- package/dist/bin.d.ts +19 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +159 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/caps.d.ts +16 -0
- package/dist/commands/caps.d.ts.map +1 -0
- package/dist/commands/caps.js +125 -0
- package/dist/commands/caps.js.map +1 -0
- package/dist/commands/doctor.d.ts +34 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +321 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gk-check.d.ts +29 -0
- package/dist/commands/gk-check.d.ts.map +1 -0
- package/dist/commands/gk-check.js +134 -0
- package/dist/commands/gk-check.js.map +1 -0
- package/dist/commands/init.d.ts +18 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +90 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/link-write.d.ts +26 -0
- package/dist/commands/link-write.d.ts.map +1 -0
- package/dist/commands/link-write.js +129 -0
- package/dist/commands/link-write.js.map +1 -0
- package/dist/commands/register.d.ts +63 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +167 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +94 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +32 -0
- package/dist/envelope.d.ts.map +1 -0
- package/dist/envelope.js +53 -0
- package/dist/envelope.js.map +1 -0
- package/dist/keygen.d.ts +29 -0
- package/dist/keygen.d.ts.map +1 -0
- package/dist/keygen.js +38 -0
- package/dist/keygen.js.map +1 -0
- package/dist/out.d.ts +33 -0
- package/dist/out.d.ts.map +1 -0
- package/dist/out.js +76 -0
- package/dist/out.js.map +1 -0
- package/dist/round-trip.d.ts +51 -0
- package/dist/round-trip.d.ts.map +1 -0
- package/dist/round-trip.js +115 -0
- package/dist/round-trip.js.map +1 -0
- package/dist/sender-key.d.ts +26 -0
- package/dist/sender-key.d.ts.map +1 -0
- package/dist/sender-key.js +35 -0
- package/dist/sender-key.js.map +1 -0
- package/dist/transport.d.ts +30 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +62 -0
- package/dist/transport.js.map +1 -0
- package/package.json +31 -0
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a §4.2 consent_attestation OCSS envelope for the doctor round-trip.
|
|
3
|
+
*
|
|
4
|
+
* Seals the payload bytes to the router's EC P-256 payload JWK (JWE),
|
|
5
|
+
* wraps in an Outer, and signs the outer with the parent Ed25519 sender key.
|
|
6
|
+
* Mirrors @phosra/link census-client.ts sealConsentAttestation without pg.
|
|
7
|
+
*
|
|
8
|
+
* SANDBOX ONLY — called with household-acme (pre-admitted test key).
|
|
9
|
+
*/
|
|
10
|
+
import { seal, signSender, defaultSenderSignatureCodec, SPEC_VERSION, } from "@openchildsafety/ocss";
|
|
11
|
+
function rfc3339SecondsZ(d) {
|
|
12
|
+
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
13
|
+
}
|
|
14
|
+
function nonceB64url() {
|
|
15
|
+
const b = crypto.getRandomValues(new Uint8Array(16));
|
|
16
|
+
let bin = "";
|
|
17
|
+
for (const x of b)
|
|
18
|
+
bin += String.fromCharCode(x);
|
|
19
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* buildConsentAttestation seals the payload to the router and signs the outer
|
|
23
|
+
* with the parent persona key. Returns the wire-ready Envelope.
|
|
24
|
+
*
|
|
25
|
+
* @param payload Consent attestation fields (idempotency_key must be unique per run)
|
|
26
|
+
* @param parentKey household-acme SenderKey (signs the outer + HTTP request)
|
|
27
|
+
* @param routerJwk Router's EC P-256 payload public key (from the trust list)
|
|
28
|
+
* @param censusBaseUrl Census base URL — sets the envelope's `resource`
|
|
29
|
+
* @param routerDid Router DID — sets `inner.receiver` + `outer.intermediary_audience`
|
|
30
|
+
*/
|
|
31
|
+
export async function buildConsentAttestation(payload, parentKey, routerJwk, censusBaseUrl, routerDid) {
|
|
32
|
+
const resource = `${censusBaseUrl}/api/v1`;
|
|
33
|
+
const sealed = await seal(new TextEncoder().encode(JSON.stringify(payload)), routerJwk);
|
|
34
|
+
const inner = {
|
|
35
|
+
receiver: routerDid,
|
|
36
|
+
envelope_type: "consent_attestation",
|
|
37
|
+
payload: sealed,
|
|
38
|
+
};
|
|
39
|
+
const now = new Date();
|
|
40
|
+
const outer = {
|
|
41
|
+
ocss_version: SPEC_VERSION,
|
|
42
|
+
intermediary_audience: routerDid,
|
|
43
|
+
issued_at: rfc3339SecondsZ(now),
|
|
44
|
+
nonce: nonceB64url(),
|
|
45
|
+
resource,
|
|
46
|
+
sender_signature: "", // filled in by signSender below
|
|
47
|
+
};
|
|
48
|
+
const envelope = { outer, inner };
|
|
49
|
+
// signSender mutates outer.sender_signature AND returns it — mutation is fine here.
|
|
50
|
+
signSender(envelope, parentKey, Math.floor(now.getTime() / 1000), defaultSenderSignatureCodec);
|
|
51
|
+
return envelope;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../src/envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,IAAI,EACJ,UAAU,EACV,2BAA2B,EAC3B,YAAY,GAMb,MAAM,uBAAuB,CAAC;AAE/B,SAAS,eAAe,CAAC,CAAO;IAC9B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;AACnD,CAAC;AAED,SAAS,WAAW;IAClB,MAAM,CAAC,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACrD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,CAAC;QAAE,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IACjD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAaD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAuB,EACvB,SAAoB,EACpB,SAAc,EACd,aAAqB,EACrB,SAAiB;IAEjB,MAAM,QAAQ,GAAG,GAAG,aAAa,SAAS,CAAC;IAC3C,MAAM,MAAM,GAAK,MAAM,IAAI,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IAE1F,MAAM,KAAK,GAAU;QACnB,QAAQ,EAAO,SAAS;QACxB,aAAa,EAAE,qBAAqB;QACpC,OAAO,EAAQ,MAAM;KACtB,CAAC;IAEF,MAAM,GAAG,GAAK,IAAI,IAAI,EAAE,CAAC;IACzB,MAAM,KAAK,GAAU;QACnB,YAAY,EAAW,YAAY;QACnC,qBAAqB,EAAE,SAAS;QAChC,SAAS,EAAc,eAAe,CAAC,GAAG,CAAC;QAC3C,KAAK,EAAkB,WAAW,EAAE;QACpC,QAAQ;QACR,gBAAgB,EAAO,EAAE,EAAI,gCAAgC;KAC9D,CAAC;IAEF,MAAM,QAAQ,GAAa,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAC5C,oFAAoF;IACpF,UAAU,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,EAAE,2BAA2B,CAAC,CAAC;IAC/F,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
package/dist/keygen.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Ed25519 key generation for `phosra init`.
|
|
3
|
+
*
|
|
4
|
+
* §12.3 clean: keys are generated LOCALLY in Node's crypto module and are NEVER
|
|
5
|
+
* transmitted to the census during init. The generated seed is printed ONCE to
|
|
6
|
+
* stdout/the env file; the partner must store it securely (it has the same threat
|
|
7
|
+
* model as any API secret key).
|
|
8
|
+
*
|
|
9
|
+
* The public X (base64url-raw) is safe to share and can be submitted to the OCSS
|
|
10
|
+
* governance process for accreditation.
|
|
11
|
+
*/
|
|
12
|
+
export interface GeneratedKey {
|
|
13
|
+
/** Base64url-encoded 32-byte Ed25519 private seed. KEEP SECRET. */
|
|
14
|
+
seedB64Url: string;
|
|
15
|
+
/** Base64url-raw Ed25519 public X. Safe to share. */
|
|
16
|
+
pubXB64Url: string;
|
|
17
|
+
/** "did:ocss:<slug>#YYYY-MM" — the keyID format the trust list uses. */
|
|
18
|
+
keyId: string;
|
|
19
|
+
/** "did:ocss:<slug>" — the partner's DID. */
|
|
20
|
+
did: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate a fresh Ed25519 key pair for a partner integration.
|
|
24
|
+
*
|
|
25
|
+
* @param slug Lowercase slug for the partner's DID (e.g. "acme-parental").
|
|
26
|
+
* Must match did:ocss identifier rules (alphanumeric + - _ .).
|
|
27
|
+
*/
|
|
28
|
+
export declare function generateKey(slug: string): GeneratedKey;
|
|
29
|
+
//# sourceMappingURL=keygen.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keygen.d.ts","sourceRoot":"","sources":["../src/keygen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAqBtD"}
|
package/dist/keygen.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side Ed25519 key generation for `phosra init`.
|
|
3
|
+
*
|
|
4
|
+
* §12.3 clean: keys are generated LOCALLY in Node's crypto module and are NEVER
|
|
5
|
+
* transmitted to the census during init. The generated seed is printed ONCE to
|
|
6
|
+
* stdout/the env file; the partner must store it securely (it has the same threat
|
|
7
|
+
* model as any API secret key).
|
|
8
|
+
*
|
|
9
|
+
* The public X (base64url-raw) is safe to share and can be submitted to the OCSS
|
|
10
|
+
* governance process for accreditation.
|
|
11
|
+
*/
|
|
12
|
+
import { randomBytes, createPrivateKey, createPublicKey } from "node:crypto";
|
|
13
|
+
import { senderKeyFromSeedB64url } from "./sender-key.js";
|
|
14
|
+
/**
|
|
15
|
+
* Generate a fresh Ed25519 key pair for a partner integration.
|
|
16
|
+
*
|
|
17
|
+
* @param slug Lowercase slug for the partner's DID (e.g. "acme-parental").
|
|
18
|
+
* Must match did:ocss identifier rules (alphanumeric + - _ .).
|
|
19
|
+
*/
|
|
20
|
+
export function generateKey(slug) {
|
|
21
|
+
const seed = randomBytes(32);
|
|
22
|
+
const seedB64Url = seed.toString("base64url");
|
|
23
|
+
// Derive public X from seed via PKCS#8 → JWK round-trip (same as gatekeeper.ts).
|
|
24
|
+
const pkcs8 = Buffer.concat([
|
|
25
|
+
Buffer.from("302e020100300506032b657004220420", "hex"),
|
|
26
|
+
seed,
|
|
27
|
+
]);
|
|
28
|
+
const priv = createPrivateKey({ key: pkcs8, format: "der", type: "pkcs8" });
|
|
29
|
+
const pubXB64Url = createPublicKey(priv).export({ format: "jwk" }).x;
|
|
30
|
+
const now = new Date();
|
|
31
|
+
const kidSuffix = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
|
|
32
|
+
const did = `did:ocss:${slug}`;
|
|
33
|
+
const keyId = `${did}#${kidSuffix}`;
|
|
34
|
+
// Validate: round-trip through senderKeyFromSeedB64url to catch slug issues early.
|
|
35
|
+
senderKeyFromSeedB64url(seedB64Url, keyId);
|
|
36
|
+
return { seedB64Url, pubXB64Url, keyId, did };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=keygen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keygen.js","sourceRoot":"","sources":["../src/keygen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAa1D;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;IAE9C,iFAAiF;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,kCAAkC,EAAE,KAAK,CAAC;QACtD,IAAI;KACL,CAAC,CAAC;IACH,MAAM,IAAI,GAAO,gBAAgB,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IAChF,MAAM,UAAU,GAAI,eAAe,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAmB,CAAC,CAAC,CAAC;IAExF,MAAM,GAAG,GAAK,IAAI,IAAI,EAAE,CAAC;IACzB,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IACxF,MAAM,GAAG,GAAK,YAAY,IAAI,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,SAAS,EAAE,CAAC;IAEpC,mFAAmF;IACnF,uBAAuB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAE3C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;AAChD,CAAC"}
|
package/dist/out.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output layer for @phosra/cli — no external colour dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Every command has two modes:
|
|
5
|
+
* human — ANSI-coloured, interactive-terminal-friendly
|
|
6
|
+
* --json — stable machine-readable JSON to stdout (for CI/agents)
|
|
7
|
+
*
|
|
8
|
+
* Invariant: no ANSI ever lands in --json mode. No JSON ever lands in
|
|
9
|
+
* human mode (use printJson only in the --json branch of each command).
|
|
10
|
+
*/
|
|
11
|
+
export type CheckStatus = "pass" | "fail" | "warn" | "skip";
|
|
12
|
+
export interface CheckResult {
|
|
13
|
+
/** Stable snake_case identifier — the --json key. */
|
|
14
|
+
name: string;
|
|
15
|
+
status: CheckStatus;
|
|
16
|
+
/** One-liner human message (optional). */
|
|
17
|
+
message?: string;
|
|
18
|
+
/** Actionable fix hint shown on fail/warn (human mode only). */
|
|
19
|
+
hint?: string;
|
|
20
|
+
/** Arbitrary structured data for --json mode. */
|
|
21
|
+
data?: unknown;
|
|
22
|
+
}
|
|
23
|
+
/** Print a single check line in human mode. No-op in --json mode. */
|
|
24
|
+
export declare function printCheck(r: CheckResult, jsonMode: boolean): void;
|
|
25
|
+
/** Emit a JSON payload to stdout. Used only in --json branches. */
|
|
26
|
+
export declare function printJson(data: unknown): void;
|
|
27
|
+
/** Section heading. No-op in --json mode. */
|
|
28
|
+
export declare function heading(text: string, jsonMode: boolean): void;
|
|
29
|
+
/** Trailing status summary line. No-op in --json mode. */
|
|
30
|
+
export declare function summary(results: CheckResult[], jsonMode: boolean): void;
|
|
31
|
+
/** Dim trailing note line. No-op in --json mode. */
|
|
32
|
+
export declare function note(text: string, jsonMode: boolean): void;
|
|
33
|
+
//# sourceMappingURL=out.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"out.d.ts","sourceRoot":"","sources":["../src/out.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAgBD,qEAAqE;AACrE,wBAAgB,UAAU,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CASlE;AAED,mEAAmE;AACnE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE7C;AAED,6CAA6C;AAC7C,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAE7D;AAED,0DAA0D;AAC1D,wBAAgB,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAgBvE;AAED,oDAAoD;AACpD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAE1D"}
|
package/dist/out.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output layer for @phosra/cli — no external colour dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Every command has two modes:
|
|
5
|
+
* human — ANSI-coloured, interactive-terminal-friendly
|
|
6
|
+
* --json — stable machine-readable JSON to stdout (for CI/agents)
|
|
7
|
+
*
|
|
8
|
+
* Invariant: no ANSI ever lands in --json mode. No JSON ever lands in
|
|
9
|
+
* human mode (use printJson only in the --json branch of each command).
|
|
10
|
+
*/
|
|
11
|
+
const IS_TTY = Boolean(process.stdout.isTTY);
|
|
12
|
+
const C = {
|
|
13
|
+
green: (s) => IS_TTY ? `\x1b[32m${s}\x1b[0m` : s,
|
|
14
|
+
red: (s) => IS_TTY ? `\x1b[31m${s}\x1b[0m` : s,
|
|
15
|
+
yellow: (s) => IS_TTY ? `\x1b[33m${s}\x1b[0m` : s,
|
|
16
|
+
dim: (s) => IS_TTY ? `\x1b[2m${s}\x1b[0m` : s,
|
|
17
|
+
bold: (s) => IS_TTY ? `\x1b[1m${s}\x1b[0m` : s,
|
|
18
|
+
};
|
|
19
|
+
const ICON = {
|
|
20
|
+
pass: "✔",
|
|
21
|
+
fail: "✘",
|
|
22
|
+
warn: "⚠",
|
|
23
|
+
skip: "—",
|
|
24
|
+
};
|
|
25
|
+
const COLOUR = {
|
|
26
|
+
pass: C.green,
|
|
27
|
+
fail: C.red,
|
|
28
|
+
warn: C.yellow,
|
|
29
|
+
skip: C.dim,
|
|
30
|
+
};
|
|
31
|
+
/** Print a single check line in human mode. No-op in --json mode. */
|
|
32
|
+
export function printCheck(r, jsonMode) {
|
|
33
|
+
if (jsonMode)
|
|
34
|
+
return;
|
|
35
|
+
const icon = COLOUR[r.status](ICON[r.status]);
|
|
36
|
+
const label = COLOUR[r.status](r.name.replace(/_/g, " "));
|
|
37
|
+
const msg = r.message ? C.dim(" " + r.message) : "";
|
|
38
|
+
console.log(` ${icon} ${label}${msg}`);
|
|
39
|
+
if ((r.status === "fail" || r.status === "warn") && r.hint) {
|
|
40
|
+
console.log(C.dim(` hint: ${r.hint}`));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Emit a JSON payload to stdout. Used only in --json branches. */
|
|
44
|
+
export function printJson(data) {
|
|
45
|
+
console.log(JSON.stringify(data, null, 2));
|
|
46
|
+
}
|
|
47
|
+
/** Section heading. No-op in --json mode. */
|
|
48
|
+
export function heading(text, jsonMode) {
|
|
49
|
+
if (!jsonMode)
|
|
50
|
+
console.log(C.bold("\n " + text + "\n"));
|
|
51
|
+
}
|
|
52
|
+
/** Trailing status summary line. No-op in --json mode. */
|
|
53
|
+
export function summary(results, jsonMode) {
|
|
54
|
+
if (jsonMode)
|
|
55
|
+
return;
|
|
56
|
+
const failed = results.filter((r) => r.status === "fail").length;
|
|
57
|
+
const warned = results.filter((r) => r.status === "warn").length;
|
|
58
|
+
const passed = results.filter((r) => r.status === "pass").length;
|
|
59
|
+
if (failed > 0) {
|
|
60
|
+
console.log(`\n ${C.red("●")} ${failed} check${failed > 1 ? "s" : ""} failed` +
|
|
61
|
+
(warned > 0 ? `, ${warned} warning${warned > 1 ? "s" : ""}` : "") +
|
|
62
|
+
"\n");
|
|
63
|
+
}
|
|
64
|
+
else if (warned > 0) {
|
|
65
|
+
console.log(`\n ${C.yellow("●")} ${passed} passed, ${warned} warning${warned > 1 ? "s" : ""}\n`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(`\n ${C.green("●")} All ${passed} checks passed\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Dim trailing note line. No-op in --json mode. */
|
|
72
|
+
export function note(text, jsonMode) {
|
|
73
|
+
if (!jsonMode)
|
|
74
|
+
console.log(C.dim(" " + text + "\n"));
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=out.js.map
|
package/dist/out.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"out.js","sourceRoot":"","sources":["../src/out.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-provisioning sandbox provider round-trip.
|
|
3
|
+
*
|
|
4
|
+
* Uses household-acme (pre-admitted, trustTier: "accredited" in the staging
|
|
5
|
+
* sandbox trust list, entry 16) to perform a full §8.3.2 + §8.3.1 write cycle:
|
|
6
|
+
*
|
|
7
|
+
* 1. Fetch + verify the staging trust list → extract router P-256 payload JWK
|
|
8
|
+
* 2. POST /api/v1/webhooks/inbound/app-store — ingest a consent_attestation
|
|
9
|
+
* sealed to the router, signed by household-acme
|
|
10
|
+
* 3. POST /api/v1/policies/{SANDBOX_MIA_POLICY_ID}/rules — write addictive_pattern_block
|
|
11
|
+
* citing the attestation's idem key as standing_ref
|
|
12
|
+
*
|
|
13
|
+
* Every idem key is suffixed with a monotonic timestamp (base36) so repeated
|
|
14
|
+
* doctor runs each succeed with 201 independently.
|
|
15
|
+
*
|
|
16
|
+
* SANDBOX ONLY — household-acme is a deterministic test key (slug bytes right-padded
|
|
17
|
+
* 0x01 to 32B; published in apps/sim-common/sandbox-seed.json). Never call this against
|
|
18
|
+
* a production census.
|
|
19
|
+
*
|
|
20
|
+
* §12.3 clean: the CLI DRIVES the census (probes it) but carries NO authority.
|
|
21
|
+
* It cannot mint grants, publish the trust list, or touch billing.
|
|
22
|
+
*/
|
|
23
|
+
import { type SenderKey } from "@openchildsafety/ocss";
|
|
24
|
+
export interface RoundTripResult {
|
|
25
|
+
/** Number of active entries in the verified trust list. */
|
|
26
|
+
trustListEntries: number;
|
|
27
|
+
/** HTTP status of the consent_attestation ingest. */
|
|
28
|
+
consentStatus: number;
|
|
29
|
+
/** HTTP status of the rule write. */
|
|
30
|
+
ruleStatus: number;
|
|
31
|
+
/** True iff all three steps succeeded (trust list verified, consent 201/200, rule 201/200). */
|
|
32
|
+
ok: boolean;
|
|
33
|
+
/** Error message if any step failed (undefined on success). */
|
|
34
|
+
error?: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* runProviderRoundTrip — the zero-provisioning doctor probe.
|
|
38
|
+
*
|
|
39
|
+
* @param senderKey household-acme SenderKey built from HOUSEHOLD_ACME_SEED
|
|
40
|
+
* @param opts.fetchImpl Injectable for unit tests (mock census)
|
|
41
|
+
* @param opts.now Injectable clock — affects idem-key suffix + attestation timestamps
|
|
42
|
+
*/
|
|
43
|
+
export declare function runProviderRoundTrip(senderKey: SenderKey, opts: {
|
|
44
|
+
censusUrl: string;
|
|
45
|
+
trustRootX: string;
|
|
46
|
+
miaPolicyId: string;
|
|
47
|
+
miaChildId: string;
|
|
48
|
+
fetchImpl?: typeof fetch;
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}): Promise<RoundTripResult>;
|
|
51
|
+
//# sourceMappingURL=round-trip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round-trip.d.ts","sourceRoot":"","sources":["../src/round-trip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAa/B,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,gBAAgB,EAAE,MAAM,CAAC;IACzB,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,qCAAqC;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,+FAA+F;IAC/F,EAAE,EAAE,OAAO,CAAC;IACZ,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE;IACJ,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,GACA,OAAO,CAAC,eAAe,CAAC,CA+F1B"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-provisioning sandbox provider round-trip.
|
|
3
|
+
*
|
|
4
|
+
* Uses household-acme (pre-admitted, trustTier: "accredited" in the staging
|
|
5
|
+
* sandbox trust list, entry 16) to perform a full §8.3.2 + §8.3.1 write cycle:
|
|
6
|
+
*
|
|
7
|
+
* 1. Fetch + verify the staging trust list → extract router P-256 payload JWK
|
|
8
|
+
* 2. POST /api/v1/webhooks/inbound/app-store — ingest a consent_attestation
|
|
9
|
+
* sealed to the router, signed by household-acme
|
|
10
|
+
* 3. POST /api/v1/policies/{SANDBOX_MIA_POLICY_ID}/rules — write addictive_pattern_block
|
|
11
|
+
* citing the attestation's idem key as standing_ref
|
|
12
|
+
*
|
|
13
|
+
* Every idem key is suffixed with a monotonic timestamp (base36) so repeated
|
|
14
|
+
* doctor runs each succeed with 201 independently.
|
|
15
|
+
*
|
|
16
|
+
* SANDBOX ONLY — household-acme is a deterministic test key (slug bytes right-padded
|
|
17
|
+
* 0x01 to 32B; published in apps/sim-common/sandbox-seed.json). Never call this against
|
|
18
|
+
* a production census.
|
|
19
|
+
*
|
|
20
|
+
* §12.3 clean: the CLI DRIVES the census (probes it) but carries NO authority.
|
|
21
|
+
* It cannot mint grants, publish the trust list, or touch billing.
|
|
22
|
+
*/
|
|
23
|
+
import { verifyDocument, fromVerifiedDocument, } from "@openchildsafety/ocss";
|
|
24
|
+
import { CensusTransport } from "./transport.js";
|
|
25
|
+
import { buildConsentAttestation } from "./envelope.js";
|
|
26
|
+
import { HOUSEHOLD_ACME_KEY_ID, ROUTER_DID, ROUNDTRIP_APP_DID, ROUNDTRIP_RULE_CATEGORY, } from "./config.js";
|
|
27
|
+
// The household-acme DID (prefix of HOUSEHOLD_ACME_KEY_ID before '#').
|
|
28
|
+
const HOUSEHOLD_ACME_DID = HOUSEHOLD_ACME_KEY_ID.split("#")[0];
|
|
29
|
+
/**
|
|
30
|
+
* runProviderRoundTrip — the zero-provisioning doctor probe.
|
|
31
|
+
*
|
|
32
|
+
* @param senderKey household-acme SenderKey built from HOUSEHOLD_ACME_SEED
|
|
33
|
+
* @param opts.fetchImpl Injectable for unit tests (mock census)
|
|
34
|
+
* @param opts.now Injectable clock — affects idem-key suffix + attestation timestamps
|
|
35
|
+
*/
|
|
36
|
+
export async function runProviderRoundTrip(senderKey, opts) {
|
|
37
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
38
|
+
const now = opts.now ?? (() => Date.now());
|
|
39
|
+
const censusUrl = opts.censusUrl;
|
|
40
|
+
// ── Step 1: fetch + verify trust list ──────────────────────────────────────
|
|
41
|
+
let tlRes;
|
|
42
|
+
try {
|
|
43
|
+
tlRes = await fetchImpl(`${censusUrl}/.well-known/ocss/trust-list`);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return { trustListEntries: 0, consentStatus: 0, ruleStatus: 0, ok: false, error: `trust-list network error: ${String(e)}` };
|
|
47
|
+
}
|
|
48
|
+
if (!tlRes.ok) {
|
|
49
|
+
return { trustListEntries: 0, consentStatus: 0, ruleStatus: 0, ok: false, error: `trust-list HTTP ${tlRes.status}` };
|
|
50
|
+
}
|
|
51
|
+
let doc;
|
|
52
|
+
try {
|
|
53
|
+
const signed = (await tlRes.json());
|
|
54
|
+
doc = verifyDocument(signed, opts.trustRootX);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return { trustListEntries: 0, consentStatus: 0, ruleStatus: 0, ok: false, error: `trust-list verify failed: ${String(e)}` };
|
|
58
|
+
}
|
|
59
|
+
const resolver = fromVerifiedDocument(doc, now);
|
|
60
|
+
const trustListEntries = doc.entries?.length ?? 0;
|
|
61
|
+
let routerJwk;
|
|
62
|
+
try {
|
|
63
|
+
routerJwk = resolver.payloadKey(ROUTER_DID);
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
return { trustListEntries, consentStatus: 0, ruleStatus: 0, ok: false, error: `router payload key not found: ${String(e)}` };
|
|
67
|
+
}
|
|
68
|
+
// ── Step 2: ingest consent_attestation ─────────────────────────────────────
|
|
69
|
+
// Timestamp-suffixed idem key → each doctor run generates a fresh pair that
|
|
70
|
+
// both succeed with 201 (no 409 ErrPayloadMismatch from attested_at drift).
|
|
71
|
+
const idemSuffix = now().toString(36);
|
|
72
|
+
const consentIdemKey = `cli-doctor-rt-${idemSuffix}`;
|
|
73
|
+
const consentPayload = {
|
|
74
|
+
app_ref: ROUNDTRIP_APP_DID,
|
|
75
|
+
target: `child:${opts.miaChildId}`,
|
|
76
|
+
band: "13_15",
|
|
77
|
+
consent_scope: "collection_parental_authority",
|
|
78
|
+
consent_method: `parental_consent_link:cli-quickstart-${idemSuffix}`,
|
|
79
|
+
attested_at: new Date(now()).toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
80
|
+
expiry: "2030-01-01T00:00:00Z",
|
|
81
|
+
idempotency_key: consentIdemKey,
|
|
82
|
+
};
|
|
83
|
+
const transport = new CensusTransport({ baseUrl: censusUrl, senderKey, fetchImpl, now });
|
|
84
|
+
let consentEnvelope;
|
|
85
|
+
try {
|
|
86
|
+
consentEnvelope = await buildConsentAttestation(consentPayload, senderKey, routerJwk, censusUrl, ROUTER_DID);
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
return { trustListEntries, consentStatus: 0, ruleStatus: 0, ok: false, error: `consent envelope build failed: ${String(e)}` };
|
|
90
|
+
}
|
|
91
|
+
const consentRes = await transport.post("/api/v1/webhooks/inbound/app-store", consentEnvelope);
|
|
92
|
+
const consentStatus = consentRes.status;
|
|
93
|
+
if (consentStatus !== 201 && consentStatus !== 200) {
|
|
94
|
+
const body = await consentRes.text().catch(() => "");
|
|
95
|
+
return { trustListEntries, consentStatus, ruleStatus: 0, ok: false, error: `consent ingest ${consentStatus}: ${body}` };
|
|
96
|
+
}
|
|
97
|
+
// ── Step 3: write rule citing the consent attestation ──────────────────────
|
|
98
|
+
// writer_ref must equal the signing DID (RFC-9421 caller.DID == writer_ref).
|
|
99
|
+
// household-acme is accredited for addictive_pattern_block (sandbox-seed.json).
|
|
100
|
+
const ruleIdemKey = `cli-rule-rt-${idemSuffix}`;
|
|
101
|
+
const ruleBody = {
|
|
102
|
+
rule_category: ROUNDTRIP_RULE_CATEGORY,
|
|
103
|
+
decision: "block",
|
|
104
|
+
target: { child_ref: `child:${opts.miaChildId}` },
|
|
105
|
+
standing_ref: `consent:attestation:${consentIdemKey}`,
|
|
106
|
+
idempotency_key: ruleIdemKey,
|
|
107
|
+
writer_ref: HOUSEHOLD_ACME_DID,
|
|
108
|
+
};
|
|
109
|
+
const ruleRes = await transport.post(`/api/v1/policies/${opts.miaPolicyId}/rules`, ruleBody);
|
|
110
|
+
const ruleStatus = ruleRes.status;
|
|
111
|
+
const ok = (consentStatus === 201 || consentStatus === 200) &&
|
|
112
|
+
(ruleStatus === 201 || ruleStatus === 200);
|
|
113
|
+
return { trustListEntries, consentStatus, ruleStatus, ok };
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=round-trip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"round-trip.js","sourceRoot":"","sources":["../src/round-trip.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SenderKey construction helpers for @phosra/cli.
|
|
3
|
+
*
|
|
4
|
+
* SenderKey = { seed: Uint8Array (32 bytes), keyID: string }
|
|
5
|
+
* Built from a base64url-encoded seed (config / .phosra.env), then passed
|
|
6
|
+
* to signRequest() (RFC-9421 census signing, @openchildsafety/ocss).
|
|
7
|
+
*
|
|
8
|
+
* §12.3 invariant: seed material is NEVER logged or placed in --json output.
|
|
9
|
+
* The keyID (DID fragment) is public; the seed bytes are not.
|
|
10
|
+
*/
|
|
11
|
+
import { type SenderKey } from "@openchildsafety/ocss";
|
|
12
|
+
/**
|
|
13
|
+
* Build a SenderKey from a base64url-encoded 32-byte Ed25519 seed.
|
|
14
|
+
*
|
|
15
|
+
* @param seedB64url Base64url-encoded 32-byte seed (padding optional).
|
|
16
|
+
* @param keyID "did:ocss:<slug>#<kid>" — must match the trust-list entry.
|
|
17
|
+
* @throws if the decoded seed is not exactly 32 bytes.
|
|
18
|
+
*/
|
|
19
|
+
export declare function senderKeyFromSeedB64url(seedB64url: string, keyID: string): SenderKey;
|
|
20
|
+
/**
|
|
21
|
+
* Derive the base64url-raw Ed25519 public key X from a SenderKey's seed.
|
|
22
|
+
* Used by the doctor key-material check to verify the seed's public half
|
|
23
|
+
* appears in the resolved trust-list entry for the keyID's DID.
|
|
24
|
+
*/
|
|
25
|
+
export declare function publicXFromSenderKey(key: SenderKey): string;
|
|
26
|
+
//# sourceMappingURL=sender-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender-key.d.ts","sourceRoot":"","sources":["../src/sender-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEzF;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,GACZ,SAAS,CAQX;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,SAAS,GAAG,MAAM,CAG3D"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SenderKey construction helpers for @phosra/cli.
|
|
3
|
+
*
|
|
4
|
+
* SenderKey = { seed: Uint8Array (32 bytes), keyID: string }
|
|
5
|
+
* Built from a base64url-encoded seed (config / .phosra.env), then passed
|
|
6
|
+
* to signRequest() (RFC-9421 census signing, @openchildsafety/ocss).
|
|
7
|
+
*
|
|
8
|
+
* §12.3 invariant: seed material is NEVER logged or placed in --json output.
|
|
9
|
+
* The keyID (DID fragment) is public; the seed bytes are not.
|
|
10
|
+
*/
|
|
11
|
+
import { ed25519PublicFromSeed, b64urlRaw } from "@openchildsafety/ocss";
|
|
12
|
+
/**
|
|
13
|
+
* Build a SenderKey from a base64url-encoded 32-byte Ed25519 seed.
|
|
14
|
+
*
|
|
15
|
+
* @param seedB64url Base64url-encoded 32-byte seed (padding optional).
|
|
16
|
+
* @param keyID "did:ocss:<slug>#<kid>" — must match the trust-list entry.
|
|
17
|
+
* @throws if the decoded seed is not exactly 32 bytes.
|
|
18
|
+
*/
|
|
19
|
+
export function senderKeyFromSeedB64url(seedB64url, keyID) {
|
|
20
|
+
const raw = Buffer.from(seedB64url, "base64url");
|
|
21
|
+
if (raw.length !== 32) {
|
|
22
|
+
throw new Error(`senderKey: seed "${keyID}" must decode to 32 bytes, got ${raw.length}`);
|
|
23
|
+
}
|
|
24
|
+
return { seed: new Uint8Array(raw), keyID };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Derive the base64url-raw Ed25519 public key X from a SenderKey's seed.
|
|
28
|
+
* Used by the doctor key-material check to verify the seed's public half
|
|
29
|
+
* appears in the resolved trust-list entry for the keyID's DID.
|
|
30
|
+
*/
|
|
31
|
+
export function publicXFromSenderKey(key) {
|
|
32
|
+
const pubBytes = ed25519PublicFromSeed(key.seed);
|
|
33
|
+
return b64urlRaw(pubBytes);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=sender-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sender-key.js","sourceRoot":"","sources":["../src/sender-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,qBAAqB,EAAE,SAAS,EAAkB,MAAM,uBAAuB,CAAC;AAEzF;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,UAAkB,EAClB,KAAa;IAEb,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IACjD,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CACb,oBAAoB,KAAK,kCAAkC,GAAG,CAAC,MAAM,EAAE,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAc;IACjD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,SAAS,CAAC,QAAQ,CAAC,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal RFC-9421 census transport — no pg dependency.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors @phosra/link's OcssHttpClient but is a standalone copy so @phosra/cli
|
|
5
|
+
* carries NO database dependency whatsoever. The CLI is a thin read/verify/probe
|
|
6
|
+
* tool; it never writes grants to a database.
|
|
7
|
+
*
|
|
8
|
+
* §12.3 clean: the transport DRIVES the census (verifies, writes demo round-trips
|
|
9
|
+
* against the pre-seeded sandbox) but carries NO billing authority and never mints
|
|
10
|
+
* grants, publishes the trust list, or touches a production census.
|
|
11
|
+
*/
|
|
12
|
+
import { type SenderKey } from "@openchildsafety/ocss";
|
|
13
|
+
export interface TransportOpts {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
senderKey: SenderKey;
|
|
16
|
+
fetchImpl?: typeof fetch;
|
|
17
|
+
now?: () => number;
|
|
18
|
+
}
|
|
19
|
+
export declare class CensusTransport {
|
|
20
|
+
private baseUrl;
|
|
21
|
+
private senderKey;
|
|
22
|
+
private fetchImpl;
|
|
23
|
+
private now;
|
|
24
|
+
constructor(opts: TransportOpts);
|
|
25
|
+
post(path: string, body: unknown): Promise<Response>;
|
|
26
|
+
get(path: string): Promise<Response>;
|
|
27
|
+
/** Unsigned GET — for well-known docs and /health (no RFC-9421 required). */
|
|
28
|
+
getPublic(path: string): Promise<Response>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,SAAS,CAAe;IAChC,OAAO,CAAC,GAAG,CAAe;gBAEd,IAAI,EAAE,aAAa;IAOzB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC;IAgBpD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAY1C,6EAA6E;IACvE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAUjD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal RFC-9421 census transport — no pg dependency.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors @phosra/link's OcssHttpClient but is a standalone copy so @phosra/cli
|
|
5
|
+
* carries NO database dependency whatsoever. The CLI is a thin read/verify/probe
|
|
6
|
+
* tool; it never writes grants to a database.
|
|
7
|
+
*
|
|
8
|
+
* §12.3 clean: the transport DRIVES the census (verifies, writes demo round-trips
|
|
9
|
+
* against the pre-seeded sandbox) but carries NO billing authority and never mints
|
|
10
|
+
* grants, publishes the trust list, or touches a production census.
|
|
11
|
+
*/
|
|
12
|
+
import { signRequest, SPEC_VERSION } from "@openchildsafety/ocss";
|
|
13
|
+
export class CensusTransport {
|
|
14
|
+
baseUrl;
|
|
15
|
+
senderKey;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
now;
|
|
18
|
+
constructor(opts) {
|
|
19
|
+
this.baseUrl = opts.baseUrl;
|
|
20
|
+
this.senderKey = opts.senderKey;
|
|
21
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
22
|
+
this.now = opts.now ?? (() => Date.now());
|
|
23
|
+
}
|
|
24
|
+
async post(path, body) {
|
|
25
|
+
const targetURI = this.baseUrl + path;
|
|
26
|
+
const bodyText = JSON.stringify(body);
|
|
27
|
+
const bodyBytes = new TextEncoder().encode(bodyText);
|
|
28
|
+
const headers = signRequest({
|
|
29
|
+
method: "POST",
|
|
30
|
+
targetURI,
|
|
31
|
+
body: bodyBytes,
|
|
32
|
+
keyID: this.senderKey.keyID,
|
|
33
|
+
seed: this.senderKey.seed,
|
|
34
|
+
created: Math.floor(this.now() / 1000),
|
|
35
|
+
});
|
|
36
|
+
headers["Content-Type"] = "application/json";
|
|
37
|
+
return this.fetchImpl(targetURI, { method: "POST", headers, body: bodyText });
|
|
38
|
+
}
|
|
39
|
+
async get(path) {
|
|
40
|
+
const targetURI = this.baseUrl + path;
|
|
41
|
+
const headers = signRequest({
|
|
42
|
+
method: "GET",
|
|
43
|
+
targetURI,
|
|
44
|
+
keyID: this.senderKey.keyID,
|
|
45
|
+
seed: this.senderKey.seed,
|
|
46
|
+
created: Math.floor(this.now() / 1000),
|
|
47
|
+
});
|
|
48
|
+
return this.fetchImpl(targetURI, { method: "GET", headers });
|
|
49
|
+
}
|
|
50
|
+
/** Unsigned GET — for well-known docs and /health (no RFC-9421 required). */
|
|
51
|
+
async getPublic(path) {
|
|
52
|
+
const targetURI = this.baseUrl + path;
|
|
53
|
+
return this.fetchImpl(targetURI, {
|
|
54
|
+
method: "GET",
|
|
55
|
+
headers: {
|
|
56
|
+
"Accept": "application/json",
|
|
57
|
+
"OCSS-Spec-Version": SPEC_VERSION,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAkB,MAAM,uBAAuB,CAAC;AASlF,MAAM,OAAO,eAAe;IAClB,OAAO,CAAS;IAChB,SAAS,CAAY;IACrB,SAAS,CAAe;IACxB,GAAG,CAAe;IAE1B,YAAY,IAAmB;QAC7B,IAAI,CAAC,OAAO,GAAK,IAAI,CAAC,OAAO,CAAC;QAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;QAChC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;QACzC,IAAI,CAAC,GAAG,GAAS,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAY,EAAE,IAAa;QACpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtC,MAAM,QAAQ,GAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,SAAS,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACrD,MAAM,OAAO,GAAK,WAAW,CAAC;YAC5B,MAAM,EAAE,MAAM;YACd,SAAS;YACT,IAAI,EAAE,SAAS;YACf,KAAK,EAAI,IAAI,CAAC,SAAS,CAAC,KAAK;YAC7B,IAAI,EAAK,IAAI,CAAC,SAAS,CAAC,IAAI;YAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;SACvC,CAAC,CAAC;QACH,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,IAAY;QACpB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtC,MAAM,OAAO,GAAK,WAAW,CAAC;YAC5B,MAAM,EAAE,KAAK;YACb,SAAS;YACT,KAAK,EAAI,IAAI,CAAC,SAAS,CAAC,KAAK;YAC7B,IAAI,EAAK,IAAI,CAAC,SAAS,CAAC,IAAI;YAC5B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;SACvC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,6EAA6E;IAC7E,KAAK,CAAC,SAAS,CAAC,IAAY;QAC1B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACtC,OAAO,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE;YAC/B,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,QAAQ,EAAE,kBAAkB;gBAC5B,mBAAmB,EAAE,YAAY;aAClC;SACF,CAAC,CAAC;IACL,CAAC;CACF"}
|