@tiangong-lca/cli 0.1.0 → 0.1.1

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 CHANGED
@@ -19,16 +19,19 @@ checkPaths:
19
19
  - bin/**
20
20
  - src/cli.ts
21
21
  - src/main.ts
22
+ - src/lib/auth-identity-receipt.ts
22
23
  - src/lib/lca-release.ts
24
+ - scripts/run-auth-identity-production-case.ts
25
+ - test/auth-identity*.test.ts
23
26
  - test/lca-release*.test.ts
24
27
  lastReviewedAt: 2026-08-25
25
- lastReviewedCommit: 9078fe123e5909b680f27af1af1d2018bcd02826
26
- lastReviewedNote: 'Reviewed for Issue #226: the public package guide identifies the 0.1.0 pnpm/TypeScript 7 compatibility release while the tarball remains package-manager neutral.'
28
+ lastReviewedCommit: 47b44c65cf4bf27070fa1057e672c6ef2b2e54f3
29
+ lastReviewedNote: 'Reviewed for Issue #230: identifies 0.1.1 as the public auth-receipt release on exact Node 24.19.0 with cryptographic Sigstore and isolated pnpm verification.'
27
30
  ---
28
31
 
29
32
  # TianGong LCA CLI
30
33
 
31
- Package: `@tiangong-lca/cli` Executable: `tiangong-lca` Current package version: `0.1.0` Node: `24.x`
34
+ Package: `@tiangong-lca/cli` Executable: `tiangong-lca` Current package version: `0.1.1` Node: `24.19.0`
32
35
 
33
36
  Repository development is single-track on pnpm `11.23.0` and TypeScript `7.0.2`. The published package remains a clean, package-manager-neutral consumer artifact: it contains runtime files only, not pnpm, TypeScript, Oxlint, tests, source-only tooling, or repository lockfiles.
34
37
 
@@ -36,6 +39,10 @@ Review note, 2026-08-25: Issue #224 migrates repository development and release
36
39
 
37
40
  Review note, 2026-08-25: Issue #226 publishes that 0.1.0 compatibility boundary after merged PR #225. Version metadata and public release evidence advance to 0.1.0; the runtime JavaScript/assets surface, command behavior, Node 24 runtime, pnpm/TypeScript 7 development baseline, native pnpm Trusted Publishing/provenance, and exact released-commit workspace handoff remain unchanged from the reviewed feature delivery.
38
41
 
42
+ Review note, 2026-08-25: Issue #228 implements `auth identity-receipt` as a bounded read-only current-user proof. It checks expected project intent before session work, verifies the live user through `/auth/v1/user`, allows one auth-refresh replay after 401/403, and emits an exact-key canonical receipt without API keys, tokens, full email addresses, session paths, or credential-derived fingerprints. Production callers must pass both expected assertions and accept only `assertions.mode="intent-bound"`.
43
+
44
+ Review note, 2026-08-25: Issue #230 publishes the merged identity receipt as `@tiangong-lca/cli@0.1.1`. Runtime logic and published dependencies remain unchanged; the sole pnpm lock changes only for exact dev-only `sigstore@5.0.0`. Local metadata, engines, and every workflow converge on Node 24.19.0. Release automation asserts actual platform/architecture across the reusable four-platform pre-tag matrix and provides `pnpm release:verify-published` for cryptographic Fulcio/CT/Rekor provenance, registry signatures, actual tarball integrity, isolated user/global package-manager configuration, exact pnpm 11.23.0, and credential-free bin/ESM/CJS consumer proof.
45
+
39
46
  Review note, 2026-07-12: `dataset maintenance plan/apply/verify` provides current-user RLS-scoped exact-row maintenance with immutable plans, explicit approval, per-action logs, platform audit correlation, and independent readback. `merge-support-aliases` now runs only in `target_mode=owner_draft`: source/target support and all changed rows stay private `state_code=0`; publication is a separate future workflow.
40
47
 
41
48
  Review note, 2026-07-13: maintenance scans now prove exact-count pagination even when PostgREST returns fewer rows than the requested `--page-size`. An incomplete or inconsistent scan fails before artifacts, approval, or mutation; under stable filtered membership/order the proof represents a complete ordered multi-request traversal, not one transaction-level/MVCC snapshot.
@@ -112,6 +119,31 @@ TIANGONG_LCA_DISABLE_SESSION_CACHE=false
112
119
  TIANGONG_LCA_FORCE_REAUTH=false
113
120
  ```
114
121
 
122
+ ## Auth Identity Receipt
123
+
124
+ Use the identity receipt before a production-backed case or any later owner-draft write gate:
125
+
126
+ ```text
127
+ tiangong-lca auth identity-receipt --expected-project-ref <project-ref> --expected-user-id <user-id> --json
128
+ ```
129
+
130
+ The command performs no dataset write. It exchanges the normal user API key for a session, checks the canonical Supabase project, and makes a bounded live `GET /auth/v1/user`. A cached token that receives `401` or `403` may be refreshed and retried exactly once; all other transport or response failures are terminal. A valid production guard requires:
131
+
132
+ - `schema` exactly `tiangong-lca.auth-identity-receipt.v1`;
133
+ - `status: "passed"`, `operation: "current-user-read"`, and `remote_write_mode: "read-only"`;
134
+ - exact expected project/user values with `assertions.mode: "intent-bound"` and `requested_count: 2`;
135
+ - a fresh capture time and a valid recomputed `receipt_scope_sha256`.
136
+
137
+ Calling without expectations is allowed for discovery but produces `assertions.mode: "observed"`; it is not an authorization guard. The safe display email is masked and must not be used as the account key. The receipt deliberately excludes credentials, full email, session-file details, raw response metadata, and all credential/token/path-derived fingerprints.
138
+
139
+ For the explicitly authorized local production read case from a validated repository checkout, invoke the narrow TypeScript runner with a new private output directory:
140
+
141
+ ```text
142
+ pnpm case:auth-identity:production -- --env-file <data-foundry-ignored-.env> --expected-project-ref <project-ref> --expected-user-id <user-id> --out-dir <new-private-case-directory>
143
+ ```
144
+
145
+ The runner reads only `TIANGONG_LCA_API_BASE_URL`, `TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY`, and `TIANGONG_LCA_TEST_API_KEY`; the last is mapped to the child process's standard API-key variable. It does not accept an alternate CLI path. The pnpm command first performs a clean TS7 build without the production env. Its plain-Node runner then single-reads source/config/lock and the freshly generated `dist/src/**/*.js`, hashes the source tree, runner, runtime, exact entrypoint, and pnpm lock, and copies those exact built buffers into a private snapshot before exposing the key. It forces reauthentication with session cache disabled, runs only the built snapshot from an exclusively created clean directory with an argv array and `shell:false`, cleans the snapshot before publishing success artifacts, and persists only the parsed receipt and case manifest. POSIX creates the case directory as `0700` and files as `0600`; Windows inherits ACLs from the caller-selected parent, so use a user-restricted parent because mode bits are not an ACL guarantee. The runner never stores raw child stdout/stderr and is intentionally not wired to CI secrets. This receipt is locally hash-verifiable, not server-signed attestation.
146
+
115
147
  ## LCI/LCIA Data Release
116
148
 
117
149
  The `release` command family is the authenticated transport used by the standalone release control plane. It does not introduce a TIDAS schema variant: result packages reuse the existing Process exchange structure for LCI and the existing Process LCIA result fields, with a LifecycleModel referencing the resulting Process.
package/dist/src/cli.js CHANGED
@@ -3,6 +3,7 @@ import { buildDoctorReport, readRuntimeEnv } from './lib/env.js';
3
3
  import { CliError, toErrorPayload } from './lib/errors.js';
4
4
  import { stringifyJson } from './lib/io.js';
5
5
  import { loadCliPackageVersion } from './lib/package-version.js';
6
+ import { AUTH_IDENTITY_MAX_TIMEOUT_MS, AUTH_IDENTITY_RECEIPT_SCHEMA, runAuthIdentityReceipt, } from './lib/auth-identity-receipt.js';
6
7
  import { runLifecyclemodelAutoBuild, } from './lib/lifecyclemodel-auto-build.js';
7
8
  import { runLifecyclemodelBuildResultingProcess, } from './lib/lifecyclemodel-resulting-process.js';
8
9
  import { runLifecyclemodelPublishResultingProcess, } from './lib/lifecyclemodel-publish-resulting-process.js';
@@ -86,6 +87,7 @@ Usage:
86
87
  Commands:
87
88
  Implemented Commands:
88
89
  doctor show environment diagnostics
90
+ auth identity-receipt
89
91
  search flow | process | lifecyclemodel
90
92
  process get | list | identity-preflight | build-plan | scope-statistics | dedup-review | auto-build | resume-build | publish-build | complete-required-fields | save-draft | batch-build | refresh-references | verify-rows
91
93
  dataset contract get | context-pack | classification children/path/audit/apply | curation-queue build/next/verify | import-lca convert | author | patch apply | save-draft | source upload-attachments | validate | verify-remote | bilingual extract/apply/validate | evidence-search plan/run | references rewrite/refresh-remote | maintenance clear-account/plan/apply/verify/flow-identity
@@ -105,6 +107,7 @@ Planned commands currently print an explicit "not implemented yet" message and e
105
107
 
106
108
  Examples:
107
109
  tiangong-lca doctor
110
+ tiangong-lca auth identity-receipt --expected-project-ref <project-ref> --expected-user-id <user-id> --json
108
111
  tiangong-lca search flow --input ./request.json
109
112
  tiangong-lca search process --input ./request.json --dry-run
110
113
  tiangong-lca process get --id <process-id>
@@ -180,6 +183,50 @@ Environment:
180
183
  .env loaded: ${dotEnvStatus.loaded ? `yes (${dotEnvStatus.path}, ${dotEnvStatus.count} keys)` : 'no'}
181
184
  `.trim();
