@tickernelz/paperclip-pro-adapter-grok-local 2026.925.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/dist/cli/format-event.d.ts +2 -0
- package/dist/cli/format-event.d.ts.map +1 -0
- package/dist/cli/format-event.js +55 -0
- package/dist/cli/format-event.js.map +1 -0
- package/dist/cli/format-event.test.d.ts +2 -0
- package/dist/cli/format-event.test.d.ts.map +1 -0
- package/dist/cli/format-event.test.js +19 -0
- package/dist/cli/format-event.test.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/server/adapter-auth-promotion.d.ts +87 -0
- package/dist/server/adapter-auth-promotion.d.ts.map +1 -0
- package/dist/server/adapter-auth-promotion.js +294 -0
- package/dist/server/adapter-auth-promotion.js.map +1 -0
- package/dist/server/adapter-auth-promotion.test.d.ts +2 -0
- package/dist/server/adapter-auth-promotion.test.d.ts.map +1 -0
- package/dist/server/adapter-auth-promotion.test.js +563 -0
- package/dist/server/adapter-auth-promotion.test.js.map +1 -0
- package/dist/server/device-login-parse.d.ts +23 -0
- package/dist/server/device-login-parse.d.ts.map +1 -0
- package/dist/server/device-login-parse.js +148 -0
- package/dist/server/device-login-parse.js.map +1 -0
- package/dist/server/device-login-parse.test.d.ts +2 -0
- package/dist/server/device-login-parse.test.d.ts.map +1 -0
- package/dist/server/device-login-parse.test.js +146 -0
- package/dist/server/device-login-parse.test.js.map +1 -0
- package/dist/server/execute.d.ts +3 -0
- package/dist/server/execute.d.ts.map +1 -0
- package/dist/server/execute.js +572 -0
- package/dist/server/execute.js.map +1 -0
- package/dist/server/execute.test.d.ts +2 -0
- package/dist/server/execute.test.d.ts.map +1 -0
- package/dist/server/execute.test.js +731 -0
- package/dist/server/execute.test.js.map +1 -0
- package/dist/server/grok-auth-copyback.d.ts +48 -0
- package/dist/server/grok-auth-copyback.d.ts.map +1 -0
- package/dist/server/grok-auth-copyback.js +92 -0
- package/dist/server/grok-auth-copyback.js.map +1 -0
- package/dist/server/grok-auth-copyback.test.d.ts +2 -0
- package/dist/server/grok-auth-copyback.test.d.ts.map +1 -0
- package/dist/server/grok-auth-copyback.test.js +246 -0
- package/dist/server/grok-auth-copyback.test.js.map +1 -0
- package/dist/server/grok-auth-merge-decision.cjs +157 -0
- package/dist/server/grok-auth-merge-decision.d.ts +21 -0
- package/dist/server/grok-auth-merge-decision.d.ts.map +1 -0
- package/dist/server/grok-auth-merge-decision.js +58 -0
- package/dist/server/grok-auth-merge-decision.js.map +1 -0
- package/dist/server/grok-auth-merge-decision.test.d.ts +2 -0
- package/dist/server/grok-auth-merge-decision.test.d.ts.map +1 -0
- package/dist/server/grok-auth-merge-decision.test.js +189 -0
- package/dist/server/grok-auth-merge-decision.test.js.map +1 -0
- package/dist/server/grok-home.d.ts +57 -0
- package/dist/server/grok-home.d.ts.map +1 -0
- package/dist/server/grok-home.js +145 -0
- package/dist/server/grok-home.js.map +1 -0
- package/dist/server/grok-home.test.d.ts +2 -0
- package/dist/server/grok-home.test.d.ts.map +1 -0
- package/dist/server/grok-home.test.js +109 -0
- package/dist/server/grok-home.test.js.map +1 -0
- package/dist/server/index.d.ts +12 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +67 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/parse.d.ts +15 -0
- package/dist/server/parse.d.ts.map +1 -0
- package/dist/server/parse.js +93 -0
- package/dist/server/parse.js.map +1 -0
- package/dist/server/parse.test.d.ts +2 -0
- package/dist/server/parse.test.d.ts.map +1 -0
- package/dist/server/parse.test.js +82 -0
- package/dist/server/parse.test.js.map +1 -0
- package/dist/server/skills.d.ts +4 -0
- package/dist/server/skills.d.ts.map +1 -0
- package/dist/server/skills.js +21 -0
- package/dist/server/skills.js.map +1 -0
- package/dist/server/test.d.ts +9 -0
- package/dist/server/test.d.ts.map +1 -0
- package/dist/server/test.js +362 -0
- package/dist/server/test.js.map +1 -0
- package/dist/server/test.test.d.ts +2 -0
- package/dist/server/test.test.d.ts.map +1 -0
- package/dist/server/test.test.js +293 -0
- package/dist/server/test.test.js.map +1 -0
- package/dist/shared/turn-boundary.d.ts +7 -0
- package/dist/shared/turn-boundary.d.ts.map +1 -0
- package/dist/shared/turn-boundary.js +44 -0
- package/dist/shared/turn-boundary.js.map +1 -0
- package/dist/shared/turn-boundary.test.d.ts +2 -0
- package/dist/shared/turn-boundary.test.d.ts.map +1 -0
- package/dist/shared/turn-boundary.test.js +43 -0
- package/dist/shared/turn-boundary.test.js.map +1 -0
- package/dist/ui/build-config.d.ts +3 -0
- package/dist/ui/build-config.d.ts.map +1 -0
- package/dist/ui/build-config.js +29 -0
- package/dist/ui/build-config.js.map +1 -0
- package/dist/ui/build-config.test.d.ts +2 -0
- package/dist/ui/build-config.test.d.ts.map +1 -0
- package/dist/ui/build-config.test.js +26 -0
- package/dist/ui/build-config.test.js.map +1 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/parse-stdout.d.ts +7 -0
- package/dist/ui/parse-stdout.d.ts.map +1 -0
- package/dist/ui/parse-stdout.js +76 -0
- package/dist/ui/parse-stdout.js.map +1 -0
- package/dist/ui/parse-stdout.test.d.ts +2 -0
- package/dist/ui/parse-stdout.test.d.ts.map +1 -0
- package/dist/ui/parse-stdout.test.js +62 -0
- package/dist/ui/parse-stdout.test.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
|
|
3
|
+
// Co-change notice: parseAuthValue below mirrors parseGrokAuthPayload and
|
|
4
|
+
// hasUsableGrokAuthValue in
|
|
5
|
+
// packages/adapters/grok-local/src/server/grok-home.ts. If the auth format
|
|
6
|
+
// changes (new shape, renamed field), update both sites together.
|
|
7
|
+
|
|
8
|
+
// Matches the composite `<issuer>::<uuid>` top-level key. See grok-home.ts
|
|
9
|
+
// for the full rationale (the greedy `.+` backtracks to the last `::`).
|
|
10
|
+
const GROK_IDENTITY_KEY_RE =
|
|
11
|
+
/^.+::[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
12
|
+
|
|
13
|
+
// Parses one already-decoded JSON value into `{ kind: "unusable" }` or
|
|
14
|
+
// `{ kind: "usable", identityKey, expiresAtRaw }`. `expiresAtRaw` is the raw,
|
|
15
|
+
// still-undecoded `expires_at` field (or `undefined` when absent) — decoding
|
|
16
|
+
// it is `readExpiry`'s job, kept separate so a caller can compare two already
|
|
17
|
+
// -parsed shapes without touching the filesystem (see `decide` below).
|
|
18
|
+
function parseAuthValue(raw) {
|
|
19
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return { kind: "unusable" };
|
|
20
|
+
const keys = Object.keys(raw);
|
|
21
|
+
if (keys.length !== 1) return { kind: "unusable" };
|
|
22
|
+
const [identityKey] = keys;
|
|
23
|
+
if (!GROK_IDENTITY_KEY_RE.test(identityKey)) return { kind: "unusable" };
|
|
24
|
+
const value = raw[identityKey];
|
|
25
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return { kind: "unusable" };
|
|
26
|
+
const key = value.key;
|
|
27
|
+
const refreshToken = value.refresh_token;
|
|
28
|
+
const hasUsableValue =
|
|
29
|
+
typeof key === "string" &&
|
|
30
|
+
key.trim().length > 0 &&
|
|
31
|
+
typeof refreshToken === "string" &&
|
|
32
|
+
refreshToken.trim().length > 0;
|
|
33
|
+
if (!hasUsableValue) return { kind: "unusable" };
|
|
34
|
+
return { kind: "usable", identityKey, expiresAtRaw: value.expires_at };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseAuthFile(filePath) {
|
|
38
|
+
let parsed;
|
|
39
|
+
try {
|
|
40
|
+
parsed = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
41
|
+
} catch {
|
|
42
|
+
return { kind: "unusable" };
|
|
43
|
+
}
|
|
44
|
+
return parseAuthValue(parsed);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ISO_8601_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
|
|
48
|
+
// Below this magnitude a numeric `expires_at` is epoch seconds; at or above
|
|
49
|
+
// it, epoch milliseconds. A modern epoch-seconds instant is around 1.7e9; a
|
|
50
|
+
// modern epoch-millisecond instant is around 1.7e12, so this threshold never
|
|
51
|
+
// confuses the two for a real-world timestamp.
|
|
52
|
+
const EPOCH_SECONDS_MAX = 1e12;
|
|
53
|
+
|
|
54
|
+
// Reads one raw `expires_at` value into `{ present, unreadable, ms }`.
|
|
55
|
+
// - `present: false` — the field is absent (missing or null).
|
|
56
|
+
// - `unreadable: true` — the field is present but not one of the three
|
|
57
|
+
// accepted encodings (ISO-8601 string, epoch-seconds number, epoch-
|
|
58
|
+
// milliseconds number).
|
|
59
|
+
// - otherwise `ms` holds the decoded epoch-millisecond value.
|
|
60
|
+
function readExpiry(raw) {
|
|
61
|
+
if (raw === undefined || raw === null) return { present: false, unreadable: false, ms: null };
|
|
62
|
+
if (typeof raw === "string") {
|
|
63
|
+
if (ISO_8601_RE.test(raw)) {
|
|
64
|
+
const ms = Date.parse(raw);
|
|
65
|
+
if (Number.isFinite(ms)) return { present: true, unreadable: false, ms };
|
|
66
|
+
}
|
|
67
|
+
return { present: true, unreadable: true, ms: null };
|
|
68
|
+
}
|
|
69
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
70
|
+
const ms = raw < EPOCH_SECONDS_MAX ? raw * 1000 : raw;
|
|
71
|
+
return { present: true, unreadable: false, ms };
|
|
72
|
+
}
|
|
73
|
+
return { present: true, unreadable: true, ms: null };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Exit contract. Exit 10 = use source; exit 20 = keep destination; exit 21 =
|
|
77
|
+
// keep destination, an expiry was present but not a recognized encoding;
|
|
78
|
+
// exit 22 = keep destination, the source expiry sat further ahead of the
|
|
79
|
+
// host clock than the plausible bound.
|
|
80
|
+
const USE_SOURCE = 10;
|
|
81
|
+
const KEEP_DESTINATION = 20;
|
|
82
|
+
const UNREADABLE_EXPIRY = 21;
|
|
83
|
+
const IMPLAUSIBLE_EXPIRY = 22;
|
|
84
|
+
const MAX_PLAUSIBLE_EXPIRY_MS = 400 * 24 * 60 * 60 * 1000;
|
|
85
|
+
|
|
86
|
+
// This predicate answers one direction-agnostic question: should the caller
|
|
87
|
+
// replace `destination` with `source`? The caller picks which copy is source
|
|
88
|
+
// and which is destination from its own frame of reference (the outbound
|
|
89
|
+
// copy-out is the only caller today) purely by argument order — there is no
|
|
90
|
+
// `--direction` flag:
|
|
91
|
+
//
|
|
92
|
+
// argv[0] (first positional) = source auth.json path
|
|
93
|
+
// argv[1] (second positional) = destination auth.json path
|
|
94
|
+
//
|
|
95
|
+
// `decide` takes already-parsed `{ kind, identityKey, expiresAtRaw }` shapes
|
|
96
|
+
// plus a caller-supplied `nowMs`, so a test can drive the exact plausibility
|
|
97
|
+
// bound without spawning a process and racing wall-clock drift. Guard order,
|
|
98
|
+
// first match wins:
|
|
99
|
+
// 1. Either side unusable, or the identity keys differ -> KEEP_DESTINATION.
|
|
100
|
+
// 2. Either side's expiry is absent -> KEEP_DESTINATION.
|
|
101
|
+
// 3. Either side's expiry is present but unreadable -> UNREADABLE_EXPIRY.
|
|
102
|
+
// 4. The source expiry sits further ahead of `nowMs` than
|
|
103
|
+
// MAX_PLAUSIBLE_EXPIRY_MS -> IMPLAUSIBLE_EXPIRY. Only the source is
|
|
104
|
+
// bounded, and only against the caller's clock: the source is the
|
|
105
|
+
// sandbox-supplied side, so it must never be allowed to supply the
|
|
106
|
+
// reference time.
|
|
107
|
+
// 5. The source expiry is strictly later than the destination expiry
|
|
108
|
+
// -> USE_SOURCE.
|
|
109
|
+
// 6. Otherwise (a tie, or the source is older) -> KEEP_DESTINATION.
|
|
110
|
+
//
|
|
111
|
+
// The predicate only ever reads the two files and exits with a code; it
|
|
112
|
+
// never prints token bytes.
|
|
113
|
+
function decide(source, destination, nowMs) {
|
|
114
|
+
if (
|
|
115
|
+
source.kind === "unusable" ||
|
|
116
|
+
destination.kind === "unusable" ||
|
|
117
|
+
source.identityKey !== destination.identityKey
|
|
118
|
+
) {
|
|
119
|
+
return KEEP_DESTINATION;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const sourceExpiry = readExpiry(source.expiresAtRaw);
|
|
123
|
+
const destinationExpiry = readExpiry(destination.expiresAtRaw);
|
|
124
|
+
|
|
125
|
+
if (!sourceExpiry.present || !destinationExpiry.present) {
|
|
126
|
+
return KEEP_DESTINATION;
|
|
127
|
+
}
|
|
128
|
+
if (sourceExpiry.unreadable || destinationExpiry.unreadable) {
|
|
129
|
+
return UNREADABLE_EXPIRY;
|
|
130
|
+
}
|
|
131
|
+
if (sourceExpiry.ms - nowMs > MAX_PLAUSIBLE_EXPIRY_MS) {
|
|
132
|
+
return IMPLAUSIBLE_EXPIRY;
|
|
133
|
+
}
|
|
134
|
+
if (sourceExpiry.ms > destinationExpiry.ms) {
|
|
135
|
+
return USE_SOURCE;
|
|
136
|
+
}
|
|
137
|
+
return KEEP_DESTINATION;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (require.main === module) {
|
|
141
|
+
const [sourceAuthPath, destinationAuthPath] = process.argv.slice(2);
|
|
142
|
+
const source = parseAuthFile(sourceAuthPath);
|
|
143
|
+
const destination = parseAuthFile(destinationAuthPath);
|
|
144
|
+
process.exit(decide(source, destination, Date.now()));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = {
|
|
148
|
+
decide,
|
|
149
|
+
parseAuthValue,
|
|
150
|
+
parseAuthFile,
|
|
151
|
+
readExpiry,
|
|
152
|
+
USE_SOURCE,
|
|
153
|
+
KEEP_DESTINATION,
|
|
154
|
+
UNREADABLE_EXPIRY,
|
|
155
|
+
IMPLAUSIBLE_EXPIRY,
|
|
156
|
+
MAX_PLAUSIBLE_EXPIRY_MS,
|
|
157
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Exit code: install the source credential over the destination. */
|
|
2
|
+
export declare const USE_SOURCE_EXIT = 10;
|
|
3
|
+
/** Exit code: keep the destination credential. */
|
|
4
|
+
export declare const KEEP_DESTINATION_EXIT = 20;
|
|
5
|
+
/** Exit code: keep the destination; an expiry was present but unreadable. */
|
|
6
|
+
export declare const UNREADABLE_EXPIRY_EXIT = 21;
|
|
7
|
+
/** Exit code: keep the destination; the source expiry was implausibly far ahead. */
|
|
8
|
+
export declare const IMPLAUSIBLE_EXPIRY_EXIT = 22;
|
|
9
|
+
export interface DecideGrokAuthMergeOptions {
|
|
10
|
+
/** The caller name that prefixes a predicate error, for example
|
|
11
|
+
* `grok auth copy-out`. */
|
|
12
|
+
errorLabel: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Runs the shared decision predicate and returns its exit code (10, 20, 21,
|
|
16
|
+
* or 22). Any other exit, or a failure to run `node`, is a hard failure: this
|
|
17
|
+
* throws so a broken predicate is never mistaken for a "keep destination"
|
|
18
|
+
* decision.
|
|
19
|
+
*/
|
|
20
|
+
export declare function decideGrokAuthMerge(sourcePath: string, destinationPath: string, options: DecideGrokAuthMergeOptions): Promise<number>;
|
|
21
|
+
//# sourceMappingURL=grok-auth-merge-decision.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok-auth-merge-decision.d.ts","sourceRoot":"","sources":["../../src/server/grok-auth-merge-decision.ts"],"names":[],"mappings":"AAqBA,qEAAqE;AACrE,eAAO,MAAM,eAAe,KAAK,CAAC;AAClC,kDAAkD;AAClD,eAAO,MAAM,qBAAqB,KAAK,CAAC;AACxC,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,KAAK,CAAC;AACzC,oFAAoF;AACpF,eAAO,MAAM,uBAAuB,KAAK,CAAC;AAS1C,MAAM,WAAW,0BAA0B;IACzC;gCAC4B;IAC5B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,MAAM,CAAC,CAuBjB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
const execFile = promisify(execFileCallback);
|
|
5
|
+
// The single identity-and-freshness predicate for the Grok credential
|
|
6
|
+
// copy-out lives in `grok-auth-merge-decision.cjs`. It reads only the two
|
|
7
|
+
// files and exits with a code; it never prints token bytes. Argument order
|
|
8
|
+
// sets source and destination (first = source, second = destination), so the
|
|
9
|
+
// caller frames the direction. Exit 10 = use source; exit 20 = keep
|
|
10
|
+
// destination; exit 21 = keep destination, an expiry was present but not a
|
|
11
|
+
// recognized encoding; exit 22 = keep destination, the source expiry sat
|
|
12
|
+
// further ahead of the host clock than the plausible bound. This module
|
|
13
|
+
// gives every caller one shared entry point, so the predicate contract can
|
|
14
|
+
// never drift between callers.
|
|
15
|
+
const DECISION_SCRIPT_PATH = fileURLToPath(new URL("./grok-auth-merge-decision.cjs", import.meta.url));
|
|
16
|
+
/** Exit code: install the source credential over the destination. */
|
|
17
|
+
export const USE_SOURCE_EXIT = 10;
|
|
18
|
+
/** Exit code: keep the destination credential. */
|
|
19
|
+
export const KEEP_DESTINATION_EXIT = 20;
|
|
20
|
+
/** Exit code: keep the destination; an expiry was present but unreadable. */
|
|
21
|
+
export const UNREADABLE_EXPIRY_EXIT = 21;
|
|
22
|
+
/** Exit code: keep the destination; the source expiry was implausibly far ahead. */
|
|
23
|
+
export const IMPLAUSIBLE_EXPIRY_EXIT = 22;
|
|
24
|
+
const KNOWN_EXIT_CODES = new Set([
|
|
25
|
+
USE_SOURCE_EXIT,
|
|
26
|
+
KEEP_DESTINATION_EXIT,
|
|
27
|
+
UNREADABLE_EXPIRY_EXIT,
|
|
28
|
+
IMPLAUSIBLE_EXPIRY_EXIT,
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Runs the shared decision predicate and returns its exit code (10, 20, 21,
|
|
32
|
+
* or 22). Any other exit, or a failure to run `node`, is a hard failure: this
|
|
33
|
+
* throws so a broken predicate is never mistaken for a "keep destination"
|
|
34
|
+
* decision.
|
|
35
|
+
*/
|
|
36
|
+
export async function decideGrokAuthMerge(sourcePath, destinationPath, options) {
|
|
37
|
+
try {
|
|
38
|
+
await execFile("node", [DECISION_SCRIPT_PATH, sourcePath, destinationPath]);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const code = error.code;
|
|
42
|
+
if (typeof code === "number" && KNOWN_EXIT_CODES.has(code)) {
|
|
43
|
+
return code;
|
|
44
|
+
}
|
|
45
|
+
const detail = typeof code === "string"
|
|
46
|
+
? `node could not be executed (${code})`
|
|
47
|
+
: typeof code === "number"
|
|
48
|
+
? `unexpected predicate exit code ${code}`
|
|
49
|
+
: error instanceof Error
|
|
50
|
+
? error.message
|
|
51
|
+
: String(error);
|
|
52
|
+
throw new Error(`${options.errorLabel} decision predicate failed: ${detail}`);
|
|
53
|
+
}
|
|
54
|
+
// `execFile` resolved, so the predicate exited 0. The predicate always
|
|
55
|
+
// exits 10, 20, 21, or 22, so a clean exit 0 is unexpected; fail loud.
|
|
56
|
+
throw new Error(`${options.errorLabel} decision predicate exited 0 (expected 10, 20, 21, or 22)`);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=grok-auth-merge-decision.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok-auth-merge-decision.js","sourceRoot":"","sources":["../../src/server/grok-auth-merge-decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,QAAQ,GAAG,SAAS,CAAC,gBAAgB,CAAC,CAAC;AAE7C,sEAAsE;AACtE,0EAA0E;AAC1E,2EAA2E;AAC3E,6EAA6E;AAC7E,oEAAoE;AACpE,2EAA2E;AAC3E,yEAAyE;AACzE,wEAAwE;AACxE,2EAA2E;AAC3E,+BAA+B;AAE/B,MAAM,oBAAoB,GAAG,aAAa,CACxC,IAAI,GAAG,CAAC,gCAAgC,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAC3D,CAAC;AAEF,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAClC,kDAAkD;AAClD,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AACxC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,oFAAoF;AACpF,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAE1C,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC;IAC/B,eAAe;IACf,qBAAqB;IACrB,sBAAsB;IACtB,uBAAuB;CACxB,CAAC,CAAC;AAQH;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,UAAkB,EAClB,eAAuB,EACvB,OAAmC;IAEnC,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,oBAAoB,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;IAC9E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAI,KAA4B,CAAC,IAAI,CAAC;QAChD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,MAAM,GACV,OAAO,IAAI,KAAK,QAAQ;YACtB,CAAC,CAAC,+BAA+B,IAAI,GAAG;YACxC,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ;gBACxB,CAAC,CAAC,kCAAkC,IAAI,EAAE;gBAC1C,CAAC,CAAC,KAAK,YAAY,KAAK;oBACtB,CAAC,CAAC,KAAK,CAAC,OAAO;oBACf,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,CAAC,UAAU,+BAA+B,MAAM,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,uEAAuE;IACvE,uEAAuE;IACvE,MAAM,IAAI,KAAK,CACb,GAAG,OAAO,CAAC,UAAU,2DAA2D,CACjF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok-auth-merge-decision.test.d.ts","sourceRoot":"","sources":["../../src/server/grok-auth-merge-decision.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { execFile as execFileCallback } from "node:child_process";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
9
|
+
const execFile = promisify(execFileCallback);
|
|
10
|
+
const decisionScriptPath = fileURLToPath(new URL("./grok-auth-merge-decision.cjs", import.meta.url));
|
|
11
|
+
// The exact-boundary cases below drive the predicate's pure `decide` function
|
|
12
|
+
// directly (via `require`, never spawning a process), so the 400-day
|
|
13
|
+
// plausibility bound can be tested to the exact millisecond without racing
|
|
14
|
+
// subprocess-spawn wall-clock drift. Every other case drives the REAL `.cjs`
|
|
15
|
+
// through a spawned `node` process (no stub), matching how the copy-out
|
|
16
|
+
// module invokes it in production.
|
|
17
|
+
const decisionModule = createRequire(import.meta.url)(decisionScriptPath);
|
|
18
|
+
const { decide, USE_SOURCE, KEEP_DESTINATION, UNREADABLE_EXPIRY, IMPLAUSIBLE_EXPIRY, MAX_PLAUSIBLE_EXPIRY_MS } = decisionModule;
|
|
19
|
+
const IDENTITY_A = "https://auth.x.ai::11111111-1111-1111-1111-111111111111";
|
|
20
|
+
const IDENTITY_B = "https://auth.x.ai::22222222-2222-2222-2222-222222222222";
|
|
21
|
+
describe("grok-auth-merge-decision predicate", () => {
|
|
22
|
+
const cleanupDirs = [];
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
while (cleanupDirs.length > 0) {
|
|
25
|
+
const dir = cleanupDirs.pop();
|
|
26
|
+
if (!dir)
|
|
27
|
+
continue;
|
|
28
|
+
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
function usableAuthJson(input) {
|
|
32
|
+
const value = {
|
|
33
|
+
key: `key-${input.marker}`,
|
|
34
|
+
refresh_token: `refresh-${input.marker}`,
|
|
35
|
+
};
|
|
36
|
+
if (input.expiresAt !== undefined)
|
|
37
|
+
value.expires_at = input.expiresAt;
|
|
38
|
+
return JSON.stringify({ [input.identityKey ?? IDENTITY_A]: value });
|
|
39
|
+
}
|
|
40
|
+
const ABSENT = Symbol("absent");
|
|
41
|
+
async function runDecision(input) {
|
|
42
|
+
const dir = await mkdtemp(path.join(os.tmpdir(), "paperclip-grok-merge-decision-"));
|
|
43
|
+
cleanupDirs.push(dir);
|
|
44
|
+
const sourcePath = path.join(dir, "source-auth.json");
|
|
45
|
+
const destinationPath = path.join(dir, "destination-auth.json");
|
|
46
|
+
if (input.sourceAuth !== ABSENT)
|
|
47
|
+
await writeFile(sourcePath, input.sourceAuth, { mode: 0o600 });
|
|
48
|
+
if (input.destinationAuth !== ABSENT)
|
|
49
|
+
await writeFile(destinationPath, input.destinationAuth, { mode: 0o600 });
|
|
50
|
+
try {
|
|
51
|
+
await execFile("node", [decisionScriptPath, sourcePath, destinationPath]);
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const failure = error;
|
|
56
|
+
if (typeof failure.code === "number")
|
|
57
|
+
return failure.code;
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
it("uses the source when the identity matches and the source expiry is later", async () => {
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
const sourceAuth = usableAuthJson({ marker: "src", expiresAt: new Date(now + 2 * 60_000).toISOString() });
|
|
64
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: new Date(now + 60_000).toISOString() });
|
|
65
|
+
expect(await runDecision({ sourceAuth, destinationAuth })).toBe(USE_SOURCE);
|
|
66
|
+
});
|
|
67
|
+
it("keeps the destination when the identity key differs", async () => {
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
const sourceAuth = usableAuthJson({
|
|
70
|
+
identityKey: IDENTITY_A,
|
|
71
|
+
marker: "src",
|
|
72
|
+
expiresAt: new Date(now + 2 * 60_000).toISOString(),
|
|
73
|
+
});
|
|
74
|
+
const destinationAuth = usableAuthJson({
|
|
75
|
+
identityKey: IDENTITY_B,
|
|
76
|
+
marker: "dst",
|
|
77
|
+
expiresAt: new Date(now + 60_000).toISOString(),
|
|
78
|
+
});
|
|
79
|
+
expect(await runDecision({ sourceAuth, destinationAuth })).toBe(KEEP_DESTINATION);
|
|
80
|
+
});
|
|
81
|
+
it("keeps the destination when the source expiry equals the destination expiry", async () => {
|
|
82
|
+
const sameExpiry = new Date(Date.now() + 60_000).toISOString();
|
|
83
|
+
const sourceAuth = usableAuthJson({ marker: "src", expiresAt: sameExpiry });
|
|
84
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: sameExpiry });
|
|
85
|
+
expect(await runDecision({ sourceAuth, destinationAuth })).toBe(KEEP_DESTINATION);
|
|
86
|
+
});
|
|
87
|
+
it("keeps the destination when either side is unusable", async () => {
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
const usable = usableAuthJson({ marker: "ok", expiresAt: new Date(now + 60_000).toISOString() });
|
|
90
|
+
const cases = [
|
|
91
|
+
{ name: "source malformed JSON", sourceAuth: "{not valid json", destinationAuth: usable },
|
|
92
|
+
{
|
|
93
|
+
name: "source missing refresh_token",
|
|
94
|
+
sourceAuth: JSON.stringify({ [IDENTITY_A]: { key: "k" } }),
|
|
95
|
+
destinationAuth: usable,
|
|
96
|
+
},
|
|
97
|
+
{ name: "destination absent", sourceAuth: usable, destinationAuth: ABSENT },
|
|
98
|
+
{ name: "destination malformed JSON", sourceAuth: usable, destinationAuth: "{not valid json" },
|
|
99
|
+
];
|
|
100
|
+
for (const entry of cases) {
|
|
101
|
+
const code = await runDecision({ sourceAuth: entry.sourceAuth, destinationAuth: entry.destinationAuth });
|
|
102
|
+
expect(code, entry.name).toBe(KEEP_DESTINATION);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
it("keeps the destination when the payload holds more than one top-level key", async () => {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: new Date(now + 60_000).toISOString() });
|
|
108
|
+
const multiKeySource = JSON.stringify({
|
|
109
|
+
[IDENTITY_A]: { key: "k", refresh_token: "r", expires_at: new Date(now + 120_000).toISOString() },
|
|
110
|
+
extra: { key: "k2", refresh_token: "r2" },
|
|
111
|
+
});
|
|
112
|
+
expect(await runDecision({ sourceAuth: multiKeySource, destinationAuth })).toBe(KEEP_DESTINATION);
|
|
113
|
+
});
|
|
114
|
+
it("reads an ISO-8601 expiry, an epoch-seconds expiry, and an epoch-milliseconds expiry", async () => {
|
|
115
|
+
const now = Date.now();
|
|
116
|
+
const encodings = [
|
|
117
|
+
{
|
|
118
|
+
name: "ISO-8601",
|
|
119
|
+
source: new Date(now + 2 * 60_000).toISOString(),
|
|
120
|
+
destination: new Date(now + 60_000).toISOString(),
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "epoch seconds",
|
|
124
|
+
source: Math.round((now + 2 * 60_000) / 1000),
|
|
125
|
+
destination: Math.round((now + 60_000) / 1000),
|
|
126
|
+
},
|
|
127
|
+
{ name: "epoch milliseconds", source: now + 2 * 60_000, destination: now + 60_000 },
|
|
128
|
+
];
|
|
129
|
+
for (const entry of encodings) {
|
|
130
|
+
const sourceAuth = usableAuthJson({ marker: "src", expiresAt: entry.source });
|
|
131
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: entry.destination });
|
|
132
|
+
const code = await runDecision({ sourceAuth, destinationAuth });
|
|
133
|
+
expect(code, entry.name).toBe(USE_SOURCE);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
it("exits 21 and keeps the destination for an unreadable expiry shape", async () => {
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: new Date(now + 60_000).toISOString() });
|
|
139
|
+
const badShapes = [true, "not-a-date", "2026/01/01 00:00:00", {}, []];
|
|
140
|
+
for (const badExpiry of badShapes) {
|
|
141
|
+
const sourceAuth = usableAuthJson({ marker: "src", expiresAt: badExpiry });
|
|
142
|
+
const code = await runDecision({ sourceAuth, destinationAuth });
|
|
143
|
+
expect(code, JSON.stringify(badExpiry)).toBe(UNREADABLE_EXPIRY);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
it("uses the source for a source expiry exactly at the 400-day bound", () => {
|
|
147
|
+
const nowMs = Date.now();
|
|
148
|
+
const source = { kind: "usable", identityKey: IDENTITY_A, expiresAtRaw: nowMs + MAX_PLAUSIBLE_EXPIRY_MS };
|
|
149
|
+
const destination = { kind: "usable", identityKey: IDENTITY_A, expiresAtRaw: nowMs };
|
|
150
|
+
expect(decide(source, destination, nowMs)).toBe(USE_SOURCE);
|
|
151
|
+
});
|
|
152
|
+
it("exits 22 and keeps the destination for a source expiry one millisecond beyond the 400-day bound", () => {
|
|
153
|
+
const nowMs = Date.now();
|
|
154
|
+
const source = {
|
|
155
|
+
kind: "usable",
|
|
156
|
+
identityKey: IDENTITY_A,
|
|
157
|
+
expiresAtRaw: nowMs + MAX_PLAUSIBLE_EXPIRY_MS + 1,
|
|
158
|
+
};
|
|
159
|
+
const destination = { kind: "usable", identityKey: IDENTITY_A, expiresAtRaw: nowMs };
|
|
160
|
+
expect(decide(source, destination, nowMs)).toBe(IMPLAUSIBLE_EXPIRY);
|
|
161
|
+
});
|
|
162
|
+
it("measures the 400-day bound against the host clock and not against a sandbox-supplied time", () => {
|
|
163
|
+
// Fixed, unambiguous epoch-millisecond instants (both well above the
|
|
164
|
+
// epoch-seconds/epoch-milliseconds threshold). Neither parsed side
|
|
165
|
+
// changes between the two assertions below — only the caller-supplied
|
|
166
|
+
// `nowMs` moves — so a passing/failing bound can only be explained by the
|
|
167
|
+
// caller's clock, never by a value embedded in either payload.
|
|
168
|
+
const sourceExpiryMs = 2_000_000_000_000;
|
|
169
|
+
const destinationExpiryMs = 1_000_000_000_000;
|
|
170
|
+
const source = { kind: "usable", identityKey: IDENTITY_A, expiresAtRaw: sourceExpiryMs };
|
|
171
|
+
const destination = { kind: "usable", identityKey: IDENTITY_A, expiresAtRaw: destinationExpiryMs };
|
|
172
|
+
expect(decide(source, destination, sourceExpiryMs - MAX_PLAUSIBLE_EXPIRY_MS)).toBe(USE_SOURCE);
|
|
173
|
+
expect(decide(source, destination, sourceExpiryMs - MAX_PLAUSIBLE_EXPIRY_MS - 1)).toBe(IMPLAUSIBLE_EXPIRY);
|
|
174
|
+
});
|
|
175
|
+
it("keeps the destination for a forged source that copies the destination identity and a far-future expiry", async () => {
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const destinationAuth = usableAuthJson({ marker: "dst", expiresAt: new Date(now + 60_000).toISOString() });
|
|
178
|
+
// 500 days ahead is comfortably beyond both the 400-day plausibility
|
|
179
|
+
// bound and any subprocess scheduling delay, so this integration-level
|
|
180
|
+
// check never depends on millisecond timing.
|
|
181
|
+
const forgedSourceAuth = usableAuthJson({
|
|
182
|
+
marker: "forged",
|
|
183
|
+
expiresAt: new Date(now + 500 * 24 * 60 * 60 * 1000).toISOString(),
|
|
184
|
+
});
|
|
185
|
+
const code = await runDecision({ sourceAuth: forgedSourceAuth, destinationAuth });
|
|
186
|
+
expect(code).toBe(IMPLAUSIBLE_EXPIRY);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
//# sourceMappingURL=grok-auth-merge-decision.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok-auth-merge-decision.test.js","sourceRoot":"","sources":["../../src/server/grok-auth-merge-decision.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEzD,MAAM,QAAQ,GAAG,SAAS,CAAC,gBAAgB,CAAC,CAAC;AAE7C,MAAM,kBAAkB,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,gCAAgC,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErG,8EAA8E;AAC9E,qEAAqE;AACrE,2EAA2E;AAC3E,6EAA6E;AAC7E,wEAAwE;AACxE,mCAAmC;AACnC,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAWvE,CAAC;AACF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,GAC5G,cAAc,CAAC;AAEjB,MAAM,UAAU,GAAG,yDAAyD,CAAC;AAC7E,MAAM,UAAU,GAAG,yDAAyD,CAAC;AAE7E,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,MAAM,WAAW,GAAa,EAAE,CAAC;IAEjC,SAAS,CAAC,KAAK,IAAI,EAAE;QACnB,OAAO,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,SAAS,cAAc,CAAC,KAAoE;QAC1F,MAAM,KAAK,GAA4B;YACrC,GAAG,EAAE,OAAO,KAAK,CAAC,MAAM,EAAE;YAC1B,aAAa,EAAE,WAAW,KAAK,CAAC,MAAM,EAAE;SACzC,CAAC;QACF,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;YAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC;QACtE,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,IAAI,UAAU,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEhC,KAAK,UAAU,WAAW,CAAC,KAG1B;QACC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,gCAAgC,CAAC,CAAC,CAAC;QACpF,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACtB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACtD,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;QAChE,IAAI,KAAK,CAAC,UAAU,KAAK,MAAM;YAAE,MAAM,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAChG,IAAI,KAAK,CAAC,eAAe,KAAK,MAAM;YAAE,MAAM,SAAS,CAAC,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC/G,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,kBAAkB,EAAE,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;YAC1E,OAAO,CAAC,CAAC;QACX,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAA2B,CAAC;YAC5C,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,OAAO,CAAC,IAAI,CAAC;YAC1D,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC1G,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC3G,MAAM,CAAC,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,UAAU,GAAG,cAAc,CAAC;YAChC,WAAW,EAAE,UAAU;YACvB,MAAM,EAAE,KAAK;YACb,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;SACpD,CAAC,CAAC;QACH,MAAM,eAAe,GAAG,cAAc,CAAC;YACrC,WAAW,EAAE,UAAU;YACvB,MAAM,EAAE,KAAK;YACb,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;SAChD,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4EAA4E,EAAE,KAAK,IAAI,EAAE;QAC1F,MAAM,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC/D,MAAM,UAAU,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QAC5E,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;QACjF,MAAM,CAAC,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACjG,MAAM,KAAK,GAAoG;YAC7G,EAAE,IAAI,EAAE,uBAAuB,EAAE,UAAU,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,EAAE;YACzF;gBACE,IAAI,EAAE,8BAA8B;gBACpC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC;gBAC1D,eAAe,EAAE,MAAM;aACxB;YACD,EAAE,IAAI,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE;YAC3E,EAAE,IAAI,EAAE,4BAA4B,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE,iBAAiB,EAAE;SAC/F,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;YACzG,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC3G,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC;YACpC,CAAC,UAAU,CAAC,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,CAAC,WAAW,EAAE,EAAE;YACjG,KAAK,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qFAAqF,EAAE,KAAK,IAAI,EAAE;QACnG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,SAAS,GAA8D;YAC3E;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;gBAChD,WAAW,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE;aAClD;YACD;gBACE,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;gBAC7C,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,IAAI,CAAC;aAC/C;YACD,EAAE,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,MAAM,EAAE;SACpF,CAAC;QACF,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9E,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC;YACxF,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC3G,MAAM,SAAS,GAAc,CAAC,IAAI,EAAE,YAAY,EAAE,qBAAqB,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;QACjF,KAAK,MAAM,SAAS,IAAI,SAAS,EAAE,CAAC;YAClC,MAAM,UAAU,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;YAC3E,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,eAAe,EAAE,CAAC,CAAC;YAChE,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAClE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,GAAG,uBAAuB,EAAE,CAAC;QACnH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;QAC9F,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iGAAiG,EAAE,GAAG,EAAE;QACzG,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG;YACb,IAAI,EAAE,QAAiB;YACvB,WAAW,EAAE,UAAU;YACvB,YAAY,EAAE,KAAK,GAAG,uBAAuB,GAAG,CAAC;SAClD,CAAC;QACF,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;QAC9F,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACtE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2FAA2F,EAAE,GAAG,EAAE;QACnG,qEAAqE;QACrE,mEAAmE;QACnE,sEAAsE;QACtE,0EAA0E;QAC1E,+DAA+D;QAC/D,MAAM,cAAc,GAAG,iBAAiB,CAAC;QACzC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;QAC9C,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;QAClG,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,EAAE,CAAC;QAE5G,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,GAAG,uBAAuB,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC/F,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,GAAG,uBAAuB,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC7G,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wGAAwG,EAAE,KAAK,IAAI,EAAE;QACtH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,eAAe,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QAC3G,qEAAqE;QACrE,uEAAuE;QACvE,6CAA6C;QAC7C,MAAM,gBAAgB,GAAG,cAAc,CAAC;YACtC,MAAM,EAAE,QAAQ;YAChB,SAAS,EAAE,IAAI,IAAI,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;SACnE,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC,CAAC;QAClF,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The allowlist of managed `GROK_HOME` entries that the grok-local adapter
|
|
3
|
+
* stages into the sandbox `home` asset (see {@link stageGrokHomeForSync}).
|
|
4
|
+
* Paperclip writes instructions and skills under the workspace, not under the
|
|
5
|
+
* Grok home, so the credential file is the only entry a sandbox run needs.
|
|
6
|
+
*/
|
|
7
|
+
export declare const GROK_SYNC_ALLOWLIST: readonly ["auth.json"];
|
|
8
|
+
/** One parsed Grok auth payload: the composite identity key and its value object. */
|
|
9
|
+
export interface GrokAuthPayload {
|
|
10
|
+
identityKey: string;
|
|
11
|
+
value: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parses a decoded JSON value into a {@link GrokAuthPayload}. Returns null when
|
|
15
|
+
* the value is not an object, holds zero or more than one top-level key, or the
|
|
16
|
+
* single key does not match the `<issuer>::<uuid>` shape, or its value is not an
|
|
17
|
+
* object. Never assumes a fixed key name.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseGrokAuthPayload(raw: unknown): GrokAuthPayload | null;
|
|
20
|
+
/** True when the payload value holds the two fields a run needs to authenticate. */
|
|
21
|
+
export declare function hasUsableGrokAuthValue(value: Record<string, unknown>): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* True when `home` has a usable `auth.json`: a single `<issuer>::<uuid>`
|
|
24
|
+
* top-level key whose value holds a non-empty `key` and `refresh_token`. A
|
|
25
|
+
* missing file, invalid JSON, or an unusable shape all resolve false.
|
|
26
|
+
*/
|
|
27
|
+
export declare function grokHomeHasUsableAuth(home: string): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Resolves the managed Grok home directory. With a `companyId`, it resolves the
|
|
30
|
+
* company-scoped home under the Paperclip instance tree, the same isolation
|
|
31
|
+
* boundary `resolveManagedCodexHomeDir` uses. Without one, it resolves the
|
|
32
|
+
* instance-global home, which a promotion must never write.
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveManagedGrokHomeDir(env: NodeJS.ProcessEnv, companyId?: string): string;
|
|
35
|
+
export interface StageGrokHomeForSyncOptions {
|
|
36
|
+
/** Run id, used only to make the staged temp-dir name traceable in logs. */
|
|
37
|
+
runId?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Stages exactly {@link GROK_SYNC_ALLOWLIST} from `effectiveGrokHome` into a
|
|
41
|
+
* fresh private temp dir and returns its path, for registration as the sandbox
|
|
42
|
+
* `home` asset. Mirrors `stageCodexHomeForSync` in the codex-local adapter.
|
|
43
|
+
*
|
|
44
|
+
* - **The symlink is dereferenced to bytes** — the single-use `auth.json`
|
|
45
|
+
* credential (a symlink into the shared source home) lands as a real file,
|
|
46
|
+
* never a dangling link.
|
|
47
|
+
* - **A missing `auth.json` is not an error** — a company with no completed
|
|
48
|
+
* device login yet stages an empty directory.
|
|
49
|
+
* - **`mkdtemp` guarantees the staged dir is `0700`** on POSIX, and the staged
|
|
50
|
+
* `auth.json` is written `0600` (least privilege).
|
|
51
|
+
* - **Fail-closed** — any *unexpected* I/O error removes the partial temp dir
|
|
52
|
+
* and re-throws, so a run never proceeds with a partial staged home.
|
|
53
|
+
*
|
|
54
|
+
* The caller owns removing the returned dir on run teardown.
|
|
55
|
+
*/
|
|
56
|
+
export declare function stageGrokHomeForSync(effectiveGrokHome: string, options?: StageGrokHomeForSyncOptions): Promise<string>;
|
|
57
|
+
//# sourceMappingURL=grok-home.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grok-home.d.ts","sourceRoot":"","sources":["../../src/server/grok-home.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,YAAI,WAAW,CAAU,CAAC;AAc1D,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CASzE;AAED,oFAAoF;AACpF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAS9E;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAS1E;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CASR;AAED,MAAM,WAAW,2BAA2B;IAC1C,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,EAAE,MAAM,EACzB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,MAAM,CAAC,CAqBjB"}
|