@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 +202 -0
- package/README.md +224 -0
- package/dist/client-calls.d.ts +212 -0
- package/dist/client-calls.d.ts.map +1 -0
- package/dist/client-calls.js +1069 -0
- package/dist/client-calls.js.map +1 -0
- package/dist/detector.d.ts +33 -0
- package/dist/detector.d.ts.map +1 -0
- package/dist/detector.js +399 -0
- package/dist/detector.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +47 -0
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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"}
|