litmus-cli 1.4.21 → 1.4.24
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 +16 -0
- package/dist/commands/connect.d.ts +231 -2
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +311 -12
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +5 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/pause.d.ts +36 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +110 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/push.d.ts +7 -1
- package/dist/commands/push.d.ts.map +1 -1
- package/dist/commands/push.js +40 -1
- package/dist/commands/push.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +6 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +48 -2
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.js +22 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/api.d.ts +27 -0
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +45 -0
- package/dist/lib/api.js.map +1 -1
- package/dist/lib/config.d.ts +21 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +53 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/git-bundle.d.ts +19 -0
- package/dist/lib/git-bundle.d.ts.map +1 -0
- package/dist/lib/git-bundle.js +71 -0
- package/dist/lib/git-bundle.js.map +1 -0
- package/dist/lib/session-end.d.ts +111 -0
- package/dist/lib/session-end.d.ts.map +1 -0
- package/dist/lib/session-end.js +126 -0
- package/dist/lib/session-end.js.map +1 -0
- package/dist/lib/submit-route.d.ts +68 -0
- package/dist/lib/submit-route.d.ts.map +1 -0
- package/dist/lib/submit-route.js +73 -0
- package/dist/lib/submit-route.js.map +1 -0
- package/dist/lib/watcher.js +23 -2
- package/dist/lib/watcher.js.map +1 -1
- package/dist/lib/workspace-token.d.ts +69 -0
- package/dist/lib/workspace-token.d.ts.map +1 -0
- package/dist/lib/workspace-token.js +93 -0
- package/dist/lib/workspace-token.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What happens to a NATIVE editor window when the session ends — ENG-2154.
|
|
3
|
+
*
|
|
4
|
+
* ## The decision, which this ticket was asked to make explicitly
|
|
5
|
+
*
|
|
6
|
+
* **We do not close the candidate's editor window. We tell them what the window
|
|
7
|
+
* is doing instead.** Elena asked for the opposite ("when I finish submitting
|
|
8
|
+
* can you exit out of the ID editor, because the file tree is still there even
|
|
9
|
+
* though I'm disconnecting the codebase"), so the reasons are worth stating
|
|
10
|
+
* rather than leaving in a PR description.
|
|
11
|
+
*
|
|
12
|
+
* MEASURED FIRST, on a real VS Code 1.136 / Remote-SSH 0.128.0 window connected
|
|
13
|
+
* over ssh into a container whose transport was then killed. The window does not
|
|
14
|
+
* go quiet: the title bar and the Explorer root still read `[SSH: <host>]`, the
|
|
15
|
+
* status bar flips to `Disconnected from SSH: <host>`, and the resolver raises a
|
|
16
|
+
* modal reporting that it could not establish the connection and offering to retry.
|
|
17
|
+
* So the state a candidate is left in is not a silent stale file tree — it is an
|
|
18
|
+
* ERROR DIALOG, arriving seconds after they pressed submit. That is the ENG-2066
|
|
19
|
+
* misreading pointed at a different surface: a workspace that stops working with
|
|
20
|
+
* no explanation reads as a FAILED SUBMISSION. Disambiguating it is the whole
|
|
21
|
+
* job; making the window vanish is not.
|
|
22
|
+
*
|
|
23
|
+
* And closing it is not reachable anyway, for three independent reasons:
|
|
24
|
+
*
|
|
25
|
+
* 1. **Nothing of ours is running on the laptop at submit time.** `litmus
|
|
26
|
+
* connect` spawns the editor detached and returns; a v2 candidate then
|
|
27
|
+
* submits from the container (`litmus push` in the editor's own remote
|
|
28
|
+
* terminal) or from the assessment page. Closing the window would mean
|
|
29
|
+
* leaving a background daemon on a candidate's machine for the life of the
|
|
30
|
+
* assessment, purely to close a window.
|
|
31
|
+
* 2. **We could not honour "only the window we opened."** `code --remote` hands
|
|
32
|
+
* the request to an already-running Electron app and exits; the window is
|
|
33
|
+
* not a process we own. Every close mechanism available (osascript UI
|
|
34
|
+
* scripting, PowerShell `CloseMainWindow`, wmctrl) picks its target by
|
|
35
|
+
* window title, which is a guess that can land on a window the candidate
|
|
36
|
+
* opened themselves. The ticket forbids exactly that.
|
|
37
|
+
* 3. **macOS gates it behind a permission the candidate has not granted.** UI
|
|
38
|
+
* scripting needs an Automation/Accessibility grant; without it the attempt
|
|
39
|
+
* is refused (`-1743`), and the first attempt in a fresh grant state blocks
|
|
40
|
+
* on a system dialog — which is what ENG-1502 measured when it made its own
|
|
41
|
+
* close best-effort and detached.
|
|
42
|
+
*
|
|
43
|
+
* A window that vanishes while somebody is reading their own code is worse than
|
|
44
|
+
* one that says what happened, and (1)-(3) mean we could only ever have vanished
|
|
45
|
+
* it unreliably. So: say it, and leave the window to them.
|
|
46
|
+
*
|
|
47
|
+
* ## Why the copy lives in a module
|
|
48
|
+
*
|
|
49
|
+
* The same answer is needed at two moments, in two commands, and a second copy
|
|
50
|
+
* of it would rot — the argument `submit-guidance.ts` and the candidate page's
|
|
51
|
+
* `workspace-guidance.ts` already make:
|
|
52
|
+
*
|
|
53
|
+
* - `litmus connect` says it AHEAD of time, because it is the only laptop-side
|
|
54
|
+
* moment we have, and it is the only thing that reaches a candidate whose
|
|
55
|
+
* session ends some other way (they submit from the assessment page, the
|
|
56
|
+
* company ends the trial, the deadline cron fires).
|
|
57
|
+
* - `litmus push` says it AT the moment, in the terminal that is very often
|
|
58
|
+
* the disconnecting window's own.
|
|
59
|
+
*
|
|
60
|
+
* ## What the copy may claim
|
|
61
|
+
*
|
|
62
|
+
* NO EDITOR-SPECIFIC CONTROL IS NAMED. The dialog's buttons were measured on VS
|
|
63
|
+
* Code only; Cursor resolves `ssh-remote+` through `anysphere.remote-ssh`, a
|
|
64
|
+
* different extension whose dialog we have not measured, and naming a button a
|
|
65
|
+
* candidate cannot find costs us the trust they need in the rest of the sentence
|
|
66
|
+
* (the same line `workspace-guidance.ts` draws over its tool list). What is true
|
|
67
|
+
* of both is the shape: the window stays, it reports a lost connection, and
|
|
68
|
+
* closing it is theirs to do. That bound is tighter than it first looks and
|
|
69
|
+
* Greptile caught the first draft crossing it — describing WHAT THE EDITOR WILL
|
|
70
|
+
* ASK ("and offers to try again") is as unmeasured on Cursor as naming the
|
|
71
|
+
* button would be, and a candidate told to expect a prompt that never appears
|
|
72
|
+
* is left doubting the sentence that was meant to reassure them. Only the fact
|
|
73
|
+
* of a reported disconnection is verified on both.
|
|
74
|
+
*
|
|
75
|
+
* It also may not claim the assessment is FINISHED. A submit can still owe a
|
|
76
|
+
* post-submit walkthrough, and `runPush` prints the next step right after this;
|
|
77
|
+
* the claim here is only about the workspace and the window.
|
|
78
|
+
*/
|
|
79
|
+
/**
|
|
80
|
+
* The state the candidate's native editor window is now in, said once.
|
|
81
|
+
*
|
|
82
|
+
* Printed after a submit that ENDED the session — a fresh capture, an
|
|
83
|
+
* `already_submitted`, or a confirmed empty submission. Never after a refusal, a
|
|
84
|
+
* cancellation or a failure: in all three the workspace is still live and the
|
|
85
|
+
* window is still connected, so this would be a lie about both.
|
|
86
|
+
*
|
|
87
|
+
* Written as a conditional ("if ... is open") because `litmus push` runs from
|
|
88
|
+
* four places — a native remote window's terminal, the browser IDE's terminal, a
|
|
89
|
+
* plain ssh session, and a laptop with an explicit token — and cannot tell them
|
|
90
|
+
* apart. The browser IDE's own tab is deliberately not mentioned: ENG-2066 ends
|
|
91
|
+
* that one by navigating it, so describing it here would contradict what that
|
|
92
|
+
* candidate is about to see.
|
|
93
|
+
*/
|
|
94
|
+
export declare function editorEndingLines(): string[];
|
|
95
|
+
/**
|
|
96
|
+
* The same fact, said before it happens, on the native-editor path of
|
|
97
|
+
* `litmus connect`.
|
|
98
|
+
*
|
|
99
|
+
* Two lines, and deliberately no more. This is the ONLY route that reaches a
|
|
100
|
+
* candidate who ends their session from somewhere we do not run — the assessment
|
|
101
|
+
* page's Submit, End trial, the deadline cron — and for them it is the whole
|
|
102
|
+
* warning. But it is also printed at the top of a session, minutes or days
|
|
103
|
+
* before it is true, next to a Cursor notice that is already long; anything more
|
|
104
|
+
* than the fact and what to do with it is noise at the wrong moment.
|
|
105
|
+
*
|
|
106
|
+
* @param editorLabel what the candidate calls the editor being opened, from
|
|
107
|
+
* `NATIVE_EDITORS` — this one CAN be named, because `connect` knows which one
|
|
108
|
+
* it is launching. The dialog's controls still cannot be.
|
|
109
|
+
*/
|
|
110
|
+
export declare function editorEndingForewarning(editorLabel: string): string[];
|
|
111
|
+
//# sourceMappingURL=session-end.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-end.d.ts","sourceRoot":"","sources":["../../src/lib/session-end.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAS5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAKrE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/**
|
|
3
|
+
* What happens to a NATIVE editor window when the session ends — ENG-2154.
|
|
4
|
+
*
|
|
5
|
+
* ## The decision, which this ticket was asked to make explicitly
|
|
6
|
+
*
|
|
7
|
+
* **We do not close the candidate's editor window. We tell them what the window
|
|
8
|
+
* is doing instead.** Elena asked for the opposite ("when I finish submitting
|
|
9
|
+
* can you exit out of the ID editor, because the file tree is still there even
|
|
10
|
+
* though I'm disconnecting the codebase"), so the reasons are worth stating
|
|
11
|
+
* rather than leaving in a PR description.
|
|
12
|
+
*
|
|
13
|
+
* MEASURED FIRST, on a real VS Code 1.136 / Remote-SSH 0.128.0 window connected
|
|
14
|
+
* over ssh into a container whose transport was then killed. The window does not
|
|
15
|
+
* go quiet: the title bar and the Explorer root still read `[SSH: <host>]`, the
|
|
16
|
+
* status bar flips to `Disconnected from SSH: <host>`, and the resolver raises a
|
|
17
|
+
* modal reporting that it could not establish the connection and offering to retry.
|
|
18
|
+
* So the state a candidate is left in is not a silent stale file tree — it is an
|
|
19
|
+
* ERROR DIALOG, arriving seconds after they pressed submit. That is the ENG-2066
|
|
20
|
+
* misreading pointed at a different surface: a workspace that stops working with
|
|
21
|
+
* no explanation reads as a FAILED SUBMISSION. Disambiguating it is the whole
|
|
22
|
+
* job; making the window vanish is not.
|
|
23
|
+
*
|
|
24
|
+
* And closing it is not reachable anyway, for three independent reasons:
|
|
25
|
+
*
|
|
26
|
+
* 1. **Nothing of ours is running on the laptop at submit time.** `litmus
|
|
27
|
+
* connect` spawns the editor detached and returns; a v2 candidate then
|
|
28
|
+
* submits from the container (`litmus push` in the editor's own remote
|
|
29
|
+
* terminal) or from the assessment page. Closing the window would mean
|
|
30
|
+
* leaving a background daemon on a candidate's machine for the life of the
|
|
31
|
+
* assessment, purely to close a window.
|
|
32
|
+
* 2. **We could not honour "only the window we opened."** `code --remote` hands
|
|
33
|
+
* the request to an already-running Electron app and exits; the window is
|
|
34
|
+
* not a process we own. Every close mechanism available (osascript UI
|
|
35
|
+
* scripting, PowerShell `CloseMainWindow`, wmctrl) picks its target by
|
|
36
|
+
* window title, which is a guess that can land on a window the candidate
|
|
37
|
+
* opened themselves. The ticket forbids exactly that.
|
|
38
|
+
* 3. **macOS gates it behind a permission the candidate has not granted.** UI
|
|
39
|
+
* scripting needs an Automation/Accessibility grant; without it the attempt
|
|
40
|
+
* is refused (`-1743`), and the first attempt in a fresh grant state blocks
|
|
41
|
+
* on a system dialog — which is what ENG-1502 measured when it made its own
|
|
42
|
+
* close best-effort and detached.
|
|
43
|
+
*
|
|
44
|
+
* A window that vanishes while somebody is reading their own code is worse than
|
|
45
|
+
* one that says what happened, and (1)-(3) mean we could only ever have vanished
|
|
46
|
+
* it unreliably. So: say it, and leave the window to them.
|
|
47
|
+
*
|
|
48
|
+
* ## Why the copy lives in a module
|
|
49
|
+
*
|
|
50
|
+
* The same answer is needed at two moments, in two commands, and a second copy
|
|
51
|
+
* of it would rot — the argument `submit-guidance.ts` and the candidate page's
|
|
52
|
+
* `workspace-guidance.ts` already make:
|
|
53
|
+
*
|
|
54
|
+
* - `litmus connect` says it AHEAD of time, because it is the only laptop-side
|
|
55
|
+
* moment we have, and it is the only thing that reaches a candidate whose
|
|
56
|
+
* session ends some other way (they submit from the assessment page, the
|
|
57
|
+
* company ends the trial, the deadline cron fires).
|
|
58
|
+
* - `litmus push` says it AT the moment, in the terminal that is very often
|
|
59
|
+
* the disconnecting window's own.
|
|
60
|
+
*
|
|
61
|
+
* ## What the copy may claim
|
|
62
|
+
*
|
|
63
|
+
* NO EDITOR-SPECIFIC CONTROL IS NAMED. The dialog's buttons were measured on VS
|
|
64
|
+
* Code only; Cursor resolves `ssh-remote+` through `anysphere.remote-ssh`, a
|
|
65
|
+
* different extension whose dialog we have not measured, and naming a button a
|
|
66
|
+
* candidate cannot find costs us the trust they need in the rest of the sentence
|
|
67
|
+
* (the same line `workspace-guidance.ts` draws over its tool list). What is true
|
|
68
|
+
* of both is the shape: the window stays, it reports a lost connection, and
|
|
69
|
+
* closing it is theirs to do. That bound is tighter than it first looks and
|
|
70
|
+
* Greptile caught the first draft crossing it — describing WHAT THE EDITOR WILL
|
|
71
|
+
* ASK ("and offers to try again") is as unmeasured on Cursor as naming the
|
|
72
|
+
* button would be, and a candidate told to expect a prompt that never appears
|
|
73
|
+
* is left doubting the sentence that was meant to reassure them. Only the fact
|
|
74
|
+
* of a reported disconnection is verified on both.
|
|
75
|
+
*
|
|
76
|
+
* It also may not claim the assessment is FINISHED. A submit can still owe a
|
|
77
|
+
* post-submit walkthrough, and `runPush` prints the next step right after this;
|
|
78
|
+
* the claim here is only about the workspace and the window.
|
|
79
|
+
*/
|
|
80
|
+
/**
|
|
81
|
+
* The state the candidate's native editor window is now in, said once.
|
|
82
|
+
*
|
|
83
|
+
* Printed after a submit that ENDED the session — a fresh capture, an
|
|
84
|
+
* `already_submitted`, or a confirmed empty submission. Never after a refusal, a
|
|
85
|
+
* cancellation or a failure: in all three the workspace is still live and the
|
|
86
|
+
* window is still connected, so this would be a lie about both.
|
|
87
|
+
*
|
|
88
|
+
* Written as a conditional ("if ... is open") because `litmus push` runs from
|
|
89
|
+
* four places — a native remote window's terminal, the browser IDE's terminal, a
|
|
90
|
+
* plain ssh session, and a laptop with an explicit token — and cannot tell them
|
|
91
|
+
* apart. The browser IDE's own tab is deliberately not mentioned: ENG-2066 ends
|
|
92
|
+
* that one by navigating it, so describing it here would contradict what that
|
|
93
|
+
* candidate is about to see.
|
|
94
|
+
*/
|
|
95
|
+
export function editorEndingLines() {
|
|
96
|
+
return [
|
|
97
|
+
"",
|
|
98
|
+
" If VS Code or Cursor is open on this workspace, that window stays open on your",
|
|
99
|
+
" machine and will report a lost connection. That is not a failed submission — it is",
|
|
100
|
+
" this session ending, and nothing is left to save.",
|
|
101
|
+
` ${chalk.dim("Close the window whenever you are ready; we do not close it for you.")}`,
|
|
102
|
+
"",
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The same fact, said before it happens, on the native-editor path of
|
|
107
|
+
* `litmus connect`.
|
|
108
|
+
*
|
|
109
|
+
* Two lines, and deliberately no more. This is the ONLY route that reaches a
|
|
110
|
+
* candidate who ends their session from somewhere we do not run — the assessment
|
|
111
|
+
* page's Submit, End trial, the deadline cron — and for them it is the whole
|
|
112
|
+
* warning. But it is also printed at the top of a session, minutes or days
|
|
113
|
+
* before it is true, next to a Cursor notice that is already long; anything more
|
|
114
|
+
* than the fact and what to do with it is noise at the wrong moment.
|
|
115
|
+
*
|
|
116
|
+
* @param editorLabel what the candidate calls the editor being opened, from
|
|
117
|
+
* `NATIVE_EDITORS` — this one CAN be named, because `connect` knows which one
|
|
118
|
+
* it is launching. The dialog's controls still cannot be.
|
|
119
|
+
*/
|
|
120
|
+
export function editorEndingForewarning(editorLabel) {
|
|
121
|
+
return [
|
|
122
|
+
chalk.dim(` When your assessment ends, this ${editorLabel} window loses its connection and`),
|
|
123
|
+
chalk.dim(" reports an error. That is the session ending, not a failure — close it then."),
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=session-end.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-end.js","sourceRoot":"","sources":["../../src/lib/session-end.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAEH;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,EAAE;QACF,kFAAkF;QAClF,sFAAsF;QACtF,qDAAqD;QACrD,KAAK,KAAK,CAAC,GAAG,CAAC,sEAAsE,CAAC,EAAE;QACxF,EAAE;KACH,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,uBAAuB,CAAC,WAAmB;IACzD,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,qCAAqC,WAAW,kCAAkC,CAAC;QAC7F,KAAK,CAAC,GAAG,CAAC,gFAAgF,CAAC;KAC5F,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command actually hands work in, for the install this CLI is running from
|
|
3
|
+
* (ENG-2130).
|
|
4
|
+
*
|
|
5
|
+
* There are two flows and each has exactly one submit command:
|
|
6
|
+
*
|
|
7
|
+
* * ON A LAPTOP (the CLI flow) it is `litmus submit`, which zips the local
|
|
8
|
+
* checkout and uploads it to `/cli/submit`.
|
|
9
|
+
* * INSIDE A CODESPACES v2 WORKSPACE it is `litmus push`, which POSTs
|
|
10
|
+
* `/workspace/submit` — the same endpoint the assessment page's Submit
|
|
11
|
+
* button calls, capturing the live container filesystem host-side (ENG-621).
|
|
12
|
+
* There is nothing local to zip.
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS MODULE EXISTS RATHER THAN A CHECK PER SITE. Before ENG-2064 the CLI
|
|
15
|
+
* was not in the container at all, so `litmus submit` there was `command not
|
|
16
|
+
* found` — useless but unambiguous. That ticket put the CLI on the container's
|
|
17
|
+
* PATH and wired the injected `LITMUS_ASSESSMENT_TOKEN` into `push` alone, so
|
|
18
|
+
* `submit` became present, runnable, and unable to authenticate: it reads its
|
|
19
|
+
* bearer from `.litmus/config.json`, and `hostd._stage_litmus_config` writes
|
|
20
|
+
* that file with DELIBERATELY no `token` because it rides the submission zip and
|
|
21
|
+
* the R2 durability tar, where a live credential must never sit. The upload
|
|
22
|
+
* therefore went out as a literal `Authorization: Bearer undefined` and was
|
|
23
|
+
* refused — AFTER the archive had been built, so a candidate read it as an
|
|
24
|
+
* upload failure rather than as the wrong command. Every surface that names a
|
|
25
|
+
* submit command was naming the laptop one to both audiences, so the answer is
|
|
26
|
+
* derived in one place and the sites ask.
|
|
27
|
+
*
|
|
28
|
+
* NOT FIXED BY GIVING `submit` THE ENV FALLBACK `push` HAS. That would make the
|
|
29
|
+
* command "work", but it routes a v2 candidate down `/cli/submit`, which grades
|
|
30
|
+
* an uploaded zip, while v2's canonical submit grades the live filesystem — the
|
|
31
|
+
* two flows would then disagree about what a submission is. That is a product
|
|
32
|
+
* decision, not a patch. And it must NOT be fixed by writing the token into
|
|
33
|
+
* `.litmus/config.json`, for the reason that file has none today.
|
|
34
|
+
*
|
|
35
|
+
* The predicate is `isWorkspaceInstall`, read from our own module path for the
|
|
36
|
+
* reasons its own docstring gives (an env marker is absent in an sshd session,
|
|
37
|
+
* and a marker injected alongside the token disappears in exactly the case we
|
|
38
|
+
* need to describe). Nothing security-relevant hangs off any of this: it only
|
|
39
|
+
* decides which command a candidate is told to run.
|
|
40
|
+
*/
|
|
41
|
+
/** The submit command inside a Codespaces v2 workspace. */
|
|
42
|
+
export declare const WORKSPACE_SUBMIT_COMMAND = "litmus push";
|
|
43
|
+
/** The submit command on the candidate's own machine. */
|
|
44
|
+
export declare const LOCAL_SUBMIT_COMMAND = "litmus submit";
|
|
45
|
+
/**
|
|
46
|
+
* The command that submits, for the install `moduleUrl` belongs to.
|
|
47
|
+
*
|
|
48
|
+
* Callers pass their own `import.meta.url`. Use this anywhere candidate-facing
|
|
49
|
+
* copy names the submit command, so a workspace terminal is never told to run
|
|
50
|
+
* the laptop one.
|
|
51
|
+
*/
|
|
52
|
+
export declare function submitCommandFor(moduleUrl: string): string;
|
|
53
|
+
/** A refusal to run, as `fatal(message, hint)` takes it. */
|
|
54
|
+
export type SubmitRefusal = {
|
|
55
|
+
message: string;
|
|
56
|
+
hint: string;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Why `litmus submit` must not run here, or `null` when it may.
|
|
60
|
+
*
|
|
61
|
+
* Non-null only for the in-workspace install. The caller is expected to refuse
|
|
62
|
+
* on it BEFORE doing any work — the whole defect is that the failure used to
|
|
63
|
+
* arrive after a full archive build, which is what made it read as an upload
|
|
64
|
+
* problem. Both routes named in the hint reach `/workspace/submit`, so this
|
|
65
|
+
* points at the two things that work and sends the candidate nowhere new.
|
|
66
|
+
*/
|
|
67
|
+
export declare function workspaceSubmitRefusal(moduleUrl: string): SubmitRefusal | null;
|
|
68
|
+
//# sourceMappingURL=submit-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit-route.d.ts","sourceRoot":"","sources":["../../src/lib/submit-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAIH,2DAA2D;AAC3D,eAAO,MAAM,wBAAwB,gBAAgB,CAAA;AAErD,yDAAyD;AACzD,eAAO,MAAM,oBAAoB,kBAAkB,CAAA;AAEnD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,4DAA4D;AAC5D,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAO9E"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command actually hands work in, for the install this CLI is running from
|
|
3
|
+
* (ENG-2130).
|
|
4
|
+
*
|
|
5
|
+
* There are two flows and each has exactly one submit command:
|
|
6
|
+
*
|
|
7
|
+
* * ON A LAPTOP (the CLI flow) it is `litmus submit`, which zips the local
|
|
8
|
+
* checkout and uploads it to `/cli/submit`.
|
|
9
|
+
* * INSIDE A CODESPACES v2 WORKSPACE it is `litmus push`, which POSTs
|
|
10
|
+
* `/workspace/submit` — the same endpoint the assessment page's Submit
|
|
11
|
+
* button calls, capturing the live container filesystem host-side (ENG-621).
|
|
12
|
+
* There is nothing local to zip.
|
|
13
|
+
*
|
|
14
|
+
* WHY THIS MODULE EXISTS RATHER THAN A CHECK PER SITE. Before ENG-2064 the CLI
|
|
15
|
+
* was not in the container at all, so `litmus submit` there was `command not
|
|
16
|
+
* found` — useless but unambiguous. That ticket put the CLI on the container's
|
|
17
|
+
* PATH and wired the injected `LITMUS_ASSESSMENT_TOKEN` into `push` alone, so
|
|
18
|
+
* `submit` became present, runnable, and unable to authenticate: it reads its
|
|
19
|
+
* bearer from `.litmus/config.json`, and `hostd._stage_litmus_config` writes
|
|
20
|
+
* that file with DELIBERATELY no `token` because it rides the submission zip and
|
|
21
|
+
* the R2 durability tar, where a live credential must never sit. The upload
|
|
22
|
+
* therefore went out as a literal `Authorization: Bearer undefined` and was
|
|
23
|
+
* refused — AFTER the archive had been built, so a candidate read it as an
|
|
24
|
+
* upload failure rather than as the wrong command. Every surface that names a
|
|
25
|
+
* submit command was naming the laptop one to both audiences, so the answer is
|
|
26
|
+
* derived in one place and the sites ask.
|
|
27
|
+
*
|
|
28
|
+
* NOT FIXED BY GIVING `submit` THE ENV FALLBACK `push` HAS. That would make the
|
|
29
|
+
* command "work", but it routes a v2 candidate down `/cli/submit`, which grades
|
|
30
|
+
* an uploaded zip, while v2's canonical submit grades the live filesystem — the
|
|
31
|
+
* two flows would then disagree about what a submission is. That is a product
|
|
32
|
+
* decision, not a patch. And it must NOT be fixed by writing the token into
|
|
33
|
+
* `.litmus/config.json`, for the reason that file has none today.
|
|
34
|
+
*
|
|
35
|
+
* The predicate is `isWorkspaceInstall`, read from our own module path for the
|
|
36
|
+
* reasons its own docstring gives (an env marker is absent in an sshd session,
|
|
37
|
+
* and a marker injected alongside the token disappears in exactly the case we
|
|
38
|
+
* need to describe). Nothing security-relevant hangs off any of this: it only
|
|
39
|
+
* decides which command a candidate is told to run.
|
|
40
|
+
*/
|
|
41
|
+
import { isWorkspaceInstall } from "./workspace-token.js";
|
|
42
|
+
/** The submit command inside a Codespaces v2 workspace. */
|
|
43
|
+
export const WORKSPACE_SUBMIT_COMMAND = "litmus push";
|
|
44
|
+
/** The submit command on the candidate's own machine. */
|
|
45
|
+
export const LOCAL_SUBMIT_COMMAND = "litmus submit";
|
|
46
|
+
/**
|
|
47
|
+
* The command that submits, for the install `moduleUrl` belongs to.
|
|
48
|
+
*
|
|
49
|
+
* Callers pass their own `import.meta.url`. Use this anywhere candidate-facing
|
|
50
|
+
* copy names the submit command, so a workspace terminal is never told to run
|
|
51
|
+
* the laptop one.
|
|
52
|
+
*/
|
|
53
|
+
export function submitCommandFor(moduleUrl) {
|
|
54
|
+
return isWorkspaceInstall(moduleUrl) ? WORKSPACE_SUBMIT_COMMAND : LOCAL_SUBMIT_COMMAND;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Why `litmus submit` must not run here, or `null` when it may.
|
|
58
|
+
*
|
|
59
|
+
* Non-null only for the in-workspace install. The caller is expected to refuse
|
|
60
|
+
* on it BEFORE doing any work — the whole defect is that the failure used to
|
|
61
|
+
* arrive after a full archive build, which is what made it read as an upload
|
|
62
|
+
* problem. Both routes named in the hint reach `/workspace/submit`, so this
|
|
63
|
+
* points at the two things that work and sends the candidate nowhere new.
|
|
64
|
+
*/
|
|
65
|
+
export function workspaceSubmitRefusal(moduleUrl) {
|
|
66
|
+
if (!isWorkspaceInstall(moduleUrl))
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
message: "`litmus submit` cannot submit a Litmus workspace: it uploads a local checkout, and your work lives in this container.",
|
|
70
|
+
hint: `Run \`${WORKSPACE_SUBMIT_COMMAND}\` in this terminal instead, or click Submit assessment on your assessment page.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=submit-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submit-route.js","sourceRoot":"","sources":["../../src/lib/submit-route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAEzD,2DAA2D;AAC3D,MAAM,CAAC,MAAM,wBAAwB,GAAG,aAAa,CAAA;AAErD,yDAAyD;AACzD,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAA;AAEnD;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,OAAO,kBAAkB,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,oBAAoB,CAAA;AACxF,CAAC;AAQD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAAC,SAAiB;IACtD,IAAI,CAAC,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAA;IAC/C,OAAO;QACL,OAAO,EACL,uHAAuH;QACzH,IAAI,EAAE,SAAS,wBAAwB,kFAAkF;KAC1H,CAAA;AACH,CAAC"}
|
package/dist/lib/watcher.js
CHANGED
|
@@ -26,6 +26,7 @@ import { getEffectiveDeadline, writeConfig, validateRebuiltTimes, computeTimeSyn
|
|
|
26
26
|
import { CLI_VERSION } from "./version.js";
|
|
27
27
|
import { armDeadlinePoll, createAutoSubmitController } from "./deadline.js";
|
|
28
28
|
import { resumeChainFromLog } from "./chain.js";
|
|
29
|
+
import { buildGitBundle, currentHead } from "./git-bundle.js";
|
|
29
30
|
import { fetchInitMetadata, ServerError } from "./api.js";
|
|
30
31
|
import { ServerTimesSchema } from "./schemas.js";
|
|
31
32
|
import { detectInstalledHooks } from "./ai-tracking.js";
|
|
@@ -1429,6 +1430,12 @@ function getRecentCommits() {
|
|
|
1429
1430
|
return [];
|
|
1430
1431
|
}
|
|
1431
1432
|
}
|
|
1433
|
+
// ── Git history (ENG-2238) ──────────────────────────────────────
|
|
1434
|
+
// Capture lives in lib/git-bundle.ts; what belongs here is the one piece of
|
|
1435
|
+
// session state: HEAD at the last bundle the SERVER ACKED, so an unchanged
|
|
1436
|
+
// history is not re-uploaded every five minutes and a failed upload is still
|
|
1437
|
+
// retried on the next tick.
|
|
1438
|
+
let lastBundledHead = null;
|
|
1432
1439
|
function uploadSnapshot() {
|
|
1433
1440
|
if (!uploadConfig)
|
|
1434
1441
|
return;
|
|
@@ -1449,7 +1456,17 @@ function uploadSnapshot() {
|
|
|
1449
1456
|
// interview agent's context. Without this, a reconstruction restores the
|
|
1450
1457
|
// candidate's code but none of their AI-usage evidence.
|
|
1451
1458
|
const activityLog = readActivityLogTail(projectDir);
|
|
1452
|
-
const
|
|
1459
|
+
const head = currentHead(projectDir);
|
|
1460
|
+
const bundle = head && head !== lastBundledHead ? buildGitBundle(projectDir) : null;
|
|
1461
|
+
const body = JSON.stringify({
|
|
1462
|
+
files,
|
|
1463
|
+
git_diff: gitDiff,
|
|
1464
|
+
recent_commits: recentCommits,
|
|
1465
|
+
activity_log: activityLog,
|
|
1466
|
+
// Omitted entirely when unchanged or unavailable; the server keeps the last
|
|
1467
|
+
// good bundle rather than reading an absent field as "no history".
|
|
1468
|
+
...(bundle ? { git_bundle_gz: bundle.toString("base64") } : {}),
|
|
1469
|
+
});
|
|
1453
1470
|
const url = new URL(`${uploadConfig.backendUrl}/cli/snapshot`);
|
|
1454
1471
|
const reqFn = url.protocol === "https:" ? https.request : http.request;
|
|
1455
1472
|
const req = reqFn(url, {
|
|
@@ -1468,7 +1485,11 @@ function uploadSnapshot() {
|
|
|
1468
1485
|
process.stderr.write(`[watcher] snapshot upload failed: ${res.statusCode}\n`);
|
|
1469
1486
|
}
|
|
1470
1487
|
else {
|
|
1471
|
-
|
|
1488
|
+
// Only now is the history durably ours: an upload that never landed
|
|
1489
|
+
// must be retried on the next tick, not suppressed as already sent.
|
|
1490
|
+
if (bundle && head)
|
|
1491
|
+
lastBundledHead = head;
|
|
1492
|
+
process.stderr.write(`[watcher] snapshot uploaded: ${fileCount} files, ${(Buffer.byteLength(body) / 1024).toFixed(0)}KB${bundle ? ` (+git history ${(bundle.length / 1024).toFixed(0)}KB)` : ""}\n`);
|
|
1472
1493
|
}
|
|
1473
1494
|
});
|
|
1474
1495
|
});
|