@gate-forge/pack-http 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 UKD
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,224 @@
1
+ # @gate-forge/pack-http — Generic HTTP exposure pack
2
+
3
+ Pure-TypeScript in-process detector that finds **externally reachable HTTP
4
+ artifacts** in `.ts`/`.tsx`/`.js`/`.jsx`/`.mjs`/`.cjs` source — no execution,
5
+ no network, no external deps — and emits `http.contract` **evidence facts**
6
+ (ADR 0004 D1) for the engine's endpoint-compiler join. It mints **no
7
+ classification signals** (dogfood remediation phase 4):
8
+
9
+ - **Server routes**: Express `app.get('/accounts', …)` / `router.post(…)`,
10
+ Fastify and Hono registrations (import-disambiguated, the pack-auth
11
+ convention), and NestJS `@Controller('accounts')` + `@Get/@Post/@Put/
12
+ @Patch/@Delete/@All('…')` decorators.
13
+ - **Frontend API-client calls** (bounded static dataflow, plan phase 3):
14
+ direct literal `fetch`/Axios, `fetch(url, { method })`, Axios config
15
+ objects and instances, configured client symbols (`apiClient.get`),
16
+ pure URL builders (`buildApiPath` with a declared base), module
17
+ constants (local and imported within the scanned set), template
18
+ literals with positional `${}` slots, and simple single-return wrapper
19
+ functions. Computed methods, arbitrary concatenation,
20
+ environment-dependent hosts, undeclared wrappers, and wrapper flows
21
+ outside the model emit **typed unresolved entries** — they never
22
+ disappear and never default to GET. A modeled Axios instance creation
23
+ with a proven literal `baseURL` joins that base into the emitted call
24
+ path (see *Instance baseURL joining* below).
25
+
26
+ ## Client-scan configuration
27
+
28
+ Configuration declares resolvable APIs, never coverage exemptions
29
+ (`.gateforge/http-clients.json`, or pass `clientScan` to the factory):
30
+
31
+ ```json
32
+ {
33
+ "clientScanRoots": ["frontend/**"],
34
+ "serverScanRoots": ["src/**", "services/**"],
35
+ "clientSymbols": [
36
+ "apiClient",
37
+ { "name": "api", "include": ["frontend/src/**"], "exclude": ["frontend/src/generated/**"] }
38
+ ],
39
+ "wrapperFunctions": [{ "name": "apiGet", "method": "GET", "include": ["frontend/**"] }],
40
+ "urlBuilders": [{ "name": "buildApiPath", "base": "/api" }],
41
+ "sameOriginHosts": ["app.example.com"]
42
+ }
43
+ ```
44
+
45
+ A module-scope function whose body issues client calls IS a client
46
+ wrapper: calling it without declaring it blocks with
47
+ `FRONTEND_CALL_TARGET_UNRESOLVED` — removing wrapper support can never
48
+ make a call silently disappear (red probe).
49
+
50
+ ### Instance baseURL joining (source-proven, fail-closed)
51
+
52
+ Frontends commonly create one Axios client with a base path and then
53
+ write every call base-relative — `axios.create({ baseURL: '/api' })`
54
+ plus `apiClient.get('/v1/x')` hits `/api/v1/x` at runtime. Without
55
+ joining, those calls compare against backend routes as `/v1/x` and go
56
+ unwired for the prefix alone. When an instance symbol's creation is
57
+ modeled, its **proven literal `baseURL` joins into the emitted call
58
+ path**: `normalizedPath = normalize(baseURL + callPath)`. This is
59
+ source-proven, not configured — no new configuration key exists.
60
+
61
+ **Extraction.** The modeled creation shape is a module-scope
62
+ `const x = axios.create({...})`. The base is read from the config
63
+ argument in property order, last writer wins (JavaScript object
64
+ semantics): a direct `baseURL` property, and spreads of a **declared
65
+ constant config object** the creation is assigned from or spreads
66
+ (`axios.create(config)`, `axios.create({ ...defaults })`), resolved
67
+ through the same bounded value table as path constants — local
68
+ module-scope constants and relative imports, cycle-guarded. A
69
+ non-literal base (env variable, computed, unmodeled factory) is simply
70
+ unproven.
71
+
72
+ **Where the creation is found (precedence).**
73
+
74
+ 1. The symbol's module-scope creation in the callsite file itself.
75
+ 2. The creation in the relative-import module providing the binding
76
+ (the existing bounded import machinery, nothing looser).
77
+ 3. Otherwise a **unique declaration of the symbol across the scanned
78
+ product set** — the configured-symbol channel already treats the
79
+ name as global, and real API clients are singletons, so an
80
+ alias-imported creation (`from '@/lib/apiClient'`) resolves here.
81
+ Fail-closed: every same-named creation must be a modeled
82
+ `axios.create` and all proven bases must agree; disagreement, a
83
+ second distinct base, or any unprovable same-named creation vetoes
84
+ the join. The search stays inside `clientScanRoots` (an out-of-scope
85
+ e2e mock instance cannot poison product calls) and never applies to
86
+ the bare `axios` global, whose base is axiomatically absent unless
87
+ shadowed in the callsite file itself.
88
+
89
+ **Join semantics.** Exactly one slash seam, axios `combineURLs` style:
90
+ trailing base slashes and leading call-path slashes collapse
91
+ (`/api/` + `/v1/x` → `/api/v1/x`). Empty and `/` bases join nothing;
92
+ an absolute call URL (`https://…`, protocol-relative `//`) ignores the
93
+ base and keeps the existing absolute-URL / `sameOriginHosts` rules; a
94
+ literal absolute base joins and canonicalizes to its path portion for
95
+ configured same-origin hosts. A base template with a resolvable hole
96
+ (`/api/${version}`) joins positionally; an unresolvable hole keeps
97
+ the base unproven.
98
+
99
+ **What changes and what never does.** Only the fact's `normalizedPath`
100
+ gains the prefix — `rawPath` stays exactly as written, and
101
+ normalization itself (query/fragment stripping, `${}`/`{}` slotting,
102
+ slash collapsing) is unchanged. A joined path then meets the
103
+ endpoint-compiler join's literal-precedence rules like any other path
104
+ (that engine is owned by `@gate-forge/http-contract`). Callsites that
105
+ join nothing behave byte-identically to before, and joining never
106
+ turns a passing callsite into a blocker. Wrapper functions do not join
107
+ their internal client's base (documented boundary — declare the
108
+ wrapper's paths fully, or use a builder with `base`). The
109
+ `urlBuilders[].base` channel is unchanged: it is configuration-declared,
110
+ so the builder's base is part of the resolved value itself.
111
+
112
+ ### Scan scoping (optional, strict, back-compatible)
113
+
114
+ All scoping keys are OPTIONAL; a config without them scans exactly as
115
+ before (byte-identical). They exist because test-harness code is a
116
+ different contract class: e2e helpers that share a configured symbol's
117
+ name were scanned as product frontend consumption (typed blockers plus
118
+ phantom consumption), and test-harness mock servers matched the generic
119
+ server-route regex, producing false `http.endpoint` resources.
120
+
121
+ - `clientScanRoots` — repo-root-relative globs. Client-call scanning
122
+ (`fetch`/Axios, configured symbols, wrappers, builders) applies ONLY
123
+ to matching files; files outside produce **no client-call facts and no
124
+ unresolved entries** (they still participate in import resolution, so
125
+ product code importing constants from outside the roots keeps
126
+ resolving).
127
+ - `serverScanRoots` — repo-root-relative globs. Generic server-route
128
+ scanning (Express/Fastify/Hono registrations and NestJS controllers)
129
+ applies ONLY to matching files; files outside produce no server-route
130
+ facts. Reported `scannedPaths` coverage is unchanged either way —
131
+ scoping narrows facts, not coverage.
132
+
133
+ Per-symbol scoping (`include`/`exclude` on a `clientSymbols`,
134
+ `wrapperFunctions`, or `urlBuilders` entry; plain-string entries remain
135
+ unscoped) composes with — never relaxes — the top-level roots. The
136
+ precedence is deterministic:
137
+
138
+ 1. **Top-level roots gate first**: a file outside `clientScanRoots`
139
+ yields no client-call facts at all, regardless of any symbol's
140
+ scoping; the same holds for `serverScanRoots` and server routes.
141
+ 2. **`include` next**: if an entry declares `include`, the file must
142
+ match at least one glob (absent `include` = every in-root file).
143
+ 3. **`exclude` wins over `include`**: a file named by both is out of
144
+ scope for that entry.
145
+
146
+ Consequences, kept deliberate and documented:
147
+
148
+ - A call to a name the configuration declares, in a file outside that
149
+ name's declared scope (the e2e `api(...)` helper), is **ignored** —
150
+ neither resolved nor blocked. The undeclared-wrapper evidence rule
151
+ still blocks any name the configuration never mentions.
152
+ - A configured builder or wrapper scoped away from a file simply stops
153
+ resolving there. If the enclosing call is still in scope (e.g. a bare
154
+ `fetch` whose target used the builder), the call fails closed with a
155
+ typed unresolved entry — it never silently vanishes.
156
+ - Server-route disambiguation is scope-aware: where a client symbol is
157
+ NOT active for a file, `api.get('/x', handler)`-shaped code is free to
158
+ be discovered as a router registration.
159
+
160
+ Malformed documents still fail closed: non-object roots, non-verb
161
+ wrapper methods, scoping entries without a `name`, and non-array
162
+ `include`/`exclude` values throw. Unknown keys and non-array known keys
163
+ are ignored, exactly as before (unchanged parser posture).
164
+
165
+ ## Resources
166
+
167
+ **None.** Routes are evidence, not business resources: an early design
168
+ emitted one `http-route` resource per artifact, but a path-derived resource
169
+ name collides with the converged SQLAlchemy table at the same
170
+ plane-qualified id, so the resource channel was removed after a red-probe
171
+ (`resources: []` always; see ADR 0004 D1 for the successor design —
172
+ endpoint identities live in the `@gate-forge/http-contract` join, never in
173
+ the path-derived table name).
174
+
175
+ ## Signals
176
+
177
+ **None.** An earlier design minted a code-positive `exposure` signal per
178
+ artifact (assertion `route`/`frontend-call`) plus `lifecycle.<op>` signals
179
+ from the HTTP method (POST⇒create, GET/HEAD⇒read, PUT/PATCH⇒update,
180
+ DELETE⇒delete; `app.all` asserted nothing), each targeted at the
181
+ **path-derived resource name** — the last non-empty, non-parameter path
182
+ segment, lower-cased, extension stripped (`/api/accounts/:id` →
183
+ `accounts`). That target is a GUESS: in real repos it mostly names no
184
+ discovered resource (route `/absences` vs table `employee_absences`), and
185
+ every minted signal surfaced as a `STALE_SIGNAL_TARGET` blocker while
186
+ adding no information — unknown exposure already defaults `user-facing`
187
+ and unknown lifecycle operations already default enabled (ADR 0003 D5),
188
+ so removing the guesses changes no classification outcome.
189
+
190
+ Consequences, kept deliberate:
191
+
192
+ - **Route→resource linkage is the CLI endpoint compiler's exclusive job**
193
+ (ADR 0004): it derives the same candidate name, links only when exactly
194
+ one discovered business resource matches AND a schema-symbol or
195
+ handler-name fact corroborates it (evidence this pack puts on the
196
+ `http.contract` facts), and emits typed
197
+ `ENDPOINT_RESOURCE_LINK_UNRESOLVED` blocks for ambiguity. Name
198
+ coincidence alone never links.
199
+ - Removing signals conserves the defaults: exposure stays `user-facing`,
200
+ every lifecycle operation stays enabled (crud:/persistence: obligation
201
+ generation via `lifecycleAllowsContract` is unchanged), and delete
202
+ semantics remain blocked until the model pack proves them.
203
+ - Core's `STALE_SIGNAL_TARGET` detection remains for genuinely stale
204
+ authority signals (declaration markers, adapter bindings, read-only
205
+ declarations) — this pack simply no longer produces false targets.
206
+ - The pack emits **no negative proof anywhere** (no regex-only negative
207
+ proof, no "not found means internal") and never writes classifications.
208
+
209
+ ## Setup
210
+
211
+ ```yaml
212
+ # .gateforge.yml
213
+ plugins:
214
+ - id: gateforge.pack-http
215
+ version: 0.1.0
216
+ transport: in-process
217
+ module: '@gate-forge/pack-http'
218
+ ```
219
+
220
+ The pack also emits `http.contract` evidence facts (one per server
221
+ artifact and one per frontend callsite) for the endpoint compiler's join
222
+ (ADR 0004 D1) — evidence-only, never business resources.
223
+
224
+ Requires Node >= 20; no network at any point.
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Bounded static dataflow for frontend API-client calls (ADR 0004 D6,
3
+ * plan phase 3).
4
+ *
5
+ * Real ASTs (the TypeScript compiler API, pure analysis — no evaluation,
6
+ * no I/O beyond the caller-provided file set) replace the old regex
7
+ * client scan. The model is deliberately bounded:
8
+ *
9
+ * - **Direct literal calls**: `fetch('/x')`, `axios.get('/x')`, instance
10
+ * verbs, `axios({url, method})` — method from verb name or a literal /
11
+ * const `method` property; never a silent GET default for computed
12
+ * methods (those become `HTTP_METHOD_DYNAMIC`).
13
+ * - **Module constants**: `const X = '/x'` / template / builder call,
14
+ * resolved across the scanned file set through relative imports with a
15
+ * cycle guard and memoization.
16
+ * - **Templates**: `${expr}` holes resolve through the same value table;
17
+ * rooted holes become positional `{}` slots without needing runtime
18
+ * values; an unresolvable hole before the path is rooted (host/base)
19
+ * makes the whole target `FRONTEND_CALL_TARGET_UNRESOLVED`.
20
+ * - **Configured client symbols** (`apiClient.get(...)`) and **pure URL
21
+ * builders** (`buildApiPath('/v1/x')` with an optional declared base)
22
+ * are configuration-declared resolvable APIs — never coverage
23
+ * exemptions: unresolved flows still block.
24
+ * - **Instance baseURL joining**: when an instance symbol's creation is
25
+ * modeled — a module-scope `const apiClient = axios.create({...})`
26
+ * whose config carries a proven LITERAL `baseURL` (a direct property,
27
+ * or one reaching it through a declared constant config object the
28
+ * creation is assigned from or spreads, resolved by the same bounded
29
+ * value table) — the base joins into the emitted call path:
30
+ * `normalizedPath = normalize(baseURL + callPath)` with exactly one
31
+ * slash seam. Empty/`/` bases, absolute call URLs, and unprovable
32
+ * (env-dependent) bases join nothing: the callsite behaves exactly as
33
+ * it would without the feature, and joining never turns a passing
34
+ * callsite into a blocker. The raw path stays exactly as written.
35
+ * - **Simple wrapper functions**: a configured wrapper whose declaration
36
+ * in the scanned set is a single `return <client call>(...)` arrow or
37
+ * function resolves its internal call with the callsite's first
38
+ * argument substituted for the wrapper's first parameter (one level,
39
+ * one parameter — anything deeper is typed unresolved).
40
+ */
41
+ import { FRONTEND_CALL_TARGET_UNRESOLVED, HTTP_METHOD_DYNAMIC, HTTP_PATH_DYNAMIC } from '@gate-forge/http-contract';
42
+ import type { HttpMethod } from '@gate-forge/http-contract';
43
+ import { type Location } from '@gate-forge/core';
44
+ /**
45
+ * Optional per-entry file scoping (phase 3 scan-scoping). Globs are
46
+ * repo-root-relative posix (`frontend/src/**`), matched with core's
47
+ * deterministic `pathInScope` machinery — the same wildcards as the
48
+ * classification policy's scan roots.
49
+ */
50
+ export interface ClientSymbolScoping {
51
+ /**
52
+ * Files the entry applies to. Absent = every scanned file (the
53
+ * back-compat default); present = at least one glob must match.
54
+ */
55
+ include?: readonly string[];
56
+ /**
57
+ * Files the entry never applies to. Any match wins over `include` —
58
+ * a deterministic precedence (documented in the README).
59
+ */
60
+ exclude?: readonly string[];
61
+ }
62
+ /** A configured client symbol: plain string (unscoped) or scoped object. */
63
+ export interface ClientSymbolConfig extends ClientSymbolScoping {
64
+ /** Instance symbol exposing verb methods, e.g. `apiClient`. */
65
+ name: string;
66
+ }
67
+ /** A configured wrapper callable: `name` + concrete method + scoping. */
68
+ export interface WrapperFunctionConfig extends ClientSymbolScoping {
69
+ name: string;
70
+ /**
71
+ * The concrete method the wrapper always issues (wrappers with
72
+ * computed methods stay unresolved).
73
+ */
74
+ method: HttpMethod;
75
+ }
76
+ /** A configured pure URL builder: `name` + optional base + scoping. */
77
+ export interface UrlBuilderConfig extends ClientSymbolScoping {
78
+ name: string;
79
+ /** Optional literal base the builder prepends. */
80
+ base?: string;
81
+ }
82
+ /**
83
+ * Configuration for the client-call scanner (all optional).
84
+ *
85
+ * Scan scoping (phase 3): `clientScanRoots` / `serverScanRoots` narrow
86
+ * WHERE client-call and generic server-route scanning apply at all —
87
+ * two real dogfood failures drove this. A consumer's e2e specs defined
88
+ * a helper also named `api`, and every harness call was scanned as
89
+ * product frontend consumption (20 FRONTEND_CALL_TARGET_UNRESOLVED
90
+ * blockers + phantom consumption); another repo discovered false
91
+ * `http.endpoint` server routes inside `tests/e2e` because test-harness
92
+ * mock servers matched the generic route regex. Test-harness calls are
93
+ * a different contract class, so a file outside the roots produces NO
94
+ * facts of that kind at all. Absent keys scan everything — the
95
+ * byte-identical back-compat contract.
96
+ */
97
+ export interface ClientScanConfig {
98
+ /** Instance symbols exposing verb methods, e.g. `['apiClient']`. */
99
+ clientSymbols?: ReadonlyArray<string | ClientSymbolConfig>;
100
+ /**
101
+ * Wrapper callables: `name` plus the concrete method the wrapper
102
+ * always issues (wrappers with computed methods stay unresolved).
103
+ */
104
+ wrapperFunctions?: ReadonlyArray<WrapperFunctionConfig>;
105
+ /** Pure URL builders with an optional literal base they prepend. */
106
+ urlBuilders?: ReadonlyArray<UrlBuilderConfig>;
107
+ /** Absolute-URL hosts treated as same-origin (canonicalized to path). */
108
+ sameOriginHosts?: readonly string[];
109
+ /**
110
+ * Repo-root-relative globs limiting client-call scanning (fetch,
111
+ * axios, configured symbols, wrappers, builders). Files outside
112
+ * produce NO client-call facts and NO unresolved entries.
113
+ */
114
+ clientScanRoots?: readonly string[];
115
+ /**
116
+ * Repo-root-relative globs limiting generic server-route scanning
117
+ * (`scanServerRoutes` / `scanNestControllers`). Files outside produce
118
+ * NO server-route facts — test servers are not product routes.
119
+ */
120
+ serverScanRoots?: readonly string[];
121
+ }
122
+ export declare const DEFAULT_CLIENT_SCAN_CONFIG: ClientScanConfig;
123
+ /**
124
+ * Whether `file` (repo-root-relative posix) is inside the top-level
125
+ * client-call scan roots. Absent roots admit EVERY file — the
126
+ * back-compat contract: without the key, scanning is exactly as before.
127
+ */
128
+ export declare function fileInClientScanRoots(config: ClientScanConfig, file: string): boolean;
129
+ /**
130
+ * Whether `file` is inside the top-level server-route scan roots.
131
+ * Absent roots admit every file (back-compat, same rule as above).
132
+ */
133
+ export declare function fileInServerScanRoots(config: ClientScanConfig, file: string): boolean;
134
+ /**
135
+ * Whether a callsite in `file` uses the configured client symbol
136
+ * `name`: the file must match the top-level clientScanRoots (if
137
+ * present) AND the symbol's own include/exclude (if present). Both
138
+ * gates must pass — per-symbol scoping composes with (never relaxes)
139
+ * the top-level roots.
140
+ */
141
+ export declare function clientSymbolActiveIn(config: ClientScanConfig, name: string, file: string): boolean;
142
+ /**
143
+ * The configured client-symbol names ACTIVE for `file` — used by the
144
+ * server-route scanner to disambiguate `api.get('/x')`-shaped client
145
+ * calls from router registrations. Scope-aware: a symbol scoped away
146
+ * from this file does not suppress route discovery in it (out of client
147
+ * scope, `<symbol>.<verb>(path, handler)` can only be a router).
148
+ */
149
+ export declare function activeClientSymbolNamesIn(config: ClientScanConfig, file: string): string[];
150
+ /** One discovered frontend call (one row per source callsite). */
151
+ export interface ClientCall {
152
+ method: HttpMethod;
153
+ /** Path exactly as written after constant substitution. */
154
+ rawPath: string;
155
+ /** Canonical positional form of {@link rawPath} (ADR 0004 D2). */
156
+ canonicalPath: string;
157
+ /**
158
+ * The proven literal instance `baseURL` that was joined into
159
+ * {@link canonicalPath} (`normalize(baseURL + rawPath)`), present ONLY
160
+ * when a join actually happened. Fact emission canonicalizes the joined
161
+ * path while {@link rawPath} stays as written; without a joined base
162
+ * this is absent and emission is byte-identical to the pre-feature
163
+ * contract.
164
+ */
165
+ joinedBaseURL?: string;
166
+ /** Producing client, e.g. `fetch`, `axios`, `apiClient`, `apiGet`. */
167
+ framework: string;
168
+ location: Location;
169
+ }
170
+ export interface ClientScanUnresolved {
171
+ code: typeof FRONTEND_CALL_TARGET_UNRESOLVED | typeof HTTP_METHOD_DYNAMIC | typeof HTTP_PATH_DYNAMIC;
172
+ detail: string;
173
+ location: Location;
174
+ }
175
+ export interface ClientScanResult {
176
+ calls: ClientCall[];
177
+ unresolved: ClientScanUnresolved[];
178
+ }
179
+ /**
180
+ * Scans one file for frontend API-client calls under the bounded model.
181
+ * `scannedFiles` maps repo-relative paths to source text for every file
182
+ * in the discovery request (import resolution stays inside the set).
183
+ *
184
+ * Scan scoping (phase 3): a file outside `clientScanRoots` (when the
185
+ * key is present) returns EMPTY — no calls AND no unresolved entries.
186
+ * The gate comes first on purpose: even bare `fetch` extraction must
187
+ * not run, because a scoped-out file (an e2e spec, a test harness) is
188
+ * not product frontend consumption and must be invisible to this
189
+ * channel, blockers included. Files outside the roots still participate
190
+ * in the value table as import targets — scoping narrows fact
191
+ * emission, not the dataflow's ability to resolve product code.
192
+ */
193
+ export declare function scanClientCalls(file: string, sourceText: string, config: ClientScanConfig, scannedFiles: ReadonlyMap<string, string>): ClientScanResult;
194
+ /**
195
+ * Reads a client-scan config document. Returns the default config when
196
+ * the file is absent; malformed documents throw (fail closed — the CLI
197
+ * surfaces the error instead of scanning with partial trust).
198
+ *
199
+ * Accepted shapes (phase 3 scan-scoping): `clientSymbols` entries are a
200
+ * plain string (back-compat, unscoped) or `{ name, include?, exclude? }`;
201
+ * `wrapperFunctions` / `urlBuilders` entries carry the same optional
202
+ * include/exclude next to their existing `method` / `base` fields; the
203
+ * top-level `clientScanRoots` / `serverScanRoots` arrays scope where
204
+ * client-call and server-route scanning apply at all. Consistent with
205
+ * the pre-existing parser posture: unknown keys are ignored, non-array
206
+ * known keys are ignored, but malformed ENTRY values throw (the wrapper
207
+ * verb check predates this; the new scoping fields throw on wrong
208
+ * shapes and missing names because silently dropping a scope widens the
209
+ * scan instead of narrowing it).
210
+ */
211
+ export declare function readClientScanConfigOrNull(path: string | null): ClientScanConfig;
212
+ //# sourceMappingURL=client-calls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-calls.d.ts","sourceRoot":"","sources":["../src/client-calls.ts"],"names":[],"mappings":"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"}