@telora/mcp-products 0.22.159 → 0.22.166
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/affordances.js +3 -3
- package/dist/affordances.js.map +1 -1
- package/dist/handlers/focusHandlers.d.ts +2 -0
- package/dist/handlers/focusHandlers.js +15 -2
- package/dist/handlers/focusHandlers.js.map +1 -1
- package/dist/handlers/index.d.ts +1 -0
- package/dist/handlers/index.js +1 -0
- package/dist/handlers/index.js.map +1 -1
- package/dist/handlers/migrationClaimHandlers.d.ts +17 -0
- package/dist/handlers/migrationClaimHandlers.js +217 -0
- package/dist/handlers/migrationClaimHandlers.js.map +1 -0
- package/dist/handlers/migrationObservation.d.ts +181 -0
- package/dist/handlers/migrationObservation.js +446 -0
- package/dist/handlers/migrationObservation.js.map +1 -0
- package/dist/handlers/organizationHandlers.js +1 -1
- package/dist/handlers/organizationHandlers.js.map +1 -1
- package/dist/index.js +10 -475
- package/dist/index.js.map +1 -1
- package/dist/shared.js +29 -0
- package/dist/shared.js.map +1 -1
- package/dist/tool-registration.d.ts +26 -0
- package/dist/tool-registration.js +554 -0
- package/dist/tool-registration.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/** What the claimant's own repository holds, right now. */
|
|
2
|
+
export interface MigrationObservation {
|
|
3
|
+
/** Every ordinal any local branch holds, deduplicated. */
|
|
4
|
+
observedHeld: string[];
|
|
5
|
+
/** How many refs were read -- evidence the observation is not vacuous. */
|
|
6
|
+
observedRefs: number;
|
|
7
|
+
/** When it was taken (epoch ms). */
|
|
8
|
+
observedAt: number;
|
|
9
|
+
/**
|
|
10
|
+
* Refs this observation could NOT read.
|
|
11
|
+
*
|
|
12
|
+
* NAMED RATHER THAN DROPPED. A ref that could not be read holds an unknown
|
|
13
|
+
* set of ordinals, and "unknown" silently folded in as "none" is the exact
|
|
14
|
+
* shape of fail-open the daemon's own reader refuses. The handler treats a
|
|
15
|
+
* non-empty list as a partial view and refuses the grant, retryably --
|
|
16
|
+
* symmetric with `migration_held_set_unavailable` on the daemon's side. A
|
|
17
|
+
* caller with NO repository at all reports no observation and is unaffected;
|
|
18
|
+
* this is only about a repository that exists and could not be fully read.
|
|
19
|
+
*/
|
|
20
|
+
observedUnreadable: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Every ref that was read, with the commit it pointed at. Recorded on the
|
|
23
|
+
* granted row, so "which commits did the allocator look at?" is answered by
|
|
24
|
+
* the claim rather than reconstructed afterwards.
|
|
25
|
+
*/
|
|
26
|
+
observedTips: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
/** A repo's migration declaration, as much of it as this reader needs. */
|
|
29
|
+
interface Declaration {
|
|
30
|
+
dir: string;
|
|
31
|
+
filePattern: string;
|
|
32
|
+
scheme: MigrationScheme;
|
|
33
|
+
journal?: {
|
|
34
|
+
path?: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The two numbering schemes; an unrecognised value is malformed, never defaulted. */
|
|
38
|
+
type MigrationScheme = "sequential" | "timestamp";
|
|
39
|
+
/** The ordinals a declaration's pattern reads out of a list of paths. */
|
|
40
|
+
export declare function ordinalsFromPaths(paths: readonly string[], declaration: Declaration): string[];
|
|
41
|
+
/**
|
|
42
|
+
* The width the declaration's own ordinal group fixes, when it fixes one.
|
|
43
|
+
*
|
|
44
|
+
* WHY THE DECLARATION AND NOT THE FILES. A journal entry is a bare index --
|
|
45
|
+
* `2`, not `0002` -- so placing it needs a width, and reading that width from
|
|
46
|
+
* the matching migration FILES fails in exactly the case that matters: a
|
|
47
|
+
* sibling branch that has advanced its journal but has no file this pattern
|
|
48
|
+
* matches, or a ref carrying journal entries and no migration file at all.
|
|
49
|
+
* The idx was then dropped, and against a stale heartbeat the allocator could
|
|
50
|
+
* grant the very number the journal already holds. The declaration fixes the
|
|
51
|
+
* width independently of what any ref happens to contain.
|
|
52
|
+
*
|
|
53
|
+
* Only a pattern that PINS the width answers -- `\d{4}` or `[0-9]{4}`. A
|
|
54
|
+
* variable-width group (`\d+`) fixes nothing and returns null, which is the
|
|
55
|
+
* honest answer rather than a guess.
|
|
56
|
+
*/
|
|
57
|
+
export declare function declaredOrdinalWidth(filePattern: string): number | null;
|
|
58
|
+
/**
|
|
59
|
+
* What reading a declared journal produced.
|
|
60
|
+
*
|
|
61
|
+
* A JOURNAL THAT CANNOT BE READ IS NOT AN EMPTY JOURNAL. This used to answer
|
|
62
|
+
* `[]` for unparseable text, a missing `entries` array, and an index it could
|
|
63
|
+
* not place -- indistinguishable from a journal that genuinely registers
|
|
64
|
+
* nothing. Against a heartbeat taken before a journal-only advance, that drops
|
|
65
|
+
* the held idx and lets the allocator grant the number again: the exact
|
|
66
|
+
* fail-open the observation exists to close, arriving through the one input
|
|
67
|
+
* that has no file to corroborate it.
|
|
68
|
+
*/
|
|
69
|
+
export type JournalReading = {
|
|
70
|
+
ok: true;
|
|
71
|
+
ordinals: string[];
|
|
72
|
+
} | {
|
|
73
|
+
ok: false;
|
|
74
|
+
reason: "unparseable" | "no_entries" | "unplaceable_entry";
|
|
75
|
+
};
|
|
76
|
+
/** The idx values a journal registers, normalised to the width in use. */
|
|
77
|
+
export declare function readJournalOrdinals(text: string, width: number | null): JournalReading;
|
|
78
|
+
/**
|
|
79
|
+
* What reading a file produced.
|
|
80
|
+
*
|
|
81
|
+
* ABSENT AND UNREADABLE ARE DIFFERENT ANSWERS, and collapsing them to `null` is
|
|
82
|
+
* how a transient failure came to mean "this product opted out". A missing
|
|
83
|
+
* declaration is a product that does not use the allocator; a declaration that
|
|
84
|
+
* exists and could not be read is a repository this process cannot currently
|
|
85
|
+
* describe, and granting against the daemon's older snapshot alone is exactly
|
|
86
|
+
* what the claimant's reading exists to prevent.
|
|
87
|
+
*/
|
|
88
|
+
export type FileReading = {
|
|
89
|
+
kind: "read";
|
|
90
|
+
text: string;
|
|
91
|
+
} | {
|
|
92
|
+
kind: "absent";
|
|
93
|
+
} | {
|
|
94
|
+
kind: "unreadable";
|
|
95
|
+
reason: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* VALIDATE TO THE CANONICAL CONTRACT.
|
|
99
|
+
*
|
|
100
|
+
* The rules here are the daemon's, transcribed:
|
|
101
|
+
* `packages/daemon/src/migrations/migration-manifest.ts` is the single
|
|
102
|
+
* definition, and `migration-manifest-parity.test.ts` pins that this reader
|
|
103
|
+
* accepts and rejects the same declarations it does.
|
|
104
|
+
*
|
|
105
|
+
* WHY TRANSCRIBED RATHER THAN IMPORTED. This package is published and installed
|
|
106
|
+
* on daemon hosts by itself; it cannot depend on `@telora/daemon`. The parity
|
|
107
|
+
* test is what keeps a transcription from drifting into a second, laxer
|
|
108
|
+
* contract.
|
|
109
|
+
*
|
|
110
|
+
* WHY IT MATTERS THAT THEY AGREE. A claimant that accepts a declaration the
|
|
111
|
+
* daemon calls unreadable reports a COMPLETE observation of a repository it
|
|
112
|
+
* cannot actually read -- an empty one, because a pattern that does not compile
|
|
113
|
+
* matches nothing -- and a focus-scoped claim then grants against the daemon's
|
|
114
|
+
* older published snapshot with an empty "current" reading vouching for it.
|
|
115
|
+
* Every present-but-invalid declaration must reach the same verdict on both
|
|
116
|
+
* sides: not usable.
|
|
117
|
+
*/
|
|
118
|
+
export declare function validateDeclaration(parsed: unknown): {
|
|
119
|
+
ok: true;
|
|
120
|
+
declaration: Declaration;
|
|
121
|
+
} | {
|
|
122
|
+
ok: false;
|
|
123
|
+
problem: string;
|
|
124
|
+
};
|
|
125
|
+
/** Injected seams, so the reader is testable over a real temp repo or a fake. */
|
|
126
|
+
export interface ObservationDeps {
|
|
127
|
+
cwd: () => string;
|
|
128
|
+
runGit: (args: string[], cwd: string) => string | null;
|
|
129
|
+
readFile: (path: string) => FileReading;
|
|
130
|
+
now: () => number;
|
|
131
|
+
}
|
|
132
|
+
export declare const defaultObservationDeps: ObservationDeps;
|
|
133
|
+
/**
|
|
134
|
+
* What observing this process's repository produced.
|
|
135
|
+
*
|
|
136
|
+
* THE THREE OUTCOMES ARE NOT INTERCHANGEABLE, and collapsing them to
|
|
137
|
+
* "observation or null" is what let a failed read be served as though the
|
|
138
|
+
* caller simply had no repository. The edge then treated the absence as an
|
|
139
|
+
* empty VALID report and granted against the daemon's older leased snapshot
|
|
140
|
+
* alone -- which is precisely the window the observation exists to close.
|
|
141
|
+
*
|
|
142
|
+
* - `not_applicable` -- there is genuinely nothing to observe: no repository,
|
|
143
|
+
* or a product that declares no numbering. The grant proceeds on the
|
|
144
|
+
* daemon's reading exactly as it did before this reader existed. This is
|
|
145
|
+
* the ONLY silent path, and it is silent on purpose: refusing here would
|
|
146
|
+
* block claiming for every caller outside a checkout.
|
|
147
|
+
* - `incomplete` -- there IS a declared repository and it could not be read
|
|
148
|
+
* whole. Nothing is sent and the claim is not attempted: a partial view
|
|
149
|
+
* folded in as "nothing" is how a held number reads as free.
|
|
150
|
+
* - `observed` -- a complete reading, taken over a ref set that did not move
|
|
151
|
+
* while it was being taken.
|
|
152
|
+
*/
|
|
153
|
+
export type ObservationOutcome = {
|
|
154
|
+
kind: "observed";
|
|
155
|
+
observation: MigrationObservation;
|
|
156
|
+
} | {
|
|
157
|
+
kind: "not_applicable";
|
|
158
|
+
reason: "no_repository" | "no_declaration";
|
|
159
|
+
} | {
|
|
160
|
+
kind: "incomplete";
|
|
161
|
+
reason: string;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Observe every local branch's migration ordinals, right now.
|
|
165
|
+
*
|
|
166
|
+
* READ BY COMMIT, NOT BY NAME. The tips are captured once, up front, and every
|
|
167
|
+
* tree and journal below is read through the captured SHA. Reading through the
|
|
168
|
+
* mutable branch NAME meant a branch that moved mid-scan was reported at its
|
|
169
|
+
* OLD tip while its NEW tree had been folded in -- `granted_against_tips` then
|
|
170
|
+
* recorded provenance that was simply false, naming commits the allocator had
|
|
171
|
+
* not looked at.
|
|
172
|
+
*
|
|
173
|
+
* AND THE SET IS RE-CHECKED AFTERWARDS. Reading each ref by SHA makes the
|
|
174
|
+
* individual reads coherent; it does not make the SET of them a single moment.
|
|
175
|
+
* A sibling that commits, or a branch created, between the first ref and the
|
|
176
|
+
* last is invisible to a scan that never looks again -- so the enumeration is
|
|
177
|
+
* repeated at the end, and any difference makes the whole reading `incomplete`
|
|
178
|
+
* rather than a snapshot of no particular instant.
|
|
179
|
+
*/
|
|
180
|
+
export declare function observeLocalMigrations(deps?: ObservationDeps): ObservationOutcome;
|
|
181
|
+
export {};
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// WHAT THE CLAIMANT CAN SEE, AT THE MOMENT IT CLAIMS.
|
|
3
|
+
//
|
|
4
|
+
// THE HOLE THIS CLOSES. The allocator grants against a held set the daemon
|
|
5
|
+
// PUBLISHED -- a snapshot of branch tips taken at some earlier instant. The
|
|
6
|
+
// grant is atomic against other CLAIMS (the partial unique index sees to
|
|
7
|
+
// that), and a lease bounds how old the snapshot may be, but neither makes the
|
|
8
|
+
// snapshot CURRENT. Between the reading and the grant a sibling branch can
|
|
9
|
+
// acquire a migration nobody claimed, and the handler -- which has no git --
|
|
10
|
+
// cannot know. It then grants that very number to somebody else.
|
|
11
|
+
//
|
|
12
|
+
// WHO CAN KNOW. The claimant. Every focus team works in a worktree of the SAME
|
|
13
|
+
// repository, sharing one object store, so a sibling branch's commit is
|
|
14
|
+
// visible from here the instant it is made -- which is exactly how the daemon
|
|
15
|
+
// reads those branches in the first place. The party asking for a number is
|
|
16
|
+
// the one party that both holds the repo and is present at grant time.
|
|
17
|
+
//
|
|
18
|
+
// SO THE CLAIMANT REPORTS WHAT IT SEES, and the handler folds that into the
|
|
19
|
+
// numbers already taken. This is SAFE BY CONSTRUCTION, and the direction
|
|
20
|
+
// matters: an observation can only ever ADD to what is taken, so a wrong or
|
|
21
|
+
// stale one can make a granted number higher than it needed to be and can
|
|
22
|
+
// never make two teams share one. It does not decide; the index still decides.
|
|
23
|
+
//
|
|
24
|
+
// FAILURE IS SILENT AND HARMLESS. No repo, no declaration, no git, a pattern
|
|
25
|
+
// that does not parse -- any of them yields no observation, and the grant
|
|
26
|
+
// proceeds exactly as it did before. A refusal here would block claiming for
|
|
27
|
+
// every caller that runs outside a checkout, which is a worse failure than the
|
|
28
|
+
// one being narrowed.
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
import { execFileSync } from "node:child_process";
|
|
31
|
+
import { readFileSync } from "node:fs";
|
|
32
|
+
const MIGRATION_SCHEMES = ["sequential", "timestamp"];
|
|
33
|
+
const MAX_OBSERVED_ORDINALS = 5000;
|
|
34
|
+
const GIT_TIMEOUT_MS = 10_000;
|
|
35
|
+
/** Run git in `cwd`, or null when it fails for any reason. */
|
|
36
|
+
function git(args, cwd) {
|
|
37
|
+
try {
|
|
38
|
+
return execFileSync("git", args, {
|
|
39
|
+
cwd,
|
|
40
|
+
encoding: "utf-8",
|
|
41
|
+
timeout: GIT_TIMEOUT_MS,
|
|
42
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
43
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** The ordinals a declaration's pattern reads out of a list of paths. */
|
|
51
|
+
export function ordinalsFromPaths(paths, declaration) {
|
|
52
|
+
let pattern;
|
|
53
|
+
try {
|
|
54
|
+
pattern = new RegExp(declaration.filePattern);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const ordinals = [];
|
|
60
|
+
for (const path of paths) {
|
|
61
|
+
const basename = path.slice(path.lastIndexOf("/") + 1);
|
|
62
|
+
const ordinal = pattern.exec(basename)?.groups?.ordinal;
|
|
63
|
+
if (ordinal)
|
|
64
|
+
ordinals.push(ordinal);
|
|
65
|
+
}
|
|
66
|
+
return ordinals;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The width the declaration's own ordinal group fixes, when it fixes one.
|
|
70
|
+
*
|
|
71
|
+
* WHY THE DECLARATION AND NOT THE FILES. A journal entry is a bare index --
|
|
72
|
+
* `2`, not `0002` -- so placing it needs a width, and reading that width from
|
|
73
|
+
* the matching migration FILES fails in exactly the case that matters: a
|
|
74
|
+
* sibling branch that has advanced its journal but has no file this pattern
|
|
75
|
+
* matches, or a ref carrying journal entries and no migration file at all.
|
|
76
|
+
* The idx was then dropped, and against a stale heartbeat the allocator could
|
|
77
|
+
* grant the very number the journal already holds. The declaration fixes the
|
|
78
|
+
* width independently of what any ref happens to contain.
|
|
79
|
+
*
|
|
80
|
+
* Only a pattern that PINS the width answers -- `\d{4}` or `[0-9]{4}`. A
|
|
81
|
+
* variable-width group (`\d+`) fixes nothing and returns null, which is the
|
|
82
|
+
* honest answer rather than a guess.
|
|
83
|
+
*/
|
|
84
|
+
export function declaredOrdinalWidth(filePattern) {
|
|
85
|
+
const group = /\(\?<ordinal>((?:\\.|[^()\\])*)\)/.exec(filePattern)?.[1];
|
|
86
|
+
if (group === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
const fixed = /^(?:\\d|\[0-9\])\{(\d+)\}$/.exec(group);
|
|
89
|
+
return fixed ? Number(fixed[1]) : null;
|
|
90
|
+
}
|
|
91
|
+
/** The idx values a journal registers, normalised to the width in use. */
|
|
92
|
+
export function readJournalOrdinals(text, width) {
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(text);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return { ok: false, reason: "unparseable" };
|
|
99
|
+
}
|
|
100
|
+
const entries = parsed?.entries;
|
|
101
|
+
if (!Array.isArray(entries))
|
|
102
|
+
return { ok: false, reason: "no_entries" };
|
|
103
|
+
const ordinals = [];
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const idx = String(entry?.idx ?? "").trim();
|
|
106
|
+
// An entry this reader cannot place is reported, never dropped. Dropping
|
|
107
|
+
// was safe only under the assumption that the daemon's reading covers it --
|
|
108
|
+
// and a journal-only advance on a branch the heartbeat predates is exactly
|
|
109
|
+
// the case the daemon's reading does NOT cover.
|
|
110
|
+
if (!/^\d+$/.test(idx) || width === null || idx.length > width) {
|
|
111
|
+
return { ok: false, reason: "unplaceable_entry" };
|
|
112
|
+
}
|
|
113
|
+
ordinals.push(idx.padStart(width, "0"));
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, ordinals };
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A repo-relative path safe to hand to git as a pathspec.
|
|
119
|
+
*
|
|
120
|
+
* Mirrors `requireRepoRelativePath` in the canonical reader.
|
|
121
|
+
*/
|
|
122
|
+
function repoRelativePath(raw, key) {
|
|
123
|
+
if (typeof raw !== "string") {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
problem: raw === undefined
|
|
127
|
+
? `missing required "${key}" string (check for a typo in the key)`
|
|
128
|
+
: `"${key}" must be a string (got ${typeof raw})`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const value = raw.trim();
|
|
132
|
+
if (value === "")
|
|
133
|
+
return { ok: false, problem: `"${key}" must not be empty` };
|
|
134
|
+
if (value.startsWith("/")) {
|
|
135
|
+
return { ok: false, problem: `"${key}" must be repo-relative, not absolute (got ${JSON.stringify(value)})` };
|
|
136
|
+
}
|
|
137
|
+
if (value.split("/").includes("..")) {
|
|
138
|
+
return { ok: false, problem: `"${key}" must not escape the repo with ".." (got ${JSON.stringify(value)})` };
|
|
139
|
+
}
|
|
140
|
+
return { ok: true, value: value.replace(/\/+$/, "") };
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* VALIDATE TO THE CANONICAL CONTRACT.
|
|
144
|
+
*
|
|
145
|
+
* The rules here are the daemon's, transcribed:
|
|
146
|
+
* `packages/daemon/src/migrations/migration-manifest.ts` is the single
|
|
147
|
+
* definition, and `migration-manifest-parity.test.ts` pins that this reader
|
|
148
|
+
* accepts and rejects the same declarations it does.
|
|
149
|
+
*
|
|
150
|
+
* WHY TRANSCRIBED RATHER THAN IMPORTED. This package is published and installed
|
|
151
|
+
* on daemon hosts by itself; it cannot depend on `@telora/daemon`. The parity
|
|
152
|
+
* test is what keeps a transcription from drifting into a second, laxer
|
|
153
|
+
* contract.
|
|
154
|
+
*
|
|
155
|
+
* WHY IT MATTERS THAT THEY AGREE. A claimant that accepts a declaration the
|
|
156
|
+
* daemon calls unreadable reports a COMPLETE observation of a repository it
|
|
157
|
+
* cannot actually read -- an empty one, because a pattern that does not compile
|
|
158
|
+
* matches nothing -- and a focus-scoped claim then grants against the daemon's
|
|
159
|
+
* older published snapshot with an empty "current" reading vouching for it.
|
|
160
|
+
* Every present-but-invalid declaration must reach the same verdict on both
|
|
161
|
+
* sides: not usable.
|
|
162
|
+
*/
|
|
163
|
+
export function validateDeclaration(parsed) {
|
|
164
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
165
|
+
return { ok: false, problem: `declaration must be a JSON object (got ${Array.isArray(parsed) ? "an array" : typeof parsed})` };
|
|
166
|
+
}
|
|
167
|
+
const raw = parsed;
|
|
168
|
+
const dir = repoRelativePath(raw.dir, "dir");
|
|
169
|
+
if (!dir.ok)
|
|
170
|
+
return dir;
|
|
171
|
+
// The pattern must COMPILE and capture both groups. A claim is recorded by
|
|
172
|
+
// number AND tag, and the confirm pass matches the file that lands by both.
|
|
173
|
+
const pattern = raw.filePattern;
|
|
174
|
+
if (typeof pattern !== "string") {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
problem: pattern === undefined
|
|
178
|
+
? `missing required "filePattern" string (check for a typo in the key)`
|
|
179
|
+
: `"filePattern" must be a string (got ${typeof pattern})`,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if (pattern.trim() === "")
|
|
183
|
+
return { ok: false, problem: `"filePattern" must not be empty` };
|
|
184
|
+
let compiled;
|
|
185
|
+
try {
|
|
186
|
+
compiled = new RegExp(pattern);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
return {
|
|
190
|
+
ok: false,
|
|
191
|
+
problem: `"filePattern" is not a valid regular expression (${String(err?.message ?? err)})`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
for (const group of ["ordinal", "tag"]) {
|
|
195
|
+
if (!compiled.source.includes(`(?<${group}>`)) {
|
|
196
|
+
return { ok: false, problem: `"filePattern" must carry a named capture group (?<${group}>...)` };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// The scheme is required and never inferred: guessing it from the ordinal's
|
|
200
|
+
// width reads a 14-digit sequential scheme as a timestamp, silently.
|
|
201
|
+
const scheme = raw.scheme;
|
|
202
|
+
if (typeof scheme !== "string") {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
problem: scheme === undefined
|
|
206
|
+
? `missing required "scheme" string -- one of ${MIGRATION_SCHEMES.join(" | ")}`
|
|
207
|
+
: `"scheme" must be a string (got ${typeof scheme})`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const matched = MIGRATION_SCHEMES.find((value) => value === scheme.trim());
|
|
211
|
+
if (matched === undefined) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
problem: `"scheme" must be one of ${MIGRATION_SCHEMES.join(" | ")} (got ${JSON.stringify(scheme)})`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// The journal is optional; present-but-wrong is malformed like every other field.
|
|
218
|
+
let journal;
|
|
219
|
+
if (raw.journal !== undefined) {
|
|
220
|
+
const value = raw.journal;
|
|
221
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
problem: `"journal" must be an object with a "path" (got ${Array.isArray(value) ? "an array" : typeof value})`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const journalPath = repoRelativePath(value.path, "journal.path");
|
|
228
|
+
if (!journalPath.ok)
|
|
229
|
+
return journalPath;
|
|
230
|
+
journal = { path: journalPath.value };
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ok: true,
|
|
234
|
+
declaration: {
|
|
235
|
+
dir: dir.value,
|
|
236
|
+
filePattern: pattern,
|
|
237
|
+
scheme: matched,
|
|
238
|
+
...(journal ? { journal } : {}),
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Read the repo's declaration.
|
|
244
|
+
*
|
|
245
|
+
* MALFORMED IS NOT MISSING. Unparseable JSON, and any shape the canonical
|
|
246
|
+
* contract rejects, are a declaration that EXISTS and cannot be used -- the
|
|
247
|
+
* same loud-on-malformed posture the daemon's own manifest reader takes. Read
|
|
248
|
+
* as "absent", any of them silently opts the product out of the allocator at
|
|
249
|
+
* the exact moment it is being asked for a number.
|
|
250
|
+
*/
|
|
251
|
+
function readDeclaration(repoRoot, readFile) {
|
|
252
|
+
const reading = readFile(`${repoRoot}/telora.migrations.json`);
|
|
253
|
+
if (reading.kind === "absent")
|
|
254
|
+
return { kind: "absent" };
|
|
255
|
+
if (reading.kind === "unreadable")
|
|
256
|
+
return { kind: "unreadable", reason: reading.reason };
|
|
257
|
+
let parsed;
|
|
258
|
+
try {
|
|
259
|
+
parsed = JSON.parse(reading.text);
|
|
260
|
+
}
|
|
261
|
+
catch (err) {
|
|
262
|
+
return { kind: "unreadable", reason: `declaration is not valid JSON: ${String(err?.message ?? err)}` };
|
|
263
|
+
}
|
|
264
|
+
const validated = validateDeclaration(parsed);
|
|
265
|
+
if (!validated.ok)
|
|
266
|
+
return { kind: "unreadable", reason: validated.problem };
|
|
267
|
+
return { kind: "declared", declaration: validated.declaration };
|
|
268
|
+
}
|
|
269
|
+
export const defaultObservationDeps = {
|
|
270
|
+
cwd: () => process.cwd(),
|
|
271
|
+
runGit: git,
|
|
272
|
+
// Imported at the top of the module, NOT `require`d here. This package is
|
|
273
|
+
// NodeNext ESM: `require` is not defined in it, so the call threw, the catch
|
|
274
|
+
// turned that into "no declaration", and every production claim sent no
|
|
275
|
+
// observation at all -- silently, because the same null means "this caller is
|
|
276
|
+
// not in a repo". The tests injected their own readFile and never ran this.
|
|
277
|
+
readFile: (path) => {
|
|
278
|
+
try {
|
|
279
|
+
return { kind: "read", text: readFileSync(path, "utf-8") };
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
// ENOENT is an ANSWER -- this product declares no numbering. Anything
|
|
283
|
+
// else (a permission error, an I/O error, a directory where a file was
|
|
284
|
+
// expected) is the read not happening, and must not read as an opt-out.
|
|
285
|
+
const code = err?.code;
|
|
286
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
287
|
+
return { kind: "absent" };
|
|
288
|
+
return { kind: "unreadable", reason: String(err?.message ?? err) };
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
now: () => Date.now(),
|
|
292
|
+
};
|
|
293
|
+
/** Enumerate local branches with the commit each points at, right now. */
|
|
294
|
+
function enumerateRefs(deps, root) {
|
|
295
|
+
const output = deps.runGit(["for-each-ref", "--format=%(refname:short) %(objectname)", "refs/heads"], root);
|
|
296
|
+
if (output === null)
|
|
297
|
+
return null;
|
|
298
|
+
const refs = new Map();
|
|
299
|
+
for (const line of output.split("\n")) {
|
|
300
|
+
const [name, tip] = line.trim().split(/\s+/);
|
|
301
|
+
if (name)
|
|
302
|
+
refs.set(name, tip ?? "");
|
|
303
|
+
}
|
|
304
|
+
return refs;
|
|
305
|
+
}
|
|
306
|
+
/** Whether two ref enumerations describe the same branches at the same commits. */
|
|
307
|
+
function sameRefs(a, b) {
|
|
308
|
+
if (a.size !== b.size)
|
|
309
|
+
return false;
|
|
310
|
+
for (const [name, tip] of a)
|
|
311
|
+
if (b.get(name) !== tip)
|
|
312
|
+
return false;
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Observe every local branch's migration ordinals, right now.
|
|
317
|
+
*
|
|
318
|
+
* READ BY COMMIT, NOT BY NAME. The tips are captured once, up front, and every
|
|
319
|
+
* tree and journal below is read through the captured SHA. Reading through the
|
|
320
|
+
* mutable branch NAME meant a branch that moved mid-scan was reported at its
|
|
321
|
+
* OLD tip while its NEW tree had been folded in -- `granted_against_tips` then
|
|
322
|
+
* recorded provenance that was simply false, naming commits the allocator had
|
|
323
|
+
* not looked at.
|
|
324
|
+
*
|
|
325
|
+
* AND THE SET IS RE-CHECKED AFTERWARDS. Reading each ref by SHA makes the
|
|
326
|
+
* individual reads coherent; it does not make the SET of them a single moment.
|
|
327
|
+
* A sibling that commits, or a branch created, between the first ref and the
|
|
328
|
+
* last is invisible to a scan that never looks again -- so the enumeration is
|
|
329
|
+
* repeated at the end, and any difference makes the whole reading `incomplete`
|
|
330
|
+
* rather than a snapshot of no particular instant.
|
|
331
|
+
*/
|
|
332
|
+
export function observeLocalMigrations(deps = defaultObservationDeps) {
|
|
333
|
+
const cwd = deps.cwd();
|
|
334
|
+
const root = deps.runGit(["rev-parse", "--show-toplevel"], cwd)?.trim();
|
|
335
|
+
if (!root)
|
|
336
|
+
return { kind: "not_applicable", reason: "no_repository" };
|
|
337
|
+
const declared = readDeclaration(root, deps.readFile);
|
|
338
|
+
if (declared.kind === "unreadable") {
|
|
339
|
+
// A declaration that EXISTS and cannot be used. Answering "no_declaration"
|
|
340
|
+
// here told the handler this product had opted out, and the grant was made
|
|
341
|
+
// against the daemon's older snapshot alone.
|
|
342
|
+
return { kind: "incomplete", reason: `migration declaration unreadable: ${declared.reason}` };
|
|
343
|
+
}
|
|
344
|
+
if (declared.kind === "absent")
|
|
345
|
+
return { kind: "not_applicable", reason: "no_declaration" };
|
|
346
|
+
const declaration = declared.declaration;
|
|
347
|
+
// Name and tip in ONE call: the tips are recorded on the granted row, so the
|
|
348
|
+
// commits the allocator looked at are readable from the claim afterwards.
|
|
349
|
+
const refs = enumerateRefs(deps, root);
|
|
350
|
+
if (refs === null) {
|
|
351
|
+
// A DECLARED repository whose branches could not be enumerated. This used
|
|
352
|
+
// to answer null -- the same answer as "not in a repository" -- so the
|
|
353
|
+
// claim went out with no observation and the grant was made blind.
|
|
354
|
+
return { kind: "incomplete", reason: "could not enumerate local branches" };
|
|
355
|
+
}
|
|
356
|
+
if (refs.size === 0) {
|
|
357
|
+
return { kind: "incomplete", reason: "a declared repository reported no local branches" };
|
|
358
|
+
}
|
|
359
|
+
const held = new Set();
|
|
360
|
+
const journalTexts = [];
|
|
361
|
+
const observedTips = {};
|
|
362
|
+
const observedUnreadable = [];
|
|
363
|
+
const journalPath = declaration.journal?.path;
|
|
364
|
+
for (const [name, tip] of refs) {
|
|
365
|
+
// By SHA. The name is carried only for reporting.
|
|
366
|
+
const listed = deps.runGit(["ls-tree", "-r", "--name-only", tip, "--", declaration.dir], root);
|
|
367
|
+
if (listed === null) {
|
|
368
|
+
// NAMED, not skipped. What this ref holds is unknown, and unknown folded
|
|
369
|
+
// in as "nothing" is how a held number reads as free.
|
|
370
|
+
observedUnreadable.push(name);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
observedTips[name] = tip;
|
|
374
|
+
for (const ordinal of ordinalsFromPaths(listed.split("\n"), declaration)) {
|
|
375
|
+
held.add(ordinal);
|
|
376
|
+
}
|
|
377
|
+
if (!journalPath)
|
|
378
|
+
continue;
|
|
379
|
+
// ABSENT vs UNREADABLE, asked separately. `git show` returns nothing for
|
|
380
|
+
// both "no such path" and "the object cannot be read", and most refs
|
|
381
|
+
// legitimately have no journal -- so the tree is asked whether the path
|
|
382
|
+
// EXISTS on this commit before its contents are held against anyone.
|
|
383
|
+
const present = deps.runGit(["ls-tree", tip, "--", journalPath], root);
|
|
384
|
+
if (present === null) {
|
|
385
|
+
observedUnreadable.push(`${name}:${journalPath}`);
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (present.trim() === "")
|
|
389
|
+
continue; // genuinely absent
|
|
390
|
+
const text = deps.runGit(["show", `${tip}:${journalPath}`], root);
|
|
391
|
+
if (text === null) {
|
|
392
|
+
// The ref SAYS it has one and it could not be read. Reading that as an
|
|
393
|
+
// empty journal drops whatever indexes it registers.
|
|
394
|
+
observedUnreadable.push(`${name}:${journalPath}`);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
journalTexts.push({ ref: name, text });
|
|
398
|
+
}
|
|
399
|
+
// The journal's bare indexes are normalised to the DECLARED width, falling
|
|
400
|
+
// back to the width the files themselves use when the pattern does not fix
|
|
401
|
+
// one. A bare 352 and a padded 0352 are two widths in one set, and the
|
|
402
|
+
// candidate rule refuses a product whose ordinals are not one fixed width.
|
|
403
|
+
const fileWidths = [...new Set([...held].map((o) => o.length))];
|
|
404
|
+
const width = declaredOrdinalWidth(declaration.filePattern)
|
|
405
|
+
?? (fileWidths.length === 1 ? (fileWidths[0] ?? null) : null);
|
|
406
|
+
for (const { ref, text } of journalTexts) {
|
|
407
|
+
const reading = readJournalOrdinals(text, width);
|
|
408
|
+
if (!reading.ok) {
|
|
409
|
+
// Malformed, or carrying an index this reader cannot place. Either way it
|
|
410
|
+
// is a declared journal whose contents are not established.
|
|
411
|
+
observedUnreadable.push(`${ref}:${journalPath ?? "journal"}`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
for (const ordinal of reading.ordinals)
|
|
415
|
+
held.add(ordinal);
|
|
416
|
+
}
|
|
417
|
+
// THE SET, RE-CHECKED. Every read above is coherent with its own commit; that
|
|
418
|
+
// says nothing about whether the branches moved underneath the scan as a
|
|
419
|
+
// whole.
|
|
420
|
+
const after = enumerateRefs(deps, root);
|
|
421
|
+
if (after === null) {
|
|
422
|
+
return { kind: "incomplete", reason: "could not re-read local branches after the scan" };
|
|
423
|
+
}
|
|
424
|
+
if (!sameRefs(refs, after)) {
|
|
425
|
+
return { kind: "incomplete", reason: "local branches moved while they were being read" };
|
|
426
|
+
}
|
|
427
|
+
// Bounded from the HIGH end. At one fixed width lexicographic order is
|
|
428
|
+
// numeric order, and it is the newest ordinals that decide the next number --
|
|
429
|
+
// keeping the oldest 5,000 and discarding the rest would drop exactly the
|
|
430
|
+
// evidence a sequential grant depends on.
|
|
431
|
+
const sorted = [...held].sort();
|
|
432
|
+
const observedHeld = sorted.length > MAX_OBSERVED_ORDINALS
|
|
433
|
+
? sorted.slice(-MAX_OBSERVED_ORDINALS)
|
|
434
|
+
: sorted;
|
|
435
|
+
return {
|
|
436
|
+
kind: "observed",
|
|
437
|
+
observation: {
|
|
438
|
+
observedHeld,
|
|
439
|
+
observedRefs: Object.keys(observedTips).length,
|
|
440
|
+
observedAt: deps.now(),
|
|
441
|
+
observedUnreadable,
|
|
442
|
+
observedTips,
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
//# sourceMappingURL=migrationObservation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrationObservation.js","sourceRoot":"","sources":["../../src/handlers/migrationObservation.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,7 +26,7 @@ import { callProductApi, successResult, validationError, wrapHandler, } from "..
|
|
|
26
26
|
// write that omits that pass leaves it untouched.
|
|
27
27
|
// '' clears (inherit the builtin stage / backend default).
|
|
28
28
|
export const SELECTABLE_PASS_MODEL_IDS = new Set([
|
|
29
|
-
"claude-fable-5-1", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5",
|
|
29
|
+
"claude-fable-5-1", "claude-opus-5-5", "claude-opus-5", "claude-opus-4-8", "claude-sonnet-5",
|
|
30
30
|
"claude-haiku-4-5-20251001", "gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
|
31
31
|
]);
|
|
32
32
|
export function isSelectablePassModelId(id) {
|