182
185
  }
186
+ function renderAuthHelp() {
187
+ return `Usage:
188
+ tiangong-lca auth identity-receipt [options]
189
+
190
+ Implemented Subcommands:
191
+ identity-receipt Live, read-only proof of the authenticated account and Supabase project
192
+
193
+ Planned Subcommands:
194
+ whoami | doctor-auth
195
+
196
+ Use "tiangong-lca auth identity-receipt --help" for the machine-receipt contract.
197
+ `.trim();
198
+ }
199
+ function renderAuthIdentityReceiptHelp() {
200
+ return `Usage:
201
+ tiangong-lca auth identity-receipt [options]
202
+
203
+ Options:
204
+ --expected-project-ref <ref> Assert the exact Supabase project before session/network access
205
+ --expected-user-id <id> Assert the exact server-verified authenticated user
206
+ --timeout-ms <n> Request timeout 1..${AUTH_IDENTITY_MAX_TIMEOUT_MS} ms (default: 10000)
207
+ --json Print compact JSON; default output is pretty JSON
208
+ -h, --help
209
+
210
+ Receipt schema:
211
+ ${AUTH_IDENTITY_RECEIPT_SCHEMA}
212
+
213
+ Safety:
214
+ This is a read-only command that performs one live read of /auth/v1/user, with at most one
215
+ force-refresh retry after a 401 or 403. It never emits API keys, bearer/session
216
+ tokens, full email addresses, session paths, or credential-derived fingerprints. Production guards
217
+ must pass both expected assertions and require assertions.mode="intent-bound".
218
+
219
+ Required env:
220
+ TIANGONG_LCA_API_BASE_URL
221
+ TIANGONG_LCA_API_KEY
222
+ TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY
223
+
224
+ Optional session env:
225
+ TIANGONG_LCA_SESSION_FILE
226
+ TIANGONG_LCA_DISABLE_SESSION_CACHE
227
+ TIANGONG_LCA_FORCE_REAUTH
228
+ `.trim();
229
+ }
183
230
  function renderDoctorHelp() {
184
231
  return `Usage:
185
232
  tiangong-lca doctor [--json]
@@ -2269,6 +2316,59 @@ function parseDoctorFlags(args) {
2269
2316
  json: Boolean(values.json),
2270
2317
  };
2271
2318
  }
2319
+ function parseAuthIdentityReceiptFlags(args) {
2320
+ let parsed;
2321
+ try {
2322
+ parsed = parseArgs({
2323
+ args,
2324
+ allowPositionals: false,
2325
+ strict: true,
2326
+ tokens: true,
2327
+ options: {
2328
+ help: { type: 'boolean', short: 'h' },
2329
+ json: { type: 'boolean' },
2330
+ 'expected-project-ref': { type: 'string' },
2331
+ 'expected-user-id': { type: 'string' },
2332
+ 'timeout-ms': { type: 'string' },
2333
+ },
2334
+ });
2335
+ }
2336
+ catch (error) {
2337
+ throw new CliError(String(error), {
2338
+ code: 'INVALID_ARGS',
2339
+ exitCode: 2,
2340
+ });
2341
+ }
2342
+ const tokens = parsed.tokens;
2343
+ for (const optionName of ['expected-project-ref', 'expected-user-id', 'timeout-ms']) {
2344
+ const occurrences = tokens.filter((entry) => entry.kind === 'option' && entry.name === optionName).length;
2345
+ if (occurrences > 1) {
2346
+ throw new CliError(`Option --${optionName} may be provided only once.`, {
2347
+ code: 'INVALID_ARGS',
2348
+ exitCode: 2,
2349
+ });
2350
+ }
2351
+ }
2352
+ const timeoutText = typeof parsed.values['timeout-ms'] === 'string' ? parsed.values['timeout-ms'] : undefined;
2353
+ const timeoutMs = timeoutText === undefined ? 10_000 : Number(timeoutText);
2354
+ if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > AUTH_IDENTITY_MAX_TIMEOUT_MS) {
2355
+ throw new CliError(`Expected --timeout-ms to be an integer between 1 and ${AUTH_IDENTITY_MAX_TIMEOUT_MS}.`, {
2356
+ code: 'INVALID_TIMEOUT',
2357
+ exitCode: 2,
2358
+ });
2359
+ }
2360
+ return {
2361
+ help: Boolean(parsed.values.help),
2362
+ json: Boolean(parsed.values.json),
2363
+ expectedProjectRef: typeof parsed.values['expected-project-ref'] === 'string'
2364
+ ? parsed.values['expected-project-ref']
2365
+ : null,
2366
+ expectedUserId: typeof parsed.values['expected-user-id'] === 'string'
2367
+ ? parsed.values['expected-user-id']
2368
+ : null,
2369
+ timeoutMs,
2370
+ };
2371
+ }
2272
2372
  function parseRemoteFlags(args) {
2273
2373
  let values;
2274
2374
  try {
@@ -5442,6 +5542,7 @@ function applyRemoteOverrides(env, overrides) {
5442
5542
  export async function executeCli(argv, deps) {
5443
5543
  try {
5444
5544
  const { flags, command, subcommand, commandArgs } = parseCommandLine(argv);
5545
+ const authIdentityReceiptImpl = deps.runAuthIdentityReceiptImpl ?? runAuthIdentityReceipt;
5445
5546
  const publishImpl = deps.runPublishImpl ?? runPublish;
5446
5547
  const validationImpl = deps.runValidationImpl ?? runValidation;
5447
5548
  const lcaReleaseImpl = deps.runLcaReleaseImpl ?? runLcaRelease;
@@ -5541,6 +5642,35 @@ export async function executeCli(argv, deps) {
5541
5642
  stderr: '',
5542
5643
  };
5543
5644
  }
5645
+ if (command === 'auth' && !subcommand) {
5646
+ if (commandArgs.length === 0 ||
5647
+ (commandArgs.length === 1 && ['--help', '-h'].includes(commandArgs[0]))) {
5648
+ return { exitCode: 0, stdout: `${renderAuthHelp()}\n`, stderr: '' };
5649
+ }
5650
+ throw new CliError('The auth namespace accepts only -h or --help.', {
5651
+ code: 'INVALID_ARGS',
5652
+ exitCode: 2,
5653
+ });
5654
+ }
5655
+ if (command === 'auth' && subcommand === 'identity-receipt') {
5656
+ const authFlags = parseAuthIdentityReceiptFlags(commandArgs);
5657
+ if (authFlags.help) {
5658
+ return { exitCode: 0, stdout: `${renderAuthIdentityReceiptHelp()}\n`, stderr: '' };
5659
+ }
5660
+ const receipt = await authIdentityReceiptImpl({
5661
+ env: deps.env,
5662
+ fetchImpl: deps.fetchImpl,
5663
+ cliVersion: loadCliPackageVersion(import.meta.url),
5664
+ expectedProjectRef: authFlags.expectedProjectRef,
5665
+ expectedUserId: authFlags.expectedUserId,
5666
+ timeoutMs: authFlags.timeoutMs,
5667
+ });
5668
+ return {
5669
+ exitCode: 0,
5670
+ stdout: stringifyJson(receipt, authFlags.json),
5671
+ stderr: '',
5672
+ };
5673
+ }
5544
5674
  if (command === 'search' && !subcommand && commandArgs.includes('--help')) {
5545
5675
  return { exitCode: 0, stdout: `${renderSearchHelp()}\n`, stderr: '' };
5546
5676
  }