@webpieces/core-context 0.4.605 → 0.4.607

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.
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CapturedContextCompileAssertions = void 0;
4
+ const CapturedContext_1 = require("./CapturedContext");
5
+ const RequestContext_1 = require("./RequestContext");
6
+ /**
7
+ * COMPILE-TIME assertions that a context can only be restored from a REAL capture.
8
+ *
9
+ * The runtime half — that a snapshot round-trips, and that mutating the source afterwards does not
10
+ * reach it — is in `CapturedContext.spec.ts`. What a spec CANNOT express is the half that matters
11
+ * most: that `RequestContext.restoreContext(new Map([['userId', 'victim']]))` does not compile. Each
12
+ * `@ts-expect-error` below fails the build with TS2578 the day its line starts compiling again.
13
+ *
14
+ * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with
15
+ * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. See
16
+ * `RequestContextTrustCompileAssertions` for the trust-verb half.
17
+ */
18
+ class CapturedContextCompileAssertions {
19
+ /** THE hole this change closes: a hand-assembled payload forging a trusted value. */
20
+ cannotRestoreAHandBuiltMap() {
21
+ // @ts-expect-error - restoreContext takes an opaque CapturedContext, never a raw Map
22
+ RequestContext_1.RequestContext.restoreContext(new Map([['userId', 'victim']]));
23
+ }
24
+ /** Same hole through the other door. */
25
+ cannotRunWithAHandBuiltMap() {
26
+ // @ts-expect-error - runWithContext takes an opaque CapturedContext, never a raw Map
27
+ RequestContext_1.RequestContext.runWithContext(new Map([['userId', 'victim']]), () => undefined);
28
+ }
29
+ /** Nor by asserting an object literal into the shape — the private `#entries` makes it nominal. */
30
+ cannotFakeTheShape() {
31
+ // @ts-expect-error - an object literal is not a CapturedContext; #entries is not satisfiable
32
+ const forged = { size: () => 1 };
33
+ void forged;
34
+ }
35
+ /** Nor by constructing one directly — the constructor is private. */
36
+ cannotConstructOneDirectly() {
37
+ // @ts-expect-error - the constructor is private; copyContext() is the only producer
38
+ const forged = new CapturedContext_1.CapturedContext(new Map([['userId', 'victim']]));
39
+ void forged;
40
+ }
41
+ /**
42
+ * Nor by calling the factory: it demands a capability token whose own constructor is private, so
43
+ * even code that can NAME the token (this package) cannot mint a second one.
44
+ */
45
+ cannotMintAnAuthority() {
46
+ // @ts-expect-error - ContextCaptureAuthority's constructor is private; INTERNAL is the only one
47
+ const forgedAuthority = new CapturedContext_1.ContextCaptureAuthority();
48
+ void forgedAuthority;
49
+ }
50
+ /** And the factory is not callable without one at all. */
51
+ cannotCaptureWithoutAnAuthority() {
52
+ // @ts-expect-error - capture() requires a ContextCaptureAuthority as its first argument
53
+ CapturedContext_1.CapturedContext.capture(new Map([['userId', 'victim']]));
54
+ }
55
+ /**
56
+ * THE new hole this pair closes: a BARE capture must not run. If it did, `runWithContext(snapshot,
57
+ * fn)` would sit next to `runWithContext(snapshot.withTrusted(), fn)` as a second spelling whose
58
+ * shorter form silently carries a user identity — a widening that is an absence rather than a
59
+ * token, and ungreppable. The capture is inert until it states its intent.
60
+ */
61
+ cannotRunABareCapture() {
62
+ const captured = RequestContext_1.RequestContext.copyContext();
63
+ // @ts-expect-error - runWithContext takes a RestorableContext: say withTrusted() or withoutTrusted()
64
+ RequestContext_1.RequestContext.runWithContext(captured, () => undefined);
65
+ }
66
+ /** Same, through the in-place door. */
67
+ cannotRestoreABareCapture() {
68
+ const captured = RequestContext_1.RequestContext.copyContext();
69
+ // @ts-expect-error - restoreContext takes a RestorableContext: say withTrusted() or withoutTrusted()
70
+ RequestContext_1.RequestContext.restoreContext(captured);
71
+ }
72
+ /**
73
+ * And the intent is stated by NARROWING, never by a flag on the run call. A `keepTrusted: boolean`
74
+ * would make the wide intent as easy to type as the narrow one and impossible to grep; this line
75
+ * fails the build the day such a parameter appears.
76
+ */
77
+ cannotStateTheIntentViaAFlagOnTheRunCall() {
78
+ const captured = RequestContext_1.RequestContext.copyContext().withTrusted();
79
+ // @ts-expect-error - there is no third parameter; narrow the snapshot instead
80
+ RequestContext_1.RequestContext.runWithContext(captured, () => undefined, false);
81
+ }
82
+ /** Nor can a narrowed snapshot be minted directly — same private constructor, same token. */
83
+ cannotConstructARestorableContextDirectly() {
84
+ // @ts-expect-error - the constructor is private; withTrusted()/withoutTrusted() are the producers
85
+ const forged = new CapturedContext_1.RestorableContext(new Map([['userId', 'victim']]));
86
+ void forged;
87
+ }
88
+ /** Nor through its factory, which demands the same unobtainable authority. */
89
+ cannotMintARestorableContextWithoutAnAuthority() {
90
+ // @ts-expect-error - of() requires a ContextCaptureAuthority as its first argument
91
+ CapturedContext_1.RestorableContext.of(new Map([['userId', 'victim']]));
92
+ }
93
+ /** POSITIVE: both narrowings produce the ONE type both consumers take. */
94
+ bothNarrowingsFeedBothConsumers() {
95
+ const captured = RequestContext_1.RequestContext.copyContext();
96
+ const wide = captured.withTrusted();
97
+ const dropped = captured.withoutTrusted();
98
+ RequestContext_1.RequestContext.runWithContext(wide, () => {
99
+ RequestContext_1.RequestContext.restoreContext(dropped);
100
+ });
101
+ }
102
+ /** POSITIVE: the real round trip must keep compiling — restoring a proven value IS the point. */
103
+ theRealRoundTripCompiles() {
104
+ const captured = RequestContext_1.RequestContext.copyContext().withTrusted();
105
+ RequestContext_1.RequestContext.runWithContext(captured, () => {
106
+ RequestContext_1.RequestContext.restoreContext(captured);
107
+ });
108
+ }
109
+ }
110
+ exports.CapturedContextCompileAssertions = CapturedContextCompileAssertions;
111
+ //# sourceMappingURL=CapturedContextCompileAssertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CapturedContextCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/CapturedContextCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { CapturedContext, ContextCaptureAuthority, RestorableContext } from './CapturedContext';\nimport { RequestContext } from './RequestContext';\n\n/**\n * COMPILE-TIME assertions that a context can only be restored from a REAL capture.\n *\n * The runtime half — that a snapshot round-trips, and that mutating the source afterwards does not\n * reach it — is in `CapturedContext.spec.ts`. What a spec CANNOT express is the half that matters\n * most: that `RequestContext.restoreContext(new Map([['userId', 'victim']]))` does not compile. Each\n * `@ts-expect-error` below fails the build with TS2578 the day its line starts compiling again.\n *\n * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with\n * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. See\n * `RequestContextTrustCompileAssertions` for the trust-verb half.\n */\nexport class CapturedContextCompileAssertions {\n /** THE hole this change closes: a hand-assembled payload forging a trusted value. */\n cannotRestoreAHandBuiltMap(): void {\n // @ts-expect-error - restoreContext takes an opaque CapturedContext, never a raw Map\n RequestContext.restoreContext(new Map([['userId', 'victim']]));\n }\n\n /** Same hole through the other door. */\n cannotRunWithAHandBuiltMap(): void {\n // @ts-expect-error - runWithContext takes an opaque CapturedContext, never a raw Map\n RequestContext.runWithContext(new Map([['userId', 'victim']]), () => undefined);\n }\n\n /** Nor by asserting an object literal into the shape — the private `#entries` makes it nominal. */\n cannotFakeTheShape(): void {\n // @ts-expect-error - an object literal is not a CapturedContext; #entries is not satisfiable\n const forged: CapturedContext = { size: () => 1 };\n void forged;\n }\n\n /** Nor by constructing one directly — the constructor is private. */\n cannotConstructOneDirectly(): void {\n // @ts-expect-error - the constructor is private; copyContext() is the only producer\n const forged = new CapturedContext(new Map([['userId', 'victim']]));\n void forged;\n }\n\n /**\n * Nor by calling the factory: it demands a capability token whose own constructor is private, so\n * even code that can NAME the token (this package) cannot mint a second one.\n */\n cannotMintAnAuthority(): void {\n // @ts-expect-error - ContextCaptureAuthority's constructor is private; INTERNAL is the only one\n const forgedAuthority = new ContextCaptureAuthority();\n void forgedAuthority;\n }\n\n /** And the factory is not callable without one at all. */\n cannotCaptureWithoutAnAuthority(): void {\n // @ts-expect-error - capture() requires a ContextCaptureAuthority as its first argument\n CapturedContext.capture(new Map([['userId', 'victim']]));\n }\n\n /**\n * THE new hole this pair closes: a BARE capture must not run. If it did, `runWithContext(snapshot,\n * fn)` would sit next to `runWithContext(snapshot.withTrusted(), fn)` as a second spelling whose\n * shorter form silently carries a user identity — a widening that is an absence rather than a\n * token, and ungreppable. The capture is inert until it states its intent.\n */\n cannotRunABareCapture(): void {\n const captured: CapturedContext = RequestContext.copyContext();\n // @ts-expect-error - runWithContext takes a RestorableContext: say withTrusted() or withoutTrusted()\n RequestContext.runWithContext(captured, () => undefined);\n }\n\n /** Same, through the in-place door. */\n cannotRestoreABareCapture(): void {\n const captured: CapturedContext = RequestContext.copyContext();\n // @ts-expect-error - restoreContext takes a RestorableContext: say withTrusted() or withoutTrusted()\n RequestContext.restoreContext(captured);\n }\n\n /**\n * And the intent is stated by NARROWING, never by a flag on the run call. A `keepTrusted: boolean`\n * would make the wide intent as easy to type as the narrow one and impossible to grep; this line\n * fails the build the day such a parameter appears.\n */\n cannotStateTheIntentViaAFlagOnTheRunCall(): void {\n const captured: RestorableContext = RequestContext.copyContext().withTrusted();\n // @ts-expect-error - there is no third parameter; narrow the snapshot instead\n RequestContext.runWithContext(captured, () => undefined, false);\n }\n\n /** Nor can a narrowed snapshot be minted directly — same private constructor, same token. */\n cannotConstructARestorableContextDirectly(): void {\n // @ts-expect-error - the constructor is private; withTrusted()/withoutTrusted() are the producers\n const forged = new RestorableContext(new Map([['userId', 'victim']]));\n void forged;\n }\n\n /** Nor through its factory, which demands the same unobtainable authority. */\n cannotMintARestorableContextWithoutAnAuthority(): void {\n // @ts-expect-error - of() requires a ContextCaptureAuthority as its first argument\n RestorableContext.of(new Map([['userId', 'victim']]));\n }\n\n /** POSITIVE: both narrowings produce the ONE type both consumers take. */\n bothNarrowingsFeedBothConsumers(): void {\n const captured: CapturedContext = RequestContext.copyContext();\n const wide: RestorableContext = captured.withTrusted();\n const dropped: RestorableContext = captured.withoutTrusted();\n RequestContext.runWithContext(wide, () => {\n RequestContext.restoreContext(dropped);\n });\n }\n\n /** POSITIVE: the real round trip must keep compiling — restoring a proven value IS the point. */\n theRealRoundTripCompiles(): void {\n const captured: RestorableContext = RequestContext.copyContext().withTrusted();\n RequestContext.runWithContext(captured, () => {\n RequestContext.restoreContext(captured);\n });\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ import { AnyContextKey } from '@webpieces/core-util';
2
+ /**
3
+ * COMPILE-TIME assertions for {@link RequestContext.runDetachedScope}.
4
+ *
5
+ * The runtime half — that the detached scope starts empty, that the enclosing scope survives, that a
6
+ * throw unwinds it — is in `DetachedScope.spec.ts`. What a spec CANNOT express is the half that makes
7
+ * the browser-log path safe BY CONSTRUCTION rather than by remembering to filter:
8
+ *
9
+ * - `runDetachedScope` has NO container-taking form, so the deleted `runWithContext(map, fn)` forgery
10
+ * path cannot come back through this door;
11
+ * - a loop over a mixed `AnyContextKey[]` cannot write ANY key until it has tested that key's trust;
12
+ * - and a TRUSTED key can never reach `putUntrusted`, so a browser — which proves nothing — cannot
13
+ * FABRICATE a proven value by naming `userId` in its payload. It is a compile error at the write,
14
+ * not a filter someone has to remember to write.
15
+ *
16
+ * That is about the SOURCE, not about the key. Writing a trusted key is ordinary and legitimate — see
17
+ * {@link putTrustedIsLegitimateInsideADetachedScope} — whenever the caller has actually proven the
18
+ * value. What cannot be written down is a claim of proof by code that has none.
19
+ *
20
+ * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with
21
+ * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. Each
22
+ * one below fails the build with TS2578 the day its line starts compiling. See
23
+ * `CapturedContextCompileAssertions` and `RequestContextTrustCompileAssertions` for the sibling halves.
24
+ */
25
+ export declare class DetachedScopeCompileAssertions {
26
+ private readonly trusted;
27
+ private readonly untrusted;
28
+ /**
29
+ * THE hole this whole shape exists to keep shut: no Map/object/array of entries may cross the
30
+ * boundary. Values are written INSIDE the closure, through the trust verbs.
31
+ */
32
+ cannotHandItAContainerOfEntries(): void;
33
+ /** Nor a plain object of entries, which is the same hole spelled differently. */
34
+ cannotHandItAnObjectOfEntries(): void;
35
+ /** A key of unknown trust cannot be written AT ALL — the branch is not optional. */
36
+ cannotWriteAMixedKeyWithoutTestingItsTrust(key: AnyContextKey): void;
37
+ /**
38
+ * And a TRUSTED key cannot be LAUNDERED through the untrusted verb, detached or not — which is
39
+ * what a caller with no proof would have to do, since `putTrusted` is the only other way in and
40
+ * saying it is a deliberate, greppable claim.
41
+ */
42
+ cannotLaunderATrustedKeyThroughTheUntrustedVerb(): void;
43
+ /**
44
+ * POSITIVE, and the point the negative above must not be mistaken for: writing a TRUSTED value
45
+ * inside a detached scope is ordinary and correct when the caller has actually proven it — the
46
+ * signed-webhook case (Twilio/WhatsApp proves the phone number, the app looks up the userId), or a
47
+ * verified JWT claim. `putTrusted` is the verb for exactly that, and a detached scope does not
48
+ * change it. Trust is about tamper-resistance, not secrecy: a trusted `userId` is a plain,
49
+ * fully-logged GUID; masking is the separate `maskInLogs` axis.
50
+ */
51
+ putTrustedIsLegitimateInsideADetachedScope(): void;
52
+ /**
53
+ * POSITIVE, and the shape the real consumer writes: emit one browser line under a fresh scope
54
+ * rebuilt from an EXTERNAL payload, driven off the registry's logged keys.
55
+ *
56
+ * The `isTrusted()` skip is what a reader sees; the compiler is what enforces it. Delete the skip
57
+ * and `isUntrusted()` still refuses the trusted key, so the trusted branch has no write at all.
58
+ */
59
+ theBrowserLogLoopCompiles(loggedKeys: AnyContextKey[], payload: Record<string, unknown>): void;
60
+ /** The return value flows through, and an async closure is a promise the caller can await. */
61
+ returnValuesFlowThrough(): Promise<void>;
62
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DetachedScopeCompileAssertions = void 0;
4
+ const core_util_1 = require("@webpieces/core-util");
5
+ const RequestContext_1 = require("./RequestContext");
6
+ /**
7
+ * COMPILE-TIME assertions for {@link RequestContext.runDetachedScope}.
8
+ *
9
+ * The runtime half — that the detached scope starts empty, that the enclosing scope survives, that a
10
+ * throw unwinds it — is in `DetachedScope.spec.ts`. What a spec CANNOT express is the half that makes
11
+ * the browser-log path safe BY CONSTRUCTION rather than by remembering to filter:
12
+ *
13
+ * - `runDetachedScope` has NO container-taking form, so the deleted `runWithContext(map, fn)` forgery
14
+ * path cannot come back through this door;
15
+ * - a loop over a mixed `AnyContextKey[]` cannot write ANY key until it has tested that key's trust;
16
+ * - and a TRUSTED key can never reach `putUntrusted`, so a browser — which proves nothing — cannot
17
+ * FABRICATE a proven value by naming `userId` in its payload. It is a compile error at the write,
18
+ * not a filter someone has to remember to write.
19
+ *
20
+ * That is about the SOURCE, not about the key. Writing a trusted key is ordinary and legitimate — see
21
+ * {@link putTrustedIsLegitimateInsideADetachedScope} — whenever the caller has actually proven the
22
+ * value. What cannot be written down is a claim of proof by code that has none.
23
+ *
24
+ * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with
25
+ * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. Each
26
+ * one below fails the build with TS2578 the day its line starts compiling. See
27
+ * `CapturedContextCompileAssertions` and `RequestContextTrustCompileAssertions` for the sibling halves.
28
+ */
29
+ class DetachedScopeCompileAssertions {
30
+ trusted = core_util_1.ContextKey.trusted('assertDetachedUserId', 'jwt claim `sub`');
31
+ untrusted = core_util_1.ContextKey.untrusted('assertDetachedActionId');
32
+ /**
33
+ * THE hole this whole shape exists to keep shut: no Map/object/array of entries may cross the
34
+ * boundary. Values are written INSIDE the closure, through the trust verbs.
35
+ */
36
+ cannotHandItAContainerOfEntries() {
37
+ // @ts-expect-error - runDetachedScope takes ONLY a closure; there is no map-taking form
38
+ RequestContext_1.RequestContext.runDetachedScope(new Map([['userId', 'victim']]), () => undefined);
39
+ }
40
+ /** Nor a plain object of entries, which is the same hole spelled differently. */
41
+ cannotHandItAnObjectOfEntries() {
42
+ // @ts-expect-error - the single parameter is the closure, not a bag of values
43
+ RequestContext_1.RequestContext.runDetachedScope({ userId: 'victim' });
44
+ }
45
+ /** A key of unknown trust cannot be written AT ALL — the branch is not optional. */
46
+ cannotWriteAMixedKeyWithoutTestingItsTrust(key) {
47
+ RequestContext_1.RequestContext.runDetachedScope(() => {
48
+ // @ts-expect-error - putUntrusted needs a key KNOWN to be untrusted; AnyContextKey is mixed
49
+ RequestContext_1.RequestContext.putUntrusted(key, 'from-the-browser');
50
+ });
51
+ }
52
+ /**
53
+ * And a TRUSTED key cannot be LAUNDERED through the untrusted verb, detached or not — which is
54
+ * what a caller with no proof would have to do, since `putTrusted` is the only other way in and
55
+ * saying it is a deliberate, greppable claim.
56
+ */
57
+ cannotLaunderATrustedKeyThroughTheUntrustedVerb() {
58
+ RequestContext_1.RequestContext.runDetachedScope(() => {
59
+ // @ts-expect-error - putUntrusted does not accept a trusted key
60
+ RequestContext_1.RequestContext.putUntrusted(this.trusted, 'browser-said-so');
61
+ });
62
+ }
63
+ /**
64
+ * POSITIVE, and the point the negative above must not be mistaken for: writing a TRUSTED value
65
+ * inside a detached scope is ordinary and correct when the caller has actually proven it — the
66
+ * signed-webhook case (Twilio/WhatsApp proves the phone number, the app looks up the userId), or a
67
+ * verified JWT claim. `putTrusted` is the verb for exactly that, and a detached scope does not
68
+ * change it. Trust is about tamper-resistance, not secrecy: a trusted `userId` is a plain,
69
+ * fully-logged GUID; masking is the separate `maskInLogs` axis.
70
+ */
71
+ putTrustedIsLegitimateInsideADetachedScope() {
72
+ RequestContext_1.RequestContext.runDetachedScope(() => {
73
+ RequestContext_1.RequestContext.putTrusted(this.trusted, 'proven-out-of-band');
74
+ const proven = RequestContext_1.RequestContext.getTrusted(this.trusted);
75
+ void proven;
76
+ });
77
+ }
78
+ /**
79
+ * POSITIVE, and the shape the real consumer writes: emit one browser line under a fresh scope
80
+ * rebuilt from an EXTERNAL payload, driven off the registry's logged keys.
81
+ *
82
+ * The `isTrusted()` skip is what a reader sees; the compiler is what enforces it. Delete the skip
83
+ * and `isUntrusted()` still refuses the trusted key, so the trusted branch has no write at all.
84
+ */
85
+ // webpieces-disable no-any-unknown -- the EXTERNAL payload is by definition untyped JSON off the wire; that is precisely why every value below is trust-branched and typeof-checked before it is written
86
+ theBrowserLogLoopCompiles(loggedKeys, payload) {
87
+ RequestContext_1.RequestContext.runDetachedScope(() => {
88
+ for (const key of loggedKeys) {
89
+ if (key.isTrusted()) {
90
+ // A browser cannot vouch for a proven fact. There is no write in this branch, and
91
+ // no cast that could produce one.
92
+ continue;
93
+ }
94
+ const value = payload[key.name];
95
+ if (key.isUntrusted() && typeof value === 'string') {
96
+ RequestContext_1.RequestContext.putUntrusted(key, value);
97
+ }
98
+ }
99
+ RequestContext_1.RequestContext.putUntrusted(this.untrusted, 'click-7');
100
+ });
101
+ }
102
+ /** The return value flows through, and an async closure is a promise the caller can await. */
103
+ async returnValuesFlowThrough() {
104
+ const sync = RequestContext_1.RequestContext.runDetachedScope(() => 'done');
105
+ const asyncResult = await RequestContext_1.RequestContext.runDetachedScope(async () => 'done');
106
+ void sync;
107
+ void asyncResult;
108
+ }
109
+ }
110
+ exports.DetachedScopeCompileAssertions = DetachedScopeCompileAssertions;
111
+ //# sourceMappingURL=DetachedScopeCompileAssertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DetachedScopeCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/DetachedScopeCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,oDAAiE;AACjE,qDAAkD;AAElD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,8BAA8B;IACtB,OAAO,GAAG,sBAAU,CAAC,OAAO,CAAS,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;IAChF,SAAS,GAAG,sBAAU,CAAC,SAAS,CAAS,wBAAwB,CAAC,CAAC;IAEpF;;;OAGG;IACH,+BAA+B;QAC3B,wFAAwF;QACxF,+BAAc,CAAC,gBAAgB,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACtF,CAAC;IAED,iFAAiF;IACjF,6BAA6B;QACzB,8EAA8E;QAC9E,+BAAc,CAAC,gBAAgB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,oFAAoF;IACpF,0CAA0C,CAAC,GAAkB;QACzD,+BAAc,CAAC,gBAAgB,CAAC,GAAG,EAAE;YACjC,4FAA4F;YAC5F,+BAAc,CAAC,YAAY,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;OAIG;IACH,+CAA+C;QAC3C,+BAAc,CAAC,gBAAgB,CAAC,GAAG,EAAE;YACjC,gEAAgE;YAChE,+BAAc,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;;OAOG;IACH,0CAA0C;QACtC,+BAAc,CAAC,gBAAgB,CAAC,GAAG,EAAE;YACjC,+BAAc,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;YAC9D,MAAM,MAAM,GAAuB,+BAAc,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC3E,KAAK,MAAM,CAAC;QAChB,CAAC,CAAC,CAAC;IACP,CAAC;IAED;;;;;;OAMG;IACH,yMAAyM;IACzM,yBAAyB,CAAC,UAA2B,EAAE,OAAgC;QACnF,+BAAc,CAAC,gBAAgB,CAAC,GAAG,EAAE;YACjC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;gBAC3B,IAAI,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;oBAClB,kFAAkF;oBAClF,kCAAkC;oBAClC,SAAS;gBACb,CAAC;gBACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAChC,IAAI,GAAG,CAAC,WAAW,EAAE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;oBACjD,+BAAc,CAAC,YAAY,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;gBAC5C,CAAC;YACL,CAAC;YACD,+BAAc,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACP,CAAC;IAED,8FAA8F;IAC9F,KAAK,CAAC,uBAAuB;QACzB,MAAM,IAAI,GAAW,+BAAc,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC;QACnE,MAAM,WAAW,GAAW,MAAM,+BAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC;QACtF,KAAK,IAAI,CAAC;QACV,KAAK,WAAW,CAAC;IACrB,CAAC;CACJ;AAvFD,wEAuFC","sourcesContent":["import { AnyContextKey, ContextKey } from '@webpieces/core-util';\nimport { RequestContext } from './RequestContext';\n\n/**\n * COMPILE-TIME assertions for {@link RequestContext.runDetachedScope}.\n *\n * The runtime half — that the detached scope starts empty, that the enclosing scope survives, that a\n * throw unwinds it — is in `DetachedScope.spec.ts`. What a spec CANNOT express is the half that makes\n * the browser-log path safe BY CONSTRUCTION rather than by remembering to filter:\n *\n * - `runDetachedScope` has NO container-taking form, so the deleted `runWithContext(map, fn)` forgery\n * path cannot come back through this door;\n * - a loop over a mixed `AnyContextKey[]` cannot write ANY key until it has tested that key's trust;\n * - and a TRUSTED key can never reach `putUntrusted`, so a browser — which proves nothing — cannot\n * FABRICATE a proven value by naming `userId` in its payload. It is a compile error at the write,\n * not a filter someone has to remember to write.\n *\n * That is about the SOURCE, not about the key. Writing a trusted key is ordinary and legitimate — see\n * {@link putTrustedIsLegitimateInsideADetachedScope} — whenever the caller has actually proven the\n * value. What cannot be written down is a claim of proof by code that has none.\n *\n * In COMPILED source deliberately — `tsconfig.lib.json` excludes specs and vitest strips types with\n * esbuild, so a `@ts-expect-error` in a `.spec.ts` is inert and the suite would pass either way. Each\n * one below fails the build with TS2578 the day its line starts compiling. See\n * `CapturedContextCompileAssertions` and `RequestContextTrustCompileAssertions` for the sibling halves.\n */\nexport class DetachedScopeCompileAssertions {\n private readonly trusted = ContextKey.trusted<string>('assertDetachedUserId', 'jwt claim `sub`');\n private readonly untrusted = ContextKey.untrusted<string>('assertDetachedActionId');\n\n /**\n * THE hole this whole shape exists to keep shut: no Map/object/array of entries may cross the\n * boundary. Values are written INSIDE the closure, through the trust verbs.\n */\n cannotHandItAContainerOfEntries(): void {\n // @ts-expect-error - runDetachedScope takes ONLY a closure; there is no map-taking form\n RequestContext.runDetachedScope(new Map([['userId', 'victim']]), () => undefined);\n }\n\n /** Nor a plain object of entries, which is the same hole spelled differently. */\n cannotHandItAnObjectOfEntries(): void {\n // @ts-expect-error - the single parameter is the closure, not a bag of values\n RequestContext.runDetachedScope({ userId: 'victim' });\n }\n\n /** A key of unknown trust cannot be written AT ALL — the branch is not optional. */\n cannotWriteAMixedKeyWithoutTestingItsTrust(key: AnyContextKey): void {\n RequestContext.runDetachedScope(() => {\n // @ts-expect-error - putUntrusted needs a key KNOWN to be untrusted; AnyContextKey is mixed\n RequestContext.putUntrusted(key, 'from-the-browser');\n });\n }\n\n /**\n * And a TRUSTED key cannot be LAUNDERED through the untrusted verb, detached or not — which is\n * what a caller with no proof would have to do, since `putTrusted` is the only other way in and\n * saying it is a deliberate, greppable claim.\n */\n cannotLaunderATrustedKeyThroughTheUntrustedVerb(): void {\n RequestContext.runDetachedScope(() => {\n // @ts-expect-error - putUntrusted does not accept a trusted key\n RequestContext.putUntrusted(this.trusted, 'browser-said-so');\n });\n }\n\n /**\n * POSITIVE, and the point the negative above must not be mistaken for: writing a TRUSTED value\n * inside a detached scope is ordinary and correct when the caller has actually proven it — the\n * signed-webhook case (Twilio/WhatsApp proves the phone number, the app looks up the userId), or a\n * verified JWT claim. `putTrusted` is the verb for exactly that, and a detached scope does not\n * change it. Trust is about tamper-resistance, not secrecy: a trusted `userId` is a plain,\n * fully-logged GUID; masking is the separate `maskInLogs` axis.\n */\n putTrustedIsLegitimateInsideADetachedScope(): void {\n RequestContext.runDetachedScope(() => {\n RequestContext.putTrusted(this.trusted, 'proven-out-of-band');\n const proven: string | undefined = RequestContext.getTrusted(this.trusted);\n void proven;\n });\n }\n\n /**\n * POSITIVE, and the shape the real consumer writes: emit one browser line under a fresh scope\n * rebuilt from an EXTERNAL payload, driven off the registry's logged keys.\n *\n * The `isTrusted()` skip is what a reader sees; the compiler is what enforces it. Delete the skip\n * and `isUntrusted()` still refuses the trusted key, so the trusted branch has no write at all.\n */\n // webpieces-disable no-any-unknown -- the EXTERNAL payload is by definition untyped JSON off the wire; that is precisely why every value below is trust-branched and typeof-checked before it is written\n theBrowserLogLoopCompiles(loggedKeys: AnyContextKey[], payload: Record<string, unknown>): void {\n RequestContext.runDetachedScope(() => {\n for (const key of loggedKeys) {\n if (key.isTrusted()) {\n // A browser cannot vouch for a proven fact. There is no write in this branch, and\n // no cast that could produce one.\n continue;\n }\n const value = payload[key.name];\n if (key.isUntrusted() && typeof value === 'string') {\n RequestContext.putUntrusted(key, value);\n }\n }\n RequestContext.putUntrusted(this.untrusted, 'click-7');\n });\n }\n\n /** The return value flows through, and an async closure is a promise the caller can await. */\n async returnValuesFlowThrough(): Promise<void> {\n const sync: string = RequestContext.runDetachedScope(() => 'done');\n const asyncResult: string = await RequestContext.runDetachedScope(async () => 'done');\n void sync;\n void asyncResult;\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { ContextKey, AnyContextKey } from '@webpieces/core-util';
2
2
  import { HttpRequest } from './HttpRequest';
3
+ import { CapturedContext, RestorableContext } from './CapturedContext';
3
4
  /**
4
5
  * Context management using AsyncLocalStorage.
5
6
  * Similar to Java WebPieces Context class that uses ThreadLocal.
@@ -28,20 +29,100 @@ declare class RequestContextImpl {
28
29
  * With this guard the setup is right or it is loud. It mirrors
29
30
  * `RequestContextHeaders.fillFromRequest()`, which throws when there is NO active scope.
30
31
  *
32
+ * If you genuinely WANT a fresh empty scope inside an active one — work that must not inherit the
33
+ * surrounding request's actionId/requestId — that is {@link runDetachedScope}, which says so by
34
+ * name. This guard exists to stop the ACCIDENTAL empty scope, not the deliberate one.
35
+ *
31
36
  * @throws Error when a RequestContext is already active.
32
37
  */
33
38
  run<T>(fn: () => T): T;
34
39
  /**
35
- * Run a function with a specific context — the restore half of {@link copyContext}, used to carry
36
- * a context across an async boundary (XPromise).
40
+ * Open a NEW scope pre-loaded with a snapshot — the restore half of {@link copyContext}, for work
41
+ * whose async chain was broken and re-rooted elsewhere (a queued job drained by a background loop,
42
+ * a batch flushed on a timer, an event listener fired from a socket the request does not own). See
43
+ * {@link CapturedContext} for the full list and for why the payload is opaque.
44
+ *
45
+ * A restored context legitimately contains TRUSTED values — reinstating what the original scope
46
+ * proved is the entire point — so this cannot type-check its contents the way the trust verbs do.
47
+ * The guarantee instead comes from the PAYLOAD: a {@link RestorableContext} can only be narrowed
48
+ * out of a {@link CapturedContext}, which only {@link copyContext} produces, so there is no
49
+ * hand-assembled Map to hand it and no way to forge one.
50
+ *
51
+ * The caller must SAY whether the proven identity travels — `snapshot.withTrusted()` (runs as that
52
+ * user) or `snapshot.withoutTrusted()` (runs as the system, keeping only the trace fields). A bare
53
+ * `CapturedContext` is deliberately not accepted; see {@link CapturedContext} for the three-case
54
+ * table and why the wide branch is spelled out rather than defaulted.
55
+ *
56
+ * The snapshot is copied into a fresh store, so writes inside `fn` stay inside `fn` and the
57
+ * snapshot stays reusable.
58
+ *
59
+ * Deliberately NOT guarded against nesting the way {@link run} is. `run`'s guard exists because a
60
+ * second EMPTY scope shadowing the first is always a bug; here the inner scope is a faithful copy
61
+ * of a real one, which is the whole point — a worker that restores a snapshot inside a scope it
62
+ * opened per job is correct, not a mistake. Prefer this over {@link restoreContext} unless you
63
+ * specifically need the CURRENT scope overwritten in place.
64
+ */
65
+ runWithContext<T>(captured: RestorableContext, fn: () => T): T;
66
+ /**
67
+ * Open a FRESH, EMPTY, NESTED scope. Nothing is inherited from the enclosing scope, and nothing
68
+ * crosses the boundary as data — every value the work runs under is WRITTEN INSIDE `fn`, through
69
+ * the ordinary trust-typed verbs:
70
+ *
71
+ * ```typescript
72
+ * RequestContext.runDetachedScope(() => {
73
+ * RequestContext.putUntrusted(WebpiecesCoreHeaders.ACTION_ID, line.actionId);
74
+ * emit(); // runs under exactly what this closure wrote, and nothing else
75
+ * });
76
+ * ```
77
+ *
78
+ * ## When you want THIS and not {@link runWithContext}
79
+ *
80
+ * The two look similar and are opposites. `runWithContext` faithfully RE-ROOTS a real snapshot of a
81
+ * real scope, for work whose async chain was broken (a queued job, a timer flush) — it exists to
82
+ * PRESERVE a context. This one exists to DISCARD one: the values do not come from any scope this
83
+ * process ever had, they were reconstructed from somewhere else, and inheriting the ambient scope
84
+ * would be actively wrong.
85
+ *
86
+ * The live case is a browser-log shipper. A batch of browser lines arrives on one HTTP request; each
87
+ * line carries the context the BROWSER captured when it was written, and a single batch routinely
88
+ * spans several user actions. Emitting a line under the shipping request's own scope would stamp
89
+ * every line with that request's actionId and requestId, silently destroying the ability to grep an
90
+ * action while the feature still appeared to work. So each line is emitted detached, under exactly
91
+ * the keys the closure re-stated from the browser's payload.
37
92
  *
38
- * FRAMEWORK-INTERNAL. Unlike the raw string accessors, this deliberately CANNOT be guarded against
39
- * registered key names: a restored context legitimately contains trusted values, since restoring
40
- * them is the entire purpose. So the guarantee here is narrower and worth stating plainly — the
41
- * Map must be one this class produced via `copyContext()`, never one assembled by hand. Handing it
42
- * a hand-built Map forges whatever it contains, and no type or check will stop you.
93
+ * ## Why it MAY nest when {@link run} may not
94
+ *
95
+ * `run`'s nesting guard is right and is not softened here. It refuses a second EMPTY scope because
96
+ * there an empty scope is always an ACCIDENT a transport opening the request scope twice, whose
97
+ * only effect is to hide the outer scope's values and mint a second request id. Here an empty scope
98
+ * is the thing that was ASKED for, in a distinctly-named verb, and the caller is normally already
99
+ * inside a request scope (the shipper's own). A guard would refuse the only situation the method has.
100
+ *
101
+ * ## No Map-taking form, ever
102
+ *
103
+ * There is deliberately no overload accepting a `Map`, an object, or an array of entries. That was
104
+ * the DELETED `runWithContext(map, fn)`, and it was a forgery path: a hand-built map is
105
+ * indistinguishable from a genuine snapshot, so `new Map([['userId','victim']])` minted a proven
106
+ * identity in one line without ever typing a trust verb. Writing the values INSIDE the closure is
107
+ * what closes it — a loop over a mixed `AnyContextKey[]` must branch on `key.isTrusted()` before it
108
+ * can write anything, and `putUntrusted` does not compile for a trusted key, so code fed by a
109
+ * BROWSER (which proves nothing) cannot fabricate a proven value. (See
110
+ * `DetachedScopeCompileAssertions`.)
111
+ *
112
+ * That is a limit on the SOURCE, not on the key. `putTrusted` inside a detached scope is ordinary
113
+ * and correct whenever the caller has actually proven the value — a verified JWT claim, or the
114
+ * signed-webhook case where Twilio/WhatsApp proves the phone number and the app looks up the
115
+ * userId. Trust is tamper-resistance, not secrecy (a trusted `userId` is a plain, fully-logged
116
+ * GUID; redaction is the separate `maskInLogs` axis on the key).
117
+ *
118
+ * SYNC AND ASYNC BOTH: `fn` may return a promise, and the detached scope follows every `await`
119
+ * inside it exactly as `run`/`runWithContext` do — same `AsyncLocalStorage.run` underneath. The
120
+ * enclosing scope is reinstated for everything after the synchronous return, INCLUDING when `fn`
121
+ * throws (AsyncLocalStorage unwinds the store as the frame unwinds); an async `fn` that is not
122
+ * awaited will therefore keep the detached scope for its own continuation while the caller has
123
+ * already resumed under the enclosing one, which is the intended and only sane reading of "detached".
43
124
  */
44
- runWithContext<T>(context: Map<string, any>, fn: () => T): T;
125
+ runDetachedScope<T>(fn: () => T): T;
45
126
  /**
46
127
  * Read a value the framework PROVED — a verified JWT claim, or a fact an app derived from a
47
128
  * verified credential. Does not compile for an untrusted key, so a reader can never mistake a
@@ -193,22 +274,38 @@ declare class RequestContextImpl {
193
274
  */
194
275
  clear(): void;
195
276
  /**
196
- * Copy the current context to a new Map.
197
- * Used by XPromise to preserve context across async boundaries.
198
- */
199
- copyContext(): Map<string, any>;
200
- /**
201
- * Set the entire context from a Map. Used by XPromise to restore context.
277
+ * Snapshot this scope so the work you are about to hand off keeps its request id, log fields and
278
+ * proven identity. The ONLY producer of a {@link CapturedContext} — which is what makes the
279
+ * restore side unforgeable, since there is no other way to obtain the payload it accepts.
280
+ *
281
+ * Outside a `run(...)` block this returns an EMPTY snapshot rather than throwing: capturing "no
282
+ * context" is a legitimate thing for a background caller to do, and restoring it simply installs
283
+ * nothing.
202
284
  *
203
- * Same FRAMEWORK-INTERNAL caveat as {@link runWithContext}: the Map must have come from
204
- * `copyContext()`. It cannot be trust-checked, because a faithful restore has to reinstate the
205
- * trusted values the original scope had proven.
285
+ * The snapshot is a defensive COPY writes to this context after capturing do not reach it.
206
286
  */
207
- setContext(context: Map<string, any>): void;
287
+ copyContext(): CapturedContext;
208
288
  /**
209
- * Get all context entries.
289
+ * Overwrite the ACTIVE scope with a snapshot. The in-place twin of {@link runWithContext}, and the
290
+ * one you almost never want: prefer `runWithContext`, which gives the restored work its OWN scope
291
+ * and cannot disturb the caller's. Reach for this only when something else owns the scope and it
292
+ * must be re-pointed in place.
293
+ *
294
+ * OVERWRITE, not merge — `clear()` runs first, so every entry the active scope holds and the
295
+ * snapshot does not is DROPPED. That includes the empty case:
296
+ * `restoreContext(copyContext().withTrusted())` taken outside a scope wipes the request id and
297
+ * every proven identity from a live request, and says nothing. That is faithful (a snapshot
298
+ * restores exactly what it captured) but it is the sharp edge of this method and the reason
299
+ * `runWithContext` is the default.
300
+ *
301
+ * Takes only a {@link RestorableContext} for the reason spelled out there — the DELETED Map-taking
302
+ * form let `new Map([['userId','victim']])` forge a proven identity in one line — and that type
303
+ * exists only via `withTrusted()` / `withoutTrusted()`, so this call site states whether the proven
304
+ * identity survives the re-point.
305
+ *
306
+ * @throws Error when no RequestContext is active.
210
307
  */
211
- getAll(): Map<string, any>;
308
+ restoreContext(captured: RestorableContext): void;
212
309
  /**
213
310
  * Check if a key exists in the context.
214
311
  */