@forestrie/forestrie-cli 0.8.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +425 -0
  3. package/TRUST-MODEL.md +8 -0
  4. package/dist/cli.js +33811 -0
  5. package/dist/decode-receipt.d.ts +19 -0
  6. package/dist/decode-receipt.d.ts.map +1 -0
  7. package/dist/decode-receipt.js +19 -0
  8. package/dist/decode-receipt.js.map +1 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +11 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/decode-receipt-cbor.d.ts +26 -0
  14. package/dist/lib/decode-receipt-cbor.d.ts.map +1 -0
  15. package/dist/lib/decode-receipt-cbor.js +182 -0
  16. package/dist/lib/decode-receipt-cbor.js.map +1 -0
  17. package/dist/lib/decode-receipt-decode.d.ts +103 -0
  18. package/dist/lib/decode-receipt-decode.d.ts.map +1 -0
  19. package/dist/lib/decode-receipt-decode.js +237 -0
  20. package/dist/lib/decode-receipt-decode.js.map +1 -0
  21. package/dist/lib/decode-receipt-labels.d.ts +54 -0
  22. package/dist/lib/decode-receipt-labels.d.ts.map +1 -0
  23. package/dist/lib/decode-receipt-labels.js +117 -0
  24. package/dist/lib/decode-receipt-labels.js.map +1 -0
  25. package/dist/lib/decode-receipt-render.d.ts +11 -0
  26. package/dist/lib/decode-receipt-render.d.ts.map +1 -0
  27. package/dist/lib/decode-receipt-render.js +242 -0
  28. package/dist/lib/decode-receipt-render.js.map +1 -0
  29. package/package.json +83 -0
  30. package/src/cli.ts +5 -0
  31. package/src/command.ts +51 -0
  32. package/src/commands/admin-onboard-token.ts +51 -0
  33. package/src/commands/admin.ts +20 -0
  34. package/src/commands/complete-grant.ts +41 -0
  35. package/src/commands/create-consistency-proof.ts +44 -0
  36. package/src/commands/create-log.ts +95 -0
  37. package/src/commands/decode-receipt.ts +20 -0
  38. package/src/commands/delegate.ts +55 -0
  39. package/src/commands/deploy.ts +72 -0
  40. package/src/commands/fetch-accumulator.ts +49 -0
  41. package/src/commands/onboard-genesis.ts +70 -0
  42. package/src/commands/onboard-request.ts +59 -0
  43. package/src/commands/register-grant.ts +74 -0
  44. package/src/commands/register.ts +54 -0
  45. package/src/commands/resolve-receipt.ts +101 -0
  46. package/src/commands/sign-statement.ts +53 -0
  47. package/src/commands/verify-grant.ts +89 -0
  48. package/src/commands/verify.ts +92 -0
  49. package/src/commoncli.ts +34 -0
  50. package/src/decode-receipt.ts +56 -0
  51. package/src/index.ts +10 -0
  52. package/src/lib/bytes.ts +7 -0
  53. package/src/lib/checkpoint-provider.ts +297 -0
  54. package/src/lib/complete-grant-derive.ts +237 -0
  55. package/src/lib/consistency-proof.ts +271 -0
  56. package/src/lib/create-log-build.ts +168 -0
  57. package/src/lib/create-log-prepare.ts +75 -0
  58. package/src/lib/create-receipt-chain-verify.ts +267 -0
  59. package/src/lib/create-receipt-derive.ts +208 -0
  60. package/src/lib/create-receipt-inputs.ts +79 -0
  61. package/src/lib/decode-checkpoint-calldata.ts +188 -0
  62. package/src/lib/decode-receipt-cbor.ts +202 -0
  63. package/src/lib/decode-receipt-decode.ts +333 -0
  64. package/src/lib/decode-receipt-labels.ts +132 -0
  65. package/src/lib/decode-receipt-render.ts +284 -0
  66. package/src/lib/delegate-flow.ts +289 -0
  67. package/src/lib/delegate-voucher.ts +122 -0
  68. package/src/lib/deploy-artifact.ts +79 -0
  69. package/src/lib/deploy-bootstrap.ts +100 -0
  70. package/src/lib/deploy-flow.ts +184 -0
  71. package/src/lib/deploy-genesis-log-id.ts +29 -0
  72. package/src/lib/deploy-rpc.ts +40 -0
  73. package/src/lib/deployment-record.ts +65 -0
  74. package/src/lib/fsio.ts +91 -0
  75. package/src/lib/grant-flags-local.ts +19 -0
  76. package/src/lib/onboard-attestation.ts +120 -0
  77. package/src/lib/onboard-genesis-body.ts +94 -0
  78. package/src/lib/openssl-error-queue.ts +56 -0
  79. package/src/lib/register-flow.ts +292 -0
  80. package/src/lib/register-grant-build.ts +173 -0
  81. package/src/lib/register-grant-complete.ts +47 -0
  82. package/src/lib/register-grant-flow.ts +258 -0
  83. package/src/lib/resolve-receipt-freshen.ts +221 -0
  84. package/src/lib/sign-statement-build.ts +117 -0
  85. package/src/lib/sign-statement-key.ts +145 -0
  86. package/src/lib/verify-anchored.ts +326 -0
  87. package/src/lib/verify-checkpoint-chain.ts +160 -0
  88. package/src/lib/verify-eventscan.ts +214 -0
  89. package/src/lib/verify-inputs.ts +143 -0
  90. package/src/lib/verify-known-accumulator.ts +161 -0
  91. package/src/lib/verify-report.ts +265 -0
  92. package/src/lib/x402-payment.ts +115 -0
  93. package/src/main/admin-onboard-token.ts +216 -0
  94. package/src/main/complete-grant.ts +265 -0
  95. package/src/main/create-consistency-proof.ts +83 -0
  96. package/src/main/create-log.ts +347 -0
  97. package/src/main/create-receipt.ts +763 -0
  98. package/src/main/decode-receipt.ts +72 -0
  99. package/src/main/delegate.ts +141 -0
  100. package/src/main/deploy.ts +273 -0
  101. package/src/main/fetch-accumulator.ts +181 -0
  102. package/src/main/not-implemented.ts +43 -0
  103. package/src/main/onboard-genesis.ts +192 -0
  104. package/src/main/onboard-request.ts +333 -0
  105. package/src/main/register-grant.ts +270 -0
  106. package/src/main/register.ts +243 -0
  107. package/src/main/sign-statement.ts +168 -0
  108. package/src/main/verify.ts +767 -0
  109. package/src/options/admin-onboard-token.ts +48 -0
  110. package/src/options/common.ts +69 -0
  111. package/src/options/complete-grant.ts +40 -0
  112. package/src/options/create-consistency-proof.ts +63 -0
  113. package/src/options/create-log.ts +99 -0
  114. package/src/options/create-receipt.ts +243 -0
  115. package/src/options/decode-receipt.ts +25 -0
  116. package/src/options/delegate.ts +93 -0
  117. package/src/options/deploy.ts +105 -0
  118. package/src/options/fetch-accumulator.ts +62 -0
  119. package/src/options/onboard-genesis.ts +74 -0
  120. package/src/options/onboard-request.ts +57 -0
  121. package/src/options/register-grant.ts +72 -0
  122. package/src/options/register.ts +71 -0
  123. package/src/options/sign-statement.ts +64 -0
  124. package/src/options/verify.ts +250 -0
