@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:
|
|
26
|
-
lastReviewedNote: 'Reviewed for Issue #
|
|
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.
|
|
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
|
}
|