@beehexa/hexasync-template-context 2608.20.18 → 2608.20.31

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 (49) hide show
  1. package/dist/completion.d.ts +80 -0
  2. package/dist/completion.d.ts.map +1 -0
  3. package/dist/completion.js +202 -0
  4. package/dist/completion.js.map +1 -0
  5. package/dist/detect.d.ts +38 -0
  6. package/dist/detect.d.ts.map +1 -0
  7. package/dist/detect.js +35 -0
  8. package/dist/detect.js.map +1 -0
  9. package/dist/environment.d.ts +79 -0
  10. package/dist/environment.d.ts.map +1 -0
  11. package/dist/environment.js +239 -0
  12. package/dist/environment.js.map +1 -0
  13. package/dist/hints.d.ts +62 -0
  14. package/dist/hints.d.ts.map +1 -0
  15. package/dist/hints.js +198 -0
  16. package/dist/hints.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/item.d.ts +87 -0
  22. package/dist/item.d.ts.map +1 -0
  23. package/dist/item.js +222 -0
  24. package/dist/item.js.map +1 -0
  25. package/dist/position.d.ts +36 -0
  26. package/dist/position.d.ts.map +1 -0
  27. package/dist/position.js +31 -0
  28. package/dist/position.js.map +1 -0
  29. package/dist/references.d.ts +60 -0
  30. package/dist/references.d.ts.map +1 -0
  31. package/dist/references.js +217 -0
  32. package/dist/references.js.map +1 -0
  33. package/dist/shape.d.ts +68 -0
  34. package/dist/shape.d.ts.map +1 -0
  35. package/dist/shape.js +23 -0
  36. package/dist/shape.js.map +1 -0
  37. package/dist/stepOutputs.d.ts +52 -0
  38. package/dist/stepOutputs.d.ts.map +1 -0
  39. package/dist/stepOutputs.js +102 -0
  40. package/dist/stepOutputs.js.map +1 -0
  41. package/dist/survival.d.ts +47 -0
  42. package/dist/survival.d.ts.map +1 -0
  43. package/dist/survival.js +50 -0
  44. package/dist/survival.js.map +1 -0
  45. package/dist/walk.d.ts +36 -0
  46. package/dist/walk.d.ts.map +1 -0
  47. package/dist/walk.js +162 -0
  48. package/dist/walk.js.map +1 -0
  49. package/package.json +1 -1
