ccqa-tools 1.37.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.
Files changed (38) hide show
  1. package/README.md +177 -0
  2. package/dist/coverage/collector.cjs +229 -0
  3. package/dist/coverage/collector.d.cts +183 -0
  4. package/dist/coverage/collector.d.ts +183 -0
  5. package/dist/coverage/collector.js +225 -0
  6. package/dist/coverage/core.cjs +176 -0
  7. package/dist/coverage/core.d.cts +157 -0
  8. package/dist/coverage/core.d.ts +157 -0
  9. package/dist/coverage/core.js +163 -0
  10. package/dist/coverage/middleware.cjs +161 -0
  11. package/dist/coverage/middleware.d.cts +23 -0
  12. package/dist/coverage/middleware.d.ts +23 -0
  13. package/dist/coverage/middleware.js +158 -0
  14. package/dist/coverage/next-loader.cjs +158 -0
  15. package/dist/coverage/next-loader.d.cts +18 -0
  16. package/dist/coverage/next-loader.d.ts +19 -0
  17. package/dist/coverage/next-loader.js +158 -0
  18. package/dist/coverage/next.cjs +101 -0
  19. package/dist/coverage/next.d.cts +33 -0
  20. package/dist/coverage/next.d.ts +33 -0
  21. package/dist/coverage/next.js +100 -0
  22. package/dist/coverage/register.cjs +741 -0
  23. package/dist/coverage/register.d.cts +1 -0
  24. package/dist/coverage/register.d.ts +1 -0
  25. package/dist/coverage/register.js +716 -0
  26. package/dist/coverage/slack.cjs +228 -0
  27. package/dist/coverage/slack.d.cts +47 -0
  28. package/dist/coverage/slack.d.ts +47 -0
  29. package/dist/coverage/slack.js +225 -0
  30. package/dist/coverage/temporal-workflow.cjs +154 -0
  31. package/dist/coverage/temporal-workflow.d.cts +28 -0
  32. package/dist/coverage/temporal-workflow.d.ts +28 -0
  33. package/dist/coverage/temporal-workflow.js +153 -0
  34. package/dist/coverage/temporal.cjs +253 -0
  35. package/dist/coverage/temporal.d.cts +40 -0
  36. package/dist/coverage/temporal.d.ts +40 -0
  37. package/dist/coverage/temporal.js +250 -0
  38. package/package.json +95 -0
