wicked-crew 0.7.25 → 0.7.26
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/dist/api/endpoint-manifest-live.d.ts.map +1 -1
- package/dist/api/endpoint-manifest-live.js +5 -0
- package/dist/api/endpoint-manifest-live.js.map +1 -1
- package/dist/api/eval-compare.d.ts +306 -0
- package/dist/api/eval-compare.d.ts.map +1 -0
- package/dist/api/eval-compare.js +610 -0
- package/dist/api/eval-compare.js.map +1 -0
- package/dist/api/eval-sample.d.ts +158 -0
- package/dist/api/eval-sample.d.ts.map +1 -0
- package/dist/api/eval-sample.js +88 -0
- package/dist/api/eval-sample.js.map +1 -0
- package/dist/api/routes.d.ts +5 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +31 -0
- package/dist/api/routes.js.map +1 -1
- package/dist/api/run-files.d.ts +12 -2
- package/dist/api/run-files.d.ts.map +1 -1
- package/dist/api/run-files.js +17 -5
- package/dist/api/run-files.js.map +1 -1
- package/dist/api/server.d.ts +20 -0
- package/dist/api/server.d.ts.map +1 -1
- package/dist/api/server.js +62 -9
- package/dist/api/server.js.map +1 -1
- package/dist/api/skills.d.ts +88 -0
- package/dist/api/skills.d.ts.map +1 -0
- package/dist/api/skills.js +264 -0
- package/dist/api/skills.js.map +1 -0
- package/dist/api/testing.d.ts +2 -75
- package/dist/api/testing.d.ts.map +1 -1
- package/dist/api/testing.js +14 -28
- package/dist/api/testing.js.map +1 -1
- package/dist/core/adapter.d.ts +42 -0
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +83 -12
- package/dist/core/adapter.js.map +1 -1
- package/dist/interactive/bridge-root.d.ts +117 -7
- package/dist/interactive/bridge-root.d.ts.map +1 -1
- package/dist/interactive/bridge-root.js +229 -9
- package/dist/interactive/bridge-root.js.map +1 -1
- package/dist/interactive/doc-delete-routes.d.ts +2 -0
- package/dist/interactive/doc-delete-routes.d.ts.map +1 -1
- package/dist/interactive/doc-delete-routes.js +3 -19
- package/dist/interactive/doc-delete-routes.js.map +1 -1
- package/dist/interactive/doc-list-routes.d.ts +42 -0
- package/dist/interactive/doc-list-routes.d.ts.map +1 -0
- package/dist/interactive/doc-list-routes.js +165 -0
- package/dist/interactive/doc-list-routes.js.map +1 -0
- package/dist/interactive/project-root.d.ts +36 -0
- package/dist/interactive/project-root.d.ts.map +1 -0
- package/dist/interactive/project-root.js +45 -0
- package/dist/interactive/project-root.js.map +1 -0
- package/dist/interactive/proxy-routes.d.ts +4 -1
- package/dist/interactive/proxy-routes.d.ts.map +1 -1
- package/dist/interactive/proxy-routes.js +4 -22
- package/dist/interactive/proxy-routes.js.map +1 -1
- package/dist/projects/default-project.d.ts +10 -0
- package/dist/projects/default-project.d.ts.map +1 -0
- package/dist/projects/default-project.js +10 -0
- package/dist/projects/default-project.js.map +1 -0
- package/dist/projects/routes.d.ts +0 -2
- package/dist/projects/routes.d.ts.map +1 -1
- package/dist/projects/routes.js +1 -2
- package/dist/projects/routes.js.map +1 -1
- package/dist/skills/bundle.d.ts +94 -0
- package/dist/skills/bundle.d.ts.map +1 -0
- package/dist/skills/bundle.js +187 -0
- package/dist/skills/bundle.js.map +1 -0
- package/dist/skills/contain.d.ts +57 -0
- package/dist/skills/contain.d.ts.map +1 -0
- package/dist/skills/contain.js +90 -0
- package/dist/skills/contain.js.map +1 -0
- package/dist/skills/core-closure.d.ts +83 -0
- package/dist/skills/core-closure.d.ts.map +1 -0
- package/dist/skills/core-closure.js +130 -0
- package/dist/skills/core-closure.js.map +1 -0
- package/dist/skills/engine-env.d.ts +48 -0
- package/dist/skills/engine-env.d.ts.map +1 -0
- package/dist/skills/engine-env.js +69 -0
- package/dist/skills/engine-env.js.map +1 -0
- package/dist/skills/frontmatter.d.ts +77 -0
- package/dist/skills/frontmatter.d.ts.map +1 -0
- package/dist/skills/frontmatter.js +190 -0
- package/dist/skills/frontmatter.js.map +1 -0
- package/dist/skills/guards.d.ts +72 -0
- package/dist/skills/guards.d.ts.map +1 -0
- package/dist/skills/guards.js +147 -0
- package/dist/skills/guards.js.map +1 -0
- package/dist/skills/live-generations.d.ts +80 -0
- package/dist/skills/live-generations.d.ts.map +1 -0
- package/dist/skills/live-generations.js +163 -0
- package/dist/skills/live-generations.js.map +1 -0
- package/dist/skills/plugin-source.d.ts +109 -0
- package/dist/skills/plugin-source.d.ts.map +1 -0
- package/dist/skills/plugin-source.js +272 -0
- package/dist/skills/plugin-source.js.map +1 -0
- package/dist/skills/refs.d.ts +58 -0
- package/dist/skills/refs.d.ts.map +1 -0
- package/dist/skills/refs.js +133 -0
- package/dist/skills/refs.js.map +1 -0
- package/dist/skills/root-fence.d.ts +53 -0
- package/dist/skills/root-fence.d.ts.map +1 -0
- package/dist/skills/root-fence.js +109 -0
- package/dist/skills/root-fence.js.map +1 -0
- package/dist/skills/root-names.d.ts +42 -0
- package/dist/skills/root-names.d.ts.map +1 -0
- package/dist/skills/root-names.js +44 -0
- package/dist/skills/root-names.js.map +1 -0
- package/dist/skills/runtime.d.ts +130 -0
- package/dist/skills/runtime.d.ts.map +1 -0
- package/dist/skills/runtime.js +246 -0
- package/dist/skills/runtime.js.map +1 -0
- package/dist/skills/store.d.ts +895 -0
- package/dist/skills/store.d.ts.map +1 -0
- package/dist/skills/store.js +3575 -0
- package/dist/skills/store.js.map +1 -0
- package/dist/skills/tree.d.ts +292 -0
- package/dist/skills/tree.d.ts.map +1 -0
- package/dist/skills/tree.js +645 -0
- package/dist/skills/tree.js.map +1 -0
- package/dist/skills/venv.d.ts +52 -0
- package/dist/skills/venv.d.ts.map +1 -0
- package/dist/skills/venv.js +80 -0
- package/dist/skills/venv.js.map +1 -0
- package/dist/studio/assets/index-CK1uJWs2.css +32 -0
- package/dist/studio/assets/index-FcZg3WDR.js +539 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/testid-inventory.json +605 -7
- package/endpoint-manifest.json +183 -1
- package/package.json +4 -3
- package/dist/studio/assets/index-C4iz4uAw.js +0 -537
- package/dist/studio/assets/index-DEEQcRQ_.css +0 -32
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-root.d.ts","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bridge-root.d.ts","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,8FAA8F;AAC9F,MAAM,WAAW,sBAAsB;IACrC,oFAAoF;IACpF,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CAC7C;AAED;;;qDAGqD;AACrD,eAAO,MAAM,QAAQ,4BAA4B,CAAC;AAElD,6FAA6F;AAC7F,eAAO,MAAM,YAAY,aAAa,CAAC;AAQvC;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEvE;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAK9F;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAkB,GAAG,MAAM,CAE/D;AAED;;;;;;;GAOG;AACH,qBAAa,gCAAiC,SAAQ,KAAK;IAGvD,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM;IAJvB,QAAQ,CAAC,UAAU,OAAO;gBAEf,SAAS,EAAE,MAAM;IAC1B,sCAAsC;IAC7B,IAAI,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAQf;AAWD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAErG;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,+BAA+B,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE,MAAkB,GAAG,MAAM,CAEnG;AA+ED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EAClD,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,IAAI,GAAE,MAAkB,GACvB,MAAM,CAER;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EAClD,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,IAAI,GAAE,MAAkB,GACvB,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,OAAO,EAAE,sBAAsB,GAAG,IAAI,GAAG,SAAS,EAClD,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,EACrD,IAAI,GAAE,MAAkB,GACvB,MAAM,CAER"}
|
|
@@ -3,8 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* §7.1 closed the identity question: a crew Project is THE entity, and an interactive
|
|
5
5
|
* "instance" (a docs directory) maps onto it through ONE nullable setting, `interactiveRoot`.
|
|
6
|
-
* Null means "the
|
|
7
|
-
*
|
|
6
|
+
* Null means "the default root" — it is a default, never a constraint (§7.2): a project that
|
|
7
|
+
* sets it gets exactly the directory it named.
|
|
8
|
+
*
|
|
9
|
+
* WHAT "THE DEFAULT" IS DEPENDS ON THE PROJECT (crew#472). It used to be one shared directory for
|
|
10
|
+
* every project, which meant `:projectId` in the proxy path was honored only to resolve a setting
|
|
11
|
+
* no project ever had — every project fell through to the same root, one bridge, one registry,
|
|
12
|
+
* the same 31 docs under every project's URL. Now:
|
|
13
|
+
*
|
|
14
|
+
* - the synthesized `default` ("Unfiled") project keeps the LEGACY shared root, byte-identical
|
|
15
|
+
* to `wicked-interactive serve`'s own default — so every doc created before partitioning stays
|
|
16
|
+
* visible under Unfiled with no data migration, and an operator's already-running default
|
|
17
|
+
* bridge is still adopted rather than duplicated;
|
|
18
|
+
* - every other project without an explicit root gets its own partition UNDER that root,
|
|
19
|
+
* `<default root>/projects/<projectId>`. Nested there on purpose: the legacy bridge's
|
|
20
|
+
* `listDocs` only lists slug-named children carrying a `versions.json`, so `projects/` is
|
|
21
|
+
* invisible to it, and the whole interactive footprint stays in the one place it always was.
|
|
8
22
|
*
|
|
9
23
|
* The resolved string is also the BRIDGE POOL KEY, which is why every spelling of the same
|
|
10
24
|
* directory has to collapse to one value here rather than in the pool. `~/decks`, `decks`
|
|
@@ -12,11 +26,22 @@
|
|
|
12
26
|
* start a second `wicked-interactive serve` on a second port for each spelling — exactly the
|
|
13
27
|
* "why is it on 5 ports" confusion ADR-0025 exists to prevent.
|
|
14
28
|
*/
|
|
29
|
+
import { lstatSync, mkdirSync, readlinkSync, realpathSync } from 'node:fs';
|
|
15
30
|
import { homedir } from 'node:os';
|
|
16
|
-
import { resolve } from 'node:path';
|
|
31
|
+
import { relative, resolve, sep } from 'node:path';
|
|
32
|
+
import { DEFAULT_PROJECT_ID } from '../projects/default-project.js';
|
|
17
33
|
/** Env override for the SHARED DEFAULT only (never for an explicit per-project setting).
|
|
18
|
-
* Exists so a test harness or an e2e run can point "the default root" at a scratch dir.
|
|
34
|
+
* Exists so a test harness or an e2e run can point "the default root" at a scratch dir. It is
|
|
35
|
+
* an explicit shared binding: while set, EVERY unbound project — `default` included — resolves
|
|
36
|
+
* to it, exactly as before partitioning existed. */
|
|
19
37
|
export const ROOT_ENV = 'WICKED_INTERACTIVE_ROOT';
|
|
38
|
+
/** The directory under the default root that holds the per-project partitions (crew#472). */
|
|
39
|
+
export const PROJECTS_DIR = 'projects';
|
|
40
|
+
/**
|
|
41
|
+
* A project id that can be a directory name: one path segment, no separators, never `.`/`..`.
|
|
42
|
+
* Engine-minted ids are `proj_<ms><seq>`; the reserved `default` never reaches this check.
|
|
43
|
+
*/
|
|
44
|
+
const PARTITION_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
20
45
|
/**
|
|
21
46
|
* What `wicked-interactive serve` uses with no `--root`: the canonical shared root
|
|
22
47
|
* `~/wicked-interactive/docs` (ADR-0025 amended, `bin/wicked-interactive.js:181`). Kept
|
|
@@ -26,23 +51,218 @@ export const ROOT_ENV = 'WICKED_INTERACTIVE_ROOT';
|
|
|
26
51
|
export function defaultInteractiveRoot(home = homedir()) {
|
|
27
52
|
return resolve(home, 'wicked-interactive', 'docs');
|
|
28
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* The default docs root of a NON-default project with no explicit binding:
|
|
56
|
+
* `<default root>/projects/<projectId>` (crew#472). The directory is created on first use by the
|
|
57
|
+
* bridge pool (it `mkdir -p`s any root before spawning a bridge), so resolution stays pure.
|
|
58
|
+
*
|
|
59
|
+
* Throws on an id that cannot be a directory name. The route layer 404s unknown projects before
|
|
60
|
+
* resolving, and the seams only see ids the engine minted, so reaching this is a bug — and the
|
|
61
|
+
* one answer that must never be given is a silent fallback to the shared root, which would
|
|
62
|
+
* quietly re-open the leak this partition closes.
|
|
63
|
+
*/
|
|
64
|
+
export function partitionedInteractiveRoot(projectId, home = homedir()) {
|
|
65
|
+
if (!PARTITION_SEGMENT.test(projectId)) {
|
|
66
|
+
throw new Error(`project id ${JSON.stringify(projectId)} cannot name an interactive docs partition`);
|
|
67
|
+
}
|
|
68
|
+
return resolve(partitionsBase(home), projectId);
|
|
69
|
+
}
|
|
70
|
+
/** The directory holding every partition: `<default root>/projects`. */
|
|
71
|
+
export function partitionsBase(home = homedir()) {
|
|
72
|
+
return resolve(defaultInteractiveRoot(home), PROJECTS_DIR);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A project's partition is NOT a real directory under the projects base — a symbolic link, a
|
|
76
|
+
* regular file, or a path whose real location sits outside the base. Thrown by
|
|
77
|
+
* `preparePartitionedInteractiveRoot`, and so by every route's `projectDocsRoot`
|
|
78
|
+
* (`project-root.ts`). Fastify's default error handler honors `statusCode` and answers with
|
|
79
|
+
* this message verbatim, so the refusal reaches the operator as a 500 that names the offending
|
|
80
|
+
* path — never as a silent fallback to some other root.
|
|
81
|
+
*/
|
|
82
|
+
export class InteractivePartitionRefusedError extends Error {
|
|
83
|
+
projectId;
|
|
84
|
+
path;
|
|
85
|
+
statusCode = 500;
|
|
86
|
+
constructor(projectId,
|
|
87
|
+
/** The path that failed the check. */
|
|
88
|
+
path, reason, base) {
|
|
89
|
+
super(`refusing to serve the interactive docs of project ${JSON.stringify(projectId)}: ${path} ${reason}. ` +
|
|
90
|
+
`A per-project partition must be a real directory under ${base} — remove or replace it and retry`);
|
|
91
|
+
this.projectId = projectId;
|
|
92
|
+
this.path = path;
|
|
93
|
+
this.name = 'InteractivePartitionRefusedError';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function lstatOrNull(path) {
|
|
97
|
+
try {
|
|
98
|
+
return lstatSync(path);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
if (err.code === 'ENOENT')
|
|
102
|
+
return null;
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve AND materialize a project's partition, refusing anything that would let the lexical
|
|
108
|
+
* path `<base>/<projectId>` name a different directory.
|
|
109
|
+
*
|
|
110
|
+
* `partitionedInteractiveRoot` is lexical: it proves the id cannot spell its way out of
|
|
111
|
+
* `projects/`, and nothing more. A symbolic link already sitting at `projects/<id>` — pointing
|
|
112
|
+
* at another project's partition, or anywhere else — passes that check and would be followed by
|
|
113
|
+
* the bridge (spawned with `--root` = this path) into the other directory: project A reading and
|
|
114
|
+
* writing B's docs through a URL the id check said was A's. So, on REAL paths:
|
|
115
|
+
*
|
|
116
|
+
* 1. every component under the base is `lstat`ed — a symlink or a non-directory anywhere on the
|
|
117
|
+
* way refuses (fail closed, naming the path); the base itself is the operator's to place (a
|
|
118
|
+
* docs root kept on another volume behind a link is legitimate) and is not judged;
|
|
119
|
+
* 2. a missing component is created with `mkdir` WITHOUT `recursive` — `mkdir(2)` never follows
|
|
120
|
+
* a link at the component it creates, so a link raced in between the `lstat` and the `mkdir`
|
|
121
|
+
* surfaces as `EEXIST` and is `lstat`ed like anything else found in place, never followed;
|
|
122
|
+
* 3. the real path of the partition must sit inside the real path of the base — belt and braces
|
|
123
|
+
* over (1), so a containment failure the walk did not name is still refused.
|
|
124
|
+
*
|
|
125
|
+
* Returns the LEXICAL path — it is the bridge pool key, and spelling collapse is unchanged. The
|
|
126
|
+
* check runs per resolution, not per file operation: a link swapped in under a bridge that is
|
|
127
|
+
* already running is that process's (and the filesystem's) business, not something crew can see
|
|
128
|
+
* from the resolution seam.
|
|
129
|
+
*/
|
|
130
|
+
export function preparePartitionedInteractiveRoot(projectId, home = homedir()) {
|
|
131
|
+
return containPartitionedInteractiveRoot(projectId, home, true);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The SAME containment check WITHOUT materialization (crew#474, Copilot) — for a caller that only
|
|
135
|
+
* reads what is already there: the interactive event seams (`server.ts` `interactiveDocsRoot`),
|
|
136
|
+
* which `readDocHead` a manifest under a root the routes materialized, or install a demo spec
|
|
137
|
+
* into a doc workspace that must already exist. Every component that EXISTS under the base is
|
|
138
|
+
* judged exactly as `preparePartitionedInteractiveRoot` judges it — a symbolic link or a
|
|
139
|
+
* non-directory refuses, an existing partition must realpath-resolve inside the base — and a
|
|
140
|
+
* component that does not exist ends the walk: nothing sits there to be followed, and a reader
|
|
141
|
+
* creates nothing (an event naming a project that never had a partition must not mint one).
|
|
142
|
+
* Returns the lexical path either way.
|
|
143
|
+
*/
|
|
144
|
+
export function checkPartitionedInteractiveRoot(projectId, home = homedir()) {
|
|
145
|
+
return containPartitionedInteractiveRoot(projectId, home, false);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The ONE containment walk both entry points above run — `materialize` is the only difference
|
|
149
|
+
* (create a missing component vs. stop at it). Kept single on purpose: the routes and the event
|
|
150
|
+
* seams must agree on what `projects/<id>` may be, or a link the routes refuse is still followed
|
|
151
|
+
* through the seam path (the gap Copilot found on #474).
|
|
152
|
+
*/
|
|
153
|
+
function containPartitionedInteractiveRoot(projectId, home, materialize) {
|
|
154
|
+
const partition = partitionedInteractiveRoot(projectId, home);
|
|
155
|
+
const base = partitionsBase(home);
|
|
156
|
+
if (materialize) {
|
|
157
|
+
mkdirSync(base, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
else if (lstatOrNull(base) === null) {
|
|
160
|
+
return partition; // no base yet ⇒ no partition ⇒ nothing on disk a link could redirect
|
|
161
|
+
}
|
|
162
|
+
const refuse = (path, reason) => {
|
|
163
|
+
throw new InteractivePartitionRefusedError(projectId, path, reason, base);
|
|
164
|
+
};
|
|
165
|
+
// One segment today (an id is a single path segment); walked as a path so a deeper layout
|
|
166
|
+
// later inherits the same rule.
|
|
167
|
+
let cursor = base;
|
|
168
|
+
for (const segment of relative(base, partition).split(sep)) {
|
|
169
|
+
cursor = resolve(cursor, segment);
|
|
170
|
+
let st = lstatOrNull(cursor);
|
|
171
|
+
if (st === null) {
|
|
172
|
+
if (!materialize)
|
|
173
|
+
return partition; // absent: nothing to follow, and a reader mints nothing
|
|
174
|
+
try {
|
|
175
|
+
mkdirSync(cursor);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
if (err.code !== 'EEXIST')
|
|
179
|
+
throw err;
|
|
180
|
+
}
|
|
181
|
+
st = lstatOrNull(cursor) ?? refuse(cursor, 'vanished while it was being created');
|
|
182
|
+
}
|
|
183
|
+
if (st.isSymbolicLink()) {
|
|
184
|
+
let target = '?';
|
|
185
|
+
try {
|
|
186
|
+
target = readlinkSync(cursor);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
/* an unreadable link — the path alone is the finding */
|
|
190
|
+
}
|
|
191
|
+
refuse(cursor, `is a symbolic link (→ ${target})`);
|
|
192
|
+
}
|
|
193
|
+
if (!st.isDirectory())
|
|
194
|
+
refuse(cursor, 'is not a directory');
|
|
195
|
+
}
|
|
196
|
+
const realBase = realpathSync.native(base);
|
|
197
|
+
const realPartition = realpathSync.native(partition);
|
|
198
|
+
if (!realPartition.startsWith(realBase + sep)) {
|
|
199
|
+
refuse(partition, `resolves to ${realPartition}, outside ${realBase}`);
|
|
200
|
+
}
|
|
201
|
+
return partition;
|
|
202
|
+
}
|
|
29
203
|
/** Expand a leading `~` and absolutize, so every spelling of one directory keys the same. */
|
|
30
204
|
function canonicalize(value, home) {
|
|
31
205
|
const expanded = value === '~' ? home : value.startsWith('~/') || value.startsWith('~\\') ? resolve(home, value.slice(2)) : value;
|
|
32
206
|
return resolve(expanded);
|
|
33
207
|
}
|
|
34
208
|
/**
|
|
35
|
-
* The
|
|
36
|
-
*
|
|
37
|
-
*
|
|
209
|
+
* The EXPLICIT half of the precedence — the project's own `interactiveRoot`, else the
|
|
210
|
+
* `WICKED_INTERACTIVE_ROOT` shared binding — or null when neither is set. A blank/whitespace
|
|
211
|
+
* setting is treated as null, not as "the cwd".
|
|
38
212
|
*/
|
|
39
|
-
|
|
213
|
+
function explicitInteractiveRoot(setting, env, home) {
|
|
40
214
|
const own = setting?.interactiveRoot;
|
|
41
215
|
if (typeof own === 'string' && own.trim() !== '')
|
|
42
216
|
return canonicalize(own.trim(), home);
|
|
43
217
|
const shared = env[ROOT_ENV];
|
|
44
218
|
if (typeof shared === 'string' && shared.trim() !== '')
|
|
45
219
|
return canonicalize(shared.trim(), home);
|
|
46
|
-
return
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The resolved, canonical docs root for a setting WITHOUT a project identity — and therefore its
|
|
224
|
+
* bridge pool key. Precedence: the own `interactiveRoot` › `WICKED_INTERACTIVE_ROOT` › the
|
|
225
|
+
* shared default. This is the `default` project's resolution; callers that know which project
|
|
226
|
+
* they are resolving for use `resolveProjectInteractiveRoot` so other projects partition.
|
|
227
|
+
*/
|
|
228
|
+
export function resolveInteractiveRoot(setting, env = process.env, home = homedir()) {
|
|
229
|
+
return explicitInteractiveRoot(setting, env, home) ?? defaultInteractiveRoot(home);
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* The resolved, canonical docs root for a PROJECT (crew#472). Precedence: the project's own
|
|
233
|
+
* `interactiveRoot` › `WICKED_INTERACTIVE_ROOT` › the project's default — the legacy shared root
|
|
234
|
+
* for `default` (and for an unknown project identity, `undefined`: an event that carries no
|
|
235
|
+
* `project_id` belongs to Unfiled), the `projects/<projectId>` partition for everything else.
|
|
236
|
+
*
|
|
237
|
+
* The partition is containment-checked on REAL paths (crew#474): whatever already sits at
|
|
238
|
+
* `projects/<projectId>` — or on the way to it — must be a real directory whose real path is
|
|
239
|
+
* inside `projects/`, else `InteractivePartitionRefusedError`; a partition that does not exist
|
|
240
|
+
* yet is returned as its lexical path and NOTHING is created (`checkPartitionedInteractiveRoot`).
|
|
241
|
+
* This is the event seams' resolver (`server.ts` `interactiveDocsRoot`): they read manifests
|
|
242
|
+
* under a root the routes materialized, and a lexical-only answer here was the one path left on
|
|
243
|
+
* which a symlinked partition would still have been followed (Copilot on #474).
|
|
244
|
+
*/
|
|
245
|
+
export function resolveProjectInteractiveRoot(projectId, setting, env = process.env, home = homedir()) {
|
|
246
|
+
return resolveProjectRootWith(checkPartitionedInteractiveRoot, projectId, setting, env, home);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* `resolveProjectInteractiveRoot` for a caller about to USE the root and entitled to create it —
|
|
250
|
+
* the routes, through `project-root.ts`. Same precedence, the SAME containment walk; the one
|
|
251
|
+
* difference is that a missing partition is materialized (`preparePartitionedInteractiveRoot`)
|
|
252
|
+
* instead of merely spelled. An explicit root and the `default` project's legacy root are
|
|
253
|
+
* returned untouched by both resolvers — an operator's own directory is theirs to place, behind
|
|
254
|
+
* a link or not (§7.2) — so those two never cost a disk access.
|
|
255
|
+
*/
|
|
256
|
+
export function ensureProjectInteractiveRoot(projectId, setting, env = process.env, home = homedir()) {
|
|
257
|
+
return resolveProjectRootWith(preparePartitionedInteractiveRoot, projectId, setting, env, home);
|
|
258
|
+
}
|
|
259
|
+
/** The one precedence rule, parameterized by how a partition is produced (spelled vs. prepared). */
|
|
260
|
+
function resolveProjectRootWith(partition, projectId, setting, env, home) {
|
|
261
|
+
const explicit = explicitInteractiveRoot(setting, env, home);
|
|
262
|
+
if (explicit !== null)
|
|
263
|
+
return explicit;
|
|
264
|
+
if (projectId === undefined || projectId === DEFAULT_PROJECT_ID)
|
|
265
|
+
return defaultInteractiveRoot(home);
|
|
266
|
+
return partition(projectId, home);
|
|
47
267
|
}
|
|
48
268
|
//# sourceMappingURL=bridge-root.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-root.js","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"bridge-root.js","sourceRoot":"","sources":["../../src/interactive/bridge-root.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAc,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAQpE;;;qDAGqD;AACrD,MAAM,CAAC,MAAM,QAAQ,GAAG,yBAAyB,CAAC;AAElD,6FAA6F;AAC7F,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;AAEvC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,8BAA8B,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAe,OAAO,EAAE;IAC7D,OAAO,OAAO,CAAC,IAAI,EAAE,oBAAoB,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,0BAA0B,CAAC,SAAiB,EAAE,OAAe,OAAO,EAAE;IACpF,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,KAAK,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,4CAA4C,CAAC,CAAC;IACvG,CAAC;IACD,OAAO,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;AAClD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,cAAc,CAAC,OAAe,OAAO,EAAE;IACrD,OAAO,OAAO,CAAC,sBAAsB,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,gCAAiC,SAAQ,KAAK;IAG9C;IAEA;IAJF,UAAU,GAAG,GAAG,CAAC;IAC1B,YACW,SAAiB;IAC1B,sCAAsC;IAC7B,IAAY,EACrB,MAAc,EACd,IAAY;QAEZ,KAAK,CACH,qDAAqD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,IAAI,IAAI,MAAM,IAAI;YACnG,0DAA0D,IAAI,mCAAmC,CACpG,CAAC;QATO,cAAS,GAAT,SAAS,CAAQ;QAEjB,SAAI,GAAJ,IAAI,CAAQ;QAQrB,IAAI,CAAC,IAAI,GAAG,kCAAkC,CAAC;IACjD,CAAC;CACF;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAClE,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,iCAAiC,CAAC,SAAiB,EAAE,OAAe,OAAO,EAAE;IAC3F,OAAO,iCAAiC,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAClE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,+BAA+B,CAAC,SAAiB,EAAE,OAAe,OAAO,EAAE;IACzF,OAAO,iCAAiC,CAAC,SAAS,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACH,SAAS,iCAAiC,CAAC,SAAiB,EAAE,IAAY,EAAE,WAAoB;IAC9F,MAAM,SAAS,GAAG,0BAA0B,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;IAClC,IAAI,WAAW,EAAE,CAAC;QAChB,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;SAAM,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QACtC,OAAO,SAAS,CAAC,CAAC,qEAAqE;IACzF,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,MAAc,EAAS,EAAE;QACrD,MAAM,IAAI,gCAAgC,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC5E,CAAC,CAAC;IAEF,0FAA0F;IAC1F,gCAAgC;IAChC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3D,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,IAAI,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;YAChB,IAAI,CAAC,WAAW;gBAAE,OAAO,SAAS,CAAC,CAAC,wDAAwD;YAC5F,IAAI,CAAC;gBACH,SAAS,CAAC,MAAM,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,GAAG,CAAC;YAClE,CAAC;YACD,EAAE,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,qCAAqC,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC;YACxB,IAAI,MAAM,GAAG,GAAG,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,wDAAwD;YAC1D,CAAC;YACD,MAAM,CAAC,MAAM,EAAE,yBAAyB,MAAM,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE;YAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,QAAQ,GAAG,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,aAAa,GAAG,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACrD,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,QAAQ,GAAG,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,CAAC,SAAS,EAAE,eAAe,aAAa,aAAa,QAAQ,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,6FAA6F;AAC7F,SAAS,YAAY,CAAC,KAAa,EAAE,IAAY;IAC/C,MAAM,QAAQ,GACZ,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IACnH,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,SAAS,uBAAuB,CAC9B,OAAkD,EAClD,GAAuC,EACvC,IAAY;IAEZ,MAAM,GAAG,GAAG,OAAO,EAAE,eAAe,CAAC;IACrC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,YAAY,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACxF,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,YAAY,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAAkD,EAClD,MAA0C,OAAO,CAAC,GAAG,EACrD,OAAe,OAAO,EAAE;IAExB,OAAO,uBAAuB,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,IAAI,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,6BAA6B,CAC3C,SAA6B,EAC7B,OAAkD,EAClD,MAA0C,OAAO,CAAC,GAAG,EACrD,OAAe,OAAO,EAAE;IAExB,OAAO,sBAAsB,CAAC,+BAA+B,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AAChG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAC1C,SAA6B,EAC7B,OAAkD,EAClD,MAA0C,OAAO,CAAC,GAAG,EACrD,OAAe,OAAO,EAAE;IAExB,OAAO,sBAAsB,CAAC,iCAAiC,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AAClG,CAAC;AAED,oGAAoG;AACpG,SAAS,sBAAsB,CAC7B,SAAsD,EACtD,SAA6B,EAC7B,OAAkD,EAClD,GAAuC,EACvC,IAAY;IAEZ,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7D,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,QAAQ,CAAC;IACvC,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,kBAAkB;QAAE,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;IACrG,OAAO,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC"}
|
|
@@ -60,6 +60,8 @@ export interface DocDeleteDeps {
|
|
|
60
60
|
* a directly-driven route set gets an inert default so unit tests never touch ~/.wicked-crew). */
|
|
61
61
|
dropDocLedgerRows: (documentId: string) => DocLedgerSweep;
|
|
62
62
|
env?: NodeJS.ProcessEnv;
|
|
63
|
+
/** The home the default root hangs off (tests point it at a scratch dir). */
|
|
64
|
+
home?: string;
|
|
63
65
|
log?: (msg: string) => void;
|
|
64
66
|
/** Budget for the bridge's retire call (tests shorten it). The tombstone write is local and
|
|
65
67
|
* fast; the default only has to outlast a busy event-loop, not a build. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doc-delete-routes.d.ts","sourceRoot":"","sources":["../../src/interactive/doc-delete-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"doc-delete-routes.d.ts","sourceRoot":"","sources":["../../src/interactive/doc-delete-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAA0B,KAAK,qBAAqB,EAAmB,MAAM,kBAAkB,CAAC;AAEvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAW5D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,KAAK,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,CAAC,GAAG,EAAE;QAAE,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,KAAK,KAAK,CAAC;IAC3C;uGACmG;IACnG,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,cAAc,CAAC;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B;gFAC4E;IAC5E,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AASD,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,eAAe,EACpB,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,aAAa,GAClB,IAAI,CA4LN"}
|
|
@@ -43,11 +43,9 @@
|
|
|
43
43
|
* Every state change lands in the actor audit trail as `interactive.doc.deleted` (task #88).
|
|
44
44
|
*/
|
|
45
45
|
import { API_PREFIX } from '../api/api-prefix.js';
|
|
46
|
-
import { ProjectsUnsupportedError } from '../core/adapter.js';
|
|
47
|
-
import { DEFAULT_PROJECT_ID } from '../projects/routes.js';
|
|
48
|
-
import { resolveInteractiveRoot } from './bridge-root.js';
|
|
49
46
|
import { BridgeUnavailableError } from './bridge-pool.js';
|
|
50
47
|
import { DOC_NAME } from './draft-events.js';
|
|
48
|
+
import { projectDocsRoot } from './project-root.js';
|
|
51
49
|
const V = API_PREFIX;
|
|
52
50
|
/** A sweep that deliberately did not run — the "nothing diverged" half of a refused delete. */
|
|
53
51
|
const SWEEP_SKIPPED = Object.freeze({
|
|
@@ -57,25 +55,11 @@ const SWEEP_SKIPPED = Object.freeze({
|
|
|
57
55
|
});
|
|
58
56
|
export function registerInteractiveDocDelete(app, adapter, deps) {
|
|
59
57
|
const { settings, pool, audit, actorOf, dropDocLedgerRows } = deps;
|
|
60
|
-
const env = deps.env ?? process.env;
|
|
61
58
|
const log = deps.log ?? (() => undefined);
|
|
62
59
|
const timeoutMs = deps.upstreamTimeoutMs ?? 30_000;
|
|
63
60
|
/** The resolved docs root for a project, or null when no such project exists — the SAME
|
|
64
|
-
* resolution the proxy
|
|
65
|
-
|
|
66
|
-
async function rootFor(projectId) {
|
|
67
|
-
if (projectId !== DEFAULT_PROJECT_ID) {
|
|
68
|
-
try {
|
|
69
|
-
if ((await adapter.projectGet(projectId)) === null)
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
if (!(err instanceof ProjectsUnsupportedError))
|
|
74
|
-
throw err;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return resolveInteractiveRoot(settings.get(projectId), env);
|
|
78
|
-
}
|
|
61
|
+
* resolution the proxy and the docs list use (`project-root.ts`). */
|
|
62
|
+
const rootFor = (projectId) => projectDocsRoot(adapter, settings, projectId, deps);
|
|
79
63
|
/** One retire call to the bridge. Throws on transport failure (caller retries once). */
|
|
80
64
|
async function retireUpstream(bridge, doc) {
|
|
81
65
|
const res = await fetch(`http://${bridge.host}:${bridge.port}/api/docs/${doc}`, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doc-delete-routes.js","sourceRoot":"","sources":["../../src/interactive/doc-delete-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"doc-delete-routes.js","sourceRoot":"","sources":["../../src/interactive/doc-delete-routes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/v1/projects/:projectId/interactive/api/docs` — the project-attributed docs list
|
|
3
|
+
* (crew#472).
|
|
4
|
+
*
|
|
5
|
+
* The bridge's own `GET /api/docs` lists the docs of ONE root and knows nothing about crew's
|
|
6
|
+
* projects: a row says `name`/`kind`/`head`/`versions`/`updated_at`, never which project it was
|
|
7
|
+
* listed for. With docs roots partitioned per project (`bridge-root.ts`), the mount a list was
|
|
8
|
+
* fetched under IS the attribution — so this route relays the bridge's rows field-for-field and
|
|
9
|
+
* stamps each with the `projectId` of the mount, the one fact the bridge cannot supply and a
|
|
10
|
+
* client rendering docs from several projects at once cannot otherwise recover.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT part of the pure-transport proxy (`proxy-routes.ts` stays response-sniffing
|
|
13
|
+
* free, the same posture as the governed delete): this is its own route, one static segment more
|
|
14
|
+
* specific than the proxy's wildcard, so fastify routes exactly this GET here while every other
|
|
15
|
+
* method on the same path — `POST /api/docs` creates a doc — and every other path keep flowing
|
|
16
|
+
* through the proxy verbatim. The query string (`?includeRetired=1`) is forwarded untouched.
|
|
17
|
+
*
|
|
18
|
+
* Non-list answers are relayed rather than reshaped: a bridge that fails its own listing answers
|
|
19
|
+
* with its status and body; a bridge that answers 200 with something other than a JSON array of
|
|
20
|
+
* doc summaries — a non-JSON body, a JSON object, a list holding `null` or a string where a row
|
|
21
|
+
* should be — is a 502 that says which, never an empty list pretending nothing exists and never
|
|
22
|
+
* `[{ projectId }]` minted from nothing. And a body that is malformed is told apart from a body
|
|
23
|
+
* that never fully ARRIVED: the first is the bridge's answer (no retry would change it), the
|
|
24
|
+
* second is a transport failure that takes the same invalidate → retry-once → diagnostic path a
|
|
25
|
+
* refused connect does.
|
|
26
|
+
*/
|
|
27
|
+
import type { FastifyInstance } from 'fastify';
|
|
28
|
+
import type { CoreAdapter } from '../core/adapter.js';
|
|
29
|
+
import { type InteractiveBridgePool } from './bridge-pool.js';
|
|
30
|
+
import type { ProjectSettingsStore } from '../projects/settings.js';
|
|
31
|
+
export interface DocListDeps {
|
|
32
|
+
settings: ProjectSettingsStore;
|
|
33
|
+
pool: InteractiveBridgePool;
|
|
34
|
+
env?: NodeJS.ProcessEnv;
|
|
35
|
+
/** The home the default root hangs off (tests point it at a scratch dir). */
|
|
36
|
+
home?: string;
|
|
37
|
+
log?: (msg: string) => void;
|
|
38
|
+
/** Budget for the bridge's list call (tests shorten it). The list is a directory scan. */
|
|
39
|
+
upstreamTimeoutMs?: number;
|
|
40
|
+
}
|
|
41
|
+
export declare function registerInteractiveDocList(app: FastifyInstance, adapter: CoreAdapter, deps: DocListDeps): void;
|
|
42
|
+
//# sourceMappingURL=doc-list-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-list-routes.d.ts","sourceRoot":"","sources":["../../src/interactive/doc-list-routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,SAAS,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAA0B,KAAK,qBAAqB,EAAmB,MAAM,kBAAkB,CAAC;AACvG,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAmCpE,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,oBAAoB,CAAC;IAC/B,IAAI,EAAE,qBAAqB,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,0FAA0F;IAC1F,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,wBAAgB,0BAA0B,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,IAAI,CA0H9G"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/v1/projects/:projectId/interactive/api/docs` — the project-attributed docs list
|
|
3
|
+
* (crew#472).
|
|
4
|
+
*
|
|
5
|
+
* The bridge's own `GET /api/docs` lists the docs of ONE root and knows nothing about crew's
|
|
6
|
+
* projects: a row says `name`/`kind`/`head`/`versions`/`updated_at`, never which project it was
|
|
7
|
+
* listed for. With docs roots partitioned per project (`bridge-root.ts`), the mount a list was
|
|
8
|
+
* fetched under IS the attribution — so this route relays the bridge's rows field-for-field and
|
|
9
|
+
* stamps each with the `projectId` of the mount, the one fact the bridge cannot supply and a
|
|
10
|
+
* client rendering docs from several projects at once cannot otherwise recover.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT part of the pure-transport proxy (`proxy-routes.ts` stays response-sniffing
|
|
13
|
+
* free, the same posture as the governed delete): this is its own route, one static segment more
|
|
14
|
+
* specific than the proxy's wildcard, so fastify routes exactly this GET here while every other
|
|
15
|
+
* method on the same path — `POST /api/docs` creates a doc — and every other path keep flowing
|
|
16
|
+
* through the proxy verbatim. The query string (`?includeRetired=1`) is forwarded untouched.
|
|
17
|
+
*
|
|
18
|
+
* Non-list answers are relayed rather than reshaped: a bridge that fails its own listing answers
|
|
19
|
+
* with its status and body; a bridge that answers 200 with something other than a JSON array of
|
|
20
|
+
* doc summaries — a non-JSON body, a JSON object, a list holding `null` or a string where a row
|
|
21
|
+
* should be — is a 502 that says which, never an empty list pretending nothing exists and never
|
|
22
|
+
* `[{ projectId }]` minted from nothing. And a body that is malformed is told apart from a body
|
|
23
|
+
* that never fully ARRIVED: the first is the bridge's answer (no retry would change it), the
|
|
24
|
+
* second is a transport failure that takes the same invalidate → retry-once → diagnostic path a
|
|
25
|
+
* refused connect does.
|
|
26
|
+
*/
|
|
27
|
+
import { API_PREFIX } from '../api/api-prefix.js';
|
|
28
|
+
import { BridgeUnavailableError } from './bridge-pool.js';
|
|
29
|
+
import { projectDocsRoot } from './project-root.js';
|
|
30
|
+
const V = API_PREFIX;
|
|
31
|
+
function isDocSummaryRow(row) {
|
|
32
|
+
if (typeof row !== 'object' || row === null || Array.isArray(row))
|
|
33
|
+
return false;
|
|
34
|
+
const r = row;
|
|
35
|
+
return typeof r['name'] === 'string' && typeof r['kind'] === 'string';
|
|
36
|
+
}
|
|
37
|
+
/** A short, safe rendering of an offending row for the 502 detail — its type and a bounded preview. */
|
|
38
|
+
function describeRow(row) {
|
|
39
|
+
if (row === null)
|
|
40
|
+
return 'null';
|
|
41
|
+
if (Array.isArray(row))
|
|
42
|
+
return 'an array';
|
|
43
|
+
if (typeof row !== 'object')
|
|
44
|
+
return `a ${typeof row} (${String(JSON.stringify(row) ?? row).slice(0, 80)})`;
|
|
45
|
+
const keys = Object.keys(row).slice(0, 8).join(', ');
|
|
46
|
+
return `an object without string name/kind (keys: ${keys === '' ? 'none' : keys})`;
|
|
47
|
+
}
|
|
48
|
+
export function registerInteractiveDocList(app, adapter, deps) {
|
|
49
|
+
const { settings, pool } = deps;
|
|
50
|
+
const log = deps.log ?? (() => undefined);
|
|
51
|
+
const timeoutMs = deps.upstreamTimeoutMs ?? 30_000;
|
|
52
|
+
/**
|
|
53
|
+
* One list call to the bridge. Throws on transport failure — a refused connect, the timeout, a
|
|
54
|
+
* connection torn down while the body was still arriving — and the caller retries once. Only a
|
|
55
|
+
* COMPLETE body reaches the parser, so a parse failure is exactly what it looks like: the
|
|
56
|
+
* bridge answered with something other than JSON (its default not-found page, say). No retry
|
|
57
|
+
* would change that, so it is reported as the bridge's answer, never as unreachability.
|
|
58
|
+
*/
|
|
59
|
+
async function listUpstream(bridge, query) {
|
|
60
|
+
const res = await fetch(`http://${bridge.host}:${bridge.port}/api/docs${query}`, {
|
|
61
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
62
|
+
});
|
|
63
|
+
const text = await res.text();
|
|
64
|
+
try {
|
|
65
|
+
return { status: res.status, body: JSON.parse(text), json: true };
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return { status: res.status, body: null, json: false };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function unavailable(reply, err) {
|
|
72
|
+
if (!(err instanceof BridgeUnavailableError))
|
|
73
|
+
throw err;
|
|
74
|
+
log(`[doc-list] interactive bridge unavailable: ${err.message}`);
|
|
75
|
+
return reply.code(503).send({ code: 'bridge_unavailable', hint: err.hint });
|
|
76
|
+
}
|
|
77
|
+
app.get(`${V}/projects/:projectId/interactive/api/docs`, {
|
|
78
|
+
config: {
|
|
79
|
+
manifest: {
|
|
80
|
+
// The wire is a JSON ARRAY of rows, spelled in the manifest's TypeScript-inline form
|
|
81
|
+
// (`{ runs: SessionView[] }` is the wrapped-list precedent). 500 is the refused
|
|
82
|
+
// partition (`InteractivePartitionRefusedError`, bridge-root.ts).
|
|
83
|
+
responseType: 'InteractiveDocSummary[]',
|
|
84
|
+
statusCodes: [200, 404, 500, 502, 503],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
}, async (req, reply) => {
|
|
88
|
+
const { projectId } = req.params;
|
|
89
|
+
// The RAW query, so percent-encoding survives the hop (the proxy's verbatim rule).
|
|
90
|
+
const raw = req.raw.url ?? '';
|
|
91
|
+
const query = raw.includes('?') ? raw.slice(raw.indexOf('?')) : '';
|
|
92
|
+
const root = await projectDocsRoot(adapter, settings, projectId, deps);
|
|
93
|
+
if (root === null)
|
|
94
|
+
return reply.code(404).send({ error: `Project ${projectId} not found` });
|
|
95
|
+
let bridge;
|
|
96
|
+
try {
|
|
97
|
+
bridge = await pool.ensure(root);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
return unavailable(reply, err);
|
|
101
|
+
}
|
|
102
|
+
// One retry on a dead cached bridge, the proxy's discipline: invalidate, let `ensure`
|
|
103
|
+
// restart it, try once more — then fail loudly instead of looping.
|
|
104
|
+
let upstream;
|
|
105
|
+
try {
|
|
106
|
+
upstream = await listUpstream(bridge, query);
|
|
107
|
+
}
|
|
108
|
+
catch (firstErr) {
|
|
109
|
+
pool.invalidate(root);
|
|
110
|
+
try {
|
|
111
|
+
bridge = await pool.ensure(root);
|
|
112
|
+
}
|
|
113
|
+
catch (startErr) {
|
|
114
|
+
return unavailable(reply, startErr);
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
upstream = await listUpstream(bridge, query);
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
121
|
+
log(`[doc-list] list call to the bridge failed for project ${projectId}: ${detail}`);
|
|
122
|
+
return reply.code(502).send({
|
|
123
|
+
error: 'wicked-interactive was unreachable — GET /api/docs did not answer',
|
|
124
|
+
detail,
|
|
125
|
+
first_attempt: firstErr instanceof Error ? firstErr.message : String(firstErr),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// The bridge's own failure, relayed with its status: never reshaped into an empty list. A
|
|
130
|
+
// non-JSON failure body (a bridge's default not-found page) gets a JSON error in its place.
|
|
131
|
+
if (upstream.status !== 200) {
|
|
132
|
+
return reply
|
|
133
|
+
.code(upstream.status)
|
|
134
|
+
.send(upstream.json && upstream.body !== null
|
|
135
|
+
? upstream.body
|
|
136
|
+
: { error: `wicked-interactive answered GET /api/docs with HTTP ${upstream.status}` });
|
|
137
|
+
}
|
|
138
|
+
if (!upstream.json) {
|
|
139
|
+
log(`[doc-list] the bridge answered GET /api/docs for project ${projectId} with a body that is not JSON`);
|
|
140
|
+
return reply
|
|
141
|
+
.code(502)
|
|
142
|
+
.send({ error: 'wicked-interactive answered GET /api/docs with a malformed body (not JSON)' });
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(upstream.body)) {
|
|
145
|
+
log(`[doc-list] the bridge answered GET /api/docs for project ${projectId} with a non-list body`);
|
|
146
|
+
return reply.code(502).send({ error: 'wicked-interactive answered GET /api/docs with a non-list body' });
|
|
147
|
+
}
|
|
148
|
+
// Every row must be a doc summary before it is vouched for: `[null]` would otherwise become
|
|
149
|
+
// `[{ projectId }]` with a 200, and a string row would spread into char-indexed keys.
|
|
150
|
+
const rows = upstream.body;
|
|
151
|
+
const summaries = [];
|
|
152
|
+
for (const [index, row] of rows.entries()) {
|
|
153
|
+
if (!isDocSummaryRow(row)) {
|
|
154
|
+
const detail = `row ${index} is not a doc summary: ${describeRow(row)}`;
|
|
155
|
+
log(`[doc-list] the bridge answered GET /api/docs for project ${projectId} with a malformed list — ${detail}`);
|
|
156
|
+
return reply
|
|
157
|
+
.code(502)
|
|
158
|
+
.send({ error: 'wicked-interactive answered GET /api/docs with a malformed list', detail });
|
|
159
|
+
}
|
|
160
|
+
summaries.push(row);
|
|
161
|
+
}
|
|
162
|
+
return reply.send(summaries.map((row) => ({ ...row, projectId })));
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=doc-list-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doc-list-routes.js","sourceRoot":"","sources":["../../src/interactive/doc-list-routes.ts"],"names":[],"mappings":"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"}
|