@tiphys/kernel 0.1.0 → 0.2.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/AGENTS.md +56 -4
- package/assurance-modes.yaml +23 -2
- package/dist/bin/tiphys.js +86 -8
- package/dist/src/adapters/load.d.ts +202 -0
- package/dist/src/adapters/load.js +440 -0
- package/dist/src/brief.js +27 -20
- package/dist/src/checks.d.ts +720 -9
- package/dist/src/checks.js +1874 -163
- package/dist/src/cli.js +11 -0
- package/dist/src/commands/brief.js +27 -4
- package/dist/src/commands/cutover.d.ts +35 -0
- package/dist/src/commands/cutover.js +448 -0
- package/dist/src/commands/doctor.d.ts +229 -0
- package/dist/src/commands/doctor.js +968 -27
- package/dist/src/commands/init.d.ts +3 -3
- package/dist/src/commands/init.js +57 -8
- package/dist/src/commands/lock.d.ts +33 -0
- package/dist/src/commands/lock.js +117 -6
- package/dist/src/commands/next.d.ts +130 -0
- package/dist/src/commands/next.js +597 -0
- package/dist/src/commands/pool.js +12 -1
- package/dist/src/commands/resume.d.ts +1 -0
- package/dist/src/commands/resume.js +88 -0
- package/dist/src/commands/spawn.js +51 -2
- package/dist/src/commands/status.d.ts +6 -4
- package/dist/src/commands/status.js +6 -4
- package/dist/src/commands/sync.d.ts +47 -0
- package/dist/src/commands/sync.js +341 -0
- package/dist/src/commands/teardown.js +10 -2
- package/dist/src/commands/validate.js +70 -0
- package/dist/src/cutover.d.ts +584 -0
- package/dist/src/cutover.js +1444 -0
- package/dist/src/exclusion.d.ts +389 -0
- package/dist/src/exclusion.js +843 -0
- package/dist/src/exec/env.d.ts +152 -2
- package/dist/src/exec/env.js +146 -2
- package/dist/src/fleet.d.ts +172 -0
- package/dist/src/fleet.js +219 -1
- package/dist/src/gates/citations.js +7 -1
- package/dist/src/gates/coverage.d.ts +113 -22
- package/dist/src/gates/coverage.js +166 -31
- package/dist/src/gates/credentials.d.ts +159 -0
- package/dist/src/gates/credentials.js +221 -2
- package/dist/src/gates/gate-classes.d.ts +56 -0
- package/dist/src/gates/gate-classes.js +633 -0
- package/dist/src/gates/merge-preconditions.d.ts +319 -0
- package/dist/src/gates/merge-preconditions.js +932 -0
- package/dist/src/gates/red-witness.js +105 -13
- package/dist/src/gates/run.d.ts +49 -1
- package/dist/src/gates/run.js +83 -5
- package/dist/src/gates/schemas/phase-declaration.schema.json +45 -0
- package/dist/src/gates/suite.js +48 -7
- package/dist/src/hooks.d.ts +55 -3
- package/dist/src/hooks.js +69 -6
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +30 -0
- package/dist/src/lock.d.ts +82 -4
- package/dist/src/lock.js +314 -22
- package/dist/src/model-resolution.d.ts +159 -0
- package/dist/src/model-resolution.js +307 -0
- package/dist/src/path-identity.d.ts +32 -0
- package/dist/src/path-identity.js +38 -0
- package/dist/src/pool.d.ts +197 -1
- package/dist/src/pool.js +289 -22
- package/dist/src/roles.d.ts +31 -0
- package/dist/src/roles.js +42 -0
- package/dist/src/spawn.d.ts +307 -2
- package/dist/src/spawn.js +690 -19
- package/dist/src/status.d.ts +27 -2
- package/dist/src/status.js +34 -5
- package/dist/src/task.d.ts +295 -55
- package/dist/src/task.js +125 -123
- package/dist/src/teardown.d.ts +7 -0
- package/dist/src/teardown.js +120 -12
- package/dist/src/validate.d.ts +44 -11
- package/dist/src/validate.js +44 -34
- package/dist/src/watcher.js +1 -11
- package/dist/src/witness/run.d.ts +32 -7
- package/dist/src/witness/run.js +76 -30
- package/dist/src/witness/spec.d.ts +168 -0
- package/dist/src/witness/spec.js +240 -18
- package/dist/tsconfig.src.tsbuildinfo +1 -1
- package/gate-registry.yaml +136 -0
- package/gates.manifest.json +63 -1
- package/package.json +18 -3
- package/roles/implementer.md +3 -0
- package/schemas/README.md +1 -0
- package/schemas/assurance-modes.schema.json +1 -1
- package/schemas/charter.schema.json +19 -0
- package/schemas/cutover-state.schema.json +64 -0
- package/schemas/executor-record.schema.json +36 -0
- package/schemas/model-resolution.schema.json +362 -0
- package/schemas/verdict.schema.json +9 -3
- package/schemas/write-bypass.schema.json +69 -0
package/AGENTS.md
CHANGED
|
@@ -245,11 +245,30 @@ conversation memory is a cache and the files are the truth, and a cache that is
|
|
|
245
245
|
never written back is lost at the next restart, which is a routine event and not
|
|
246
246
|
an incident.
|
|
247
247
|
|
|
248
|
+
HOW: `tiphys sync`, which is the mechanism this clause is discharged by. It is
|
|
249
|
+
not a reminder to run git carefully. It enumerates what changed, asks git which
|
|
250
|
+
of those paths the fleet `.gitignore` covers, commits exactly the rest, and
|
|
251
|
+
pushes. The partition above is therefore DERIVED from the fleet's own ignore
|
|
252
|
+
rules rather than from a reader's judgment about which of two files is durable,
|
|
253
|
+
and a prefix added to that ignore set is honoured by the same derivation with
|
|
254
|
+
no further edit anywhere.
|
|
255
|
+
|
|
256
|
+
TWO THINGS IT REFUSES RATHER THAN GUESSES, because both are states where being
|
|
257
|
+
helpful would be destructive. An ephemeral path that is ALREADY STAGED stops the
|
|
258
|
+
run: git commits the index, not the pathspec it was handed, so committing at all
|
|
259
|
+
would commit that path, and unstaging on your behalf is a change to work the
|
|
260
|
+
command did not create. A fleet home with no remote stops the run BEFORE the
|
|
261
|
+
commit, because the discipline is commit AND push and a command that did the
|
|
262
|
+
first half here would have done the half that gets dropped and reported the half
|
|
263
|
+
that does not.
|
|
264
|
+
|
|
248
265
|
AND PUSHED, WHICH IS THE HALF THAT GETS DROPPED. Evidence that lives only on a
|
|
249
266
|
long-lived side branch dies with that branch. Process paperwork reaches the
|
|
250
267
|
default branch through a pull request like everything else, batched rather than
|
|
251
268
|
one request per file, and it is not allowed to accumulate unpushed while the
|
|
252
|
-
code it proves lands ahead of it.
|
|
269
|
+
code it proves lands ahead of it. A failed push is a NONZERO exit carrying git's
|
|
270
|
+
own first stderr line, never a warning printed beside a success: the work is
|
|
271
|
+
committed locally and still owed to the remote, and you are the one who owes it.
|
|
253
272
|
|
|
254
273
|
## clause merge-authority: the declared mode says who signs, and for a delegated grant the signature is dual clean review
|
|
255
274
|
|
|
@@ -292,12 +311,45 @@ content of this clause: a ref update is a recorded, reversible act over reviewed
|
|
|
292
311
|
commits, and a file edit is unreviewed work by the one role whose independence
|
|
293
312
|
every later check assumes.
|
|
294
313
|
|
|
314
|
+
WHICH MECHANISM ENFORCES IT, replacing nothing above. The clause was a sentence
|
|
315
|
+
you were trusted to remember until M4-P9 shipped a `PreToolUse` hook for it. The
|
|
316
|
+
hook draws exactly the line the two paragraphs above draw, and it draws it over
|
|
317
|
+
the TARGET rather than over a tool name, because "designated merge tooling" has
|
|
318
|
+
no referent in this kernel: a write resolving inside a project working tree and
|
|
319
|
+
outside that clone's `.git/` is REFUSED, and a write under `.git/` is the ref
|
|
320
|
+
update and is PERMITTED. Its refusal is an exit code, not a warning.
|
|
321
|
+
|
|
322
|
+
THE HOTFIX CARVE-OUT IS A DECLARED ACT AND IT EXPIRES. When the pipeline is
|
|
323
|
+
wedged and the fix has to be made in the tree, you write one declaration naming
|
|
324
|
+
the project, the paths, the reason and an ABSOLUTE EXPIRY INSTANT, validated by
|
|
325
|
+
`schemas/write-bypass.schema.json#properties.expiresAt`. Nothing about it is
|
|
326
|
+
optional: a bypass with no expiry is a block switched off permanently by the
|
|
327
|
+
first hotfix, which is why the expiry is required rather than encouraged. Every
|
|
328
|
+
write it permits is appended to a separate evidence log, and NO DECISION EVER
|
|
329
|
+
READS THAT LOG, which is constraint C-1 and is the reason the declaration and
|
|
330
|
+
the evidence are two files rather than one.
|
|
331
|
+
|
|
332
|
+
WHAT IT DOES NOT COVER, stated here because a partial guard read as a total one
|
|
333
|
+
is worse than none. The hook adjudicates the file-writing tools. A shell command
|
|
334
|
+
is NOT adjudicated, because the payload a shell tool call delivers carries the
|
|
335
|
+
command string and no resolvable write target, which was measured rather than
|
|
336
|
+
assumed. So the hook makes an accidental edit impossible and a deliberate one
|
|
337
|
+
through a shell still yours to not do.
|
|
338
|
+
|
|
295
339
|
## clause fleet-resume-specification: what survives reclamation, what is rebuilt, and what doctor reports
|
|
296
340
|
|
|
297
341
|
Assigned to this document by plan v1 PR-201. THIS CLAUSE IS A SPECIFICATION AND
|
|
298
|
-
NOT A MECHANISM
|
|
299
|
-
|
|
300
|
-
|
|
342
|
+
NOT A MECHANISM: what is settled here is what the machinery has to do, and the
|
|
343
|
+
commands that do it are named below rather than described here.
|
|
344
|
+
|
|
345
|
+
WHICH PHASES DELIVERED IT, replacing the sentence that deferred the machinery to
|
|
346
|
+
"a later milestone" and gave a reader no way to tell whether that milestone had
|
|
347
|
+
arrived. `tiphys resume` (M4-P16) rebuilds the ephemeral half a clone does not
|
|
348
|
+
carry, creating only what is absent and never removing anything. `tiphys sync`
|
|
349
|
+
(M4-P18) commits and pushes the durable half, which is what makes "everything
|
|
350
|
+
committed and pushed" below a reachable state rather than a hope. What remains
|
|
351
|
+
deferred is the doctor reporting in the third paragraph, which is owned by its
|
|
352
|
+
own phase; nothing else in this clause is waiting on a milestone.
|
|
301
353
|
|
|
302
354
|
WHAT MUST SURVIVE a cloud fleet being reclaimed: everything committed and pushed
|
|
303
355
|
under `fleet-state-commit-discipline` above. Nothing else is promised, and
|
package/assurance-modes.yaml
CHANGED
|
@@ -108,7 +108,8 @@ modes:
|
|
|
108
108
|
blueprint section 8 (the mode and its pipeline); process doc section 9
|
|
109
109
|
item 3 enumerated (R-096); T-003 for the fix-round-verification stage;
|
|
110
110
|
DR-0012 and DR-0015 for the merge authority actually in force; DR-0016
|
|
111
|
-
for the escalation response;
|
|
111
|
+
for the escalation response; DR-0035 for the fix-round ceiling; T-007 and
|
|
112
|
+
D-M3-32 for the review contracts.
|
|
112
113
|
# ORDER IS LOAD-BEARING. R-024: an adversarial plan review happens before
|
|
113
114
|
# anyone builds, which is a property of this sequence and not of its set,
|
|
114
115
|
# and `mode-stage-order` is the check that says so.
|
|
@@ -193,8 +194,28 @@ modes:
|
|
|
193
194
|
verdict.
|
|
194
195
|
# DR-0012's two limits with DR-0016's response. DATA THE ORCHESTRATOR BRIEF
|
|
195
196
|
# CITES, NOT AN ENFORCEMENT ENGINE: nothing in M3 counts fix rounds.
|
|
197
|
+
#
|
|
198
|
+
# THE FIX-ROUND NUMBER IS DR-0035'S CEILING OF 3, NOT DR-0012'S 2 (M4-P30).
|
|
199
|
+
# DR-0035 replaced the single bound with a table: the dial is the number of
|
|
200
|
+
# rounds between the clean-room reviewer and the implementer, and it runs
|
|
201
|
+
# from one round (a zero or small subject) to three (a large subject at
|
|
202
|
+
# high impact). A single integer cannot carry a table, so what this field
|
|
203
|
+
# records is the CEILING, which is the only reading under which the value
|
|
204
|
+
# is true of every phase rather than wrong for some of them. Writing 2 here
|
|
205
|
+
# made this file state a bound DR-0035 had already lifted, and the kernel's
|
|
206
|
+
# own charter had to go out of its way to avoid repeating it: charter.yaml
|
|
207
|
+
# names the BUDGET rather than a number precisely because the two documents
|
|
208
|
+
# disagreed. They no longer do.
|
|
209
|
+
#
|
|
210
|
+
# NOTHING READS THIS VALUE, and the derivation is published rather than
|
|
211
|
+
# asserted: `grep -rn 'max-fix-rounds-after-review' --include='*.ts'
|
|
212
|
+
# --include='*.mjs' src/ bin/ scripts/` returns zero hits, so no code path
|
|
213
|
+
# branches on it. The readers are this file's schema, the test that pins
|
|
214
|
+
# the shipped values, and `tiphys mode show`, which prints whatever is
|
|
215
|
+
# here. See delivery/work-history/m4-p30.md:1 for the full output and for
|
|
216
|
+
# the regions that derivation did NOT cover.
|
|
196
217
|
escalation-bounds:
|
|
197
|
-
max-fix-rounds-after-review:
|
|
218
|
+
max-fix-rounds-after-review: 3
|
|
198
219
|
recurrence-of-high-in-one-component: 1
|
|
199
220
|
on-exceeded: fresh-implementer-and-third-contract
|
|
200
221
|
|
package/dist/bin/tiphys.js
CHANGED
|
@@ -1,14 +1,92 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { run, usageLine } from "../src/cli.js";
|
|
3
|
+
/**
|
|
4
|
+
* THE TOP-LEVEL ERROR PRESENTATION HANDLER (kernel plan M3, M3-P1 step 8b;
|
|
5
|
+
* D-M3-21).
|
|
6
|
+
*
|
|
7
|
+
* `delivery/STATE.md` carried this as an unowned seam: "clean presentation of
|
|
8
|
+
* a load-time configuration error ... a seam no M1 phase owns". M3-P1 owns it
|
|
9
|
+
* because M3-P1 is the first phase to add commands whose ordinary input is a
|
|
10
|
+
* HAND-AUTHORED FILE that will routinely be malformed, and a validator that
|
|
11
|
+
* answers malformed YAML with a stack trace is a validator nobody trusts
|
|
12
|
+
* (DR-0013 YAML clause 4).
|
|
13
|
+
*
|
|
14
|
+
* The rule: a thrown error from any subcommand is ONE diagnostic line on
|
|
15
|
+
* stderr and a nonzero exit. Never a stack trace, on either stream.
|
|
16
|
+
*
|
|
17
|
+
* THE DOCUMENTED USAGE-MARKING MECHANISM IS GONE, and its removal is the
|
|
18
|
+
* fix rather than the loss. This docblock used to promise that "an `Error`
|
|
19
|
+
* carrying `usage: true` exits 64", with an `UsageMarkedError` interface
|
|
20
|
+
* beside it, and a grep over `src/` and `bin/` found ZERO code that ever set
|
|
21
|
+
* the field: every command reaches EX_USAGE by returning it from its own
|
|
22
|
+
* handler. A comment describing a contract nothing implements is how the
|
|
23
|
+
* next implementer reaches for a mechanism that silently does nothing, so
|
|
24
|
+
* the branch and the sentence are removed together and `EX_USAGE` stays the
|
|
25
|
+
* value handlers return.
|
|
26
|
+
*
|
|
27
|
+
* SCOPE IS EXACTLY THIS HANDLER, THE HELP AFFORDANCE, AND EPIPE (D-M3-21
|
|
28
|
+
* plus the DR-0047 sweep's two CLI findings). Nothing else in this file
|
|
29
|
+
* changes, because a dispatcher is the last place to accumulate incidental
|
|
30
|
+
* edits.
|
|
31
|
+
*/
|
|
3
32
|
function singleLine(text) {
|
|
4
33
|
return text.replace(/\s+/g, " ").trim();
|
|
5
34
|
}
|
|
6
|
-
|
|
7
|
-
|
|
35
|
+
/**
|
|
36
|
+
* AN EXIT CODE MUST NOT CONTRADICT THE STATE THE COMMAND LEFT BEHIND.
|
|
37
|
+
*
|
|
38
|
+
* Measured before this handler existed: `tiphys lock acquire --duration 900 |
|
|
39
|
+
* head -1` printed the acquired line, exited 1 on an unhandled EPIPE, and
|
|
40
|
+
* left BOTH layers mutated, the local lease on disk and the shared register
|
|
41
|
+
* advanced to the next counter. A wrapper of the shape
|
|
42
|
+
* `if ! tiphys lock acquire | grep -q acquired` therefore took its failure
|
|
43
|
+
* branch while holding the fleet, which is the one wrong conclusion the
|
|
44
|
+
* exclusion commands must never produce.
|
|
45
|
+
*
|
|
46
|
+
* EPIPE ONLY, and that word is load-bearing. A consumer closing the pipe is
|
|
47
|
+
* not a failure of the command; any OTHER write error still surfaces, so
|
|
48
|
+
* this does not become a blanket swallow of output failures. Both streams
|
|
49
|
+
* are covered because a short consumer of stderr is the same hazard as a
|
|
50
|
+
* short consumer of stdout.
|
|
51
|
+
*/
|
|
52
|
+
function ignoreEpipe(stream) {
|
|
53
|
+
stream.on("error", (error) => {
|
|
54
|
+
if (error.code === "EPIPE") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
throw error;
|
|
58
|
+
});
|
|
8
59
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
60
|
+
ignoreEpipe(process.stdout);
|
|
61
|
+
ignoreEpipe(process.stderr);
|
|
62
|
+
/**
|
|
63
|
+
* THE HELP AFFORDANCE (DR-0047 sweep, group cli, CR-F04).
|
|
64
|
+
*
|
|
65
|
+
* `tiphys --help` exited 64 with ZERO bytes on stdout and the usage line on
|
|
66
|
+
* stderr, so `tiphys --help | less` showed nothing, while this project's own
|
|
67
|
+
* evidence uses `tiphys --help` as a probe. That satisfied M1-P1 criterion 4
|
|
68
|
+
* to the letter, because `--help` is an unknown subcommand, and it is still
|
|
69
|
+
* the first thing a consumer types.
|
|
70
|
+
*
|
|
71
|
+
* The usage text is `usageLine()`, which src/cli.ts DERIVES from the dispatch
|
|
72
|
+
* table, so this cannot drift from the commands that exist. M1-P1 criterion
|
|
73
|
+
* 4's contract is untouched: an unknown subcommand still prints usage to
|
|
74
|
+
* STDERR with an EMPTY stdout and exit 64, and the help words alone print to
|
|
75
|
+
* STDOUT with exit 0.
|
|
76
|
+
*/
|
|
77
|
+
const HELP_WORDS = new Set(["--help", "-h", "help"]);
|
|
78
|
+
const argv = process.argv.slice(2);
|
|
79
|
+
if (argv.length === 1 && HELP_WORDS.has(argv[0])) {
|
|
80
|
+
process.stdout.write(`${usageLine()}\n`);
|
|
81
|
+
process.exitCode = 0;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
try {
|
|
85
|
+
process.exitCode = await run(argv);
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
89
|
+
process.stderr.write(`tiphys: ${singleLine(message)}\n`);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
}
|
|
14
92
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Fleet } from "../fleet.ts";
|
|
2
|
+
import type { ExecutorAdapter } from "../spawn.ts";
|
|
3
|
+
/**
|
|
4
|
+
* ADAPTER SELECTION AND LOADING (kernel plan M4, M4-P4; M4-D-03 at
|
|
5
|
+
* delivery/plan/m4-intake.md:729).
|
|
6
|
+
*
|
|
7
|
+
* THE RESOLUTION ROOT IS THE WHOLE SECURITY PROPERTY, and it is the reason
|
|
8
|
+
* this module exists rather than a one-line `await import(specifier)` at the
|
|
9
|
+
* spawn call site. Loading an adapter executes third-party code INSIDE the
|
|
10
|
+
* orchestrator process, the same process that holds delegated merge
|
|
11
|
+
* authority under DR-0012. DR-0029 Part 2c's untrusted-project-content
|
|
12
|
+
* boundary does not exist and M4-D-23 recommends it is not built in M4, so
|
|
13
|
+
* the only mitigation available is WHERE the code is resolved from:
|
|
14
|
+
*
|
|
15
|
+
* - the FLEET HOME is owner-controlled and already pins `@tiphys/kernel`
|
|
16
|
+
* exactly, which is what makes it a boundary at all;
|
|
17
|
+
* - the PROJECT CLONE is the thing under review and may contain anything
|
|
18
|
+
* a contributor pushed, including a `node_modules/` directory.
|
|
19
|
+
*
|
|
20
|
+
* So resolution is rooted at `<fleet home>/package.json` rather than at this
|
|
21
|
+
* module or at `process.cwd()`. A plain `await import(specifier)` resolves
|
|
22
|
+
* relative to THIS MODULE's URL, which is the kernel checkout, and
|
|
23
|
+
* `import.meta.resolve(specifier, parent)` silently ignores its second
|
|
24
|
+
* argument unless Node is started with `--experimental-import-meta-resolve`
|
|
25
|
+
* (measured on v26.6.0, 2026-09-17: the parent was ignored and the specifier
|
|
26
|
+
* resolved from the CALLER's file). Both of those are the shape this module
|
|
27
|
+
* refuses to be.
|
|
28
|
+
*
|
|
29
|
+
* THE ROOTING IS NOT BY ITSELF THE PROPERTY, AND THE SENTENCE THAT STOOD HERE
|
|
30
|
+
* SAID IT WAS (CR-B-004, re-raised as CR-F-CRED-002 and CH-002).
|
|
31
|
+
*
|
|
32
|
+
* Until this round this paragraph asserted, without qualification, that
|
|
33
|
+
* "NOTHING here ever consults the project clone". That is true of BARE
|
|
34
|
+
* specifiers, which is the only shape M4-P4 criterion 2's witness exercises:
|
|
35
|
+
* `createRequire` walks the fleet home and its PARENTS looking for
|
|
36
|
+
* `node_modules`, and the project clone sits BELOW the fleet home at
|
|
37
|
+
* `<fleet>/projects/<name>`, so no such walk reaches it. It was FALSE of the
|
|
38
|
+
* module. Measured at `ad2428b`, with a module in the project clone that drops
|
|
39
|
+
* a sentinel on import: a bare specifier did not evaluate it, an ABSOLUTE
|
|
40
|
+
* specifier did, and the fleet-relative `./projects/demo/evil.mjs` did,
|
|
41
|
+
* because a relative path does not walk up looking for `node_modules`, it
|
|
42
|
+
* walks DOWN the tree the rooting chose.
|
|
43
|
+
*
|
|
44
|
+
* The mechanism is a constraint that holds for one INPUT SHAPE, documented as
|
|
45
|
+
* holding for the module. Both halves are now checked against the RESOLVED
|
|
46
|
+
* REAL PATH, which is the only thing that describes where the code actually
|
|
47
|
+
* comes from, rather than against the specifier's spelling:
|
|
48
|
+
*
|
|
49
|
+
* - a resolved path inside `<fleet>/projects/` is refused WHATEVER the
|
|
50
|
+
* specifier's shape, because the project tree is the thing under review;
|
|
51
|
+
* - a PATH-SHAPED specifier (absolute, or beginning `./` or `../`) whose
|
|
52
|
+
* resolved path is outside the fleet home is refused, because an absolute
|
|
53
|
+
* specifier can point anywhere and a relative one can climb out with
|
|
54
|
+
* `../`. A BARE specifier is exempt from the second check only, because
|
|
55
|
+
* `createRequire`'s parent walk legitimately finds a hoisted
|
|
56
|
+
* `node_modules` above the fleet home, and refusing that would break an
|
|
57
|
+
* ordinary install rather than a hazard.
|
|
58
|
+
*
|
|
59
|
+
* `realpathSync` is used for both, so a symlink planted inside the fleet home
|
|
60
|
+
* pointing into the project clone is refused by the same check rather than by
|
|
61
|
+
* a second one.
|
|
62
|
+
*
|
|
63
|
+
* WHAT IS STILL NOT DEFENDED, said here so the next reader does not re-derive
|
|
64
|
+
* it: an operator who types an absolute path INTO the fleet home gets what
|
|
65
|
+
* they typed, and the fleet home is owner-controlled by assumption. This moves
|
|
66
|
+
* the boundary to the fleet home for every specifier shape; it does not defend
|
|
67
|
+
* inside it.
|
|
68
|
+
*
|
|
69
|
+
* WHAT THIS MODULE DOES NOT DEFEND. Once a specifier resolves inside the
|
|
70
|
+
* fleet home, its code runs. A legitimate fleet-home adapter that is later
|
|
71
|
+
* compromised is not covered by anything here, and no criterion of this
|
|
72
|
+
* phase claims otherwise: this phase MOVES the boundary to the fleet home,
|
|
73
|
+
* it does not defend inside it. That residue is M4-D-23's, stated rather
|
|
74
|
+
* than implied.
|
|
75
|
+
*/
|
|
76
|
+
/**
|
|
77
|
+
* The built-in adapter's name, and the one name a loaded adapter may not
|
|
78
|
+
* claim (M4-P4 criterion 5).
|
|
79
|
+
*
|
|
80
|
+
* IT LIVES HERE RATHER THAN IN `src/spawn.ts` so that the runtime import
|
|
81
|
+
* edge runs one way only: `src/spawn.ts` imports this value, and this module
|
|
82
|
+
* imports nothing but a TYPE back. A value-level cycle between the launch
|
|
83
|
+
* module and the loader would evaluate one of them inside the other's
|
|
84
|
+
* temporal dead zone the first time an import order changed, which is a
|
|
85
|
+
* failure nothing in the suite would predict.
|
|
86
|
+
*
|
|
87
|
+
* The launch record's `adapter` field (src/spawn.ts:113) is the only thing
|
|
88
|
+
* that ever says what ran. A loaded adapter naming itself after the built-in
|
|
89
|
+
* one makes every later record ambiguous, and the ambiguity is unresolvable
|
|
90
|
+
* after the fact because the record is the only witness. This is the
|
|
91
|
+
* misattribution guard src/gates/schemas/release-record.schema.json:26
|
|
92
|
+
* already instantiates for release records, applied one layer down.
|
|
93
|
+
*/
|
|
94
|
+
export declare const BUILT_IN_ADAPTER_NAME = "subprocess";
|
|
95
|
+
/**
|
|
96
|
+
* The fleet-home `package.json` field that names a default adapter, as a
|
|
97
|
+
* JSON pointer written out in the one place that reads it.
|
|
98
|
+
*
|
|
99
|
+
* `tiphys.adapter` rather than a top-level key: the kernel's own
|
|
100
|
+
* `package.json` already namespaces its configuration under `tiphys`
|
|
101
|
+
* (package.json:52), so a fleet home carrying kernel configuration uses the
|
|
102
|
+
* same namespace rather than inventing a second convention.
|
|
103
|
+
*/
|
|
104
|
+
export declare const FLEET_ADAPTER_FIELD = "tiphys.adapter";
|
|
105
|
+
export type AdapterSelection = {
|
|
106
|
+
ok: true;
|
|
107
|
+
adapter: ExecutorAdapter | undefined;
|
|
108
|
+
origin: string;
|
|
109
|
+
} | {
|
|
110
|
+
ok: false;
|
|
111
|
+
reason: string;
|
|
112
|
+
};
|
|
113
|
+
export type AdapterLoad = {
|
|
114
|
+
ok: true;
|
|
115
|
+
adapter: ExecutorAdapter;
|
|
116
|
+
} | {
|
|
117
|
+
ok: false;
|
|
118
|
+
reason: string;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* The specifier the fleet home declares as its default, if any.
|
|
122
|
+
*
|
|
123
|
+
* READ THROUGH `readRegularFileIfPresent`, never `readFileSync`: the fleet
|
|
124
|
+
* home is a directory this process does not own the contents of, and a FIFO
|
|
125
|
+
* at `package.json` would hang the spawn rather than refuse it. That is the
|
|
126
|
+
* hazard CR-520 records for `meta.json`, one path along.
|
|
127
|
+
*
|
|
128
|
+
* A fleet home with no `package.json`, no `tiphys` object, or no `adapter`
|
|
129
|
+
* key declares NOTHING, which is a different fact from declaring something
|
|
130
|
+
* unusable: the first yields `undefined` and the built-in default, the
|
|
131
|
+
* second is a refusal naming the field.
|
|
132
|
+
*/
|
|
133
|
+
export declare function fleetAdapterSpecifier(fleet: Fleet): {
|
|
134
|
+
ok: true;
|
|
135
|
+
specifier: string | undefined;
|
|
136
|
+
} | {
|
|
137
|
+
ok: false;
|
|
138
|
+
reason: string;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* THE CONTAINMENT RULES, applied to the RESOLVED REAL PATH (CR-F-CRED-002,
|
|
142
|
+
* CH-002). Exported so a test can drive both rules directly without staging a
|
|
143
|
+
* module on disk for every member; `loadAdapter` is the only caller in `src/`.
|
|
144
|
+
*
|
|
145
|
+
* Returns a refusal reason, or undefined to allow. Both rules name the
|
|
146
|
+
* resolved path, because an operator reading the refusal has to be able to see
|
|
147
|
+
* WHERE the specifier landed, which is precisely the thing the specifier's own
|
|
148
|
+
* spelling hides.
|
|
149
|
+
*/
|
|
150
|
+
export declare function refuseResolvedAdapterPath(fleet: Fleet, specifier: string, origin: string, resolved: string): string | undefined;
|
|
151
|
+
export declare function loadAdapter(fleet: Fleet, specifier: string, origin: string): Promise<AdapterLoad>;
|
|
152
|
+
/**
|
|
153
|
+
* IS THIS AN ADAPTER (M4-P4 criterion 4)?
|
|
154
|
+
*
|
|
155
|
+
* The three shapes the criterion names are three ways a module can be
|
|
156
|
+
* IMPORTABLE and not be an adapter, and each of them is silent at the import
|
|
157
|
+
* itself: a module with no default export, a default export with no `launch`,
|
|
158
|
+
* and a `launch` that is not callable. Left unchecked, the first two surface
|
|
159
|
+
* as `TypeError: adapter.launch is not a function` thrown out of `spawnTask`
|
|
160
|
+
* AFTER pool create has made a worktree, a branch and a pool record, which is
|
|
161
|
+
* the same "refusal that arrives as a crash, after creation" shape M4-P3's
|
|
162
|
+
* requirement checks exist to prevent (src/spawn.ts's checkAdapterRequirements).
|
|
163
|
+
*
|
|
164
|
+
* A FOURTH CHECK IS HERE THAT THE CRITERION DOES NOT NAME, and it is declared
|
|
165
|
+
* rather than smuggled: the adapter's `name` must be a non-empty string.
|
|
166
|
+
* Criterion 5 refuses one particular name, and `undefined` written into the
|
|
167
|
+
* launch record's `adapter` field would defeat the whole point of that
|
|
168
|
+
* criterion by making the record say nothing at all. A guard on the value of
|
|
169
|
+
* a field that may be absent is a guard that cannot go red for the absent
|
|
170
|
+
* case.
|
|
171
|
+
*
|
|
172
|
+
* Every refusal names the SPECIFIER and the member, because an operator
|
|
173
|
+
* reading it has a module to go and fix and needs to know which one.
|
|
174
|
+
*/
|
|
175
|
+
export declare function checkAdapterShape(module: unknown, specifier: string, origin: string, resolved: string): AdapterLoad;
|
|
176
|
+
/**
|
|
177
|
+
* WHICH ADAPTER THIS SPAWN USES, AND WHY (M4-P4 criteria 1 and 6).
|
|
178
|
+
*
|
|
179
|
+
* WHICH SOURCE WINS, and it is explicit in the record rather than silent:
|
|
180
|
+
* the `--adapter` FLAG outranks the fleet-home default, and the fleet-home
|
|
181
|
+
* default outranks the built-in one.
|
|
182
|
+
*
|
|
183
|
+
* THE WORDING IS DELIBERATE AND IS NOT A STYLE CHOICE. The word this sentence
|
|
184
|
+
* would naturally use is claimed by four rows of
|
|
185
|
+
* delivery/plan/cutover/retirement-inventory.json, which assert that no kernel
|
|
186
|
+
* artifact states an authority order among charter, plan and decision records
|
|
187
|
+
* and enumerate BY NAME the `src/` files allowed to carry the token. A new
|
|
188
|
+
* file carrying it reddens `scripts/check-retirement-inventory.mjs`, correctly,
|
|
189
|
+
* because the checker cannot tell a benign use from a refuting one without a
|
|
190
|
+
* human reading it. This use is benign and incidental, so the token is not
|
|
191
|
+
* used here rather than the rows being widened from a phase that does not own
|
|
192
|
+
* that document. A flag is a per-spawn instruction and
|
|
193
|
+
* a fleet-home field is a standing one, so the narrower statement wins; the
|
|
194
|
+
* opposite order would make the flag unusable on any fleet that declared a
|
|
195
|
+
* default.
|
|
196
|
+
*
|
|
197
|
+
* `undefined` for the adapter means USE THE BUILT-IN ONE, and it is returned
|
|
198
|
+
* as `undefined` rather than as `subprocessAdapter` so that this module
|
|
199
|
+
* imports nothing but a type from `src/spawn.ts`. See BUILT_IN_ADAPTER_NAME
|
|
200
|
+
* above for why the import edge runs one way.
|
|
201
|
+
*/
|
|
202
|
+
export declare function selectAdapter(fleet: Fleet, flagSpecifier: string | undefined): Promise<AdapterSelection>;
|