@@ -0,0 +1,105 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ export type BootstrapAlg = "es256" | "ks256";
10
+
11
+ /** Default univocity contract release tag (deploy-core resolves `latest`). */
12
+ export const DEFAULT_DEPLOY_RELEASE_TAG = "latest";
13
+
14
+ /** `forestrie deploy` — FOR-340. */
15
+ export type DeployOptions = ForestrieCommonOptions & {
16
+ /** Bootstrap key algorithm. ES256 is the paved path; KS256 is the Safe/ERC-1271 aside. */
17
+ bootstrapAlg: BootstrapAlg;
18
+ /** Generate a fresh ES256 P-256 bootstrap keypair. */
19
+ bootstrapEs256Generate: boolean;
20
+ /** Where to write the generated ES256 PKCS#8 PEM. */
21
+ bootstrapEs256PemOut: string | undefined;
22
+ /** Existing ES256 bootstrap key PEM (PKCS#8/SEC1 private or SPKI public). */
23
+ bootstrapEs256Pem: string | undefined;
24
+ /** Deployer / owner address (`OWNER_ADDRESS`). */
25
+ ownerAddress: string | undefined;
26
+ /** JSON-RPC endpoint (`RPC_URL`). */
27
+ rpcUrl: string;
28
+ /**
29
+ * Funded secp256k1 key that signs and pays gas for the deploy
30
+ * transaction (`DEPLOYER_KEY`). Gas-only: it has no role in the log's
31
+ * trust model — the bootstrap key (ES256) is what the contract binds.
32
+ * Normalized to a 0x-prefixed 32-byte hex string.
33
+ */
34
+ deployerKey: `0x${string}`;
35
+ /** Univocity contract release tag for the deploy manifest (default `latest`). */
36
+ releaseTag: string;
37
+ /** Local deploy-manifest JSON path — skips the GitHub release fetch. */
38
+ releaseManifest: string | undefined;
39
+ /** Deployment record output path (default: stdout). */
40
+ out: string | undefined;
41
+ };
42
+
43
+ /** Validate/normalize a 32-byte secp256k1 private key hex string. */
44
+ function parseDeployerKey(raw: string): `0x${string}` {
45
+ const hex = raw.startsWith("0x") ? raw.slice(2) : raw;
46
+ if (!/^[0-9a-fA-F]{64}$/.test(hex)) {
47
+ throw new Error(
48
+ "--deployer-key must be a 32-byte hex secp256k1 private key (0x…64 hex chars)",
49
+ );
50
+ }
51
+ return `0x${hex.toLowerCase()}` as `0x${string}`;
52
+ }
53
+
54
+ export function parseDeployOptions(args: LooseParsedArgs): DeployOptions {
55
+ const alg = optionalStringOption(args, "bootstrap-alg") ?? "es256";
56
+ if (alg !== "es256" && alg !== "ks256") {
57
+ throw new Error(
58
+ `invalid --bootstrap-alg '${alg}' (expected es256 or ks256)`,
59
+ );
60
+ }
61
+
62
+ const generate = args["bootstrap-es256-generate"] === true;
63
+ const pemOut = optionalStringOption(args, "bootstrap-es256-pem-out");
64
+ const pem = optionalStringOption(args, "bootstrap-es256-pem");
65
+
66
+ if (alg === "es256") {
67
+ if (generate && pem !== undefined) {
68
+ throw new Error(
69
+ "--bootstrap-es256-generate and --bootstrap-es256-pem are mutually exclusive",
70
+ );
71
+ }
72
+ if (generate && pemOut === undefined) {
73
+ throw new Error(
74
+ "--bootstrap-es256-generate requires --bootstrap-es256-pem-out (the generated private key must be kept — it signs the root grant)",
75
+ );
76
+ }
77
+ if (!generate && pemOut !== undefined) {
78
+ throw new Error(
79
+ "--bootstrap-es256-pem-out is only meaningful with --bootstrap-es256-generate",
80
+ );
81
+ }
82
+ if (!generate && pem === undefined) {
83
+ throw new Error(
84
+ "es256 bootstrap needs a key: pass --bootstrap-es256-generate (with --bootstrap-es256-pem-out) or --bootstrap-es256-pem <path>",
85
+ );
86
+ }
87
+ }
88
+
89
+ return {
90
+ ...parseForestrieCommonOptions(args),
91
+ bootstrapAlg: alg,
92
+ bootstrapEs256Generate: generate,
93
+ bootstrapEs256PemOut: pemOut,
94
+ bootstrapEs256Pem: pem,
95
+ ownerAddress: optionalStringOption(args, "owner-address", "OWNER_ADDRESS"),
96
+ rpcUrl: requiredStringOption(args, "rpc-url", "RPC_URL"),
97
+ deployerKey: parseDeployerKey(
98
+ requiredStringOption(args, "deployer-key", "DEPLOYER_KEY"),
99
+ ),
100
+ releaseTag:
101
+ optionalStringOption(args, "release-tag") ?? DEFAULT_DEPLOY_RELEASE_TAG,
102
+ releaseManifest: optionalStringOption(args, "release-manifest"),
103
+ out: optionalStringOption(args, "out"),
104
+ };
105
+ }
@@ -0,0 +1,62 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ /**
10
+ * `forestrie fetch-accumulator` (FOR-297 D5): read the log's on-chain
11
+ * `logState` and cache it as a structured snapshot artifact — the
12
+ * `--known-accumulator` input for fully offline chain-anchored verification.
13
+ * The snapshot binds `(chainId, univocity, logId, size, block)` so the read
14
+ * is auditable and falsifiable: anyone with RPC can re-run it at that block.
15
+ */
16
+ export type FetchAccumulatorOptions = ForestrieCommonOptions & {
17
+ /** ImutableUnivocity contract address. */
18
+ univocity: string;
19
+ /** Log id (UUID or hex). */
20
+ logId: string;
21
+ /** JSON-RPC endpoint (`RPC_URL`) — the trusted chain reader. */
22
+ rpcUrl: string;
23
+ /** Output path for the snapshot CBOR. */
24
+ out: string;
25
+ /** Snapshot the latest anchored state at/before this block, sourced
26
+ * from CheckpointPublished events (FOR-368; no archive node needed). */
27
+ atBlock: bigint | undefined;
28
+ /** Lower bound for the event scan (`--at-block` mode). */
29
+ fromBlock: bigint | undefined;
30
+ };
31
+
32
+ function optionalBlockOption(
33
+ args: LooseParsedArgs,
34
+ name: string,
35
+ ): bigint | undefined {
36
+ const raw = optionalStringOption(args, name);
37
+ if (raw === undefined) return undefined;
38
+ let value: bigint;
39
+ try {
40
+ value = BigInt(raw);
41
+ } catch {
42
+ throw new Error(`--${name} must be a block number`);
43
+ }
44
+ if (value < 0n) {
45
+ throw new Error(`--${name} must be a non-negative block number`);
46
+ }
47
+ return value;
48
+ }
49
+
50
+ export function parseFetchAccumulatorOptions(
51
+ args: LooseParsedArgs,
52
+ ): FetchAccumulatorOptions {
53
+ return {
54
+ ...parseForestrieCommonOptions(args),
55
+ univocity: requiredStringOption(args, "univocity"),
56
+ logId: requiredStringOption(args, "log-id"),
57
+ rpcUrl: requiredStringOption(args, "rpc-url", "RPC_URL"),
58
+ out: requiredStringOption(args, "out"),
59
+ atBlock: optionalBlockOption(args, "at-block"),
60
+ fromBlock: optionalBlockOption(args, "from-block"),
61
+ };
62
+ }
@@ -0,0 +1,74 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ /** Default chain id (Base Sepolia) when `--chain-id`/`CHAIN_ID` absent. */
10
+ export const DEFAULT_CHAIN_ID = "84532";
11
+
12
+ /** `forestrie onboard-genesis` — FOR-406 (plan-2607-27 W2). */
13
+ export type OnboardGenesisOptions = ForestrieCommonOptions & {
14
+ /** SCRAPI origin, no trailing slash (`FORESTRIE_BASE_URL`). */
15
+ baseUrl: string;
16
+ /** deploy's `--out` JSON; supplies univocity + logId when set. */
17
+ deployment: string | undefined;
18
+ /** ImutableUnivocity contract address (from `--deployment` when absent). */
19
+ univocity: string | undefined;
20
+ /** Forest (genesis) log id (from `--deployment` when absent). */
21
+ logId: string | undefined;
22
+ /** ES256 bootstrap key PEM (private or public; only the pubkey is used). */
23
+ bootstrapPem: string;
24
+ /** Chain id recorded in the genesis (`CHAIN_ID`, default 84532). */
25
+ chainId: string;
26
+ /** Delegation coordinator origin; signing-route webhook is derived. */
27
+ coordinatorUrl: string | undefined;
28
+ /** Explicit webhook override (wins over `--coordinator-url`). */
29
+ webhookUrl: string | undefined;
30
+ /**
31
+ * Pre-minted onboard token (`ONBOARD_TOKEN`) — from
32
+ * `forestrie admin onboard-token` today, x402 settlement (ARC-0015) in
33
+ * future. This command never touches the operator credential.
34
+ */
35
+ onboardToken: string;
36
+ /** Fetch the public genesis back to this path after onboarding. */
37
+ out: string | undefined;
38
+ };
39
+
40
+ export function parseOnboardGenesisOptions(
41
+ args: LooseParsedArgs,
42
+ ): OnboardGenesisOptions {
43
+ const deployment = optionalStringOption(args, "deployment");
44
+ const univocity = optionalStringOption(args, "univocity");
45
+ const logId = optionalStringOption(args, "log-id");
46
+ if (deployment === undefined && (univocity === undefined || logId === undefined)) {
47
+ throw new Error(
48
+ "onboard-genesis needs --deployment <deployment.json> (from deploy --out) " +
49
+ "or both --univocity and --log-id",
50
+ );
51
+ }
52
+ const coordinatorUrl = optionalStringOption(args, "coordinator-url");
53
+ const webhookUrl = optionalStringOption(args, "webhook-url");
54
+ if (coordinatorUrl === undefined && webhookUrl === undefined) {
55
+ throw new Error(
56
+ "onboard-genesis needs --coordinator-url (signing-route webhook derived) " +
57
+ "or an explicit --webhook-url",
58
+ );
59
+ }
60
+ return {
61
+ ...parseForestrieCommonOptions(args),
62
+ baseUrl: requiredStringOption(args, "base-url", "FORESTRIE_BASE_URL"),
63
+ deployment,
64
+ univocity,
65
+ logId,
66
+ bootstrapPem: requiredStringOption(args, "bootstrap-pem"),
67
+ chainId:
68
+ optionalStringOption(args, "chain-id", "CHAIN_ID") ?? DEFAULT_CHAIN_ID,
69
+ coordinatorUrl,
70
+ webhookUrl,
71
+ onboardToken: requiredStringOption(args, "onboard-token", "ONBOARD_TOKEN"),
72
+ out: optionalStringOption(args, "out"),
73
+ };
74
+ }
@@ -0,0 +1,57 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ /** Default request label when `--label` is absent. */
10
+ export const DEFAULT_ONBOARD_REQUEST_LABEL = "forestrie-cli";
11
+
12
+ /**
13
+ * `forestrie onboard-request` — the self-service front door (plan-2607-43
14
+ * slice 06): create a bootstrap-key-attested onboard request and redeem it
15
+ * into an onboard token, paying the x402 challenge when the deployment asks.
16
+ */
17
+ export type OnboardRequestOptions = ForestrieCommonOptions & {
18
+ /** SCRAPI origin, no trailing slash (`FORESTRIE_BASE_URL`). */
19
+ baseUrl: string;
20
+ /** `forestrie deploy --out` artifact — the chain binding + alg source. */
21
+ deployment: string;
22
+ /** Bootstrap private key PEM (ES256 deployments) — signs the attestation. */
23
+ bootstrapPem: string | undefined;
24
+ /** Bootstrap private key hex (KS256 deployments) — signs the attestation. */
25
+ bootstrapKeyHex: string | undefined;
26
+ /** Contact email recorded on the request (`ONBOARD_CONTACT_EMAIL`). */
27
+ contactEmail: string;
28
+ label: string;
29
+ /**
30
+ * x402 payer key (`X402_PAYER_KEY`). Optional: without it, only requests
31
+ * the deployment approves (ops/auto) can redeem; with it, a 402 challenge
32
+ * at redeem is paid. REAL funds move on settlement.
33
+ */
34
+ payerKey: string | undefined;
35
+ /** Write the redeemed token here instead of stdout. */
36
+ out: string | undefined;
37
+ };
38
+
39
+ export function parseOnboardRequestOptions(
40
+ args: LooseParsedArgs,
41
+ ): OnboardRequestOptions {
42
+ return {
43
+ ...parseForestrieCommonOptions(args),
44
+ baseUrl: requiredStringOption(args, "base-url", "FORESTRIE_BASE_URL"),
45
+ deployment: requiredStringOption(args, "deployment"),
46
+ bootstrapPem: optionalStringOption(args, "bootstrap-pem"),
47
+ bootstrapKeyHex: optionalStringOption(args, "bootstrap-key-hex"),
48
+ contactEmail: requiredStringOption(
49
+ args,
50
+ "contact-email",
51
+ "ONBOARD_CONTACT_EMAIL",
52
+ ),
53
+ label: optionalStringOption(args, "label") ?? DEFAULT_ONBOARD_REQUEST_LABEL,
54
+ payerKey: optionalStringOption(args, "payer-key", "X402_PAYER_KEY"),
55
+ out: optionalStringOption(args, "out"),
56
+ };
57
+ }
@@ -0,0 +1,72 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+ import {
9
+ DEFAULT_POLL_INTERVAL_SECONDS,
10
+ DEFAULT_TIMEOUT_SECONDS,
11
+ durationOptionMs,
12
+ } from "./register.js";
13
+
14
+ /**
15
+ * `forestrie register-grant` — FOR-343 (writer-only; ADR-0052).
16
+ *
17
+ * Authorizes ONE statement writer on an existing data log (extend-only).
18
+ * Several signers on one data log means several grants, each naming that log.
19
+ * Grant leaves are sequenced into the OWNER (auth) log; the data log holds
20
+ * only statements. Log creation and bootstrap shapes live in `create-log`.
21
+ */
22
+ export type RegisterGrantOptions = ForestrieCommonOptions & {
23
+ /** SCRAPI origin (`FORESTRIE_BASE_URL`). */
24
+ baseUrl: string;
25
+ /** Owner (auth) log the grant leaf is sequenced into. */
26
+ ownerLog: string;
27
+ /** Child/data log the grant authorizes. */
28
+ dataLog: string;
29
+ /** PKCS#8 PEM key that signs the grant statement (the granting authority). */
30
+ signWith: string;
31
+ /** PEM of the signer being authorized (grantData = ES256 x||y). */
32
+ signerPem: string;
33
+ /** Parent grant credential authorizing this registration. */
34
+ parentGrantB64: string;
35
+ /** Completed grant base64 output path (default: stdout). */
36
+ outB64: string | undefined;
37
+ /**
38
+ * Forest bootstrap/root log id — first `/register/` path segment
39
+ * (grant genesis lookup is keyed by the forest root). Defaults to
40
+ * `--owner-log`, which is correct whenever the owner IS the root (the
41
+ * self-referential bootstrap and first-level child grants).
42
+ */
43
+ bootstrapLog: string;
44
+ /** Overall receipt wait budget, milliseconds (`--timeout` seconds). */
45
+ timeoutMs: number;
46
+ /** Poll pacing, milliseconds (`--poll-interval` seconds). */
47
+ pollIntervalMs: number;
48
+ };
49
+
50
+ export function parseRegisterGrantOptions(
51
+ args: LooseParsedArgs,
52
+ ): RegisterGrantOptions {
53
+ const ownerLog = requiredStringOption(args, "owner-log");
54
+ const options: RegisterGrantOptions = {
55
+ ...parseForestrieCommonOptions(args),
56
+ baseUrl: requiredStringOption(args, "base-url", "FORESTRIE_BASE_URL"),
57
+ ownerLog,
58
+ dataLog: requiredStringOption(args, "data-log"),
59
+ signWith: requiredStringOption(args, "sign-with"),
60
+ signerPem: requiredStringOption(args, "signer-pem"),
61
+ parentGrantB64: requiredStringOption(args, "parent-grant-b64"),
62
+ outB64: optionalStringOption(args, "out-b64"),
63
+ bootstrapLog: optionalStringOption(args, "bootstrap-log") ?? ownerLog,
64
+ timeoutMs: durationOptionMs(args, "timeout", DEFAULT_TIMEOUT_SECONDS),
65
+ pollIntervalMs: durationOptionMs(
66
+ args,
67
+ "poll-interval",
68
+ DEFAULT_POLL_INTERVAL_SECONDS,
69
+ ),
70
+ };
71
+ return options;
72
+ }
@@ -0,0 +1,71 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ /** Default overall receipt wait (`--timeout`), seconds. */
10
+ export const DEFAULT_TIMEOUT_SECONDS = 60;
11
+ /** Default poll pacing (`--poll-interval`), seconds. */
12
+ export const DEFAULT_POLL_INTERVAL_SECONDS = 1;
13
+
14
+ /** `forestrie register` — FOR-342. */
15
+ export type RegisterOptions = ForestrieCommonOptions & {
16
+ /** SCRAPI origin, no trailing slash (`FORESTRIE_BASE_URL`). */
17
+ baseUrl: string;
18
+ /** Target log id (UUID). */
19
+ logId: string;
20
+ /**
21
+ * COSE Sign1 signed statement file to register; `-` or absent reads
22
+ * the statement bytes from stdin.
23
+ */
24
+ statement: string | undefined;
25
+ /** `Authorization: Forestrie-Grant` bearer credential (`GRANT_B64`). */
26
+ grantB64: string;
27
+ /** Receipt output path (default: human/JSON summary only). */
28
+ out: string | undefined;
29
+ /** Overall receipt wait budget, milliseconds (`--timeout` seconds). */
30
+ timeoutMs: number;
31
+ /** Poll pacing, milliseconds (`--poll-interval` seconds). */
32
+ pollIntervalMs: number;
33
+ };
34
+
35
+ /**
36
+ * Read `--<name>` as a positive (fractional allowed) number of seconds,
37
+ * returning milliseconds; a missing flag yields the default. Shared with
38
+ * `register-grant`, which follows the same 303 receipt-redirect contract.
39
+ */
40
+ export function durationOptionMs(
41
+ args: LooseParsedArgs,
42
+ name: string,
43
+ defaultSeconds: number,
44
+ ): number {
45
+ const raw = optionalStringOption(args, name);
46
+ if (raw === undefined) {
47
+ return Math.round(defaultSeconds * 1000);
48
+ }
49
+ const seconds = Number(raw);
50
+ if (!Number.isFinite(seconds) || seconds <= 0) {
51
+ throw new Error(`--${name} must be a positive number of seconds`);
52
+ }
53
+ return Math.round(seconds * 1000);
54
+ }
55
+
56
+ export function parseRegisterOptions(args: LooseParsedArgs): RegisterOptions {
57
+ return {
58
+ ...parseForestrieCommonOptions(args),
59
+ baseUrl: requiredStringOption(args, "base-url", "FORESTRIE_BASE_URL"),
60
+ logId: requiredStringOption(args, "log-id"),
61
+ statement: optionalStringOption(args, "statement"),
62
+ grantB64: requiredStringOption(args, "grant-b64", "GRANT_B64"),
63
+ out: optionalStringOption(args, "out"),
64
+ timeoutMs: durationOptionMs(args, "timeout", DEFAULT_TIMEOUT_SECONDS),
65
+ pollIntervalMs: durationOptionMs(
66
+ args,
67
+ "poll-interval",
68
+ DEFAULT_POLL_INTERVAL_SECONDS,
69
+ ),
70
+ };
71
+ }
@@ -0,0 +1,64 @@
1
+ import type { LooseParsedArgs } from "@forestrie/cli-kit";
2
+ import {
3
+ optionalStringOption,
4
+ parseForestrieCommonOptions,
5
+ requiredStringOption,
6
+ type ForestrieCommonOptions,
7
+ } from "./common.js";
8
+
9
+ /** `forestrie sign-statement` — FOR-341. */
10
+ export type SignStatementOptions = ForestrieCommonOptions & {
11
+ /** ES256 P-256 private signing key path (SEC1/PKCS#8 PEM or JWK). */
12
+ key: string;
13
+ /** Payload file to wrap as the COSE Sign1 payload (`-` = stdin). */
14
+ payload: string;
15
+ /** Payload content type (COSE `content type` header, label 3, protected). */
16
+ contentType: string;
17
+ /**
18
+ * Issuer (CWT claim 1): literal StringOrURI, or `ckt` for the RFC 9679
19
+ * thumbprint URI. Default (unset): hex kid (devdocs ADR-0055).
20
+ * An explicit empty string is preserved so the run layer can reject it
21
+ * through the structured error path (a silent default substitution
22
+ * would sign a different issuer than the caller asked for).
23
+ */
24
+ iss: string | undefined;
25
+ /** Subject (CWT claim 2). Default (unset): `sha-256:<hex>` of payload. */
26
+ sub: string | undefined;
27
+ /**
28
+ * Issued-at (CWT claim 6), raw: `now` or unix-seconds digits. Validated
29
+ * and resolved in the run layer so a bad value produces the structured
30
+ * error report, not a parse-time stack trace.
31
+ */
32
+ iat: string | undefined;
33
+ /** Signed statement output path (default: stdout). */
34
+ out: string | undefined;
35
+ };
36
+
37
+ export function parseSignStatementOptions(
38
+ args: LooseParsedArgs,
39
+ ): SignStatementOptions {
40
+ return {
41
+ ...parseForestrieCommonOptions(args),
42
+ key: requiredStringOption(args, "key"),
43
+ payload: requiredStringOption(args, "payload"),
44
+ contentType:
45
+ optionalStringOption(args, "content-type") ?? "application/json",
46
+ iss: claimStringOption(args, "iss"),
47
+ sub: claimStringOption(args, "sub"),
48
+ iat: optionalStringOption(args, "iat"),
49
+ out: optionalStringOption(args, "out"),
50
+ };
51
+ }
52
+
53
+ /**
54
+ * As `optionalStringOption`, but an explicit `""` survives (that helper
55
+ * folds it into "unset", which for signed-claim inputs would silently
56
+ * substitute the default).
57
+ */
58
+ function claimStringOption(
59
+ args: LooseParsedArgs,
60
+ name: string,
61
+ ): string | undefined {
62
+ if (args[name] === "") return "";
63
+ return optionalStringOption(args, name);
64
+ }