@coreplane/switchboard 1.252.0 → 1.254.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/dist/assets/config/config.example.yaml +54 -1
- package/dist/assets/deploy/cloudflare-memory/runMetricsSink.ts +31 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +455 -32
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +12 -0
- package/dist/assets/deploy/profile.example.json +1 -1
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/core/authz/grants.ts +6 -0
- package/dist/assets/src/core/authz/policy.ts +19 -0
- package/dist/assets/src/core/budgets.ts +25 -0
- package/dist/assets/src/core/coordinator/contract.ts +51 -1
- package/dist/assets/src/core/coordinator/driver.ts +111 -11
- package/dist/assets/src/core/costs.ts +221 -2
- package/dist/assets/src/core/costsSnapshotStore.ts +22 -1
- package/dist/assets/src/core/memory/engine.ts +25 -4
- package/dist/assets/src/core/memory/types.ts +6 -2
- package/dist/assets/src/core/pipelineStanding.ts +294 -0
- package/dist/assets/src/core/plane/decide.ts +203 -0
- package/dist/assets/src/core/provider.ts +11 -0
- package/dist/assets/src/core/runEvents.ts +57 -1
- package/dist/assets/src/core/runFriction.ts +1 -0
- package/dist/assets/src/core/runLedger/transcript.ts +23 -3
- package/dist/assets/src/core/runMetrics.ts +237 -0
- package/dist/assets/src/core/runRecord.ts +61 -0
- package/dist/assets/src/core/runUsage.ts +41 -9
- package/dist/assets/src/core/ship/contract.ts +8 -15
- package/dist/assets/src/core/ship/coordinator.ts +150 -11
- package/dist/assets/src/deploy/profile.ts +16 -0
- package/dist/assets/web/dist/.vite/manifest.json +461 -427
- package/dist/assets/web/dist/assets/{AppShell-DAtHiuI6.js → AppShell-CSXDCV4x.js} +1 -1
- package/dist/assets/web/dist/assets/CostChart-DsuPS2g7.js +2 -0
- package/dist/assets/web/dist/assets/CostsPage-BuKjw3nv.js +1 -0
- package/dist/assets/web/dist/assets/{DeliveryPage-3ELQWM0r.js → DeliveryPage-ngPsO2to.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-DvxTHzPx.js +3 -0
- package/dist/assets/web/dist/assets/{InputMenu-C6aPpe30.js → InputMenu-BdZXnibG.js} +1 -1
- package/dist/assets/web/dist/assets/MetricsPage-o5fSaCTe.js +1 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-DtE-GgTk.js → NotFoundPage-DHuWyjlq.js} +1 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-ZYIRCCZ2.js +1 -0
- package/dist/assets/web/dist/assets/PlanePage-DpWfiX4C.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-C9y3nbo8.js → ResidentDetailPage-DG86v39Y.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-i1RG9e7g.js → ResidentsIndexPage-x6p689VH.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-DG29LOTs.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-ysJBY8xQ.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-CuzFchcn.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-YSbUu5py.js → RunsTabs-kHbbxVHx.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-DvYwM2TE.js → ScheduledPage-BuLmfcbG.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingSelect-BIzAsLk1.js → SettingSelect-BfID5nF4.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-Bo6yCyXZ.js → SettingsPage-BujWkdU_.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-CAfS1AUi.js → StatusDot-C8Bc0pTX.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-tZoum_T-.js → Tooltip-BWwJx27K.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BmdOHwNn.js → UnitRoutePage-B9kjA1AT.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-BjeQZdCq.js → angular-html-C8b5YTHf.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-ZGdORrL2.js → angular-ts-C3f4WYNe.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DhvV_X93.js → apl-DSHxUJc3.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DiRSE4Ug.js → astro-BSGCCBP6.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-D8Fr5Wvq.js → blade-DFeYrMKk.js} +1 -1
- package/dist/assets/web/dist/assets/{c-BwbC64D4.js → c-L_QOnFVv.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DgmqRK97.js → chapel-DLQWUwhu.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-DJod2RNK.js → cobol-1DlMmrEO.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-D1Y2CgBY.js → coffee-BTB6os6N.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CsO97YOM.js → cpp-BvJI2z3u.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CJ7m2tOc.js → crystal-B11wGW_h.js} +1 -1
- package/dist/assets/web/dist/assets/{css-B2M-NKoj.js → css-BiIWx5Pw.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-DfbEpHXR.js → dist-CpnyQGOb.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-CFVW-m9B.js → edge-nSglEXaD.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-B2mtRgCr.js → elixir-x7o8qMl8.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-2teVAwCi.js → elm-DGrRyqTQ.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-LAbeNqen.js → erb--_dh58Nw.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DnhzH_sp.js → git-rebase-DbpUXwyg.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-DPKjXf1-.js → glimmer-js-BG0CuZG5.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CABXnt5z.js → glimmer-ts-CQIl5qyR.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-Dz4-3-gJ.js → glsl-9PJNQTcw.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-ByWUD1DL.js → graphql-CzVTzjEI.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-CdmOK2K-.js → hack-BhCmAz3Q.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DzegMpd2.js → haml-BmI12COR.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-B5NeiP0e.js → handlebars-Cib-ISTS.js} +1 -1
- package/dist/assets/web/dist/assets/{html-Zg_NHLv9.js → html-D3lDapvI.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DZf9JNzh.js → html-derivative-DRPY8vOK.js} +1 -1
- package/dist/assets/web/dist/assets/{http-CVFrQZeN.js → http-B9VttK6m.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-L5brWAJe.js → hurl-C0saNJYj.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-DABQtONT.js +1 -0
- package/dist/assets/web/dist/assets/{java-hFphyZ4R.js → java-BCFbtYAj.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CAJO1WKX.js → javascript-nwBjnrhh.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-N4A4srXI.js → jinja-CMYO9byN.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-D3BZa_iU.js → jison-Cko8HnE_.js} +1 -1
- package/dist/assets/web/dist/assets/{json-DxD1Qh8W.js → json-Dzv__YsX.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CHhag69S.js → jsx-CTFxagih.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CrhkZ6Tt.js → julia-_Xvten1-.js} +1 -1
- package/dist/assets/web/dist/assets/{just-D0LFOuhq.js → just-bLps4qOu.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BHAlwCXK.js → latex-D6UaYsUP.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-CkaLfCWh.js → liquid-CoDIeULa.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-B1h4aAzP.js → lua-D1KFRnVf.js} +1 -1
- package/dist/assets/web/dist/assets/main-C4QfwybO.css +1 -0
- package/dist/assets/web/dist/assets/main-Comxmwi4.js +28 -0
- package/dist/assets/web/dist/assets/{marko-D6CG4hvX.js → marko-B5N5XcOw.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-CVQzimAU.js → mdc-uxzjQO3q.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-h-9Ir54h.js → nginx-BMJxs2TK.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DHVI30sp.js → nim-DqQ3-8p1.js} +1 -1
- package/dist/assets/web/dist/assets/{org-AasAXfMF.js → org-DsY6LK59.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-BVi5aTEy.js → perl--pq9iJFp.js} +1 -1
- package/dist/assets/web/dist/assets/{php-qOkCeUKr.js → php-RHCMbsM2.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-8TnUGvEX.js → pug-CFNq_FCp.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-CpJbhoxn.js → qml-C9Acx9E8.js} +1 -1
- package/dist/assets/web/dist/assets/{r-JkUNcAJW.js → r-DxlU8FBd.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-CtU4ws5a.js → razor-CLJ2kKih.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-J2zA6ceN.js → regexp-CzmdGoEp.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-CMCvf-yZ.js → rst-D9yuxBET.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-ChLBY_iR.js → ruby-CIRcEvnD.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-cwn4x2VY.js → sas-LOl-uUPh.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-jSrJlzLI.js → scss-BX2_sZHY.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-D5nAd0gs.js → shellscript-4d-QvKkY.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BZY4-Tdh.js → shellsession-DhB16UGy.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-COppPOKr.js → soy-Cm1xTUBr.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-Blc_6dOA.js → sql-BsfcBJdu.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-DmyMXfRC.js → sseReplay-IzTdD4-3.js} +5 -5
- package/dist/assets/web/dist/assets/{stata-DRv3OY-h.js → stata-Dwat3BSw.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-qMv_eeJ6.js → surrealql-P4rtxzAW.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-d1Edb6Rz.js → svelte-DTcsi8S8.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CUMSIOXG.js → templ-D-kP0Tg5.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-BGZQA-Ei.js → tex-D-4vuedK.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-Z9ulxVsq.js → ts-tags-jEEWYxsu.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-oI4bI3Vf.js → tsx-CFnlrVzT.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-D_RXwBIX.js → twig-D5rGrPjs.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BgqQDYoN.js → typescript-nYIQ35or.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-5MW-KRDd.js → typst-3-UX8dna.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CZhHHsPm.js → vue-CLIPIX03.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-DlYy2d8v.js → vue-html-Ci9CpkHU.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-CJDnx-qn.js → vue-vine-DL5pEYYk.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-BRBDk65Q.js → xml-bO80qikC.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BlCykPxo.js → xsl-BPze60Ej.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BqW48szf.js → yaml-BQUSon_o.js} +1 -1
- package/dist/cli.js +36552 -33171
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-BoKzYa4B.js +0 -2
- package/dist/assets/web/dist/assets/HomePage-BG_ok-K2.js +0 -2
- package/dist/assets/web/dist/assets/PendingTurnRow-ChCQOLgZ.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-D3wVpzBa.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-B3IirUVi.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-DiFmtGaJ.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BT0cPVRw.js +0 -1
- package/dist/assets/web/dist/assets/main-5Gm_1Gv8.js +0 -28
- package/dist/assets/web/dist/assets/main-zbP_dTjR.css +0 -1
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type ReviewVerdict,
|
|
15
15
|
} from "./reviewVerdict.js";
|
|
16
16
|
import { IDEMPOTENCY_KEY_PATTERN, INSTANCE_ID_PATTERN } from "./coordinator/contract.js";
|
|
17
|
+
import { isPipelineSummaryShape, type PipelineSummary } from "./pipelineStanding.js";
|
|
17
18
|
import {
|
|
18
19
|
FRICTION_CATEGORIES,
|
|
19
20
|
type CategoryTotals,
|
|
@@ -184,6 +185,13 @@ export interface RunRecord {
|
|
|
184
185
|
* reading the events. Absent for a preset a person, a scope or the default
|
|
185
186
|
* chose, and on records written before it existed. */
|
|
186
187
|
route?: RunRouteDecision;
|
|
188
|
+
/** The operator's shadow decision beside the routed request (record 0057;
|
|
189
|
+
* the one-door plan's operator unit; run-history item 60): what the operator
|
|
190
|
+
* bound for the same event, redacted like the receipt — never the message
|
|
191
|
+
* text — with the intake gate's verdict when the gate was present. The
|
|
192
|
+
* agreement row reads it from here; it follows the record's retention.
|
|
193
|
+
* Absent under `routing.operator: off` and on records written before it. */
|
|
194
|
+
operator?: RunOperatorDecision;
|
|
187
195
|
/** The effective profile the run was admitted with (docs/decisions/0026-capability-profiles-and-request-routing.md):
|
|
188
196
|
* the preset, its machine class and identity, the minutes it ran on and —
|
|
189
197
|
* when a boundary clipped the budget — the scope that did, so a reader can
|
|
@@ -208,6 +216,15 @@ export interface RunRecord {
|
|
|
208
216
|
* coordinator tag. Present only on a ship run whose hand-off succeeded;
|
|
209
217
|
* a record written before the event has none. */
|
|
210
218
|
instanceId?: string;
|
|
219
|
+
/** The pipeline's standing (record 0065): the fold of the run's own
|
|
220
|
+
* `ship_round`/`ship_unit` events, summarized at the seal so the stored row
|
|
221
|
+
* reads it without the events. Present only on a run that published ship
|
|
222
|
+
* facts; a record written before the field carries none and reads as today. */
|
|
223
|
+
pipeline?: PipelineSummary;
|
|
224
|
+
/** A ship pipeline's parent run (record 0060; record 0065): `RunMeta.hosted`
|
|
225
|
+
* carried onto the record at the seal, so a history reader draws the run as
|
|
226
|
+
* a pipeline. Absent on every other run and on older records. */
|
|
227
|
+
hosted?: true;
|
|
211
228
|
/** Where the run's conversation started (item 52): `channel` — its own
|
|
212
229
|
* thread's history, as for every run a person, a schedule or a coordinator
|
|
213
230
|
* started — or `parent` — a spawned child seeded from its parent's text
|
|
@@ -404,6 +421,45 @@ export function routeOfEvents(events: readonly RunEvent[]): RunRouteDecision | u
|
|
|
404
421
|
};
|
|
405
422
|
}
|
|
406
423
|
|
|
424
|
+
/** The operator's decision as a record carries it — the `operator` event's
|
|
425
|
+
* fields less the stream bookkeeping (record 0057; run-history item 60). */
|
|
426
|
+
export interface RunOperatorDecision {
|
|
427
|
+
mode: "shadow" | "on";
|
|
428
|
+
outcome: "binds" | "question" | "refusal";
|
|
429
|
+
reason: string;
|
|
430
|
+
binds?: { line: string; reason: string }[];
|
|
431
|
+
question?: string;
|
|
432
|
+
/** A question's proposed line — what the next turn's "yes" binds. */
|
|
433
|
+
proposal?: string;
|
|
434
|
+
refusalCause?: string;
|
|
435
|
+
refusalText?: string;
|
|
436
|
+
intake?: { verdict: string; reason: string };
|
|
437
|
+
latencyMs?: number;
|
|
438
|
+
outputTokens?: number;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/** The operator's decision the run's events carry: the one `operator` event
|
|
442
|
+
* (record 0057), whatever the run's own agent source — the shadow decision
|
|
443
|
+
* rides routed and unrouted runs alike, or there would be no disagreement to
|
|
444
|
+
* count. Undefined on a run the operator never saw. */
|
|
445
|
+
export function operatorOfEvents(events: readonly RunEvent[]): RunOperatorDecision | undefined {
|
|
446
|
+
const e = events.find((e) => e.type === "operator");
|
|
447
|
+
if (e?.type !== "operator") return undefined;
|
|
448
|
+
return {
|
|
449
|
+
mode: e.mode,
|
|
450
|
+
outcome: e.outcome,
|
|
451
|
+
reason: e.reason,
|
|
452
|
+
...(e.binds ? { binds: e.binds.map((b) => ({ line: b.line, reason: b.reason })) } : {}),
|
|
453
|
+
...(e.question !== undefined ? { question: e.question } : {}),
|
|
454
|
+
...(e.proposal !== undefined ? { proposal: e.proposal } : {}),
|
|
455
|
+
...(e.refusalCause !== undefined ? { refusalCause: e.refusalCause } : {}),
|
|
456
|
+
...(e.refusalText !== undefined ? { refusalText: e.refusalText } : {}),
|
|
457
|
+
...(e.intake ? { intake: { verdict: e.intake.verdict, reason: e.intake.reason } } : {}),
|
|
458
|
+
...(e.latencyMs !== undefined ? { latencyMs: e.latencyMs } : {}),
|
|
459
|
+
...(e.outputTokens !== undefined ? { outputTokens: e.outputTokens } : {}),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
407
463
|
function isRunRouteShape(v: unknown): v is RunRouteDecision {
|
|
408
464
|
if (typeof v !== "object" || v === null) return false;
|
|
409
465
|
const r = v as Record<string, unknown>;
|
|
@@ -939,6 +995,11 @@ export function isRunRecord(v: unknown): v is RunRecord {
|
|
|
939
995
|
// The instance a ship run's hand-off created (record 0051 R2; item 2).
|
|
940
996
|
if (r.instanceId !== undefined && (typeof r.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.instanceId)))
|
|
941
997
|
return false;
|
|
998
|
+
// The pipeline's standing and the hosted marker (record 0065): the summary
|
|
999
|
+
// is checked for shape — an older reader accepts a newer record — and the
|
|
1000
|
+
// marker, like `provisional`, is the literal `true` or absent.
|
|
1001
|
+
if (r.pipeline !== undefined && !isPipelineSummaryShape(r.pipeline)) return false;
|
|
1002
|
+
if (r.hosted !== undefined && r.hosted !== true) return false;
|
|
942
1003
|
if (typeof r.channelId !== "string" || typeof r.userId !== "string" || typeof r.threadKey !== "string") return false;
|
|
943
1004
|
if (r.relayedBy !== undefined && typeof r.relayedBy !== "string") return false;
|
|
944
1005
|
if (r.authenticatedAs !== undefined && typeof r.authenticatedAs !== "string") return false;
|
|
@@ -31,6 +31,11 @@ export interface ModelUsage {
|
|
|
31
31
|
* a record whose turns predate the meter row (the price table then prices
|
|
32
32
|
* the tokens, costs.md item 4b). */
|
|
33
33
|
usd?: number | null;
|
|
34
|
+
/** The model's BYOK aggregator fees summed from its turns' `feeUsd` attrs —
|
|
35
|
+
* the share of `usd` that is the aggregator's own charge, what the biller's
|
|
36
|
+
* invoice tie-out compares against its `usage` column (costs.md item 4d).
|
|
37
|
+
* Absent when no turn carried one. */
|
|
38
|
+
feeUsd?: number;
|
|
34
39
|
/** The distinct `priceSource` words the turns carried, sorted — what the
|
|
35
40
|
* by-model view names as the source. Absent when no turn carried one. */
|
|
36
41
|
priceSources?: string[];
|
|
@@ -59,12 +64,24 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
|
|
|
59
64
|
const usage = emptyUsage();
|
|
60
65
|
// The meter row per model (model-proxy item 6): the turns' own dollars and
|
|
61
66
|
// sources. `usd` sums only when every turn that counted tokens carried one —
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
67
|
+
// the token-counting turns that lack a figure are counted directly
|
|
68
|
+
// (`unpricedTokenTurns`), so a priced zero-token turn can never stand in for
|
|
69
|
+
// an unpriced one; a turn that counted none (an upstream error, a retry the
|
|
70
|
+
// harness's SDK spent, a stream broken before its usage) has nothing a sum
|
|
71
|
+
// could leave out, so it never turns the model unpriced; a model none of
|
|
72
|
+
// whose turns carried price attrs stays without the fields (a record from
|
|
73
|
+
// before the meter row — the table prices it, costs.md item 4b).
|
|
74
|
+
const priced = new Map<
|
|
75
|
+
string,
|
|
76
|
+
{
|
|
77
|
+
usd: number;
|
|
78
|
+
feeUsd: number;
|
|
79
|
+
feeTurns: number;
|
|
80
|
+
pricedTurns: number;
|
|
81
|
+
unpricedTokenTurns: number;
|
|
82
|
+
sources: Set<string>;
|
|
83
|
+
}
|
|
84
|
+
>();
|
|
68
85
|
for (const e of events) {
|
|
69
86
|
if (e.type !== "span_end" || e.name !== MODEL_TURN) continue;
|
|
70
87
|
const attrs = (e.attrs ?? {}) as Record<string, unknown>;
|
|
@@ -87,19 +104,31 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
|
|
|
87
104
|
usage.turns += 1;
|
|
88
105
|
const hasUsd = typeof attrs.usd === "number" && Number.isFinite(attrs.usd);
|
|
89
106
|
const hasSource = typeof attrs.priceSource === "string" && attrs.priceSource !== "";
|
|
90
|
-
const p = priced.get(model) ?? {
|
|
91
|
-
|
|
107
|
+
const p = priced.get(model) ?? {
|
|
108
|
+
usd: 0,
|
|
109
|
+
feeUsd: 0,
|
|
110
|
+
feeTurns: 0,
|
|
111
|
+
pricedTurns: 0,
|
|
112
|
+
unpricedTokenTurns: 0,
|
|
113
|
+
sources: new Set<string>(),
|
|
114
|
+
};
|
|
115
|
+
if (counted > 0 && !hasUsd) p.unpricedTokenTurns += 1;
|
|
92
116
|
if (hasUsd) {
|
|
93
117
|
p.usd += attrs.usd as number;
|
|
94
118
|
p.pricedTurns += 1;
|
|
95
119
|
}
|
|
120
|
+
if (typeof attrs.feeUsd === "number" && Number.isFinite(attrs.feeUsd)) {
|
|
121
|
+
p.feeUsd += attrs.feeUsd;
|
|
122
|
+
p.feeTurns += 1;
|
|
123
|
+
}
|
|
96
124
|
if (hasSource) p.sources.add(attrs.priceSource as string);
|
|
97
125
|
priced.set(model, p);
|
|
98
126
|
}
|
|
99
127
|
for (const [model, p] of priced) {
|
|
100
128
|
if (p.pricedTurns === 0 && p.sources.size === 0) continue;
|
|
101
129
|
const m = usage.byModel[model]!;
|
|
102
|
-
m.usd = p.
|
|
130
|
+
m.usd = p.unpricedTokenTurns > 0 ? null : p.usd;
|
|
131
|
+
if (p.feeTurns > 0) m.feeUsd = p.feeUsd;
|
|
103
132
|
if (p.sources.size > 0) m.priceSources = [...p.sources].sort();
|
|
104
133
|
}
|
|
105
134
|
return usage;
|
|
@@ -128,6 +157,8 @@ export function addUsage(a: RunUsage, b: RunUsage): RunUsage {
|
|
|
128
157
|
const usd = foldUsd(acc.usd, m.usd);
|
|
129
158
|
if (usd === undefined) delete acc.usd;
|
|
130
159
|
else acc.usd = usd;
|
|
160
|
+
// A side without a fee spent none — a fee of nothing, never an unknown.
|
|
161
|
+
if (acc.feeUsd !== undefined || m.feeUsd !== undefined) acc.feeUsd = (acc.feeUsd ?? 0) + (m.feeUsd ?? 0);
|
|
131
162
|
const sources = new Set([...(acc.priceSources ?? []), ...(m.priceSources ?? [])]);
|
|
132
163
|
if (sources.size > 0) acc.priceSources = [...sources].sort();
|
|
133
164
|
}
|
|
@@ -145,6 +176,7 @@ const isModelUsage = (v: unknown): v is ModelUsage => {
|
|
|
145
176
|
)
|
|
146
177
|
return false;
|
|
147
178
|
if (m.usd !== undefined && m.usd !== null && typeof m.usd !== "number") return false;
|
|
179
|
+
if (m.feeUsd !== undefined && typeof m.feeUsd !== "number") return false;
|
|
148
180
|
return (
|
|
149
181
|
m.priceSources === undefined ||
|
|
150
182
|
(Array.isArray(m.priceSources) && m.priceSources.every((s) => typeof s === "string"))
|
|
@@ -451,16 +451,16 @@ export const TIMEOUT_ON_LONG_COMMANDS =
|
|
|
451
451
|
* exit line goes into the PR description's validation table as the row's proof
|
|
452
452
|
* (the handoff has no verified list; parseHandoff carries deviations, followUps,
|
|
453
453
|
* unproven and landed), and a gate the child could not run goes under the
|
|
454
|
-
* handoff's unproven list, never claimed clean. The
|
|
455
|
-
*
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
*
|
|
459
|
-
export const
|
|
454
|
+
* handoff's unproven list, never claimed clean. The test gate is the touched
|
|
455
|
+
* files by name, never a changed-set or directory run. */
|
|
456
|
+
/** The test command the contract hands a child: the touched files by name, once.
|
|
457
|
+
* Never `--changed`: against a base that moves, it selects most of the suite, and on the
|
|
458
|
+
* shared resident that is the memory incident the coding contract exists to prevent. */
|
|
459
|
+
export const TOUCHED_TESTS_COMMAND = "`npx vitest run` on the test files you touched, by name,";
|
|
460
460
|
|
|
461
461
|
export const FAST_GATES_BEFORE_PUSH =
|
|
462
462
|
"The fast gates, before every push — each scoped to the changed set, never the whole project: " +
|
|
463
|
-
`${
|
|
463
|
+
`${TOUCHED_TESTS_COMMAND} once (never \`--changed\`, never a directory: on a moving base that is most of the suite), ` +
|
|
464
464
|
"`tsc --noEmit -p` the touched tsconfig under `NODE_OPTIONS=--max-old-space-size=6144`, " +
|
|
465
465
|
"`npx prettier --check` on the changed files, `npm run hygiene:check` and `npm run specs:check` — " +
|
|
466
466
|
"then your judgement on what else this change needs, not a longer checklist. Every CI pipeline runs the " +
|
|
@@ -474,14 +474,7 @@ export const FAST_GATES_BEFORE_PUSH =
|
|
|
474
474
|
function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
475
475
|
const branch = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
476
476
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
477
|
-
|
|
478
|
-
// contract knows as the rebase target; unknown, the placeholder stands
|
|
479
|
-
// split/join, never String.replace: a `$` in a branch name is literal text here
|
|
480
|
-
const gates = rebase.onto
|
|
481
|
-
? FAST_GATES_BEFORE_PUSH.split(`${CHANGED_SET_TEST_PLACEHOLDER} (the pull request's base; or`).join(
|
|
482
|
-
`\`npx vitest run --changed origin/${rebase.onto}\` (or`,
|
|
483
|
-
)
|
|
484
|
-
: FAST_GATES_BEFORE_PUSH;
|
|
477
|
+
const gates = FAST_GATES_BEFORE_PUSH;
|
|
485
478
|
return (
|
|
486
479
|
`Rebase ${branch} onto ${onto} before any other work — the parent unit has merged and the base has moved; ` +
|
|
487
480
|
`the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
|
|
@@ -461,6 +461,12 @@ export type CoordinatorAction =
|
|
|
461
461
|
| {
|
|
462
462
|
type: "pr-check";
|
|
463
463
|
step: string;
|
|
464
|
+
/** The unit-start's pre-check (issue 1689): the bot reads the entry facts
|
|
465
|
+
* beside the listing — the branch's own tip, whether its approval
|
|
466
|
+
* stands at the head, and the checks there — so a re-issued plan's
|
|
467
|
+
* unit resumes at review, or at the merge decision, never at coding
|
|
468
|
+
* over an already-shipped pull request. */
|
|
469
|
+
entry?: true;
|
|
464
470
|
/** Set after a coding child died: the bot opens the pull request from the
|
|
465
471
|
* pushed branch itself (title from the unit, body from this run's
|
|
466
472
|
* submitted description when the record holds one) instead of answering
|
|
@@ -545,6 +551,15 @@ export type PrCheck =
|
|
|
545
551
|
prNumber: number;
|
|
546
552
|
url: string;
|
|
547
553
|
headSha?: string;
|
|
554
|
+
/** The unit branch's own tip, read beside the listing on the entry check
|
|
555
|
+
* (issue 1689): the listing's `headSha` can lag a force-push, so the
|
|
556
|
+
* machine resumes at review only when the pull request heads the
|
|
557
|
+
* branch's actual tip — a head behind the branch is round 0's. */
|
|
558
|
+
branchHead?: string;
|
|
559
|
+
/** Whether an approving review by the bot stands at the head (the entry
|
|
560
|
+
* check, issue 1689): with green checks the attempt resumes straight at
|
|
561
|
+
* the merge decision instead of a review round. */
|
|
562
|
+
approved?: boolean;
|
|
548
563
|
autoMergeEnabled?: boolean;
|
|
549
564
|
/** The check runs at the head, when the read asked for them (the ending's facts). */
|
|
550
565
|
checks?: CommitChecksFacts;
|
|
@@ -696,7 +711,46 @@ export type UnitEnding =
|
|
|
696
711
|
}
|
|
697
712
|
| { kind: "no_verdict"; round: RoundRef; reviewRounds: number; finalReply?: string }
|
|
698
713
|
| { kind: "interrupted"; round: RoundRef; runId: string; reviewRounds: number }
|
|
699
|
-
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number }
|
|
714
|
+
| { kind: "refused"; refusal: string; message?: string; round: RoundRef; reviewRounds: number }
|
|
715
|
+
/** The unit idles instead of ending (record 0051): with the resolved
|
|
716
|
+
* `ship.idleDays` above zero, `end()` wraps an idling kind — every kind but
|
|
717
|
+
* the four ended ones (`merged`, `already_landed`, `merge_ready`,
|
|
718
|
+
* `refused`) — in this ending: the old kind as `why`, the old kind's
|
|
719
|
+
* report unchanged, and what a continuation needs (the renewals the grant
|
|
720
|
+
* still holds — unspent: the wake spends one, this plan's fifth unit — the
|
|
721
|
+
* head to continue from, the last coding child's run, the spend and its
|
|
722
|
+
* handoff). `runId` is absent when the pipeline capped before any coding
|
|
723
|
+
* child ran. */
|
|
724
|
+
| {
|
|
725
|
+
kind: "idle";
|
|
726
|
+
why: IdleWhy;
|
|
727
|
+
/** The ending the idle stands in for, whole: its report is rendered per
|
|
728
|
+
* copy at that copy's level — the row's and the board's in full, the
|
|
729
|
+
* thread's at the request's verbosity — never once at mapping time. */
|
|
730
|
+
idled: Exclude<UnitEnding, { kind: "idle" }>;
|
|
731
|
+
renewalsLeft: number;
|
|
732
|
+
from?: string;
|
|
733
|
+
runId?: string;
|
|
734
|
+
spendUsd: number | null;
|
|
735
|
+
handoff?: Handoff;
|
|
736
|
+
round?: RoundRef;
|
|
737
|
+
reviewRounds: number;
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
/** The kinds that idle: every ending but the four ended ones — the unit is
|
|
741
|
+
* unfinished (a cap, a stop, an abort, a refused merge, a segment's end) and
|
|
742
|
+
* a reply could continue it. `merged`, `already_landed`, `merge_ready` and
|
|
743
|
+
* `refused` never idle: the first two are done, merge-ready waits only for a
|
|
744
|
+
* person's merge, and a refused child would be refused again. */
|
|
745
|
+
export type IdleWhy = Exclude<UnitEnding["kind"], "idle" | "merged" | "already_landed" | "merge_ready" | "refused">;
|
|
746
|
+
|
|
747
|
+
const NEVER_IDLES: ReadonlySet<UnitEnding["kind"]> = new Set([
|
|
748
|
+
"idle",
|
|
749
|
+
"merged",
|
|
750
|
+
"already_landed",
|
|
751
|
+
"merge_ready",
|
|
752
|
+
"refused",
|
|
753
|
+
]);
|
|
700
754
|
|
|
701
755
|
/** What a transition tells the driver beyond the next action: a round boundary
|
|
702
756
|
* the card draws (`shipRoundHeader`) and the run stream records, and the end. */
|
|
@@ -783,6 +837,11 @@ export interface UnitPipelineInput {
|
|
|
783
837
|
* `plan` with an id and no `path` — whose unit runs in the requesting
|
|
784
838
|
* thread and is re-issued with the request's own text, never a plan path. */
|
|
785
839
|
generated: boolean;
|
|
840
|
+
/** The idle flag (record 0051; agent-ship item 8): `ship.idleDays` as the
|
|
841
|
+
* ship fork resolved it onto the instance — above zero, an idling kind ends
|
|
842
|
+
* `idle` instead; zero or absent is today's behavior byte for byte. The
|
|
843
|
+
* wait itself lands with this plan's fifth unit. */
|
|
844
|
+
idleDays?: number;
|
|
786
845
|
/** Resume at review: an open pull request of ship's own the requester named. */
|
|
787
846
|
resume?: { pr: number; headSha?: string; url?: string };
|
|
788
847
|
/** The head the previous attempt's coding child last pushed (a
|
|
@@ -876,6 +935,10 @@ export interface UnitPipelineState {
|
|
|
876
935
|
readonly findingsRunByRound: Readonly<Record<number, string>>;
|
|
877
936
|
/** The last coding run (round 0's child or a findings step's): its record carries the unit's handoff. */
|
|
878
937
|
readonly lastCodingRunId?: string;
|
|
938
|
+
/** The head the last coding child left the branch at, and its handoff — the
|
|
939
|
+
* continuation facts an `idle` ending carries (record 0051). */
|
|
940
|
+
readonly lastChildHead?: string;
|
|
941
|
+
readonly lastCodingHandoff?: Handoff;
|
|
879
942
|
/** How the budget went so far, accrued as each answer moves the clock. */
|
|
880
943
|
readonly spentMs: ShipBudgetSpent;
|
|
881
944
|
/** The session's dollars so far: the input's from earlier segments plus each
|
|
@@ -1033,7 +1096,7 @@ export function nextAction(s: UnitPipelineState): CoordinatorAction {
|
|
|
1033
1096
|
const p = s.phase;
|
|
1034
1097
|
switch (p.at) {
|
|
1035
1098
|
case "pre-check":
|
|
1036
|
-
return { type: "pr-check", step: `${unit}/pr-check
|
|
1099
|
+
return { type: "pr-check", step: `${unit}/pr-check`, entry: true };
|
|
1037
1100
|
case "branch":
|
|
1038
1101
|
return { type: "branch", step: `${unit}/branch`, branch: s.input.unit.branch, from: s.input.base };
|
|
1039
1102
|
case "spawn": {
|
|
@@ -1117,7 +1180,55 @@ function addSpend(sum: number | null, cost: number | null | undefined): number |
|
|
|
1117
1180
|
}
|
|
1118
1181
|
|
|
1119
1182
|
function end(s: UnitPipelineState, ending: UnitEnding, notes: CoordinatorNote[] = []): Transition {
|
|
1120
|
-
|
|
1183
|
+
const idled = idleEnding(s, ending);
|
|
1184
|
+
const final = idled ?? ending;
|
|
1185
|
+
return { state: { ...s, phase: ENDED, ending: final }, notes: [...notes, { type: "ended", ending: final }] };
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/** The idle mapping (record 0051; agent-ship item 8): with the input's
|
|
1189
|
+
* `idleDays` above zero an idling kind ends `idle` — the old kind as `why`,
|
|
1190
|
+
* the old ending itself for the report, and the continuation facts off the state
|
|
1191
|
+
* (a `continued` ending's own where it carries them). At zero, undefined:
|
|
1192
|
+
* every ending is byte for byte today's. The round notes keep the old kind's
|
|
1193
|
+
* outcome — the row and the card say what happened. */
|
|
1194
|
+
function idleEnding(s: UnitPipelineState, ending: UnitEnding): Extract<UnitEnding, { kind: "idle" }> | undefined {
|
|
1195
|
+
if ((s.input.idleDays ?? 0) <= 0) return undefined;
|
|
1196
|
+
if (NEVER_IDLES.has(ending.kind)) return undefined;
|
|
1197
|
+
const old = ending as Exclude<UnitEnding, { kind: "idle" | "merged" | "already_landed" | "merge_ready" | "refused" }>;
|
|
1198
|
+
const grant = s.input.grant ?? DEFAULT_GRANT;
|
|
1199
|
+
// Unspent: an idle spends no renewal — the wake's segment does (this plan's
|
|
1200
|
+
// fifth unit) — so the row says what the grant still holds.
|
|
1201
|
+
const renewalsLeft = Math.max(0, grant.renewals - (s.input.session?.renewalsSpent ?? 0));
|
|
1202
|
+
// The head to continue from: a segment's own; a review_pending's reviewed
|
|
1203
|
+
// head — the pull request's, known even when the coding record carried none,
|
|
1204
|
+
// so the re-issue's resume-at-review fact survives the idle; else the last
|
|
1205
|
+
// coding child's recorded head.
|
|
1206
|
+
const from =
|
|
1207
|
+
old.kind === "continued"
|
|
1208
|
+
? old.from
|
|
1209
|
+
: old.kind === "review_pending"
|
|
1210
|
+
? (old.headSha ?? s.lastChildHead)
|
|
1211
|
+
: s.lastChildHead;
|
|
1212
|
+
// The last coding child's run: a segment's own, a dead coding child's own —
|
|
1213
|
+
// a review child that died names nothing here, the coding run before it does.
|
|
1214
|
+
const runId =
|
|
1215
|
+
old.kind === "continued" || (old.kind === "interrupted" && old.round.kind !== "review")
|
|
1216
|
+
? old.runId
|
|
1217
|
+
: s.lastCodingRunId;
|
|
1218
|
+
const handoff = old.kind === "continued" ? old.handoff : s.lastCodingHandoff;
|
|
1219
|
+
const round = "round" in old ? old.round : undefined;
|
|
1220
|
+
return {
|
|
1221
|
+
kind: "idle",
|
|
1222
|
+
why: old.kind,
|
|
1223
|
+
idled: old,
|
|
1224
|
+
renewalsLeft,
|
|
1225
|
+
...(from !== undefined ? { from } : {}),
|
|
1226
|
+
...(runId !== undefined ? { runId } : {}),
|
|
1227
|
+
spendUsd: s.spendUsd,
|
|
1228
|
+
...(handoff !== undefined ? { handoff } : {}),
|
|
1229
|
+
...(round !== undefined ? { round } : {}),
|
|
1230
|
+
reviewRounds: s.reviewRounds,
|
|
1231
|
+
};
|
|
1121
1232
|
}
|
|
1122
1233
|
|
|
1123
1234
|
/** A gated finding as the gate note names it: `F1 (minor)`. */
|
|
@@ -1190,6 +1301,9 @@ function settleCoding(
|
|
|
1190
1301
|
let next: UnitPipelineState = {
|
|
1191
1302
|
...s,
|
|
1192
1303
|
spendUsd: addSpend(s.spendUsd, facts.costUsd),
|
|
1304
|
+
// The continuation facts an idle ending reads off the state (record 0051).
|
|
1305
|
+
...(facts.headSha !== undefined ? { lastChildHead: facts.headSha } : {}),
|
|
1306
|
+
...(facts.handoffLists !== undefined ? { lastCodingHandoff: facts.handoffLists } : {}),
|
|
1193
1307
|
...(facts.pr !== undefined ? { pr: { number: facts.pr.number, url: facts.pr.url } } : {}),
|
|
1194
1308
|
...(round.kind === "findings" && facts.dispositions !== undefined
|
|
1195
1309
|
? {
|
|
@@ -1760,19 +1874,40 @@ export function applyReturn(s: UnitPipelineState, ret: StepReturn): Transition {
|
|
|
1760
1874
|
// by the coding child and adopted at the round's own pr-check.
|
|
1761
1875
|
const r = ret as Extract<StepReturn, { type: "pr-check" }>;
|
|
1762
1876
|
if (r.pr.state === "merged") return foundMerged(clocked, r.pr);
|
|
1763
|
-
// The open pull request still heads
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1766
|
-
//
|
|
1877
|
+
// The open pull request still heads the branch — its head is the
|
|
1878
|
+
// branch's own tip (the entry facts, issue 1689) or the child's own last
|
|
1879
|
+
// push (the previous attempt ended `review_pending`, the row's
|
|
1880
|
+
// `lastPush`): nothing to code, so the attempt adopts it and starts at
|
|
1881
|
+
// the review round — never a fresh coding round on an already-shipped
|
|
1882
|
+
// pull request. Approved at that very head with green checks, there is
|
|
1883
|
+
// nothing to review either: the attempt resumes straight at the merge
|
|
1884
|
+
// decision — `merge_ready` for a person, the merge door under
|
|
1885
|
+
// `merge: runner`, which re-verifies the approval and the checks itself.
|
|
1767
1886
|
if (r.pr.state === "open") {
|
|
1768
1887
|
const head = normalizeHead(r.pr.headSha);
|
|
1888
|
+
const branchHead = normalizeHead(r.pr.branchHead);
|
|
1769
1889
|
const lastPush = normalizeHead(s.input.lastPush);
|
|
1770
|
-
|
|
1771
|
-
|
|
1890
|
+
const atBranchHead = head !== undefined && branchHead !== undefined && sameCommit(head, branchHead);
|
|
1891
|
+
const atLastPush = head !== undefined && lastPush !== undefined && sameCommit(head, lastPush);
|
|
1892
|
+
if (atBranchHead || atLastPush) {
|
|
1893
|
+
// The entry facts — the approval and the checks — were read at the
|
|
1894
|
+
// branch's own tip when GitHub answered it (`branchHead`, the
|
|
1895
|
+
// fresher read of the same head ref), else at the listing's sha.
|
|
1896
|
+
// The resume pins the review — and the merge decision — at that
|
|
1897
|
+
// same head, so the approved+green shortcut never fires at a stale
|
|
1898
|
+
// listing sha the facts do not describe.
|
|
1899
|
+
const entryHead = branchHead ?? head;
|
|
1900
|
+
const adopted: UnitPipelineState = {
|
|
1772
1901
|
...clocked,
|
|
1773
1902
|
pr: { number: r.pr.prNumber, url: r.pr.url },
|
|
1774
|
-
lastReviewHead:
|
|
1775
|
-
}
|
|
1903
|
+
lastReviewHead: entryHead,
|
|
1904
|
+
};
|
|
1905
|
+
const checks = r.pr.checks;
|
|
1906
|
+
const green =
|
|
1907
|
+
checks !== undefined && checks.total > 0 && checks.failed.length === 0 && checks.pending.length === 0;
|
|
1908
|
+
if (r.pr.approved === true && green) return approveOutcome(adopted, []);
|
|
1909
|
+
return nextReview(adopted);
|
|
1910
|
+
}
|
|
1776
1911
|
}
|
|
1777
1912
|
return { state: { ...clocked, phase: { at: "branch" } }, notes: [] };
|
|
1778
1913
|
}
|
|
@@ -2242,5 +2377,9 @@ export function renderUnitReport(
|
|
|
2242
2377
|
`🚫 The ${presetOf(e.round.kind)} child of round ${e.round.index} was refused by the authorize stage (${e.refusal})${e.message ? `: ${e.message}` : ""} — every child is authorized as the requesting user, so the pipeline ends here.`,
|
|
2243
2378
|
reissue,
|
|
2244
2379
|
]);
|
|
2380
|
+
case "idle":
|
|
2381
|
+
// The old kind's sentence at this copy's level — the report is unchanged
|
|
2382
|
+
// by the idle at every verbosity (record 0051).
|
|
2383
|
+
return renderUnitReport({ ...s, ending: e.idled }, facts, verbosity);
|
|
2245
2384
|
}
|
|
2246
2385
|
}
|
|
@@ -100,6 +100,22 @@ export const profileSchema = z.object({
|
|
|
100
100
|
.regex(/^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/, "an R2 bucket name: 3–63 lowercase letters, digits and hyphens"),
|
|
101
101
|
})
|
|
102
102
|
.optional(),
|
|
103
|
+
/** The run-metrics dataset (docs/reference/specs/run-metrics.md item 6), when the installation
|
|
104
|
+
* writes points: the state Worker's template binds it as `RUN_METRICS` with the name beside it
|
|
105
|
+
* in `RUN_METRICS_DATASET`, and `deploy plan` names the binding on the memory step. Nothing is
|
|
106
|
+
* created ahead of the deploy — the platform creates a dataset on first write. The bot's runtime
|
|
107
|
+
* config (`metrics.dataset`) must name the same dataset; the two are held together by the bot's
|
|
108
|
+
* boot warning, not by the profile. Analytics Engine's own dataset-name rules. */
|
|
109
|
+
metrics: z
|
|
110
|
+
.object({
|
|
111
|
+
dataset: z
|
|
112
|
+
.string()
|
|
113
|
+
.regex(
|
|
114
|
+
/^[A-Za-z_][A-Za-z0-9_]{0,63}$/,
|
|
115
|
+
"an Analytics Engine dataset name: 1–64 letters, digits and underscores, not starting with a digit",
|
|
116
|
+
),
|
|
117
|
+
})
|
|
118
|
+
.optional(),
|
|
103
119
|
});
|
|
104
120
|
|
|
105
121
|
export type DeploymentProfile = z.infer<typeof profileSchema>;
|