@mjasnikovs/pi-task 0.40.38 → 0.40.39
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.
|
@@ -65,6 +65,9 @@ export declare function pinnedLocalPort(vars: Record<string, string>): number |
|
|
|
65
65
|
export interface SessionRequest {
|
|
66
66
|
method: string;
|
|
67
67
|
path: string;
|
|
68
|
+
/** Where the chain ENDED — the path `status` and `mimeType` actually describe.
|
|
69
|
+
* Absent in a pre-existing recorded session, where it falls back to `path`. */
|
|
70
|
+
finalPath?: string;
|
|
68
71
|
status: number | null;
|
|
69
72
|
mimeType: string | null;
|
|
70
73
|
failed: boolean;
|
|
@@ -96,6 +99,8 @@ export interface DeepSessionFacts {
|
|
|
96
99
|
path: string;
|
|
97
100
|
status: number | null;
|
|
98
101
|
failed: boolean;
|
|
102
|
+
/** The sign-in chain redirected, so `status` is the hop it LANDED on. */
|
|
103
|
+
redirected?: boolean;
|
|
99
104
|
} | null;
|
|
100
105
|
/** Same-origin XHR/fetch requests issued at or after the sign-in request,
|
|
101
106
|
* excluding that request itself. Derived: `sessionRequests` in phase 'post'. */
|
|
@@ -207,7 +207,13 @@ export function deriveLegacyFacts(log) {
|
|
|
207
207
|
const auth = log.find(r => r.phase === 'auth') ?? null;
|
|
208
208
|
const data = log.filter(r => r.phase === 'post' && r.initiator === 'xhr');
|
|
209
209
|
return {
|
|
210
|
-
authRequest: auth === null ? null : ({
|
|
210
|
+
authRequest: auth === null ? null : ({
|
|
211
|
+
method: auth.method,
|
|
212
|
+
path: auth.path,
|
|
213
|
+
status: auth.status,
|
|
214
|
+
failed: auth.failed,
|
|
215
|
+
redirected: auth.redirected
|
|
216
|
+
}),
|
|
211
217
|
postAuthDataAttempted: data.length,
|
|
212
218
|
postAuthData2xx: data.filter(r => r.status !== null && r.status >= 200 && r.status < 300)
|
|
213
219
|
.length
|
|
@@ -217,6 +223,11 @@ export function deriveLegacyFacts(log) {
|
|
|
217
223
|
* answered No. 501 is included because a server that routes but implements
|
|
218
224
|
* nothing is the same dead call from the client's side. */
|
|
219
225
|
const MISSING_ROUTE_STATUS = new Set([404, 405, 501]);
|
|
226
|
+
/** A redirected entry's `status` and `mimeType` belong to its LAST hop, so the two
|
|
227
|
+
* rules below may read them only where that hop is still the client's own business.
|
|
228
|
+
* Before and during sign-in, a 302 to the login page is the normal unauthenticated
|
|
229
|
+
* flow; after sign-in, that same redirect IS the defect. */
|
|
230
|
+
const lastHopJudgesTheClient = (r) => !r.redirected || r.phase === 'post';
|
|
220
231
|
/**
|
|
221
232
|
* Judge a recorded session. The ONE thing that may FAIL is a session the SERVER
|
|
222
233
|
* authenticated (2xx on the sign-in request) whose client then could not use it:
|
|
@@ -283,13 +294,15 @@ export function judgeDeepSession(f) {
|
|
|
283
294
|
// password, a missing permission — the app working), 5xx is the server failing,
|
|
284
295
|
// and both keep their existing behaviour. Only "no such route" is here.
|
|
285
296
|
const missingRoute = (f.sessionRequests ?? []).find(r => r.initiator === 'xhr'
|
|
286
|
-
&&
|
|
297
|
+
&& lastHopJudgesTheClient(r)
|
|
287
298
|
&& r.status !== null
|
|
288
299
|
&& MISSING_ROUTE_STATUS.has(r.status));
|
|
289
300
|
if (missingRoute) {
|
|
301
|
+
const landed = missingRoute.finalPath ?? missingRoute.path;
|
|
302
|
+
const hop = landed === missingRoute.path ? '' : ` → \`${landed}\``;
|
|
290
303
|
return {
|
|
291
304
|
outcome: 'fail',
|
|
292
|
-
detail: `\`${missingRoute.method} ${missingRoute.path}
|
|
305
|
+
detail: `\`${missingRoute.method} ${missingRoute.path}\`${hop} → ${String(missingRoute.status)}: `
|
|
293
306
|
+ 'the client sent this to a path the server does not route. This is a dead client '
|
|
294
307
|
+ 'call — a base URL joined twice, a renamed route, a wrong method. No type or mock '
|
|
295
308
|
+ 'can produce a route that is not mounted.'
|
|
@@ -299,10 +312,11 @@ export function judgeDeepSession(f) {
|
|
|
299
312
|
// `GET /*` → index.html fallback returns 200 for a route it does not have, so
|
|
300
313
|
// the status is healthy and the body is the app shell. A document navigation
|
|
301
314
|
// answered with HTML is normal; an XHR asking for data and getting HTML is a
|
|
302
|
-
// call that reached nothing. A
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
315
|
+
// call that reached nothing. A fetch login that 302s to a page reads as HTML
|
|
316
|
+
// while being a sign-in that worked, hence the last-hop guard.
|
|
317
|
+
const swallowed = (f.sessionRequests ?? []).find(r => r.initiator === 'xhr'
|
|
318
|
+
&& lastHopJudgesTheClient(r)
|
|
319
|
+
&& (r.mimeType ?? '').startsWith('text/html'));
|
|
306
320
|
if (swallowed) {
|
|
307
321
|
return {
|
|
308
322
|
outcome: 'fail',
|
|
@@ -312,7 +326,7 @@ export function judgeDeepSession(f) {
|
|
|
312
326
|
+ 'sees a 200 it cannot parse. No status check can see this.'
|
|
313
327
|
};
|
|
314
328
|
}
|
|
315
|
-
const { method, path: p, status, failed } = f.authRequest;
|
|
329
|
+
const { method, path: p, status, failed, redirected: authRedirected } = f.authRequest;
|
|
316
330
|
if (failed || status === null || status < 200 || status >= 300) {
|
|
317
331
|
return {
|
|
318
332
|
outcome: 'skip',
|
|
@@ -323,6 +337,18 @@ export function judgeDeepSession(f) {
|
|
|
323
337
|
}
|
|
324
338
|
const signedIn = `signed in (\`${method} ${p}\` → ${status})`;
|
|
325
339
|
if (!f.leftAuthWall) {
|
|
340
|
+
// A form sign-in that redirects lands its status on the hop it reached, so a
|
|
341
|
+
// 200 here is the landing page's, not a verdict on the credentials. Landing
|
|
342
|
+
// back on the wall is what a REJECTED password looks like, and the gate's one
|
|
343
|
+
// FAIL needs the server to have said yes.
|
|
344
|
+
if (authRedirected === true) {
|
|
345
|
+
return {
|
|
346
|
+
outcome: 'skip',
|
|
347
|
+
note: `the sign-in request (\`${method} ${p}\`) redirected, so its ${String(status)} `
|
|
348
|
+
+ 'describes the page it landed on and not the credentials, and the client is '
|
|
349
|
+
+ 'still on the wall — the authenticated half of the app was NOT observed'
|
|
350
|
+
};
|
|
351
|
+
}
|
|
326
352
|
return {
|
|
327
353
|
outcome: 'fail',
|
|
328
354
|
detail: `${signedIn} but the client NEVER LEFT THE SIGN-IN WALL: the page is still `
|
|
@@ -748,6 +774,7 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
|
|
|
748
774
|
out.push({
|
|
749
775
|
method: r.method,
|
|
750
776
|
path: pathOf(r.url),
|
|
777
|
+
finalPath: pathOf(r.finalUrl),
|
|
751
778
|
status: r.status,
|
|
752
779
|
mimeType: r.mimeType,
|
|
753
780
|
failed: r.failed,
|
|
@@ -813,9 +840,12 @@ export async function driveSession(cdp, { url, credentials, judge, quietMs }) {
|
|
|
813
840
|
?? firstRequest(r => r.seq >= fillSeq && sameOrigin(r) && r.method !== 'GET' && r.type === 'Document');
|
|
814
841
|
// An SSO sign-in addresses this app and ends on the provider, so it is absent
|
|
815
842
|
// from the same-origin log above and "no request to our own origin" would
|
|
816
|
-
// misname it.
|
|
843
|
+
// misname it. Document-only for the same reason the authId fallback is: a
|
|
844
|
+
// telemetry beacon the fill races that happens to end off-origin would otherwise
|
|
845
|
+
// name a bogus identity provider.
|
|
817
846
|
const offsiteSignIn = authId !== null ? null : (firstRequest(r => r.seq >= fillSeq
|
|
818
847
|
&& r.method !== 'GET'
|
|
848
|
+
&& r.type === 'Document'
|
|
819
849
|
&& addressedOrigin(r)
|
|
820
850
|
&& !sameOrigin(r)
|
|
821
851
|
&& r.finalUrl.startsWith('http')));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.39",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|