@@ -0,0 +1,217 @@
1
+ import { ALLOWS_NESTING } from './position.js';
2
+ /**
3
+ * Every context path an expression references, and whether the position can provide it — Story 8.6.
4
+ *
5
+ * ⛔ **The known-inert allowlist.** `puller.isInit` appears **79 times** in the corpus (**77** once deduplicated per
6
+ * step) and the string `isInit` appears NOWHERE in `core.api` or `hexasync.worker.proxy`. Every one of those guards
7
+ * is testing an always-falsy value. Whether that is intended is a question for the platform, not for this feature —
8
+ * and 77 findings about a pattern nobody intends to change is noise, not a finding. Decided by Jazz, 2026-08-09:
9
+ * ignore it.
10
+ *
11
+ * ⚠️ Both numbers are correct and they are NOT the same measurement: the corpus spec's `inertSkipped` counts raw
12
+ * occurrences (79), while a report would show one line per step (77). They were conflated in three places until the
13
+ * Epic 8 review separated them — the assertion below is on the string, so it silently accepted either.
14
+ *
15
+ * The allowlist needs a REASON written next to each entry, or it becomes a place findings go to disappear.
16
+ */
17
+ export const KNOWN_INERT = {
18
+ 'puller.isInit': 'Measured 2026-08-09: 79 corpus occurrences (77 deduplicated per step), and `isInit` exists in no runtime. Ignored by decision — the pattern is not one this feature is asking anyone to change.',
19
+ };
20
+ /**
21
+ * Pull every `$( )`, `$$( )` and `{{ }}` payload out of one authored value.
22
+ *
23
+ * ⛔ These were two lazy regexes — `/\{\{([\s\S]*?)\}\}/g` and `/\$\$?\(([\s\S]*?)\)/g` — and CodeQL was
24
+ * right about both (`js/polynomial-redos`, High). A lazy `[\s\S]*?` looking for a closer scans to the END OF
25
+ * THE VALUE from every opener that has no closer after it, so a value with many `{{` and no `}}` is quadratic.
26
+ * Authored values reach this from any project, and it runs per value while typing.
27
+ *
28
+ * Scanning for the delimiters is what the lazy quantifier meant: the payload of an opener is everything up to
29
+ * the FIRST closer after it. Once a closer cannot be found, no later opener can find one either — which is why
30
+ * the loop stops rather than continuing to search, and why this is one pass over the value.
31
+ */
32
+ export function expressionsIn(value) {
33
+ return [...curlyPayloads(value), ...dollarPayloads(value)];
34
+ }
35
+ /** `{{ … }}` — payload up to the first `}}` after the opener. */
36
+ function curlyPayloads(value) {
37
+ const out = [];
38
+ let at = value.indexOf('{{');
39
+ while (at !== -1) {
40
+ const close = value.indexOf('}}', at + 2);
41
+ // No closer after this opener means none after any later one — the lazy regex found nothing either.
42
+ if (close === -1)
43
+ break;
44
+ out.push(value.slice(at + 2, close));
45
+ at = value.indexOf('{{', close + 2);
46
+ }
47
+ return out;
48
+ }
49
+ /**
50
+ * `$( … )` and `$$( … )` — payload up to the first `)` after the opener.
51
+ *
52
+ * The opener is matched at the LAST possible `$`, which is what `\$\$?\(` does: in `$$$(`, the regex matches
53
+ * `$$(` starting at the second `$`, because the first attempt cannot reach the `(`. So a `$` that is not
54
+ * followed by `(` or by `$(` advances by one and is not part of anything.
55
+ */
56
+ function dollarPayloads(value) {
57
+ const out = [];
58
+ let at = value.indexOf('$');
59
+ while (at !== -1) {
60
+ const opens = value.startsWith('$$(', at) ? 3 : value.startsWith('$(', at) ? 2 : 0;
61
+ if (opens === 0) {
62
+ at = value.indexOf('$', at + 1);
63
+ continue;
64
+ }
65
+ const close = value.indexOf(')', at + opens);
66
+ if (close === -1)
67
+ break;
68
+ out.push(value.slice(at + opens, close));
69
+ at = value.indexOf('$', close + 1);
70
+ }
71
+ return out;
72
+ }
73
+ /** The dotted paths inside one expression payload. */
74
+ export function pathsIn(expression) {
75
+ return [
76
+ ...expression.matchAll(/\b([A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)+)/g),
77
+ ].map((match) => match[1]);
78
+ }
79
+ /**
80
+ * Check one path against one environment.
81
+ *
82
+ * ⚠️ Only the ROOT is checked, deliberately. A deeper segment can be legitimately dynamic — a step key composed at
83
+ * run time, an option whose name comes from a connection — and reporting those would bury the one finding that is
84
+ * always true: a root the position does not have cannot resolve, ever, under any composition.
85
+ */
86
+ export function checkPath(path, environment) {
87
+ if (KNOWN_INERT[path] !== undefined)
88
+ return undefined;
89
+ const [root, second] = path.split('.');
90
+ if (!root)
91
+ return undefined;
92
+ if (!environment.roots.has(root)) {
93
+ // ⚠️ Unknown roots are NOT reported. An expression may reference a local variable, a loop binding or a helper,
94
+ // and this engine knows only the context — so "not a context root" is not evidence of "does not exist".
95
+ return undefined;
96
+ }
97
+ /**
98
+ * ⛔ `pusher.lastToken` — the handler gates that write on `root == "puller"`, so it does not exist on a pusher.
99
+ * Measured: **0 corpus occurrences**, so this reports nothing today and guards a mistake that reads exactly like
100
+ * the puller idiom an author has just come from.
101
+ */
102
+ const rootShape = environment.roots.get(root)?.shape;
103
+ if (second !== undefined &&
104
+ rootShape?.kind === 'object' &&
105
+ rootShape.fields &&
106
+ !rootShape.fields.has(second)) {
107
+ const siblings = [...rootShape.fields.keys()];
108
+ return {
109
+ path,
110
+ reason: `\`${root}\` has no \`${second}\`. It has: ${siblings.map((name) => `\`${name}\``).join(', ')}.`,
111
+ available: siblings,
112
+ };
113
+ }
114
+ return undefined;
115
+ }
116
+ /**
117
+ * Which phase's outputs a context segment addresses.
118
+ *
119
+ * ⛔ `history` and `lastToken` are **aliases, not stores** (§2.2): `LegacyStepContextHandler.UpdateContext` writes
120
+ * the phase's outputs to the phase key and then writes *the same object* to the alias. So a step key absent from
121
+ * `before` is absent from `history` too, and one absent from `current` is absent from `lastToken` — on every
122
+ * iteration, because there is only ever one object.
123
+ */
124
+ export const SEGMENT_PHASE = {
125
+ before: 'before',
126
+ current: 'current',
127
+ after: 'after',
128
+ final: 'final',
129
+ history: 'before',
130
+ lastToken: 'current',
131
+ };
132
+ /**
133
+ * A step key that the addressed phase does not contain at all — a typo, or a step someone renamed (§2.3).
134
+ *
135
+ * ⛔ **NOT a forward-reference rule, though AC-2 asked for one.** A key declared LATER still resolves on every
136
+ * iteration after the first: `ExecuteSteps` runs afresh per page with an empty outputs dict, so at the top of page 2
137
+ * `puller.current` still holds page 1's values (§2.4). Measured 2026-08-14: **112** such references in the corpus,
138
+ * and every one is the pagination idiom. A rule with 112 false positives and no true ones does not get triaged, it
139
+ * gets switched off, taking the real class with it. AC-2 was amended to what the corpus supports.
140
+ *
141
+ * ⚠️ **112 and 199 are PRE-FIX figures — they no longer reproduce, and that is expected.** Both were measured before
142
+ * the harness's pusher-stage bug was corrected and before this rule was restricted to depth 1. Re-derived after both,
143
+ * the declared-later class counts ~116 under a phase's own name and ~213 across both arms. They are kept, dated, as
144
+ * the evidence the 2026-08-14 ruling was actually made on; they are not current measurements. The Epic 8 review
145
+ * flagged them as non-reproducing, which is correct and is why they now say so.
146
+ *
147
+ * ⛔ **The alias arm is reported too** (Jazz, 2026-08-14). The 199 → 112 narrowing moved along two axes at once and
148
+ * measured only one: *which key class* (declared-later vs absent-entirely) was evidenced, *which segment* (own name
149
+ * vs alias) never was. The first pass's 199 were suppressed for being declared-later reads, not for being alias
150
+ * reads. Applying this rule under the alias adds **67** occurrences across 12 distinct paths and no false positives
151
+ * — every one a renamed watermark step, e.g. `puller.history.GET_LAST_MODIFIED_DATE` in a component whose
152
+ * `beforePullSteps` declares only `GET_LAST_UPDATED`. Those guards fall to their `else` branch on every run and
153
+ * always have, which is precisely the harm this story was written to catch.
154
+ */
155
+ /**
156
+ * The only roots that own phases. A phase word anywhere else is an ordinary field name.
157
+ *
158
+ * ⛔ Added by the Epic 8 review after TWO reviewers found the same defect independently. The first version scanned
159
+ * every segment from index 1 for a member of `SEGMENT_PHASE`, and the caller gated only on the ROOT existing — so any
160
+ * field called `after`, `before`, `current`, `final`, `history` or `lastToken` under a legitimate root was read as a
161
+ * phase. `__this.arguments.after.value` was reported `high` as *"`after` has no steps at all"*, and the corpus already
162
+ * carries **32** `__this.arguments.after` reads (an `after` pagination cursor) plus 2 of `before` — every one a single
163
+ * authored segment from firing. `item.history.status`, `dependencies.current.foo`, `_outputs.STEP.history.id` and
164
+ * `runStart.before.id` were all the same class.
165
+ *
166
+ * Widening the rule to the alias is what created the exposure: `history` and `lastToken` are ordinary English words,
167
+ * where `beforePullSteps` never collided with anything.
168
+ */
169
+ const PHASE_OWNING_ROOTS = new Set(['puller', 'pusher']);
170
+ export function unknownStepKey(path, keysByPhase) {
171
+ const segments = path.split('.');
172
+ const root = segments[0];
173
+ if (!root || !PHASE_OWNING_ROOTS.has(root))
174
+ return undefined;
175
+ /**
176
+ * ⛔ Depth 1, exactly. Per §2.1-§2.4 and §4 a phase is always addressed directly under its root —
177
+ * `puller.current.STEP`, `pusher.before.STEP`, `puller.history.STEP`. There is no shape in which a phase word is
178
+ * legitimately deeper, so scanning deeper only ever finds a field that happens to share the name.
179
+ */
180
+ const segment = segments[1];
181
+ if (segment === undefined)
182
+ return undefined;
183
+ const phase = SEGMENT_PHASE[segment];
184
+ if (phase === undefined)
185
+ return undefined;
186
+ /**
187
+ * ⛔ `lastToken` is written only when the root is `puller` — the handler gates that write on `root == "puller"`.
188
+ * On a pusher the whole root is wrong, which `checkPath` already reports as `pusher` having no `lastToken`;
189
+ * answering again here would name a phase for a path that never resolves at all.
190
+ */
191
+ if (segment === 'lastToken' && root !== 'puller')
192
+ return undefined;
193
+ const referenced = segments[2];
194
+ if (referenced === undefined)
195
+ return undefined;
196
+ const stepKeys = keysByPhase[phase] ?? [];
197
+ /**
198
+ * ⛔ Self-reference is NOT reported (§2.4). Each pagination iteration calls `ExecuteSteps` afresh, so
199
+ * `puller.lastToken.PULL_DATA.offset` read from inside `PULL_DATA` is the PREVIOUS page's value — the idiom the
200
+ * alias is named for, and 5,933 corpus uses of `puller.lastToken` are built on it. Membership is what keeps it
201
+ * out: the step is in its own phase, so it is never absent.
202
+ */
203
+ if (stepKeys.includes(referenced))
204
+ return undefined;
205
+ const named = segment === phase ? `\`${phase}\`` : `\`${segment}\` (\`${phase}\`)`;
206
+ return {
207
+ path,
208
+ reason: stepKeys.length === 0
209
+ ? `${named} has no steps at all, so \`${referenced}\` resolves to nothing — no error, an empty value.`
210
+ : `${named} has no step called \`${referenced}\`. It has: ${stepKeys.map((key) => `\`${key}\``).join(', ')}. ` +
211
+ 'A step key that is not in the phase resolves to nothing — no error, an empty value.',
212
+ available: stepKeys,
213
+ };
214
+ }
215
+ /** Whether a position can carry a nested path at all — a validation's cannot. */
216
+ export const nestingAllowed = (position) => ALLOWS_NESTING[position];
217
+ //# sourceMappingURL=references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"references.js","sourceRoot":"","sources":["../src/references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * What an author may legally reference at one caret — and nothing else.
3
+ *
4
+ * ### Why a shape rather than a list of strings
5
+ *
6
+ * Four surfaces answer *"what can this path be"* — completion, hover, diagnostics and the agent tool — and every one
7
+ * of them needs a different slice of the same answer: completion needs the keys, hover needs the provenance,
8
+ * diagnostics needs to know a path is absent, and the agent needs the whole thing as data. A list of strings serves
9
+ * the first and lies to the other three.
10
+ *
11
+ * ⛔ **`unknown` carries its reason.** G-8: an alphabet the engine could not enumerate is stated, never silently
12
+ * empty. The distinction is the whole difference between *"this transformation contributes no fields"* and *"this
13
+ * transformation is a `SELECT *` and its fields cannot be known from the document"* — the first invites an author to
14
+ * stop looking, the second tells them where to look. Measured: `SELECT *` is 4.3% of authored SQL (210 of 4,832
15
+ * blocks), so the unknown case is the exception that must be visible, not the default that can be ignored.
16
+ */
17
+ export type ShapeKind = 'object' | 'array' | 'scalar' | 'unknown';
18
+ /** Where a field came from, so hover can say it and an author can go read it. */
19
+ export type Provenance = {
20
+ readonly kind: 'column';
21
+ readonly table?: string;
22
+ readonly dataType?: string;
23
+ } | {
24
+ readonly kind: 'transformation';
25
+ readonly type: string;
26
+ readonly index: number;
27
+ } | {
28
+ readonly kind: 'step';
29
+ readonly stepKey: string;
30
+ readonly phase?: string;
31
+ } | {
32
+ readonly kind: 'hint';
33
+ readonly declaredOn?: string;
34
+ } | {
35
+ readonly kind: 'builtin';
36
+ readonly note?: string;
37
+ };
38
+ export interface Shape {
39
+ readonly kind: ShapeKind;
40
+ /** Present on `object`: the fields, by name. */
41
+ readonly fields?: ReadonlyMap<string, Field>;
42
+ /** Present on `array`: the shape of one element, so `[0].` resolves. */
43
+ readonly element?: Shape;
44
+ /**
45
+ * Present on `unknown`, ALWAYS — G-8. A shape that cannot say why it is unknown is indistinguishable from a bug in
46
+ * the engine, and an author reading "unknown" with no reason learns nothing they did not already know.
47
+ */
48
+ readonly reason?: string;
49
+ /** Present on `scalar` where the document declares one: `string`, `number`, `boolean`, `date`… */
50
+ readonly scalarType?: string;
51
+ }
52
+ export interface Field {
53
+ readonly name: string;
54
+ readonly shape: Shape;
55
+ readonly provenance: Provenance;
56
+ /** The author-facing prose, where the document carries any — a column description, a step's own docs. */
57
+ readonly description?: string;
58
+ }
59
+ export declare const objectShape: (fields: Iterable<Field>) => Shape;
60
+ export declare const arrayShape: (element: Shape) => Shape;
61
+ export declare const scalarShape: (scalarType?: string) => Shape;
62
+ /**
63
+ * ⛔ The reason is REQUIRED by the signature, not by a convention. An optional reason is one a caller forgets, and
64
+ * the forgetting is invisible: the shape still reads `unknown` and the surface still renders it.
65
+ */
66
+ export declare const unknownShape: (reason: string) => Shape;
67
+ export declare const field: (name: string, shape: Shape, provenance: Provenance, description?: string) => Field;
68
+ //# sourceMappingURL=shape.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.d.ts","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElE,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAClB;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC7C,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,kGAAkG;IAClG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,yGAAyG;IACzG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,WAAW,WAAY,QAAQ,CAAC,KAAK,CAAC,KAAG,KAGpD,CAAC;AAEH,eAAO,MAAM,UAAU,YAAa,KAAK,KAAG,KAG1C,CAAC;AAEH,eAAO,MAAM,WAAW,gBAAiB,MAAM,KAAG,KAGd,CAAC;AAErC;;;GAGG;AACH,eAAO,MAAM,YAAY,WAAY,MAAM,KAAG,KAG5C,CAAC;AAEH,eAAO,MAAM,KAAK,SACV,MAAM,SACL,KAAK,cACA,UAAU,gBACR,MAAM,KACnB,KAG2C,CAAC"}
package/dist/shape.js ADDED
@@ -0,0 +1,23 @@
1
+ export const objectShape = (fields) => ({
2
+ kind: 'object',
3
+ fields: new Map([...fields].map((field) => [field.name, field])),
4
+ });
5
+ export const arrayShape = (element) => ({
6
+ kind: 'array',
7
+ element,
8
+ });
9
+ export const scalarShape = (scalarType) => scalarType === undefined
10
+ ? { kind: 'scalar' }
11
+ : { kind: 'scalar', scalarType };
12
+ /**
13
+ * ⛔ The reason is REQUIRED by the signature, not by a convention. An optional reason is one a caller forgets, and
14
+ * the forgetting is invisible: the shape still reads `unknown` and the surface still renders it.
15
+ */
16
+ export const unknownShape = (reason) => ({
17
+ kind: 'unknown',
18
+ reason,
19
+ });
20
+ export const field = (name, shape, provenance, description) => description === undefined
21
+ ? { name, shape, provenance }
22
+ : { name, shape, provenance, description };
23
+ //# sourceMappingURL=shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.js","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAyDA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAuB,EAAS,EAAE,CAAC,CAAC;IAC9D,IAAI,EAAE,QAAQ;IACd,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;CACjE,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAc,EAAS,EAAE,CAAC,CAAC;IACpD,IAAI,EAAE,OAAO;IACb,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,UAAmB,EAAS,EAAE,CACxD,UAAU,KAAK,SAAS;IACtB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE;IACpB,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC;AAErC;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,MAAc,EAAS,EAAE,CAAC,CAAC;IACtD,IAAI,EAAE,SAAS;IACf,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,KAAK,GAAG,CACnB,IAAY,EACZ,KAAY,EACZ,UAAsB,EACtB,WAAoB,EACb,EAAE,CACT,WAAW,KAAK,SAAS;IACvB,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE;IAC7B,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { type Field, type Shape } from './shape.js';
2
+ /**
3
+ * A step's own outputs are EXACTLY its response-filter keys — `CONTEXT-MODEL.md` §7.
4
+ *
5
+ * This is the fact that makes `puller.current.STEP_X.<field>` exact rather than heuristic:
6
+ * `ResponseFilterEngine.HandleResponse` merges every engine block's parsed output into one flat result, so the step's
7
+ * output key set is the union of the keys across its four engine blocks — written in the same file, twenty lines
8
+ * above the caret.
9
+ *
10
+ * ⛔ **Engine order is fixed and asymmetric.** Non-scriban engines run first, in declaration order, each merged into
11
+ * `filters.<engine>.<key>`; **scriban always runs last**. So a scriban expression may read `filters.jsonata.items` —
12
+ * the corpus does — while a jsonata expression may only read an engine declared before it. Getting this backwards
13
+ * would offer an author a `filters.*` path that is empty at the moment their expression runs.
14
+ */
15
+ export declare const ENGINES: readonly ['jsonata', 'jsonpath', 'xpath', 'scriban'];
16
+ export type Engine = (typeof ENGINES)[number];
17
+ /** Scriban last, everything else in declaration order. The ordering rule, as data. */
18
+ export declare function engineOrder(declared: readonly string[]): readonly string[];
19
+ /**
20
+ * The literal keys of a JSONata object constructor, and whether it yields an array.
21
+ *
22
+ * Measured: **34 of 54** authored `Jsonata` blocks (62%) are object constructors with literal keys, and a trailing
23
+ * `[]` forces an array. So `$.{ "id": id, "order_id": orderId }[]` fully determines
24
+ * `…PULL_DATA.items[0].order_id` — the path the corpus actually uses.
25
+ *
26
+ * ⚠️ Deliberately shallow: it reads the literal keys of ONE constructor, and makes no attempt to evaluate the
27
+ * expressions behind them. A key whose value is itself a constructor resolves to `unknown` with its reason, which is
28
+ * the honest answer — the alternative is a parser for a language we do not own.
29
+ */
30
+ export declare function jsonataShape(expression: string): Shape | undefined;
31
+ export interface StepOutputs {
32
+ /** The step's own output keys, merged across engines — what `…current.STEP_X.` offers. */
33
+ readonly shape: Shape;
34
+ /** `filters.<engine>.<key>`, available only INSIDE a response filter. */
35
+ readonly filters: Field;
36
+ /** Keys declared in more than one engine block. Last writer wins, silently — §7. */
37
+ readonly collisions: readonly {
38
+ readonly key: string;
39
+ readonly engines: readonly string[];
40
+ }[];
41
+ }
42
+ /**
43
+ * Read one step's `responseFilters` into the shapes four surfaces need.
44
+ *
45
+ * ⛔ `collisions` is not a nicety. `results.Merge` is last-writer-wins, so a key declared in two engine blocks
46
+ * silently loses one of its two values — a diagnostic nobody has today (Story 8.6), and it can only be seen by
47
+ * comparing the blocks, which is what this does once for everyone.
48
+ */
49
+ export declare function stepOutputs(step: unknown): StepOutputs;
50
+ /** The response headers, a root available only inside a response filter. */
51
+ export declare const HEADERS_ROOT: Field;
52
+ //# sourceMappingURL=stepOutputs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepOutputs.d.ts","sourceRoot":"","sources":["../src/stepOutputs.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,KAAK,EACV,KAAK,KAAK,EACX,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,OAAO,YAAI,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAC5E,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9C,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAG1E;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CA2BlE;AAED,MAAM,WAAW,WAAW;IAC1B,0FAA0F;IAC1F,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC;IACxB,oFAAoF;IACpF,QAAQ,CAAC,UAAU,EAAE,SAAS;QAC5B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC,EAAE,CAAC;CACL;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,CA4EtD;AAED,4EAA4E;AAC5E,eAAO,MAAM,YAAY,OAMxB,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { arrayShape, field, objectShape, unknownShape, } from './shape.js';
2
+ /**
3
+ * A step's own outputs are EXACTLY its response-filter keys — `CONTEXT-MODEL.md` §7.
4
+ *
5
+ * This is the fact that makes `puller.current.STEP_X.<field>` exact rather than heuristic:
6
+ * `ResponseFilterEngine.HandleResponse` merges every engine block's parsed output into one flat result, so the step's
7
+ * output key set is the union of the keys across its four engine blocks — written in the same file, twenty lines
8
+ * above the caret.
9
+ *
10
+ * ⛔ **Engine order is fixed and asymmetric.** Non-scriban engines run first, in declaration order, each merged into
11
+ * `filters.<engine>.<key>`; **scriban always runs last**. So a scriban expression may read `filters.jsonata.items` —
12
+ * the corpus does — while a jsonata expression may only read an engine declared before it. Getting this backwards
13
+ * would offer an author a `filters.*` path that is empty at the moment their expression runs.
14
+ */
15
+ export const ENGINES = ['jsonata', 'jsonpath', 'xpath', 'scriban'];
16
+ /** Scriban last, everything else in declaration order. The ordering rule, as data. */
17
+ export function engineOrder(declared) {
18
+ const nonScriban = declared.filter((engine) => engine !== 'scriban');
19
+ return declared.includes('scriban') ? [...nonScriban, 'scriban'] : nonScriban;
20
+ }
21
+ const record = (value) => typeof value === 'object' && value !== null && !Array.isArray(value)
22
+ ? value
23
+ : undefined;
24
+ /**
25
+ * The literal keys of a JSONata object constructor, and whether it yields an array.
26
+ *
27
+ * Measured: **34 of 54** authored `Jsonata` blocks (62%) are object constructors with literal keys, and a trailing
28
+ * `[]` forces an array. So `$.{ "id": id, "order_id": orderId }[]` fully determines
29
+ * `…PULL_DATA.items[0].order_id` — the path the corpus actually uses.
30
+ *
31
+ * ⚠️ Deliberately shallow: it reads the literal keys of ONE constructor, and makes no attempt to evaluate the
32
+ * expressions behind them. A key whose value is itself a constructor resolves to `unknown` with its reason, which is
33
+ * the honest answer — the alternative is a parser for a language we do not own.
34
+ */
35
+ export function jsonataShape(expression) {
36
+ const open = expression.indexOf('{');
37
+ if (open === -1)
38
+ return undefined;
39
+ const close = expression.lastIndexOf('}');
40
+ if (close <= open)
41
+ return undefined;
42
+ const body = expression.slice(open + 1, close);
43
+ const keys = [...body.matchAll(/"([^"]+)"\s*:/g)].map((match) => match[1]);
44
+ if (keys.length === 0)
45
+ return undefined;
46
+ const shape = objectShape(keys.map((key) => field(key, unknownShape('A JSONata expression — its key is declared here, its value is computed at run time.'), {
47
+ kind: 'builtin',
48
+ })));
49
+ // A trailing `[]` after the constructor forces an array, which is what gives the path its index.
50
+ return /\}\s*\[\s*\]\s*;?\s*$/.test(expression.trimEnd())
51
+ ? arrayShape(shape)
52
+ : shape;
53
+ }
54
+ /**
55
+ * Read one step's `responseFilters` into the shapes four surfaces need.
56
+ *
57
+ * ⛔ `collisions` is not a nicety. `results.Merge` is last-writer-wins, so a key declared in two engine blocks
58
+ * silently loses one of its two values — a diagnostic nobody has today (Story 8.6), and it can only be seen by
59
+ * comparing the blocks, which is what this does once for everyone.
60
+ */
61
+ export function stepOutputs(step) {
62
+ const filters = record(record(step)?.['responseFilters']);
63
+ const declared = Object.keys(filters ?? {}).filter((name) => ENGINES.includes(name));
64
+ const perEngine = new Map();
65
+ const owners = new Map();
66
+ for (const engine of engineOrder(declared)) {
67
+ const block = record(filters?.[engine]);
68
+ const keys = new Map();
69
+ for (const [key, value] of Object.entries(block ?? {})) {
70
+ const expression = record(value)?.['expression'];
71
+ const shape = engine === 'jsonata' && typeof expression === 'string'
72
+ ? (jsonataShape(expression) ??
73
+ unknownShape(`\`${key}\` is a JSONata expression that is not an object constructor, so its keys are computed at run time.`))
74
+ : unknownShape(`\`${key}\` is produced by a ${engine} expression, so its shape is not declared in this document.`);
75
+ keys.set(key, shape);
76
+ owners.set(key, [...(owners.get(key) ?? []), engine]);
77
+ }
78
+ perEngine.set(engine, keys);
79
+ }
80
+ const merged = [];
81
+ for (const [engine, keys] of perEngine) {
82
+ for (const [key, shape] of keys) {
83
+ merged.push(field(key, shape, { kind: 'step', stepKey: key }, `From the ${engine} block.`));
84
+ }
85
+ }
86
+ return {
87
+ shape: merged.length > 0
88
+ ? objectShape(merged)
89
+ : unknownShape('This step declares no response filters, so its output keys are not in this document.'),
90
+ filters: field('filters', objectShape([...perEngine].map(([engine, keys]) => field(engine, objectShape([...keys].map(([key, shape]) => field(key, shape, { kind: 'step', stepKey: key }))), { kind: 'builtin', note: `The ${engine} block's own results.` }))), {
91
+ kind: 'builtin',
92
+ note: 'Available only inside a response filter. Non-scriban engines run first in declaration order; scriban ' +
93
+ 'always runs last, so only scriban can read every other engine.',
94
+ }),
95
+ collisions: [...owners]
96
+ .filter(([, engines]) => engines.length > 1)
97
+ .map(([key, engines]) => ({ key, engines })),
98
+ };
99
+ }
100
+ /** The response headers, a root available only inside a response filter. */
101
+ export const HEADERS_ROOT = field('__headers', unknownShape("The response's headers, keyed as the source system returned them."), { kind: 'builtin' });
102
+ //# sourceMappingURL=stepOutputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stepOutputs.js","sourceRoot":"","sources":["../src/stepOutputs.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,EACL,WAAW,EACX,YAAY,GAGb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAG5E,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,QAA2B;IACrD,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC;IACrE,OAAO,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;AAChF,CAAC;AAED,MAAM,MAAM,GAAG,CAAC,KAAc,EAAuC,EAAE,CACrE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;IAClE,CAAC,CAAE,KAAiC;IACpC,CAAC,CAAC,SAAS,CAAC;AAEhB;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,IAAI,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IAClC,MAAM,KAAK,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IAEpC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC5E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAExC,MAAM,KAAK,GAAG,WAAW,CACvB,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CACf,KAAK,CACH,GAAG,EACH,YAAY,CACV,qFAAqF,CACtF,EACD;QACE,IAAI,EAAE,SAAS;KAChB,CACF,CACF,CACF,CAAC;IACF,iGAAiG;IACjG,OAAO,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;QACvD,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC;QACnB,CAAC,CAAC,KAAK,CAAC;AACZ,CAAC;AAcD;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAC1D,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACzD,OAA6B,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC9C,CAAC;IAEF,MAAM,SAAS,GAAG,IAAI,GAAG,EAA8B,CAAC;IACxD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE3C,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAiB,CAAC;QACtC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;YACvD,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC;YACjD,MAAM,KAAK,GACT,MAAM,KAAK,SAAS,IAAI,OAAO,UAAU,KAAK,QAAQ;gBACpD,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC;oBACzB,YAAY,CACV,KAAK,GAAG,qGAAqG,CAC9G,CAAC;gBACJ,CAAC,CAAC,YAAY,CACV,KAAK,GAAG,uBAAuB,MAAM,6DAA6D,CACnG,CAAC;YACR,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrB,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,SAAS,EAAE,CAAC;QACvC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CACT,KAAK,CACH,GAAG,EACH,KAAK,EACL,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,EAC9B,YAAY,MAAM,SAAS,CAC5B,CACF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,EACH,MAAM,CAAC,MAAM,GAAG,CAAC;YACf,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC;YACrB,CAAC,CAAC,YAAY,CACV,sFAAsF,CACvF;QACP,OAAO,EAAE,KAAK,CACZ,SAAS,EACT,WAAW,CACT,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,CACpC,KAAK,CACH,MAAM,EACN,WAAW,CACT,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAC7B,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAClD,CACF,EACD,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,MAAM,uBAAuB,EAAE,CAChE,CACF,CACF,EACD;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EACF,uGAAuG;gBACvG,gEAAgE;SACnE,CACF;QACD,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC;aACpB,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;aAC3C,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAC/B,WAAW,EACX,YAAY,CACV,mEAAmE,CACpE,EACD,EAAE,IAAI,EAAE,SAAS,EAAE,CACpB,CAAC"}
@@ -0,0 +1,47 @@
1
+ import type { Shape } from './shape.js';
2
+ /**
3
+ * Fields that will not survive the pull — Story 8.7.
4
+ *
5
+ * A puller can produce a field the task has no column for. It is written into the raw payload and it is readable
6
+ * there, so nothing fails — until someone tries to reference it as a column, or a customer asks why it is missing
7
+ * from the destination. The author learns this the slow way today.
8
+ *
9
+ * ⚠️ The finding is *"this survives only inside the raw payload"*, not *"this is wrong"*. Pulling a field you do not
10
+ * store is a legitimate choice — it may exist to be read by a transformation in the same run. What is never
11
+ * legitimate is believing it became a column.
12
+ */
13
+ export interface SurvivalFinding {
14
+ readonly field: string;
15
+ readonly reason: string;
16
+ /** The fix an editor can offer: the column this field would need. */
17
+ readonly addColumn?: {
18
+ readonly table: string;
19
+ readonly name: string;
20
+ readonly dataType: string;
21
+ };
22
+ }
23
+ /**
24
+ * Compare what a puller produces against the columns its task declares.
25
+ *
26
+ * `produced` is the union of the puller's step output keys — Story 8.3 derives it — and `columns` is what
27
+ * `tables[*].columns` declares. A produced key with no column survives only in `__raw`.
28
+ */
29
+ export declare function fieldsThatWillNotSurvive(input: {
30
+ readonly produced: readonly string[];
31
+ readonly columns: readonly string[];
32
+ readonly table?: string;
33
+ }): readonly SurvivalFinding[];
34
+ /**
35
+ * A validation naming a field that is HINTED rather than declared — Story 8.7 AC-3.
36
+ *
37
+ * ⛔ A validation cannot address a non-column at all. `BuildConditionsAsync` interpolates the field name directly
38
+ * into SQL against the task's table, so a hinted field produces a query naming a column that does not exist; and
39
+ * `ValidateAsync` does a flat `TryGetValue`, which reports *"the field is missing from the item"*. Two halves, two
40
+ * different failures, neither of them a message that points at the real cause.
41
+ */
42
+ export declare function validationAgainstNonColumn(input: {
43
+ readonly fieldName: string;
44
+ readonly columns: readonly string[];
45
+ readonly item?: Shape;
46
+ }): SurvivalFinding | undefined;
47
+ //# sourceMappingURL=survival.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"survival.d.ts","sourceRoot":"","sources":["../src/survival.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;GAUG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;KAC3B,CAAC;CACH;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB,GAAG,SAAS,eAAe,EAAE,CAiB7B;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE;IAChD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC;CACvB,GAAG,eAAe,GAAG,SAAS,CAsB9B"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Compare what a puller produces against the columns its task declares.
3
+ *
4
+ * `produced` is the union of the puller's step output keys — Story 8.3 derives it — and `columns` is what
5
+ * `tables[*].columns` declares. A produced key with no column survives only in `__raw`.
6
+ */
7
+ export function fieldsThatWillNotSurvive(input) {
8
+ const declared = new Set(input.columns);
9
+ return (input.produced
10
+ // ⛔ A `__`-prefixed key is the platform's own — `__raw`, `__headers`, `__last_seen`. Reporting those would fire
11
+ // on every puller in the corpus and teach an author to ignore the whole class.
12
+ .filter((name) => !declared.has(name) && !name.startsWith('__'))
13
+ .map((name) => ({
14
+ field: name,
15
+ reason: `\`${name}\` is pulled but has no column on this task, so it survives only inside the raw payload. A ` +
16
+ 'transformation in the same run can read it; a validation cannot, and it will never reach the destination.',
17
+ ...(input.table === undefined
18
+ ? {}
19
+ : { addColumn: { table: input.table, name, dataType: 'text' } }),
20
+ })));
21
+ }
22
+ /**
23
+ * A validation naming a field that is HINTED rather than declared — Story 8.7 AC-3.
24
+ *
25
+ * ⛔ A validation cannot address a non-column at all. `BuildConditionsAsync` interpolates the field name directly
26
+ * into SQL against the task's table, so a hinted field produces a query naming a column that does not exist; and
27
+ * `ValidateAsync` does a flat `TryGetValue`, which reports *"the field is missing from the item"*. Two halves, two
28
+ * different failures, neither of them a message that points at the real cause.
29
+ */
30
+ export function validationAgainstNonColumn(input) {
31
+ if (input.columns.includes(input.fieldName))
32
+ return undefined;
33
+ const known = input.item?.kind === 'object'
34
+ ? input.item.fields?.get(input.fieldName)
35
+ : undefined;
36
+ if (!known)
37
+ return undefined; // Not a column and not on the item either — Story 8.6's rule owns that case.
38
+ const source = known.provenance.kind === 'hint'
39
+ ? 'declared as a data hint'
40
+ : known.provenance.kind === 'transformation'
41
+ ? `produced by the ${known.provenance.type} transformation at index ${known.provenance.index}`
42
+ : 'not a column';
43
+ return {
44
+ field: input.fieldName,
45
+ reason: `\`${input.fieldName}\` is ${source}, not a column on this task. A validation's field is interpolated ` +
46
+ 'directly into SQL against the table, so this names a column that does not exist — and the item lookup is ' +
47
+ 'flat, so it reports "the field is missing" rather than pointing here.',
48
+ };
49
+ }
50
+ //# sourceMappingURL=survival.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"survival.js","sourceRoot":"","sources":["../src/survival.ts"],"names":[],"mappings":"AAwBA;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,KAIxC;IACC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,CACL,KAAK,CAAC,QAAQ;QACZ,gHAAgH;QAChH,+EAA+E;SAC9E,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;SAC/D,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,KAAK,EAAE,IAAI;QACX,MAAM,EACJ,KAAK,IAAI,6FAA6F;YACtG,2GAA2G;QAC7G,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS;YAC3B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,CAAC;KACnE,CAAC,CAAC,CACN,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAI1C;IACC,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC;QAAE,OAAO,SAAS,CAAC;IAE9D,MAAM,KAAK,GACT,KAAK,CAAC,IAAI,EAAE,IAAI,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC;QACzC,CAAC,CAAC,SAAS,CAAC;IAChB,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC,CAAC,6EAA6E;IAE3G,MAAM,MAAM,GACV,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,MAAM;QAC9B,CAAC,CAAC,yBAAyB;QAC3B,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,gBAAgB;YAC1C,CAAC,CAAC,mBAAmB,KAAK,CAAC,UAAU,CAAC,IAAI,4BAA4B,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE;YAC9F,CAAC,CAAC,cAAc,CAAC;IACvB,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,SAAS;QACtB,MAAM,EACJ,KAAK,KAAK,CAAC,SAAS,SAAS,MAAM,oEAAoE;YACvG,2GAA2G;YAC3G,uEAAuE;KAC1E,CAAC;AACJ,CAAC"}