@@ -0,0 +1,154 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _temporalio_workflow = require("@temporalio/workflow");
3
+ //#region src/coverage/wire.ts
4
+ /** Temporal header key, for the hop from client to workflow to activity. */
5
+ const TEMPORAL_HEADER = "ccqa-tools";
6
+ /**
7
+ * `<provider>:<identity>` — who caused a request, for the requests that cannot
8
+ * say which spec they belong to.
9
+ *
10
+ * The application stamps this and nothing else: which spec an identity was
11
+ * acting for at a given instant is decided by the run, which is the only side
12
+ * that knows. Nothing about the mapping is ever sent back here.
13
+ */
14
+ const ACTOR_TAG = /^[A-Za-z0-9][A-Za-z0-9._-]*:\S{1,200}$/;
15
+ function parseActorTag(raw) {
16
+ if (!raw) return void 0;
17
+ const value = raw.trim();
18
+ return ACTOR_TAG.test(value) ? value : void 0;
19
+ }
20
+ /** Reads a mark off the wire, refusing anything that is not one of the two shapes. */
21
+ function parseMark(value) {
22
+ if (typeof value === "string") {
23
+ const spec = parseSpecId(value);
24
+ return spec === void 0 ? void 0 : { spec };
25
+ }
26
+ if (typeof value !== "object" || value === null) return void 0;
27
+ const { tag, at } = value;
28
+ if (typeof at !== "number" || !Number.isFinite(at)) return void 0;
29
+ const parsed = parseActorTag(typeof tag === "string" ? tag : void 0);
30
+ return parsed === void 0 ? void 0 : {
31
+ tag: parsed,
32
+ at
33
+ };
34
+ }
35
+ const SPEC_ID = /^[A-Za-z0-9._\-/]{1,200}$/;
36
+ /**
37
+ * Accepts a carrier value only if it looks like an id we wrote.
38
+ *
39
+ * The cookie is client-controlled, so this is the first of two gates: the
40
+ * second is the hub refusing runs it never started.
41
+ */
42
+ function parseSpecId(raw) {
43
+ if (!raw) return void 0;
44
+ const value = raw.trim();
45
+ if (!SPEC_ID.test(value)) return void 0;
46
+ if (value === "1" || value === "true") return void 0;
47
+ return value;
48
+ }
49
+ //#endregion
50
+ //#region src/coverage/temporal/header.ts
51
+ /**
52
+ * Reads and writes the one Temporal header this package uses.
53
+ *
54
+ * The payload is built by hand rather than through `defaultPayloadConverter` so
55
+ * that the workflow half of the integration stays free of Temporal imports —
56
+ * the workflow sandbox is picky, and a plain `json/plain` string is exactly
57
+ * what the default converter would have produced anyway.
58
+ */
59
+ const ENCODING = "json/plain";
60
+ const textEncoder = new TextEncoder();
61
+ const textDecoder = new TextDecoder();
62
+ /**
63
+ * A spec travels as a bare string and an identity as an object, so the two are
64
+ * told apart by shape rather than by a discriminator nobody else would read.
65
+ */
66
+ function toHeader(mark) {
67
+ const value = "spec" in mark ? mark.spec : {
68
+ tag: mark.tag,
69
+ at: mark.at
70
+ };
71
+ return { [TEMPORAL_HEADER]: {
72
+ metadata: { encoding: encode(ENCODING) },
73
+ data: encode(JSON.stringify(value))
74
+ } };
75
+ }
76
+ /** The raw value off the header. Callers validate it with `parseMark`. */
77
+ function fromHeader(headers) {
78
+ const payload = headers?.[TEMPORAL_HEADER];
79
+ if (!payload?.data) return void 0;
80
+ try {
81
+ return JSON.parse(decode(payload.data));
82
+ } catch {
83
+ return;
84
+ }
85
+ }
86
+ function encode(value) {
87
+ return textEncoder.encode(value);
88
+ }
89
+ function decode(value) {
90
+ return textDecoder.decode(value);
91
+ }
92
+ //#endregion
93
+ //#region src/coverage/temporal/workflow.ts
94
+ /**
95
+ * The workflow hop, kept in its own module because Temporal evaluates it inside
96
+ * the deterministic sandbox. Nothing here may reach for a Node built-in — not
97
+ * even transitively — which rules out importing `core.ts`'s neighbours.
98
+ *
99
+ * Register it with the worker:
100
+ *
101
+ * interceptors: { workflowModules: ["ccqa-tools/coverage/temporal/workflow"] }
102
+ *
103
+ * or, for a pre-built bundle, pass the same specifier to
104
+ * `bundleWorkflowCode({ workflowInterceptorModules })`.
105
+ */
106
+ /**
107
+ * One entry per workflow execution.
108
+ *
109
+ * A module-level variable would leak between workflows: with `reuseV8Context`
110
+ * (the worker default) many executions share a V8 isolate and interleave at
111
+ * every await. Keying on `runId` gives each execution its own slot, and
112
+ * `execute`'s `finally` removes it.
113
+ */
114
+ const markByRunId = /* @__PURE__ */ new Map();
115
+ function propagate(input) {
116
+ const mark = markByRunId.get((0, _temporalio_workflow.workflowInfo)().runId);
117
+ if (mark === void 0) return input;
118
+ return {
119
+ ...input,
120
+ headers: {
121
+ ...input.headers,
122
+ ...toHeader(mark)
123
+ }
124
+ };
125
+ }
126
+ const interceptors = () => {
127
+ const propagator = {
128
+ async execute(input, next) {
129
+ const mark = parseMark(fromHeader(input.headers));
130
+ const { runId } = (0, _temporalio_workflow.workflowInfo)();
131
+ if (mark !== void 0) markByRunId.set(runId, mark);
132
+ try {
133
+ return await next(input);
134
+ } finally {
135
+ markByRunId.delete(runId);
136
+ }
137
+ },
138
+ scheduleActivity(input, next) {
139
+ return next(propagate(input));
140
+ },
141
+ scheduleLocalActivity(input, next) {
142
+ return next(propagate(input));
143
+ },
144
+ startChildWorkflowExecution(input, next) {
145
+ return next(propagate(input));
146
+ }
147
+ };
148
+ return {
149
+ inbound: [propagator],
150
+ outbound: [propagator]
151
+ };
152
+ };
153
+ //#endregion
154
+ exports.interceptors = interceptors;
@@ -0,0 +1,28 @@
1
+ //#region src/coverage/temporal/header.d.ts
2
+ interface TemporalPayload {
3
+ metadata?: Record<string, Uint8Array> | null;
4
+ data?: Uint8Array | null;
5
+ }
6
+ type TemporalHeaders = Record<string, TemporalPayload | undefined>;
7
+ //#endregion
8
+ //#region src/coverage/temporal/workflow.d.ts
9
+ interface WithHeaders {
10
+ headers: TemporalHeaders;
11
+ }
12
+ type Next<I, O> = (input: I) => O;
13
+ declare const interceptors: () => {
14
+ inbound: {
15
+ execute<I extends WithHeaders, O>(input: I, next: Next<I, Promise<O>>): Promise<O>;
16
+ scheduleActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
17
+ scheduleLocalActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
18
+ startChildWorkflowExecution<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
19
+ }[];
20
+ outbound: {
21
+ execute<I extends WithHeaders, O>(input: I, next: Next<I, Promise<O>>): Promise<O>;
22
+ scheduleActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
23
+ scheduleLocalActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
24
+ startChildWorkflowExecution<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
25
+ }[];
26
+ };
27
+ //#endregion
28
+ export { interceptors };
@@ -0,0 +1,28 @@
1
+ //#region src/coverage/temporal/header.d.ts
2
+ interface TemporalPayload {
3
+ metadata?: Record<string, Uint8Array> | null;
4
+ data?: Uint8Array | null;
5
+ }
6
+ type TemporalHeaders = Record<string, TemporalPayload | undefined>;
7
+ //#endregion
8
+ //#region src/coverage/temporal/workflow.d.ts
9
+ interface WithHeaders {
10
+ headers: TemporalHeaders;
11
+ }
12
+ type Next<I, O> = (input: I) => O;
13
+ declare const interceptors: () => {
14
+ inbound: {
15
+ execute<I extends WithHeaders, O>(input: I, next: Next<I, Promise<O>>): Promise<O>;
16
+ scheduleActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
17
+ scheduleLocalActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
18
+ startChildWorkflowExecution<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
19
+ }[];
20
+ outbound: {
21
+ execute<I extends WithHeaders, O>(input: I, next: Next<I, Promise<O>>): Promise<O>;
22
+ scheduleActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
23
+ scheduleLocalActivity<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
24
+ startChildWorkflowExecution<I extends WithHeaders, O>(input: I, next: Next<I, O>): O;
25
+ }[];
26
+ };
27
+ //#endregion
28
+ export { interceptors };
@@ -0,0 +1,153 @@
1
+ import { workflowInfo } from "@temporalio/workflow";
2
+ //#region src/coverage/wire.ts
3
+ /** Temporal header key, for the hop from client to workflow to activity. */
4
+ const TEMPORAL_HEADER = "ccqa-tools";
5
+ /**
6
+ * `<provider>:<identity>` — who caused a request, for the requests that cannot
7
+ * say which spec they belong to.
8
+ *
9
+ * The application stamps this and nothing else: which spec an identity was
10
+ * acting for at a given instant is decided by the run, which is the only side
11
+ * that knows. Nothing about the mapping is ever sent back here.
12
+ */
13
+ const ACTOR_TAG = /^[A-Za-z0-9][A-Za-z0-9._-]*:\S{1,200}$/;
14
+ function parseActorTag(raw) {
15
+ if (!raw) return void 0;
16
+ const value = raw.trim();
17
+ return ACTOR_TAG.test(value) ? value : void 0;
18
+ }
19
+ /** Reads a mark off the wire, refusing anything that is not one of the two shapes. */
20
+ function parseMark(value) {
21
+ if (typeof value === "string") {
22
+ const spec = parseSpecId(value);
23
+ return spec === void 0 ? void 0 : { spec };
24
+ }
25
+ if (typeof value !== "object" || value === null) return void 0;
26
+ const { tag, at } = value;
27
+ if (typeof at !== "number" || !Number.isFinite(at)) return void 0;
28
+ const parsed = parseActorTag(typeof tag === "string" ? tag : void 0);
29
+ return parsed === void 0 ? void 0 : {
30
+ tag: parsed,
31
+ at
32
+ };
33
+ }
34
+ const SPEC_ID = /^[A-Za-z0-9._\-/]{1,200}$/;
35
+ /**
36
+ * Accepts a carrier value only if it looks like an id we wrote.
37
+ *
38
+ * The cookie is client-controlled, so this is the first of two gates: the
39
+ * second is the hub refusing runs it never started.
40
+ */
41
+ function parseSpecId(raw) {
42
+ if (!raw) return void 0;
43
+ const value = raw.trim();
44
+ if (!SPEC_ID.test(value)) return void 0;
45
+ if (value === "1" || value === "true") return void 0;
46
+ return value;
47
+ }
48
+ //#endregion
49
+ //#region src/coverage/temporal/header.ts
50
+ /**
51
+ * Reads and writes the one Temporal header this package uses.
52
+ *
53
+ * The payload is built by hand rather than through `defaultPayloadConverter` so
54
+ * that the workflow half of the integration stays free of Temporal imports —
55
+ * the workflow sandbox is picky, and a plain `json/plain` string is exactly
56
+ * what the default converter would have produced anyway.
57
+ */
58
+ const ENCODING = "json/plain";
59
+ const textEncoder = new TextEncoder();
60
+ const textDecoder = new TextDecoder();
61
+ /**
62
+ * A spec travels as a bare string and an identity as an object, so the two are
63
+ * told apart by shape rather than by a discriminator nobody else would read.
64
+ */
65
+ function toHeader(mark) {
66
+ const value = "spec" in mark ? mark.spec : {
67
+ tag: mark.tag,
68
+ at: mark.at
69
+ };
70
+ return { [TEMPORAL_HEADER]: {
71
+ metadata: { encoding: encode(ENCODING) },
72
+ data: encode(JSON.stringify(value))
73
+ } };
74
+ }
75
+ /** The raw value off the header. Callers validate it with `parseMark`. */
76
+ function fromHeader(headers) {
77
+ const payload = headers?.[TEMPORAL_HEADER];
78
+ if (!payload?.data) return void 0;
79
+ try {
80
+ return JSON.parse(decode(payload.data));
81
+ } catch {
82
+ return;
83
+ }
84
+ }
85
+ function encode(value) {
86
+ return textEncoder.encode(value);
87
+ }
88
+ function decode(value) {
89
+ return textDecoder.decode(value);
90
+ }
91
+ //#endregion
92
+ //#region src/coverage/temporal/workflow.ts
93
+ /**
94
+ * The workflow hop, kept in its own module because Temporal evaluates it inside
95
+ * the deterministic sandbox. Nothing here may reach for a Node built-in — not
96
+ * even transitively — which rules out importing `core.ts`'s neighbours.
97
+ *
98
+ * Register it with the worker:
99
+ *
100
+ * interceptors: { workflowModules: ["ccqa-tools/coverage/temporal/workflow"] }
101
+ *
102
+ * or, for a pre-built bundle, pass the same specifier to
103
+ * `bundleWorkflowCode({ workflowInterceptorModules })`.
104
+ */
105
+ /**
106
+ * One entry per workflow execution.
107
+ *
108
+ * A module-level variable would leak between workflows: with `reuseV8Context`
109
+ * (the worker default) many executions share a V8 isolate and interleave at
110
+ * every await. Keying on `runId` gives each execution its own slot, and
111
+ * `execute`'s `finally` removes it.
112
+ */
113
+ const markByRunId = /* @__PURE__ */ new Map();
114
+ function propagate(input) {
115
+ const mark = markByRunId.get(workflowInfo().runId);
116
+ if (mark === void 0) return input;
117
+ return {
118
+ ...input,
119
+ headers: {
120
+ ...input.headers,
121
+ ...toHeader(mark)
122
+ }
123
+ };
124
+ }
125
+ const interceptors = () => {
126
+ const propagator = {
127
+ async execute(input, next) {
128
+ const mark = parseMark(fromHeader(input.headers));
129
+ const { runId } = workflowInfo();
130
+ if (mark !== void 0) markByRunId.set(runId, mark);
131
+ try {
132
+ return await next(input);
133
+ } finally {
134
+ markByRunId.delete(runId);
135
+ }
136
+ },
137
+ scheduleActivity(input, next) {
138
+ return next(propagate(input));
139
+ },
140
+ scheduleLocalActivity(input, next) {
141
+ return next(propagate(input));
142
+ },
143
+ startChildWorkflowExecution(input, next) {
144
+ return next(propagate(input));
145
+ }
146
+ };
147
+ return {
148
+ inbound: [propagator],
149
+ outbound: [propagator]
150
+ };
151
+ };
152
+ //#endregion
153
+ export { interceptors };
@@ -0,0 +1,253 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/coverage/core.ts
3
+ const RUNTIME_KEY = Symbol.for("ccqa.coverage.runtime");
4
+ function globals() {
5
+ return globalThis;
6
+ }
7
+ /**
8
+ * Memoized so the hot path skips the `globalThis` read. `installRuntime`
9
+ * primes it and never replaces an installed runtime, so once set it never
10
+ * goes stale — but it must stay undefined (and keep re-reading `globalThis`)
11
+ * until then, or a module that finished loading before `register` installs
12
+ * the runtime would be stuck uninstrumented for the rest of the process.
13
+ */
14
+ let cachedRuntime;
15
+ function runtime() {
16
+ if (cachedRuntime === void 0) cachedRuntime = globals()[RUNTIME_KEY];
17
+ return cachedRuntime;
18
+ }
19
+ /** The spec the current async context belongs to, if any. */
20
+ function currentSpecId() {
21
+ return runtime()?.als.getStore()?.specId;
22
+ }
23
+ /**
24
+ * Opens `specId`'s bucket and runs `fn` inside its context. Every entry point —
25
+ * HTTP, Temporal activity, manual — funnels through here.
26
+ *
27
+ * The bucket outlives `fn`: work a request schedules and does not await still
28
+ * belongs to the spec that caused it.
29
+ */
30
+ function runInSpec(specId, fn) {
31
+ const rt = runtime();
32
+ if (rt === void 0) return fn();
33
+ return rt.als.run({
34
+ specId,
35
+ files: openBucket(rt, specId)
36
+ }, fn);
37
+ }
38
+ /**
39
+ * Records that `tag` caused this work at `at`, without deciding whose it is.
40
+ *
41
+ * Carrier wins: a request that already named a spec needs no identity, and
42
+ * overwriting it would replace a fact with something the run still has to
43
+ * interpret. Everything else is recorded whoever it came from — the application
44
+ * is never told which identities are being measured, so it cannot filter, and
45
+ * the run discards the ones it did not ask for.
46
+ */
47
+ function runAsActor(tag, at, fn) {
48
+ const rt = runtime();
49
+ if (rt === void 0) return fn();
50
+ if (rt.als.getStore()?.specId !== void 0) return fn();
51
+ return rt.als.run({
52
+ actor: {
53
+ tag,
54
+ at
55
+ },
56
+ files: openActorBucket(rt, tag, at)
57
+ }, fn);
58
+ }
59
+ /** The identity mark on the current async context, if it has one. */
60
+ function currentActor() {
61
+ return runtime()?.als.getStore()?.actor;
62
+ }
63
+ /** Returns `specId`'s file set, creating it — and arming the gate — if new. */
64
+ function openBucket(runtime, specId) {
65
+ let files = runtime.buckets.get(specId);
66
+ if (files === void 0) {
67
+ files = /* @__PURE__ */ new Set();
68
+ runtime.buckets.set(specId, files);
69
+ armGate(runtime);
70
+ }
71
+ return files;
72
+ }
73
+ /**
74
+ * The key both halves of the collector agree on. A space separates them safely:
75
+ * a spec id can never contain one, so identity keys and spec ids stay disjoint
76
+ * where the collector tracks both in one map.
77
+ */
78
+ function actorBucketKey(tag, at) {
79
+ return `${tag} ${at}`;
80
+ }
81
+ /** Returns the identity's file set for that instant, creating it if new. */
82
+ function openActorBucket(runtime, tag, at) {
83
+ const key = actorBucketKey(tag, at);
84
+ let bucket = runtime.actors.get(key);
85
+ if (bucket === void 0) {
86
+ bucket = {
87
+ tag,
88
+ at,
89
+ files: /* @__PURE__ */ new Set()
90
+ };
91
+ runtime.actors.set(key, bucket);
92
+ armGate(runtime);
93
+ }
94
+ return bucket.files;
95
+ }
96
+ function armGate(runtime) {
97
+ runtime.active = runtime.buckets.size + runtime.actors.size;
98
+ }
99
+ //#endregion
100
+ //#region src/coverage/wire.ts
101
+ /** Temporal header key, for the hop from client to workflow to activity. */
102
+ const TEMPORAL_HEADER = "ccqa-tools";
103
+ /**
104
+ * `<provider>:<identity>` — who caused a request, for the requests that cannot
105
+ * say which spec they belong to.
106
+ *
107
+ * The application stamps this and nothing else: which spec an identity was
108
+ * acting for at a given instant is decided by the run, which is the only side
109
+ * that knows. Nothing about the mapping is ever sent back here.
110
+ */
111
+ const ACTOR_TAG = /^[A-Za-z0-9][A-Za-z0-9._-]*:\S{1,200}$/;
112
+ function parseActorTag(raw) {
113
+ if (!raw) return void 0;
114
+ const value = raw.trim();
115
+ return ACTOR_TAG.test(value) ? value : void 0;
116
+ }
117
+ /** Reads a mark off the wire, refusing anything that is not one of the two shapes. */
118
+ function parseMark(value) {
119
+ if (typeof value === "string") {
120
+ const spec = parseSpecId(value);
121
+ return spec === void 0 ? void 0 : { spec };
122
+ }
123
+ if (typeof value !== "object" || value === null) return void 0;
124
+ const { tag, at } = value;
125
+ if (typeof at !== "number" || !Number.isFinite(at)) return void 0;
126
+ const parsed = parseActorTag(typeof tag === "string" ? tag : void 0);
127
+ return parsed === void 0 ? void 0 : {
128
+ tag: parsed,
129
+ at
130
+ };
131
+ }
132
+ const SPEC_ID = /^[A-Za-z0-9._\-/]{1,200}$/;
133
+ /**
134
+ * Accepts a carrier value only if it looks like an id we wrote.
135
+ *
136
+ * The cookie is client-controlled, so this is the first of two gates: the
137
+ * second is the hub refusing runs it never started.
138
+ */
139
+ function parseSpecId(raw) {
140
+ if (!raw) return void 0;
141
+ const value = raw.trim();
142
+ if (!SPEC_ID.test(value)) return void 0;
143
+ if (value === "1" || value === "true") return void 0;
144
+ return value;
145
+ }
146
+ //#endregion
147
+ //#region src/coverage/temporal/header.ts
148
+ /**
149
+ * Reads and writes the one Temporal header this package uses.
150
+ *
151
+ * The payload is built by hand rather than through `defaultPayloadConverter` so
152
+ * that the workflow half of the integration stays free of Temporal imports —
153
+ * the workflow sandbox is picky, and a plain `json/plain` string is exactly
154
+ * what the default converter would have produced anyway.
155
+ */
156
+ const ENCODING = "json/plain";
157
+ const textEncoder = new TextEncoder();
158
+ const textDecoder = new TextDecoder();
159
+ /**
160
+ * A spec travels as a bare string and an identity as an object, so the two are
161
+ * told apart by shape rather than by a discriminator nobody else would read.
162
+ */
163
+ function toHeader(mark) {
164
+ const value = "spec" in mark ? mark.spec : {
165
+ tag: mark.tag,
166
+ at: mark.at
167
+ };
168
+ return { [TEMPORAL_HEADER]: {
169
+ metadata: { encoding: encode(ENCODING) },
170
+ data: encode(JSON.stringify(value))
171
+ } };
172
+ }
173
+ /** The raw value off the header. Callers validate it with `parseMark`. */
174
+ function fromHeader(headers) {
175
+ const payload = headers?.[TEMPORAL_HEADER];
176
+ if (!payload?.data) return void 0;
177
+ try {
178
+ return JSON.parse(decode(payload.data));
179
+ } catch {
180
+ return;
181
+ }
182
+ }
183
+ function encode(value) {
184
+ return textEncoder.encode(value);
185
+ }
186
+ function decode(value) {
187
+ return textDecoder.decode(value);
188
+ }
189
+ //#endregion
190
+ //#region src/coverage/temporal/index.ts
191
+ /**
192
+ * Carries the spec id across Temporal, whose activities are the one async-job
193
+ * boundary that can be attributed at all: the SDK hands interceptors the same
194
+ * header map on the way out of a client and on the way into an activity.
195
+ *
196
+ * Three hops, three interceptors. The middle one — the workflow — lives in
197
+ * `ccqa-tools/coverage/temporal/workflow` because it is evaluated inside a
198
+ * deterministic sandbox that has no Node built-ins.
199
+ */
200
+ /**
201
+ * Client side: stamps the spec of whatever request is starting the workflow.
202
+ *
203
+ * Both `start` and `signalWithStart` begin executions; covering only one leaves
204
+ * a hole that shows up as an unattributed activity much later.
205
+ */
206
+ function createClientInterceptor() {
207
+ const stamp = (input, next) => {
208
+ const mark = currentMark();
209
+ if (mark === void 0) return next(input);
210
+ return next({
211
+ ...input,
212
+ headers: {
213
+ ...input.headers,
214
+ ...toHeader(mark)
215
+ }
216
+ });
217
+ };
218
+ return {
219
+ start: stamp,
220
+ signalWithStart: stamp
221
+ };
222
+ }
223
+ /**
224
+ * Activity side: reopens the context the work came from. Activities run in an
225
+ * ordinary Node context, so everything the activity touches is attributed from
226
+ * here on.
227
+ *
228
+ * An identity mark keeps the instant it was stamped with rather than taking
229
+ * the current time — an activity may run long after the request that scheduled
230
+ * it, and the run matches on when the work was asked for.
231
+ */
232
+ function createActivityInterceptor() {
233
+ return { execute: (input, next) => {
234
+ const mark = parseMark(fromHeader(input.headers));
235
+ if (mark === void 0) return next(input);
236
+ if ("spec" in mark) return runInSpec(mark.spec, () => next(input));
237
+ return runAsActor(mark.tag, mark.at, () => next(input));
238
+ } };
239
+ }
240
+ /** What the current context has to hand on: its spec, or who caused it. */
241
+ function currentMark() {
242
+ const specId = currentSpecId();
243
+ if (specId !== void 0) return { spec: specId };
244
+ const actor = currentActor();
245
+ return actor === void 0 ? void 0 : {
246
+ tag: actor.tag,
247
+ at: actor.at
248
+ };
249
+ }
250
+ //#endregion
251
+ exports.TEMPORAL_HEADER = TEMPORAL_HEADER;
252
+ exports.createActivityInterceptor = createActivityInterceptor;
253
+ exports.createClientInterceptor = createClientInterceptor;
@@ -0,0 +1,40 @@
1
+ //#region src/coverage/wire.d.ts
2
+ /** Temporal header key, for the hop from client to workflow to activity. */
3
+ declare const TEMPORAL_HEADER = "ccqa-tools";
4
+ //#endregion
5
+ //#region src/coverage/temporal/header.d.ts
6
+ interface TemporalPayload {
7
+ metadata?: Record<string, Uint8Array> | null;
8
+ data?: Uint8Array | null;
9
+ }
10
+ type TemporalHeaders = Record<string, TemporalPayload | undefined>;
11
+ //#endregion
12
+ //#region src/coverage/temporal/index.d.ts
13
+ interface WithHeaders {
14
+ headers: TemporalHeaders;
15
+ }
16
+ type Next<I, O> = (input: I) => O;
17
+ /**
18
+ * Client side: stamps the spec of whatever request is starting the workflow.
19
+ *
20
+ * Both `start` and `signalWithStart` begin executions; covering only one leaves
21
+ * a hole that shows up as an unattributed activity much later.
22
+ */
23
+ declare function createClientInterceptor(): {
24
+ start: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
25
+ signalWithStart: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
26
+ };
27
+ /**
28
+ * Activity side: reopens the context the work came from. Activities run in an
29
+ * ordinary Node context, so everything the activity touches is attributed from
30
+ * here on.
31
+ *
32
+ * An identity mark keeps the instant it was stamped with rather than taking
33
+ * the current time — an activity may run long after the request that scheduled
34
+ * it, and the run matches on when the work was asked for.
35
+ */
36
+ declare function createActivityInterceptor(): {
37
+ execute: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
38
+ };
39
+ //#endregion
40
+ export { TEMPORAL_HEADER, createActivityInterceptor, createClientInterceptor };
@@ -0,0 +1,40 @@
1
+ //#region src/coverage/wire.d.ts
2
+ /** Temporal header key, for the hop from client to workflow to activity. */
3
+ declare const TEMPORAL_HEADER = "ccqa-tools";
4
+ //#endregion
5
+ //#region src/coverage/temporal/header.d.ts
6
+ interface TemporalPayload {
7
+ metadata?: Record<string, Uint8Array> | null;
8
+ data?: Uint8Array | null;
9
+ }
10
+ type TemporalHeaders = Record<string, TemporalPayload | undefined>;
11
+ //#endregion
12
+ //#region src/coverage/temporal/index.d.ts
13
+ interface WithHeaders {
14
+ headers: TemporalHeaders;
15
+ }
16
+ type Next<I, O> = (input: I) => O;
17
+ /**
18
+ * Client side: stamps the spec of whatever request is starting the workflow.
19
+ *
20
+ * Both `start` and `signalWithStart` begin executions; covering only one leaves
21
+ * a hole that shows up as an unattributed activity much later.
22
+ */
23
+ declare function createClientInterceptor(): {
24
+ start: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
25
+ signalWithStart: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
26
+ };
27
+ /**
28
+ * Activity side: reopens the context the work came from. Activities run in an
29
+ * ordinary Node context, so everything the activity touches is attributed from
30
+ * here on.
31
+ *
32
+ * An identity mark keeps the instant it was stamped with rather than taking
33
+ * the current time — an activity may run long after the request that scheduled
34
+ * it, and the run matches on when the work was asked for.
35
+ */
36
+ declare function createActivityInterceptor(): {
37
+ execute: <I extends WithHeaders, O>(input: I, next: Next<I, O>) => O;
38
+ };
39
+ //#endregion
40
+ export { TEMPORAL_HEADER, createActivityInterceptor, createClientInterceptor };