@gate-forge/pack-fastapi 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.
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Post-canonicalization for the FastAPI detector.
3
+ *
4
+ * The Python scanner emits raw effective paths; this module is the single
5
+ * place where contract facts get their canonical `normalizedPath` (via
6
+ * `@gate-forge/http-contract`, so canonicalization has exactly one
7
+ * implementation across languages).
8
+ *
9
+ * Classification signals (dogfood remediation phase 4): NONE. The wrapper
10
+ * once minted `exposure: route` / `lifecycle.<op>` signals targeted at the
11
+ * PATH-DERIVED resource name (`pathDerivedResourceName`) — a guess that
12
+ * mostly names no discovered resource (route `/admin-bypasses` vs the real
13
+ * table), so the signals surfaced as STALE_SIGNAL_TARGET blockers while
14
+ * adding nothing: unknown exposure already defaults user-facing and unknown
15
+ * lifecycle operations already default enabled (ADR 0003 D5). Route→resource
16
+ * linkage belongs to the CLI endpoint compiler (schema-symbol/handler
17
+ * corroboration over these very facts' `requestSchemaSymbols`/
18
+ * `responseSchemaSymbols`/`handlerSymbol`, with typed
19
+ * ENDPOINT_RESOURCE_LINK_UNRESOLVED blocks for ambiguity). Core's
20
+ * STALE_SIGNAL_TARGET detection remains for genuinely stale authority
21
+ * signals (declaration markers, adapter bindings, read-only declarations)
22
+ * — this pack simply no longer produces false targets. The
23
+ * `classificationSignals` outcome field stays in the wire shape (protocol
24
+ * contract) and is always empty.
25
+ */
26
+ import { type HttpContractFact, type HttpLocation } from '@gate-forge/http-contract';
27
+ import type { ClassificationSignal } from '@gate-forge/core';
28
+ /** Source location shape shared by facts, resources, and signals. */
29
+ type Location = HttpLocation;
30
+ /** Wire shape of one python-emitted contract resource (open attributes). */
31
+ export interface ContractResource {
32
+ schemaVersion: 1;
33
+ kind: 'http.contract';
34
+ source: string;
35
+ location: Location;
36
+ detectorVersion: string;
37
+ attributes: Record<string, unknown>;
38
+ id: string;
39
+ }
40
+ export interface CanonicalizationOutcome {
41
+ /** Facts whose effective path canonicalized; `normalizedPath` filled in. */
42
+ facts: HttpContractFact[];
43
+ /** Raw resources kept for the graph's evidence-only channel. */
44
+ resources: ContractResource[];
45
+ /** Typed blocking entries for facts whose path could not canonicalize. */
46
+ unresolved: Array<{
47
+ code: string;
48
+ detail: string;
49
+ location: Location;
50
+ }>;
51
+ /** Exposure/lifecycle signals minted from the canonical facts. */
52
+ classificationSignals: ClassificationSignal[];
53
+ }
54
+ /**
55
+ * Canonicalizes python-emitted contract resources into typed facts.
56
+ *
57
+ * A fact whose effective path canonicalizes to a dynamic outcome becomes a
58
+ * blocking `HTTP_PATH_DYNAMIC` unresolved entry — never a dropped claim
59
+ * and never a guess. No classification signals are minted (phase 4; see
60
+ * the module doc). Facts and resources are returned in deterministic
61
+ * canonical order.
62
+ */
63
+ export declare function canonicalizeFacts(resources: readonly ContractResource[]): CanonicalizationOutcome;
64
+ export {};
65
+ //# sourceMappingURL=facts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EAClB,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,qEAAqE;AACrE,KAAK,QAAQ,GAAG,YAAY,CAAC;AAE7B,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,CAAC,CAAC;IACjB,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,uBAAuB;IACtC,4EAA4E;IAC5E,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,gEAAgE;IAChE,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,0EAA0E;IAC1E,UAAU,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IACxE,kEAAkE;IAClE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;CAC/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,gBAAgB,EAAE,GACrC,uBAAuB,CA0EzB"}
package/dist/facts.js ADDED
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Post-canonicalization for the FastAPI detector.
3
+ *
4
+ * The Python scanner emits raw effective paths; this module is the single
5
+ * place where contract facts get their canonical `normalizedPath` (via
6
+ * `@gate-forge/http-contract`, so canonicalization has exactly one
7
+ * implementation across languages).
8
+ *
9
+ * Classification signals (dogfood remediation phase 4): NONE. The wrapper
10
+ * once minted `exposure: route` / `lifecycle.<op>` signals targeted at the
11
+ * PATH-DERIVED resource name (`pathDerivedResourceName`) — a guess that
12
+ * mostly names no discovered resource (route `/admin-bypasses` vs the real
13
+ * table), so the signals surfaced as STALE_SIGNAL_TARGET blockers while
14
+ * adding nothing: unknown exposure already defaults user-facing and unknown
15
+ * lifecycle operations already default enabled (ADR 0003 D5). Route→resource
16
+ * linkage belongs to the CLI endpoint compiler (schema-symbol/handler
17
+ * corroboration over these very facts' `requestSchemaSymbols`/
18
+ * `responseSchemaSymbols`/`handlerSymbol`, with typed
19
+ * ENDPOINT_RESOURCE_LINK_UNRESOLVED blocks for ambiguity). Core's
20
+ * STALE_SIGNAL_TARGET detection remains for genuinely stale authority
21
+ * signals (declaration markers, adapter bindings, read-only declarations)
22
+ * — this pack simply no longer produces false targets. The
23
+ * `classificationSignals` outcome field stays in the wire shape (protocol
24
+ * contract) and is always empty.
25
+ */
26
+ import { HTTP_PATH_DYNAMIC, normalizeHttpPath, } from '@gate-forge/http-contract';
27
+ /**
28
+ * Canonicalizes python-emitted contract resources into typed facts.
29
+ *
30
+ * A fact whose effective path canonicalizes to a dynamic outcome becomes a
31
+ * blocking `HTTP_PATH_DYNAMIC` unresolved entry — never a dropped claim
32
+ * and never a guess. No classification signals are minted (phase 4; see
33
+ * the module doc). Facts and resources are returned in deterministic
34
+ * canonical order.
35
+ */
36
+ export function canonicalizeFacts(resources) {
37
+ const facts = [];
38
+ const keptResources = [];
39
+ const unresolved = [];
40
+ // Phase 4: no classification signals are minted — the field stays in
41
+ // the wire shape (protocol contract) and is always empty.
42
+ const signals = [];
43
+ for (const resource of resources) {
44
+ if (resource.kind !== 'http.contract') {
45
+ keptResources.push(resource);
46
+ continue;
47
+ }
48
+ const attributes = resource.attributes;
49
+ const effectivePath = attributes['effectivePath'];
50
+ const method = attributes['method'];
51
+ if (typeof effectivePath !== 'string' || typeof method !== 'string') {
52
+ unresolved.push({
53
+ code: HTTP_PATH_DYNAMIC,
54
+ detail: `contract fact '${resource.id}' carries no provable effective path`,
55
+ location: resource.location,
56
+ });
57
+ continue;
58
+ }
59
+ const canonical = normalizeHttpPath(effectivePath);
60
+ if (!canonical.ok) {
61
+ unresolved.push({
62
+ code: HTTP_PATH_DYNAMIC,
63
+ detail: `route '${effectivePath}' in ${resource.source}: ${canonical.detail}`,
64
+ location: resource.location,
65
+ });
66
+ continue;
67
+ }
68
+ const fact = {
69
+ schemaVersion: 1,
70
+ role: 'server-route',
71
+ method: method,
72
+ normalizedPath: canonical.canonical,
73
+ rawPath: effectivePath,
74
+ framework: typeof attributes['framework'] === 'string' ? attributes['framework'] : 'fastapi',
75
+ handlerSymbol: typeof attributes['handlerSymbol'] === 'string'
76
+ ? attributes['handlerSymbol']
77
+ : undefined,
78
+ source: resource.location,
79
+ };
80
+ const responseModel = attributes['responseModel'];
81
+ if (typeof responseModel === 'string' && responseModel.length > 0) {
82
+ fact.responseSchemaSymbols = [responseModel];
83
+ }
84
+ const requestSchemas = attributes['requestSchemaSymbols'];
85
+ if (Array.isArray(requestSchemas)) {
86
+ const names = requestSchemas.filter((name) => typeof name === 'string' && name.length > 0);
87
+ if (names.length > 0)
88
+ fact.requestSchemaSymbols = names;
89
+ }
90
+ facts.push(fact);
91
+ keptResources.push({
92
+ ...resource,
93
+ attributes: { ...attributes, normalizedPath: canonical.canonical },
94
+ });
95
+ }
96
+ facts.sort((a, b) => compareText(JSON.stringify(a), JSON.stringify(b)));
97
+ keptResources.sort((a, b) => compareText(a.id, b.id));
98
+ unresolved.sort((a, b) => compareText(a.location.file, b.location.file) ||
99
+ (a.location.line || 0) - (b.location.line || 0) ||
100
+ compareText(a.code, b.code) ||
101
+ compareText(a.detail, b.detail));
102
+ return { facts, resources: keptResources, unresolved, classificationSignals: signals };
103
+ }
104
+ function compareText(a, b) {
105
+ if (a === b)
106
+ return 0;
107
+ return a < b ? -1 : 1;
108
+ }
109
+ //# sourceMappingURL=facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facts.js","sourceRoot":"","sources":["../src/facts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@gate-forge/pack-fastapi` — FastAPI server-route detector (ADR 0004,
3
+ * plan phase 2). The default export is the CLI in-process contract.
4
+ */
5
+ export { createFastapiDetector, DEFAULT_COMMAND, DEFAULT_FASTAPI_SCAN_CONFIG, FASTAPI_SCAN_CONFIG_PATH, pythonEnvironment, readFastapiScanConfigOrNull, type FastapiDetector, type FastapiDetectorOptions, type FastapiScanConfig, } from './detector.js';
6
+ export { PACK_PLUGIN_ID, PACK_VERSION } from './version.js';
7
+ declare const _default: import("./detector.js").FastapiDetector;
8
+ export default _default;
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,2BAA2B,EAC3B,wBAAwB,EACxB,iBAAiB,EACjB,2BAA2B,EAC3B,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;;AAG5D,wBAAuC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `@gate-forge/pack-fastapi` — FastAPI server-route detector (ADR 0004,
3
+ * plan phase 2). The default export is the CLI in-process contract.
4
+ */
5
+ export { createFastapiDetector, DEFAULT_COMMAND, DEFAULT_FASTAPI_SCAN_CONFIG, FASTAPI_SCAN_CONFIG_PATH, pythonEnvironment, readFastapiScanConfigOrNull, } from './detector.js';
6
+ export { PACK_PLUGIN_ID, PACK_VERSION } from './version.js';
7
+ import { createFastapiDetector } from './detector.js';
8
+ export default createFastapiDetector();
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,2BAA2B,EAC3B,wBAAwB,EACxB,iBAAiB,EACjB,2BAA2B,GAI5B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAEtD,eAAe,qBAAqB,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Pack identity constants. The GPP/3 handshake pins these values: the
3
+ * `.gateforge.yml` plugin entry MUST declare the same `id` and `version`
4
+ * (see the pack README).
5
+ */
6
+ /** Plugin id every FastAPI pack contribution is pinned to. */
7
+ export declare const PACK_PLUGIN_ID = "gateforge.pack-fastapi";
8
+ /** Detector/pack version. Must match `python/gateforge_fastapi_detector/__init__.py`. */
9
+ export declare const PACK_VERSION = "0.1.0";
10
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8DAA8D;AAC9D,eAAO,MAAM,cAAc,2BAA2B,CAAC;AAEvD,yFAAyF;AACzF,eAAO,MAAM,YAAY,UAAU,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Pack identity constants. The GPP/3 handshake pins these values: the
3
+ * `.gateforge.yml` plugin entry MUST declare the same `id` and `version`
4
+ * (see the pack README).
5
+ */
6
+ /** Plugin id every FastAPI pack contribution is pinned to. */
7
+ export const PACK_PLUGIN_ID = 'gateforge.pack-fastapi';
8
+ /** Detector/pack version. Must match `python/gateforge_fastapi_detector/__init__.py`. */
9
+ export const PACK_VERSION = '0.1.0';
10
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEvD,yFAAyF;AACzF,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@gate-forge/pack-fastapi",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0",
5
+ "description": "FastAPI server-route detector: Python AST scanner served over GPP/3 (pack-sqlalchemy pattern), emitting http.contract evidence facts with canonical effective paths plus exposure/lifecycle signals (ADR 0004 D1, plan phase 2).",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js",
15
+ "default": "./dist/index.js"
16
+ },
17
+ "./package.json": "./package.json"
18
+ },
19
+ "files": [
20
+ "README.md",
21
+ "dist",
22
+ "python"
23
+ ],
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "typecheck": "tsc -p tsconfig.json --noEmit",
27
+ "test": "vitest run"
28
+ },
29
+ "dependencies": {
30
+ "@gate-forge/core": "^0.1.0",
31
+ "@gate-forge/http-contract": "^0.1.0",
32
+ "@gate-forge/plugin-protocol": "^0.1.0",
33
+ "zod": "^4.1.5"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/umiddey/gateforge.git",
41
+ "directory": "packages/pack-fastapi"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/umiddey/gateforge/issues"
45
+ },
46
+ "homepage": "https://github.com/umiddey/gateforge#readme"
47
+ }
@@ -0,0 +1,9 @@
1
+ """FastAPI route detection for Gateforge (GPP/3 plugin).
2
+
3
+ The detector vocabulary is frozen in this module's docstring and mirrored
4
+ by ``gateforge_fastapi_detector/scan.py`` (the implementation). See the
5
+ pack README and ADR 0004 D1 for the contract-resource semantics.
6
+ """
7
+
8
+ PLUGIN_ID = "gateforge.pack-fastapi"
9
+ VERSION = "0.1.0"
@@ -0,0 +1,99 @@
1
+ """GPP/3 serve entry for the Gateforge FastAPI detector.
2
+
3
+ Runnable as a module so the CLI's subprocess transport can invoke it with
4
+ ``python3 -m gateforge_fastapi_detector``: handshake, one lock-step
5
+ ``discover`` per request, shutdown handshake — all through the reference
6
+ client in @gate-forge/plugin-protocol (stdlib only).
7
+
8
+ The module locates the ``gateforge_plugin`` client itself: when it is not
9
+ importable from the environment (e.g. only this pack's ``python/`` dir is
10
+ on ``PYTHONPATH`` in the monorepo layout), the sibling copy shipped by
11
+ ``packages/plugin-protocol/python`` is bootstrapped onto ``sys.path``.
12
+
13
+ Usage:
14
+ python3 -m gateforge_fastapi_detector [root] [--import-roots <json>]
15
+ # root: scan root (default: process cwd, which the CLI sets to the
16
+ # repo root); every discover path resolves under it.
17
+ # --import-roots: a JSON array of repo-root-relative directories that
18
+ # act as Python import roots for ABSOLUTE imports (e.g.
19
+ # '["backend"]' for the central-router-registry pattern). The
20
+ # TypeScript wrapper passes the ``importRoots`` of
21
+ # ``.gateforge/fastapi.json`` through this flag. Resolution is
22
+ # fail-closed: an import matching more than one scanned file
23
+ # under the roots is a typed unresolved entry, never a guess.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ try: # installed / environment-provided client
33
+ from gateforge_plugin import ProtocolError, serve
34
+ except ImportError: # monorepo bootstrap: ../plugin-protocol/python
35
+ _protocol_py = Path(__file__).resolve().parents[3] / "plugin-protocol" / "python"
36
+ sys.path.insert(0, str(_protocol_py))
37
+ from gateforge_plugin import ProtocolError, serve # noqa: E402
38
+
39
+ from gateforge_fastapi_detector import PLUGIN_ID, VERSION
40
+
41
+ _USAGE = (
42
+ "gateforge_fastapi_detector: usage: "
43
+ "python3 -m gateforge_fastapi_detector [root] [--import-roots <json-array>]"
44
+ )
45
+
46
+
47
+ def _parse_import_roots(raw: str) -> list[str]:
48
+ """Parses the ``--import-roots`` value: a JSON array of strings."""
49
+ try:
50
+ parsed = json.loads(raw)
51
+ except json.JSONDecodeError as error:
52
+ raise SystemExit(
53
+ f"gateforge_fastapi_detector: --import-roots must be a JSON array "
54
+ f"of repo-relative directories: {error.msg}"
55
+ ) from None
56
+ if not isinstance(parsed, list) or not all(isinstance(item, str) for item in parsed):
57
+ raise SystemExit(
58
+ "gateforge_fastapi_detector: --import-roots must be a JSON array "
59
+ "of repo-relative directories"
60
+ )
61
+ return parsed
62
+
63
+
64
+ _scan_root: Path | None = None
65
+ _import_roots: list[str] | None = None
66
+
67
+ _args = sys.argv[1:]
68
+ _index = 0
69
+ while _index < len(_args):
70
+ _arg = _args[_index]
71
+ if _arg == "--import-roots":
72
+ _index += 1
73
+ if _index >= len(_args):
74
+ raise SystemExit("gateforge_fastapi_detector: --import-roots requires a JSON array value")
75
+ _import_roots = _parse_import_roots(_args[_index])
76
+ elif _arg.startswith("--import-roots="):
77
+ _import_roots = _parse_import_roots(_arg.split("=", 1)[1])
78
+ elif _arg.startswith("-"):
79
+ raise SystemExit(f"gateforge_fastapi_detector: unknown option {_arg!r}; {_USAGE}")
80
+ elif _scan_root is None:
81
+ _scan_root = Path(_arg).resolve()
82
+ else:
83
+ raise SystemExit(f"gateforge_fastapi_detector: unexpected argument {_arg!r}; {_USAGE}")
84
+ _index += 1
85
+
86
+
87
+ def _discover(paths: list[str]) -> dict:
88
+ """GPP/3 discover handler: scans each repo-relative path in order."""
89
+ from gateforge_fastapi_detector import scan
90
+
91
+ return scan.scan(paths, _scan_root, _import_roots)
92
+
93
+
94
+ if __name__ == "__main__":
95
+ try:
96
+ raise SystemExit(serve(PLUGIN_ID, VERSION, _discover))
97
+ except ProtocolError as error:
98
+ print(f"gateforge_fastapi_detector: {error}", file=sys.stderr)
99
+ raise SystemExit(4) from None