@zivis/cli 0.1.0-alpha.5 → 0.1.0-alpha.52
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/LICENSE +201 -0
- package/README.md +31 -0
- package/dist/commands/app/index.d.ts +0 -8
- package/dist/commands/app/index.js +8 -18
- package/dist/commands/assurance/index.d.ts +35 -0
- package/dist/commands/assurance/index.js +154 -0
- package/dist/commands/auth/bind.d.ts +4 -0
- package/dist/commands/auth/bind.js +97 -0
- package/dist/commands/auth/index.js +21 -2
- package/dist/commands/auth/init.d.ts +2 -0
- package/dist/commands/auth/init.js +248 -96
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.js +136 -18
- package/dist/commands/auth/logout.js +19 -7
- package/dist/commands/auth/sessions.js +7 -4
- package/dist/commands/auth/status.js +33 -12
- package/dist/commands/auth/token.d.ts +4 -0
- package/dist/commands/auth/token.js +73 -0
- package/dist/commands/check/index.d.ts +2 -0
- package/dist/commands/check/index.js +463 -0
- package/dist/commands/credits/index.js +2 -3
- package/dist/commands/gate/index.d.ts +2 -0
- package/dist/commands/gate/index.js +171 -0
- package/dist/commands/inspect/index.d.ts +2 -0
- package/dist/commands/inspect/index.js +136 -0
- package/dist/commands/mcp/index.js +113 -14
- package/dist/commands/open/index.d.ts +0 -8
- package/dist/commands/open/index.js +0 -11
- package/dist/commands/run/index.d.ts +2 -0
- package/dist/commands/run/index.js +206 -0
- package/dist/commands/scan/index.js +3 -8
- package/dist/commands/share/index.d.ts +14 -0
- package/dist/commands/share/index.js +100 -0
- package/dist/commands/sync/index.d.ts +2 -0
- package/dist/commands/sync/index.js +193 -0
- package/dist/commands/target/index.js +0 -3
- package/dist/commands/test/index.d.ts +2 -0
- package/dist/commands/test/index.js +198 -0
- package/dist/commands/threatmodel/index.d.ts +2 -0
- package/dist/commands/threatmodel/index.js +167 -0
- package/dist/index.js +28 -3
- package/dist/internal/application-binding.d.ts +8 -0
- package/dist/internal/application-binding.js +167 -0
- package/dist/internal/binding-token-mint.d.ts +33 -0
- package/dist/internal/binding-token-mint.js +174 -0
- package/dist/internal/change-recommendation.d.ts +6 -0
- package/dist/internal/change-recommendation.js +113 -0
- package/dist/internal/cli-output.d.ts +16 -0
- package/dist/internal/cli-output.js +39 -0
- package/dist/internal/devx-run.d.ts +129 -0
- package/dist/internal/devx-run.js +39 -0
- package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/index.js +0 -6
- package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
- package/dist/internal/extractor/adapters/go/manifest.js +19 -25
- package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
- package/dist/internal/extractor/adapters/go/parser.js +0 -5
- package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
- package/dist/internal/extractor/adapters/go/scanner.js +0 -43
- package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/index.js +0 -4
- package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
- package/dist/internal/extractor/adapters/python/manifest.js +21 -18
- package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
- package/dist/internal/extractor/adapters/python/parser.js +0 -4
- package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
- package/dist/internal/extractor/adapters/python/scanner.js +1 -54
- package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
- package/dist/internal/extractor/adapters/typescript/index.js +1 -21
- package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
- package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
- package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
- package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
- package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
- package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
- package/dist/internal/extractor/graph-builder.d.ts +10 -0
- package/dist/internal/extractor/graph-builder.js +245 -0
- package/dist/internal/extractor/graph-types.d.ts +89 -0
- package/dist/internal/extractor/graph-types.js +1 -0
- package/dist/internal/extractor/index.d.ts +0 -14
- package/dist/internal/extractor/index.js +0 -13
- package/dist/internal/extractor/types.d.ts +0 -45
- package/dist/internal/extractor/types.js +0 -27
- package/dist/internal/extractor/walker.d.ts +0 -18
- package/dist/internal/extractor/walker.js +0 -18
- package/dist/internal/gate-evaluate.d.ts +50 -0
- package/dist/internal/gate-evaluate.js +111 -0
- package/dist/internal/gate-policy.d.ts +38 -0
- package/dist/internal/gate-policy.js +167 -0
- package/dist/internal/git-metadata.d.ts +8 -0
- package/dist/internal/git-metadata.js +38 -0
- package/dist/internal/git.d.ts +9 -0
- package/dist/internal/git.js +45 -0
- package/dist/internal/github-linkage-prompt.d.ts +7 -0
- package/dist/internal/github-linkage-prompt.js +95 -0
- package/dist/internal/ide-setup.d.ts +17 -7
- package/dist/internal/ide-setup.js +379 -46
- package/dist/internal/inventory-sync.d.ts +74 -0
- package/dist/internal/inventory-sync.js +189 -0
- package/dist/internal/org-picker.d.ts +11 -0
- package/dist/internal/org-picker.js +34 -0
- package/dist/internal/orgs.js +0 -1
- package/dist/internal/packs/cache.d.ts +8 -0
- package/dist/internal/packs/cache.js +60 -0
- package/dist/internal/packs/index.d.ts +10 -0
- package/dist/internal/packs/index.js +8 -0
- package/dist/internal/packs/integrity.d.ts +9 -0
- package/dist/internal/packs/integrity.js +24 -0
- package/dist/internal/packs/jcs.d.ts +2 -0
- package/dist/internal/packs/jcs.js +57 -0
- package/dist/internal/packs/local-paths.d.ts +4 -0
- package/dist/internal/packs/local-paths.js +26 -0
- package/dist/internal/packs/npm-registry-client.d.ts +14 -0
- package/dist/internal/packs/npm-registry-client.js +87 -0
- package/dist/internal/packs/registry-client.d.ts +9 -0
- package/dist/internal/packs/registry-client.js +25 -0
- package/dist/internal/packs/resolve.d.ts +8 -0
- package/dist/internal/packs/resolve.js +89 -0
- package/dist/internal/packs/semver.d.ts +9 -0
- package/dist/internal/packs/semver.js +57 -0
- package/dist/internal/packs/signing.d.ts +5 -0
- package/dist/internal/packs/signing.js +56 -0
- package/dist/internal/packs/types.d.ts +77 -0
- package/dist/internal/packs/types.js +20 -0
- package/dist/internal/project-binding.d.ts +1 -0
- package/dist/internal/project-binding.js +16 -11
- package/dist/internal/resolve-init-session.d.ts +8 -0
- package/dist/internal/resolve-init-session.js +36 -0
- package/dist/internal/run-workdir.d.ts +2 -0
- package/dist/internal/run-workdir.js +19 -0
- package/dist/internal/security-context.d.ts +63 -0
- package/dist/internal/security-context.js +50 -0
- package/dist/internal/sync-outbox.d.ts +40 -0
- package/dist/internal/sync-outbox.js +66 -0
- package/dist/internal/target-auth/api-config.d.ts +0 -17
- package/dist/internal/target-auth/api-config.js +0 -18
- package/dist/internal/target-auth/config.d.ts +0 -12
- package/dist/internal/target-auth/config.js +0 -13
- package/dist/internal/target-auth/index.d.ts +0 -18
- package/dist/internal/target-auth/index.js +0 -19
- package/dist/internal/target-auth/keychain.d.ts +0 -18
- package/dist/internal/target-auth/keychain.js +3 -23
- package/dist/internal/target-auth/types.d.ts +0 -26
- package/dist/internal/target-auth/types.js +0 -10
- package/dist/internal/target-auth/workos.d.ts +0 -15
- package/dist/internal/target-auth/workos.js +9 -18
- package/dist/internal/test-scope.d.ts +99 -0
- package/dist/internal/test-scope.js +101 -0
- package/dist/internal/threatmodel-run-state.d.ts +15 -0
- package/dist/internal/threatmodel-run-state.js +43 -0
- package/dist/internal/threatmodel-run.d.ts +85 -0
- package/dist/internal/threatmodel-run.js +159 -0
- package/dist/internal/trust-room-link.d.ts +10 -0
- package/dist/internal/trust-room-link.js +15 -0
- package/dist/internal/zivis-local-state.d.ts +1 -0
- package/dist/internal/zivis-local-state.js +22 -0
- package/package.json +21 -5
- package/dist/commands/app/index.js.map +0 -1
- package/dist/commands/auth/index.js.map +0 -1
- package/dist/commands/auth/init.js.map +0 -1
- package/dist/commands/auth/login.js.map +0 -1
- package/dist/commands/auth/logout.js.map +0 -1
- package/dist/commands/auth/sessions.js.map +0 -1
- package/dist/commands/auth/status.js.map +0 -1
- package/dist/commands/credits/index.js.map +0 -1
- package/dist/commands/mcp/index.js.map +0 -1
- package/dist/commands/open/index.js.map +0 -1
- package/dist/commands/scan/index.js.map +0 -1
- package/dist/commands/target/index.js.map +0 -1
- package/dist/commands/tm/index.d.ts +0 -2
- package/dist/commands/tm/index.js +0 -266
- package/dist/commands/tm/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/index.js.map +0 -1
- package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/python/index.js.map +0 -1
- package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
- package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
- package/dist/internal/extractor/index.js.map +0 -1
- package/dist/internal/extractor/types.js.map +0 -1
- package/dist/internal/extractor/walker.js.map +0 -1
- package/dist/internal/ide-setup.js.map +0 -1
- package/dist/internal/orgs.js.map +0 -1
- package/dist/internal/project-binding.js.map +0 -1
- package/dist/internal/target-auth/api-config.js.map +0 -1
- package/dist/internal/target-auth/config.js.map +0 -1
- package/dist/internal/target-auth/index.js.map +0 -1
- package/dist/internal/target-auth/keychain.js.map +0 -1
- package/dist/internal/target-auth/types.js.map +0 -1
- package/dist/internal/target-auth/workos.js.map +0 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { PackIntegrityError, PackUnavailableError, UnknownScopeError } from "./types.js";
|
|
2
|
+
import { verifyPack } from "./integrity.js";
|
|
3
|
+
import { compareVersions, satisfiesMinVersion } from "./semver.js";
|
|
4
|
+
import { listCachedVersions, loadVerifiedFromCache, writeVerifiedPackToCache } from "./cache.js";
|
|
5
|
+
import { packVersionDir } from "./local-paths.js";
|
|
6
|
+
function contentPaths(manifest) {
|
|
7
|
+
return [manifest.base.path, ...(manifest.scopes ?? []).map((s) => s.path)];
|
|
8
|
+
}
|
|
9
|
+
async function fetchAndVerify(packType, version, client) {
|
|
10
|
+
const manifest = await client.getManifest(packType, version);
|
|
11
|
+
const contentByPath = new Map();
|
|
12
|
+
for (const relPath of contentPaths(manifest)) {
|
|
13
|
+
contentByPath.set(relPath, await client.getContent(packType, version, relPath));
|
|
14
|
+
}
|
|
15
|
+
const result = verifyPack(manifest, contentByPath);
|
|
16
|
+
if (!result.valid) {
|
|
17
|
+
throw new PackIntegrityError(`Downloaded ${packType} pack ${version} failed verification: ${result.reason}`);
|
|
18
|
+
}
|
|
19
|
+
return { manifest, contentByPath };
|
|
20
|
+
}
|
|
21
|
+
function toResolvedPack(manifest, contentByPath, source, cacheDir) {
|
|
22
|
+
return {
|
|
23
|
+
packId: manifest.pack_id,
|
|
24
|
+
packType: manifest.pack_type,
|
|
25
|
+
version: manifest.version,
|
|
26
|
+
minCliVersion: manifest.min_cli_version,
|
|
27
|
+
source,
|
|
28
|
+
basePath: `${cacheDir}/${manifest.base.path}`,
|
|
29
|
+
baseContent: contentByPath.get(manifest.base.path) ?? "",
|
|
30
|
+
scopes: manifest.scopes ?? [],
|
|
31
|
+
cacheDir,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function resolveCompatiblePack(packType, opts) {
|
|
35
|
+
try {
|
|
36
|
+
const listing = await opts.registryClient.listVersions(packType);
|
|
37
|
+
const compatible = listing.versions
|
|
38
|
+
.filter((v) => satisfiesMinVersion(opts.cliVersion, v.min_cli_version))
|
|
39
|
+
.sort((a, b) => compareVersions(b.version, a.version));
|
|
40
|
+
if (compatible.length === 0) {
|
|
41
|
+
throw new PackUnavailableError(`No ${packType} pack is compatible with @zivis/cli ${opts.cliVersion}. ` +
|
|
42
|
+
`Available versions: ${listing.versions.map((v) => v.version).join(", ") || "(none)"}`);
|
|
43
|
+
}
|
|
44
|
+
const target = compatible[0];
|
|
45
|
+
const cached = await loadVerifiedFromCache(packType, target.version);
|
|
46
|
+
if (cached) {
|
|
47
|
+
return toResolvedPack(cached.manifest, cached.contentByPath, "cache-verified", cached.cacheDir);
|
|
48
|
+
}
|
|
49
|
+
const { manifest, contentByPath } = await fetchAndVerify(packType, target.version, opts.registryClient);
|
|
50
|
+
const cacheDir = await writeVerifiedPackToCache(packType, target.version, manifest, contentByPath);
|
|
51
|
+
return toResolvedPack(manifest, contentByPath, "network", cacheDir);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (err instanceof PackIntegrityError || err instanceof PackUnavailableError) {
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
return resolveFromOfflineCache(packType, opts.cliVersion, err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function resolveFromOfflineCache(packType, cliVersion, networkError) {
|
|
61
|
+
const cachedVersions = await listCachedVersions(packType);
|
|
62
|
+
const verified = [];
|
|
63
|
+
for (const version of cachedVersions) {
|
|
64
|
+
const entry = await loadVerifiedFromCache(packType, version);
|
|
65
|
+
if (entry && satisfiesMinVersion(cliVersion, entry.manifest.min_cli_version)) {
|
|
66
|
+
verified.push(entry);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (verified.length === 0) {
|
|
70
|
+
const reason = networkError instanceof Error ? networkError.message : String(networkError);
|
|
71
|
+
throw new PackUnavailableError(`Could not reach the ZIVIS pack registry (${reason}), and no previously verified ${packType} ` +
|
|
72
|
+
`pack compatible with @zivis/cli ${cliVersion} is cached at ${packVersionDir(packType, "*")}.`);
|
|
73
|
+
}
|
|
74
|
+
verified.sort((a, b) => compareVersions(b.manifest.version, a.manifest.version));
|
|
75
|
+
const best = verified[0];
|
|
76
|
+
return toResolvedPack(best.manifest, best.contentByPath, "cache-offline", best.cacheDir);
|
|
77
|
+
}
|
|
78
|
+
export async function resolveScope(resolved, scopeId) {
|
|
79
|
+
const scope = resolved.scopes.find((s) => s.id === scopeId);
|
|
80
|
+
if (!scope) {
|
|
81
|
+
throw new UnknownScopeError(scopeId, resolved.scopes);
|
|
82
|
+
}
|
|
83
|
+
const fs = await import("node:fs/promises");
|
|
84
|
+
const content = await fs.readFile(`${resolved.cacheDir}/${scope.path}`, "utf-8");
|
|
85
|
+
return { id: scope.id, label: scope.label, description: scope.description, content, tests: scope.tests ?? [] };
|
|
86
|
+
}
|
|
87
|
+
export function listAvailableScopes(resolved) {
|
|
88
|
+
return resolved.scopes.map(({ id, label, description }) => ({ id, label, description }));
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedVersion {
|
|
2
|
+
major: number;
|
|
3
|
+
minor: number;
|
|
4
|
+
patch: number;
|
|
5
|
+
prerelease: (string | number)[];
|
|
6
|
+
}
|
|
7
|
+
export declare function parseVersion(version: string): ParsedVersion;
|
|
8
|
+
export declare function compareVersions(a: string, b: string): number;
|
|
9
|
+
export declare function satisfiesMinVersion(cliVersion: string, minCliVersion: string): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/;
|
|
2
|
+
export function parseVersion(version) {
|
|
3
|
+
const m = SEMVER_RE.exec(version.trim());
|
|
4
|
+
if (!m) {
|
|
5
|
+
throw new Error(`Not a valid semver-like version: "${version}"`);
|
|
6
|
+
}
|
|
7
|
+
const [, major, minor, patch, prerelease] = m;
|
|
8
|
+
return {
|
|
9
|
+
major: Number(major),
|
|
10
|
+
minor: Number(minor),
|
|
11
|
+
patch: Number(patch),
|
|
12
|
+
prerelease: prerelease
|
|
13
|
+
? prerelease.split(".").map((part) => (/^\d+$/.test(part) ? Number(part) : part))
|
|
14
|
+
: [],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function compareIdentifier(a, b) {
|
|
18
|
+
if (typeof a === "number" && typeof b === "number")
|
|
19
|
+
return a - b;
|
|
20
|
+
const as = String(a);
|
|
21
|
+
const bs = String(b);
|
|
22
|
+
if (typeof a === "number")
|
|
23
|
+
return -1;
|
|
24
|
+
if (typeof b === "number")
|
|
25
|
+
return 1;
|
|
26
|
+
return as < bs ? -1 : as > bs ? 1 : 0;
|
|
27
|
+
}
|
|
28
|
+
export function compareVersions(a, b) {
|
|
29
|
+
const pa = parseVersion(a);
|
|
30
|
+
const pb = parseVersion(b);
|
|
31
|
+
if (pa.major !== pb.major)
|
|
32
|
+
return pa.major - pb.major;
|
|
33
|
+
if (pa.minor !== pb.minor)
|
|
34
|
+
return pa.minor - pb.minor;
|
|
35
|
+
if (pa.patch !== pb.patch)
|
|
36
|
+
return pa.patch - pb.patch;
|
|
37
|
+
if (pa.prerelease.length === 0 && pb.prerelease.length === 0)
|
|
38
|
+
return 0;
|
|
39
|
+
if (pa.prerelease.length === 0)
|
|
40
|
+
return 1;
|
|
41
|
+
if (pb.prerelease.length === 0)
|
|
42
|
+
return -1;
|
|
43
|
+
const len = Math.max(pa.prerelease.length, pb.prerelease.length);
|
|
44
|
+
for (let i = 0; i < len; i++) {
|
|
45
|
+
if (i >= pa.prerelease.length)
|
|
46
|
+
return -1;
|
|
47
|
+
if (i >= pb.prerelease.length)
|
|
48
|
+
return 1;
|
|
49
|
+
const cmp = compareIdentifier(pa.prerelease[i], pb.prerelease[i]);
|
|
50
|
+
if (cmp !== 0)
|
|
51
|
+
return cmp;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
export function satisfiesMinVersion(cliVersion, minCliVersion) {
|
|
56
|
+
return compareVersions(cliVersion, minCliVersion) >= 0;
|
|
57
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PackManifestV1, PackSignature } from "./types.js";
|
|
2
|
+
export declare const PRODUCTION_PACK_SIGNING_KEY_ID = "zivis-pack-prod-2026-08";
|
|
3
|
+
export declare function manifestSigningPayload(manifest: Omit<PackManifestV1, "signature">): string;
|
|
4
|
+
export declare function verifyManifestSignature(manifest: PackManifestV1): boolean;
|
|
5
|
+
export declare function signManifest(manifest: Omit<PackManifestV1, "signature">, privateKeySpkiB64: string, keyId: string): PackSignature;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import { canonicalize } from "./jcs.js";
|
|
3
|
+
export const PRODUCTION_PACK_SIGNING_KEY_ID = "zivis-pack-prod-2026-08";
|
|
4
|
+
const PRODUCTION_PACK_PUBLIC_KEY_SPKI_B64 = "MCowBQYDK2VwAyEAxp1BFtbclfI8mB7NV4hwQ2Gx5OcSM3Irj+w4oZrooss=";
|
|
5
|
+
const KNOWN_PUBLIC_KEYS = {
|
|
6
|
+
[PRODUCTION_PACK_SIGNING_KEY_ID]: PRODUCTION_PACK_PUBLIC_KEY_SPKI_B64,
|
|
7
|
+
};
|
|
8
|
+
function loadTestOverrideKey(keyId) {
|
|
9
|
+
const testKeyId = process.env.ZIVIS_PACK_VERIFY_TEST_KEY_ID;
|
|
10
|
+
const testPublicKey = process.env.ZIVIS_PACK_VERIFY_TEST_PUBLIC_KEY;
|
|
11
|
+
if (testKeyId && testPublicKey && testKeyId === keyId)
|
|
12
|
+
return testPublicKey;
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function loadPublicKey(keyId) {
|
|
16
|
+
const spkiB64 = KNOWN_PUBLIC_KEYS[keyId] ?? loadTestOverrideKey(keyId);
|
|
17
|
+
if (!spkiB64)
|
|
18
|
+
return null;
|
|
19
|
+
return crypto.createPublicKey({
|
|
20
|
+
key: Buffer.from(spkiB64, "base64"),
|
|
21
|
+
format: "der",
|
|
22
|
+
type: "spki",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function manifestSigningPayload(manifest) {
|
|
26
|
+
return canonicalize(manifest);
|
|
27
|
+
}
|
|
28
|
+
export function verifyManifestSignature(manifest) {
|
|
29
|
+
const { signature, ...unsigned } = manifest;
|
|
30
|
+
if (!signature || signature.algorithm !== "ed25519")
|
|
31
|
+
return false;
|
|
32
|
+
const publicKey = loadPublicKey(signature.key_id);
|
|
33
|
+
if (!publicKey)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
const payload = manifestSigningPayload(unsigned);
|
|
37
|
+
return crypto.verify(null, new Uint8Array(Buffer.from(payload, "utf8")), publicKey, new Uint8Array(Buffer.from(signature.signature, "base64")));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function signManifest(manifest, privateKeySpkiB64, keyId) {
|
|
44
|
+
const privateKey = crypto.createPrivateKey({
|
|
45
|
+
key: Buffer.from(privateKeySpkiB64, "base64"),
|
|
46
|
+
format: "der",
|
|
47
|
+
type: "pkcs8",
|
|
48
|
+
});
|
|
49
|
+
const payload = manifestSigningPayload(manifest);
|
|
50
|
+
const sig = crypto.sign(null, new Uint8Array(Buffer.from(payload, "utf8")), privateKey);
|
|
51
|
+
return {
|
|
52
|
+
algorithm: "ed25519",
|
|
53
|
+
key_id: keyId,
|
|
54
|
+
signature: sig.toString("base64"),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export type PackType = "appsec" | "threatmodel";
|
|
2
|
+
export interface PackContentEntry {
|
|
3
|
+
path: string;
|
|
4
|
+
sha256: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PackTestEntry {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
deprecated?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PackScopeEntry extends PackContentEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
description: string;
|
|
15
|
+
tests?: PackTestEntry[];
|
|
16
|
+
}
|
|
17
|
+
export interface PackSignature {
|
|
18
|
+
algorithm: "ed25519";
|
|
19
|
+
key_id: string;
|
|
20
|
+
signature: string;
|
|
21
|
+
}
|
|
22
|
+
export interface PackManifestV1 {
|
|
23
|
+
schema_version: "1.0.0";
|
|
24
|
+
pack_id: string;
|
|
25
|
+
pack_type: PackType;
|
|
26
|
+
version: string;
|
|
27
|
+
min_cli_version: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
built_at: string;
|
|
30
|
+
base: PackContentEntry;
|
|
31
|
+
scopes?: PackScopeEntry[];
|
|
32
|
+
signature: PackSignature;
|
|
33
|
+
}
|
|
34
|
+
export interface PackVersionSummary {
|
|
35
|
+
version: string;
|
|
36
|
+
min_cli_version: string;
|
|
37
|
+
manifest_sha256: string;
|
|
38
|
+
published_at: string;
|
|
39
|
+
}
|
|
40
|
+
export interface PackVersionListing {
|
|
41
|
+
pack_type: PackType;
|
|
42
|
+
versions: PackVersionSummary[];
|
|
43
|
+
}
|
|
44
|
+
export type PackSource = "network" | "cache-verified" | "cache-offline";
|
|
45
|
+
export interface ResolvedScope {
|
|
46
|
+
id: string;
|
|
47
|
+
label: string;
|
|
48
|
+
description: string;
|
|
49
|
+
content: string;
|
|
50
|
+
tests: PackTestEntry[];
|
|
51
|
+
}
|
|
52
|
+
export interface ResolvedPack {
|
|
53
|
+
packId: string;
|
|
54
|
+
packType: PackType;
|
|
55
|
+
version: string;
|
|
56
|
+
minCliVersion: string;
|
|
57
|
+
source: PackSource;
|
|
58
|
+
basePath: string;
|
|
59
|
+
baseContent: string;
|
|
60
|
+
scopes: PackScopeEntry[];
|
|
61
|
+
cacheDir: string;
|
|
62
|
+
}
|
|
63
|
+
export declare class PackIntegrityError extends Error {
|
|
64
|
+
constructor(message: string);
|
|
65
|
+
}
|
|
66
|
+
export declare class PackUnavailableError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
69
|
+
export declare class UnknownScopeError extends Error {
|
|
70
|
+
readonly availableScopes: PackScopeEntry[];
|
|
71
|
+
constructor(scopeId: string, availableScopes: PackScopeEntry[]);
|
|
72
|
+
}
|
|
73
|
+
export interface PackRegistryClient {
|
|
74
|
+
listVersions(packType: PackType): Promise<PackVersionListing>;
|
|
75
|
+
getManifest(packType: PackType, version: string): Promise<PackManifestV1>;
|
|
76
|
+
getContent(packType: PackType, version: string, relativePath: string): Promise<string>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export class PackIntegrityError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = "PackIntegrityError";
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class PackUnavailableError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "PackUnavailableError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class UnknownScopeError extends Error {
|
|
14
|
+
availableScopes;
|
|
15
|
+
constructor(scopeId, availableScopes) {
|
|
16
|
+
super(`Unknown scope "${scopeId}". Available scopes: ${availableScopes.map((s) => s.id).join(", ") || "(none)"}`);
|
|
17
|
+
this.name = "UnknownScopeError";
|
|
18
|
+
this.availableScopes = availableScopes;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -7,25 +7,31 @@ import { fetchUserOrgs } from "./orgs.js";
|
|
|
7
7
|
export async function ensureProjectBinding(opts) {
|
|
8
8
|
const { config, session, accessToken, cwd, autoBind, skipBind } = opts;
|
|
9
9
|
const claims = decodeJwtPayload(accessToken);
|
|
10
|
-
const jwtOrgId = claims?.org_id;
|
|
10
|
+
const jwtOrgId = claims?.org_id ?? opts.organizationId;
|
|
11
11
|
if (!jwtOrgId) {
|
|
12
12
|
console.log("");
|
|
13
|
-
console.log(" Note: JWT has no
|
|
13
|
+
console.log(" Note: no org_id available (JWT has none, and no organizationId was supplied) — skipping project binding.");
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
let detected = detectProjectBinding(cwd);
|
|
17
17
|
if (detected?.binding.workosOrgId === jwtOrgId) {
|
|
18
18
|
console.log(`✓ Project binding verified: ${detected.filePath}`);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
21
|
if (detected?.binding.workosOrgId && detected.binding.workosOrgId !== jwtOrgId) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
if (!opts.force) {
|
|
23
|
+
console.error("");
|
|
24
|
+
console.error("[zivis] ERROR: Project binding org mismatches JWT org.");
|
|
25
|
+
console.error(`[zivis] Binding file: ${detected.filePath}`);
|
|
26
|
+
console.error(`[zivis] Binding org: ${detected.binding.workosOrgId}${detected.binding.orgName ? ` (${detected.binding.orgName})` : ""}`);
|
|
27
|
+
console.error(`[zivis] JWT org: ${jwtOrgId}`);
|
|
28
|
+
console.error("[zivis] This should not happen — investigate before proceeding.");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const oldLabel = detected.binding.orgName ?? detected.binding.workosOrgId;
|
|
32
|
+
console.log(` Replacing existing binding (was: ${oldLabel})`);
|
|
33
|
+
fs.unlinkSync(detected.filePath);
|
|
34
|
+
detected = null;
|
|
29
35
|
}
|
|
30
36
|
let orgInfo;
|
|
31
37
|
try {
|
|
@@ -93,4 +99,3 @@ export async function ensureProjectBinding(opts) {
|
|
|
93
99
|
console.log(` File: ${bindingPath}`);
|
|
94
100
|
console.log(" Commit this file so future sessions in this repo are org-locked.");
|
|
95
101
|
}
|
|
96
|
-
//# sourceMappingURL=project-binding.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ZivisConfig } from "@zivis/mcp/types";
|
|
2
|
+
export interface ExistingSessionAuth {
|
|
3
|
+
accessToken: string;
|
|
4
|
+
idToken?: string;
|
|
5
|
+
organizationId?: string;
|
|
6
|
+
email?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveExistingSessionAuth(config: ZivisConfig, session: string, cwd: string): Promise<ExistingSessionAuth | null>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loadTokens, loadSessionIndex, resolveAuth } from "@zivis/mcp/auth";
|
|
2
|
+
import { getValidAccessToken } from "@zivis/mcp/auth/token-refresh";
|
|
3
|
+
import { credentialStorageFromConfig } from "@zivis/mcp/types";
|
|
4
|
+
import { detectProjectBinding } from "@zivis/mcp/project-binding";
|
|
5
|
+
import { fetchUserOrgs } from "./orgs.js";
|
|
6
|
+
export async function resolveExistingSessionAuth(config, session, cwd) {
|
|
7
|
+
const sessionConfig = { ...config, session };
|
|
8
|
+
const oauthAccessToken = await getValidAccessToken(sessionConfig);
|
|
9
|
+
if (oauthAccessToken) {
|
|
10
|
+
const stored = await loadTokens(session, credentialStorageFromConfig(config));
|
|
11
|
+
return { accessToken: oauthAccessToken, idToken: stored?.id_token };
|
|
12
|
+
}
|
|
13
|
+
const creds = await resolveAuth(sessionConfig, "apikey");
|
|
14
|
+
if (creds.method !== "apikey" || !creds.apiKey)
|
|
15
|
+
return null;
|
|
16
|
+
const binding = detectProjectBinding(cwd);
|
|
17
|
+
const workosOrgId = binding?.binding.workosOrgId;
|
|
18
|
+
if (!workosOrgId)
|
|
19
|
+
return null;
|
|
20
|
+
let orgs;
|
|
21
|
+
try {
|
|
22
|
+
orgs = await fetchUserOrgs(config, creds.apiKey);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (!orgs.some((o) => o.workosOrgId === workosOrgId)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const index = await loadSessionIndex(credentialStorageFromConfig(config));
|
|
31
|
+
return {
|
|
32
|
+
accessToken: creds.apiKey,
|
|
33
|
+
organizationId: workosOrgId,
|
|
34
|
+
email: index[session]?.email,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { ensureZivisGitignore } from "./zivis-local-state.js";
|
|
4
|
+
export function writeRunWorkFile(cwd, runId, filename, data) {
|
|
5
|
+
ensureZivisGitignore(cwd);
|
|
6
|
+
const dir = path.join(cwd, ".zivis", "work", runId);
|
|
7
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
8
|
+
const filePath = path.join(dir, filename);
|
|
9
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
|
|
10
|
+
return filePath;
|
|
11
|
+
}
|
|
12
|
+
export function writeRunWorkTextFile(cwd, runId, filename, text) {
|
|
13
|
+
ensureZivisGitignore(cwd);
|
|
14
|
+
const dir = path.join(cwd, ".zivis", "work", runId);
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
const filePath = path.join(dir, filename);
|
|
17
|
+
fs.writeFileSync(filePath, text);
|
|
18
|
+
return filePath;
|
|
19
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface SecurityContextFinding {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
severity: string;
|
|
5
|
+
status: string;
|
|
6
|
+
owaspId: string | null;
|
|
7
|
+
sourceSubtype: string;
|
|
8
|
+
resourceId: string | null;
|
|
9
|
+
firstDetectedAt: string;
|
|
10
|
+
dispositionAt?: string | null;
|
|
11
|
+
dispositionRef?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface SecurityContext {
|
|
14
|
+
application: {
|
|
15
|
+
id: string;
|
|
16
|
+
name: string;
|
|
17
|
+
};
|
|
18
|
+
repo: {
|
|
19
|
+
repoFullName: string;
|
|
20
|
+
defaultBranch: string | null;
|
|
21
|
+
latestKnownCommitSha: string | null;
|
|
22
|
+
revisionSyncedAt: string | null;
|
|
23
|
+
} | null;
|
|
24
|
+
lastTestedGitSha: string | null;
|
|
25
|
+
lastThreatModeledGitSha: string | null;
|
|
26
|
+
openFindings: {
|
|
27
|
+
total: number;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
items: SecurityContextFinding[];
|
|
30
|
+
};
|
|
31
|
+
findingsAwaitingRetest: {
|
|
32
|
+
total: number;
|
|
33
|
+
truncated: boolean;
|
|
34
|
+
items: SecurityContextFinding[];
|
|
35
|
+
};
|
|
36
|
+
inconclusiveAreas: unknown[];
|
|
37
|
+
weakOrUncoveredAreas: unknown[];
|
|
38
|
+
latestThreatModel: {
|
|
39
|
+
version: number;
|
|
40
|
+
gitCommitSha: string | null;
|
|
41
|
+
updatedAt: string;
|
|
42
|
+
methodology: string | null;
|
|
43
|
+
} | null;
|
|
44
|
+
trustRoomUrl: string | null;
|
|
45
|
+
trustRoomSource?: string | null;
|
|
46
|
+
generatedAt: string;
|
|
47
|
+
gaps: {
|
|
48
|
+
field: string;
|
|
49
|
+
reason: string;
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
52
|
+
export interface SecurityContextApi {
|
|
53
|
+
get<T>(path: string): Promise<T>;
|
|
54
|
+
}
|
|
55
|
+
export type SecurityContextOutcome = {
|
|
56
|
+
available: true;
|
|
57
|
+
context: SecurityContext;
|
|
58
|
+
} | {
|
|
59
|
+
available: false;
|
|
60
|
+
reason: string;
|
|
61
|
+
};
|
|
62
|
+
export declare function fetchSecurityContext(client: SecurityContextApi, applicationId: string): Promise<SecurityContextOutcome>;
|
|
63
|
+
export declare function renderSecurityContext(outcome: SecurityContextOutcome, format?: "human" | "json"): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export async function fetchSecurityContext(client, applicationId) {
|
|
2
|
+
try {
|
|
3
|
+
const context = await client.get(`/api/rt/applications/${encodeURIComponent(applicationId)}/security-context`);
|
|
4
|
+
return { available: true, context };
|
|
5
|
+
}
|
|
6
|
+
catch (err) {
|
|
7
|
+
return { available: false, reason: err instanceof Error ? err.message : String(err) };
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function summarizeFinding(f) {
|
|
11
|
+
return ` - [${f.severity}] ${f.title} (${f.id})${f.owaspId ? ` — ${f.owaspId}` : ""}`;
|
|
12
|
+
}
|
|
13
|
+
export function renderSecurityContext(outcome, format = "human") {
|
|
14
|
+
if (format === "json") {
|
|
15
|
+
return JSON.stringify(outcome, null, 2);
|
|
16
|
+
}
|
|
17
|
+
if (!outcome.available) {
|
|
18
|
+
return `[zivis] No connected security context available (${outcome.reason}). Continuing with local-only context.`;
|
|
19
|
+
}
|
|
20
|
+
const c = outcome.context;
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`ZIVIS connected security context — ${c.application.name}`);
|
|
23
|
+
if (c.repo) {
|
|
24
|
+
lines.push(`Last known repo commit: ${c.repo.latestKnownCommitSha ?? "unknown"} (${c.repo.repoFullName})`);
|
|
25
|
+
}
|
|
26
|
+
lines.push(c.lastThreatModeledGitSha
|
|
27
|
+
? `Last threat-modeled commit: ${c.lastThreatModeledGitSha}; compare local HEAD using git.`
|
|
28
|
+
: `No prior threat model on record for this Application.`);
|
|
29
|
+
if (c.lastTestedGitSha) {
|
|
30
|
+
lines.push(`Last tested commit: ${c.lastTestedGitSha}; compare local HEAD using git.`);
|
|
31
|
+
}
|
|
32
|
+
if (c.openFindings.total > 0) {
|
|
33
|
+
lines.push(`Prior open findings: ${c.openFindings.total}${c.openFindings.truncated ? ` (showing ${c.openFindings.items.length})` : ""}`);
|
|
34
|
+
c.openFindings.items.forEach((f) => lines.push(summarizeFinding(f)));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
lines.push(`No prior open findings on record.`);
|
|
38
|
+
}
|
|
39
|
+
if (c.findingsAwaitingRetest.total > 0) {
|
|
40
|
+
lines.push(`Findings claimed fixed but not yet retested: ${c.findingsAwaitingRetest.total}${c.findingsAwaitingRetest.truncated ? ` (showing ${c.findingsAwaitingRetest.items.length})` : ""}`);
|
|
41
|
+
c.findingsAwaitingRetest.items.forEach((f) => lines.push(` - Finding ${f.id} was remediated but has not been retested.${f.dispositionRef ? ` (ref: ${f.dispositionRef})` : ""}`));
|
|
42
|
+
}
|
|
43
|
+
if (c.latestThreatModel) {
|
|
44
|
+
lines.push(`Latest threat model: v${c.latestThreatModel.version}${c.latestThreatModel.methodology ? `, methodology: ${c.latestThreatModel.methodology}` : ""}`);
|
|
45
|
+
}
|
|
46
|
+
if (c.trustRoomUrl) {
|
|
47
|
+
lines.push(`Trust Room: ${c.trustRoomUrl}`);
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { InventorySyncInput, InventorySyncProvenance } from "./inventory-sync.js";
|
|
2
|
+
interface OutboxEntryBase {
|
|
3
|
+
id: string;
|
|
4
|
+
queuedAt: string;
|
|
5
|
+
attempts: number;
|
|
6
|
+
lastError: string;
|
|
7
|
+
}
|
|
8
|
+
export interface InventorySyncOutboxEntry extends OutboxEntryBase {
|
|
9
|
+
kind: "inventory-sync";
|
|
10
|
+
applicationId: string;
|
|
11
|
+
input: InventorySyncInput;
|
|
12
|
+
provenance: InventorySyncProvenance;
|
|
13
|
+
}
|
|
14
|
+
export interface DevXRunCompleteOutboxEntry extends OutboxEntryBase {
|
|
15
|
+
kind: "devx-run-complete";
|
|
16
|
+
runId: string;
|
|
17
|
+
contractVersion: string;
|
|
18
|
+
result: unknown;
|
|
19
|
+
}
|
|
20
|
+
export type OutboxEntry = InventorySyncOutboxEntry | DevXRunCompleteOutboxEntry;
|
|
21
|
+
export declare function writeOutboxEntry(cwd: string, params: {
|
|
22
|
+
applicationId: string;
|
|
23
|
+
input: InventorySyncInput;
|
|
24
|
+
provenance: InventorySyncProvenance;
|
|
25
|
+
error: string;
|
|
26
|
+
}): string;
|
|
27
|
+
export declare function writeDevXRunCompleteOutboxEntry(cwd: string, params: {
|
|
28
|
+
runId: string;
|
|
29
|
+
contractVersion: string;
|
|
30
|
+
result: unknown;
|
|
31
|
+
error: string;
|
|
32
|
+
}): string;
|
|
33
|
+
export interface OutboxListing {
|
|
34
|
+
filePath: string;
|
|
35
|
+
entry: OutboxEntry;
|
|
36
|
+
}
|
|
37
|
+
export declare function listOutboxEntries(cwd: string): OutboxListing[];
|
|
38
|
+
export declare function removeOutboxEntry(filePath: string): void;
|
|
39
|
+
export declare function recordOutboxRetryFailure(filePath: string, entry: OutboxEntry, error: string): void;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as crypto from "node:crypto";
|
|
4
|
+
import { ensureZivisGitignore } from "./zivis-local-state.js";
|
|
5
|
+
function outboxDir(cwd) {
|
|
6
|
+
return path.join(cwd, ".zivis", "outbox");
|
|
7
|
+
}
|
|
8
|
+
function writeEntryFile(cwd, entry) {
|
|
9
|
+
ensureZivisGitignore(cwd);
|
|
10
|
+
const dir = outboxDir(cwd);
|
|
11
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
12
|
+
const filePath = path.join(dir, `${entry.queuedAt.replace(/[:.]/g, "-")}-${entry.id.slice(0, 8)}.json`);
|
|
13
|
+
fs.writeFileSync(filePath, JSON.stringify(entry, null, 2) + "\n");
|
|
14
|
+
return filePath;
|
|
15
|
+
}
|
|
16
|
+
export function writeOutboxEntry(cwd, params) {
|
|
17
|
+
const entry = {
|
|
18
|
+
kind: "inventory-sync",
|
|
19
|
+
id: crypto.randomUUID(),
|
|
20
|
+
applicationId: params.applicationId,
|
|
21
|
+
input: params.input,
|
|
22
|
+
provenance: params.provenance,
|
|
23
|
+
queuedAt: new Date().toISOString(),
|
|
24
|
+
attempts: 1,
|
|
25
|
+
lastError: params.error,
|
|
26
|
+
};
|
|
27
|
+
return writeEntryFile(cwd, entry);
|
|
28
|
+
}
|
|
29
|
+
export function writeDevXRunCompleteOutboxEntry(cwd, params) {
|
|
30
|
+
const entry = {
|
|
31
|
+
kind: "devx-run-complete",
|
|
32
|
+
id: crypto.randomUUID(),
|
|
33
|
+
runId: params.runId,
|
|
34
|
+
contractVersion: params.contractVersion,
|
|
35
|
+
result: params.result,
|
|
36
|
+
queuedAt: new Date().toISOString(),
|
|
37
|
+
attempts: 1,
|
|
38
|
+
lastError: params.error,
|
|
39
|
+
};
|
|
40
|
+
return writeEntryFile(cwd, entry);
|
|
41
|
+
}
|
|
42
|
+
export function listOutboxEntries(cwd) {
|
|
43
|
+
const dir = outboxDir(cwd);
|
|
44
|
+
if (!fs.existsSync(dir))
|
|
45
|
+
return [];
|
|
46
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json")).sort();
|
|
47
|
+
const listings = [];
|
|
48
|
+
for (const file of files) {
|
|
49
|
+
const filePath = path.join(dir, file);
|
|
50
|
+
try {
|
|
51
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
52
|
+
const entry = (raw.kind ? raw : { ...raw, kind: "inventory-sync" });
|
|
53
|
+
listings.push({ filePath, entry });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return listings;
|
|
59
|
+
}
|
|
60
|
+
export function removeOutboxEntry(filePath) {
|
|
61
|
+
fs.rmSync(filePath, { force: true });
|
|
62
|
+
}
|
|
63
|
+
export function recordOutboxRetryFailure(filePath, entry, error) {
|
|
64
|
+
const updated = { ...entry, attempts: entry.attempts + 1, lastError: error };
|
|
65
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2) + "\n");
|
|
66
|
+
}
|