@coreplane/switchboard 1.251.0 → 1.253.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 +28 -2
- package/dist/assets/deploy/cloudflare/preflight.mjs +19 -21
- package/dist/assets/deploy/cloudflare/worker.ts +6 -3
- package/dist/assets/deploy/cloudflare-memory/runMetricsSink.ts +31 -0
- package/dist/assets/deploy/cloudflare-memory/worker.ts +223 -38
- package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +12 -0
- package/dist/assets/deploy/cloudflare-resident/memoryGuard.ts +212 -0
- package/dist/assets/deploy/cloudflare-resident/refresh.ts +1 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +317 -56
- package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +4 -2
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.d.mts +31 -0
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.mjs +119 -0
- package/dist/assets/deploy/profile.example.json +1 -1
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +3 -2
- package/dist/assets/project.json +13 -9
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +5 -5
- package/dist/assets/src/core/authz/policy.ts +8 -0
- package/dist/assets/src/core/budgets.ts +39 -0
- package/dist/assets/src/core/coordinator/contract.ts +93 -1
- package/dist/assets/src/core/coordinator/driver.ts +236 -21
- package/dist/assets/src/core/drain.ts +50 -0
- package/dist/assets/src/core/memory/engine.ts +123 -4
- package/dist/assets/src/core/memory/scorer.ts +12 -4
- package/dist/assets/src/core/memory/types.ts +73 -12
- package/dist/assets/src/core/modelCard.ts +32 -4
- package/dist/assets/src/core/modelPricing.ts +111 -1
- package/dist/assets/src/core/modelProxy/usage.ts +88 -0
- package/dist/assets/src/core/modelRegistry.ts +15 -1
- package/dist/assets/src/core/refusal.ts +4 -7
- package/dist/assets/src/core/reviewVerdict.ts +4 -0
- package/dist/assets/src/core/runEvents.ts +82 -3
- package/dist/assets/src/core/runFriction.ts +8 -2
- package/dist/assets/src/core/runLedger/types.ts +11 -0
- package/dist/assets/src/core/runMetrics.ts +237 -0
- package/dist/assets/src/core/runRecord.ts +46 -0
- package/dist/assets/src/core/runUsage.ts +67 -13
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +34 -14
- package/dist/assets/src/core/ship/coordinator.ts +485 -55
- package/dist/assets/src/core/ship/renewal.ts +10 -5
- package/dist/assets/src/core/trace/attrs.ts +24 -0
- package/dist/assets/src/core/types.ts +5 -5
- package/dist/assets/src/core/verbosity.ts +48 -0
- package/dist/assets/src/deploy/liveGate.ts +40 -13
- package/dist/assets/src/deploy/profile.ts +16 -0
- package/dist/assets/src/deploy/restart.ts +11 -12
- package/dist/assets/src/execution/residentDepCache.ts +50 -1
- package/dist/assets/src/execution/residentDepsStore.ts +40 -2
- package/dist/assets/src/execution/residentRefresh.ts +55 -3
- package/dist/assets/src/execution/residentSteps.ts +4 -0
- package/dist/assets/src/execution/sandboxErrors.ts +8 -0
- package/dist/assets/web/dist/.vite/manifest.json +444 -430
- package/dist/assets/web/dist/assets/{AppShell-DAtHiuI6.js → AppShell-Bmz8nzz3.js} +1 -1
- package/dist/assets/web/dist/assets/{CostsPage-BoKzYa4B.js → CostsPage-CxTKdpzU.js} +1 -1
- package/dist/assets/web/dist/assets/{DeliveryPage-DUXd-Sl-.js → DeliveryPage-C7jaEJip.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-cWbupQzy.js +3 -0
- package/dist/assets/web/dist/assets/{InputMenu-C6aPpe30.js → InputMenu-C9E7YoII.js} +1 -1
- package/dist/assets/web/dist/assets/{NotFoundPage-DtE-GgTk.js → NotFoundPage-4nlrq7DP.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-DDhMhrI7.js → PendingTurnRow-C-pUgyyX.js} +1 -1
- package/dist/assets/web/dist/assets/PlanePage-BNy4tSgJ.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-BnEoOnGQ.js → ResidentDetailPage-Dbtiq6_J.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-Dxpgf-l-.js → ResidentsIndexPage-CKtz9H1o.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-sYFtuK2G.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-Clh68nw9.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DPDbcOA_.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-YSbUu5py.js → RunsTabs-DotpuCSP.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-B_GgeJrb.js → ScheduledPage-C8FYoZy5.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingSelect-BIzAsLk1.js → SettingSelect-CSiBQCSX.js} +1 -1
- package/dist/assets/web/dist/assets/{SettingsPage-BXX4R113.js → SettingsPage-CkYIhLUl.js} +1 -1
- package/dist/assets/web/dist/assets/{StatusDot-BOaw8le9.js → StatusDot-C0TUgnC1.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DYZZ4l4V.js → Tooltip-CN5cZdl3.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-BaSW5Odq.js → UnitRoutePage-D_EtAl8r.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-BjeQZdCq.js → angular-html-qW40ZL9x.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-ZGdORrL2.js → angular-ts-PUtZU8Cq.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-DhvV_X93.js → apl-D3fC7Qjd.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DiRSE4Ug.js → astro-Bi3JFHhh.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-D8Fr5Wvq.js → blade-CSpsf9Z9.js} +1 -1
- package/dist/assets/web/dist/assets/budgets-CbIyPAER.js +1 -0
- package/dist/assets/web/dist/assets/{c-BwbC64D4.js → c-DE_b1oWP.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DgmqRK97.js → chapel-CWXd9giL.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-DJod2RNK.js → cobol-CnMOmWNF.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-D1Y2CgBY.js → coffee-B9WvURu8.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-CsO97YOM.js → cpp-BZAJXlWI.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CJ7m2tOc.js → crystal-Bm48wAlb.js} +1 -1
- package/dist/assets/web/dist/assets/{css-B2M-NKoj.js → css-WtMeC3ab.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BCVXeBJ9.js → dist-B1Mf2JbM.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-CFVW-m9B.js → edge-BmwNa6Ss.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-B2mtRgCr.js → elixir-BotfP-Mn.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-2teVAwCi.js → elm-DAFc6laN.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-LAbeNqen.js → erb-QkOON3bB.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-DnhzH_sp.js → git-rebase-BPs-dd73.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-DPKjXf1-.js → glimmer-js-D8eooepk.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-CABXnt5z.js → glimmer-ts-B0bRiDVg.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-Dz4-3-gJ.js → glsl-Bv6bMe1n.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-ByWUD1DL.js → graphql-Bh9y-n9o.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-CdmOK2K-.js → hack-DSEbn7Ir.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DzegMpd2.js → haml-0NW1-z1f.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-B5NeiP0e.js → handlebars-BfgCHFbK.js} +1 -1
- package/dist/assets/web/dist/assets/{html-Zg_NHLv9.js → html-CQHc0gQ5.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DZf9JNzh.js → html-derivative-B0Tm7wLP.js} +1 -1
- package/dist/assets/web/dist/assets/{http-CVFrQZeN.js → http-GAuU3aOF.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-L5brWAJe.js → hurl-CrDFbF5B.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-BxKswdlK.js +1 -0
- package/dist/assets/web/dist/assets/{java-hFphyZ4R.js → java-DYv5KhUe.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CAJO1WKX.js → javascript-32SiLGg0.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-N4A4srXI.js → jinja--gJDUGYa.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-D3BZa_iU.js → jison-BlKLGzZH.js} +1 -1
- package/dist/assets/web/dist/assets/{json-DxD1Qh8W.js → json-BICanD1N.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CHhag69S.js → jsx-CgW3-uFT.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CrhkZ6Tt.js → julia-BErRpwwL.js} +1 -1
- package/dist/assets/web/dist/assets/{just-D0LFOuhq.js → just-CordHv0h.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BHAlwCXK.js → latex-D7LVPLbi.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-CkaLfCWh.js → liquid-3ehz2i4U.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-B1h4aAzP.js → lua-C9ppSVrm.js} +1 -1
- package/dist/assets/web/dist/assets/main-Cv0q9y56.js +28 -0
- package/dist/assets/web/dist/assets/main-DKFCM-sy.css +1 -0
- package/dist/assets/web/dist/assets/{marko-D6CG4hvX.js → marko-xBZLUBEJ.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-CVQzimAU.js → mdc-BJxGoVgD.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-h-9Ir54h.js → nginx-B-sZmpsb.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-DHVI30sp.js → nim--s9wRABK.js} +1 -1
- package/dist/assets/web/dist/assets/{org-AasAXfMF.js → org-DoUvssoI.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-BVi5aTEy.js → perl-B-vGHKOy.js} +1 -1
- package/dist/assets/web/dist/assets/{php-qOkCeUKr.js → php-CerWx_Xb.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-8TnUGvEX.js → pug-CzKrBMb1.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-CpJbhoxn.js → qml-Bs8L8I6k.js} +1 -1
- package/dist/assets/web/dist/assets/{r-JkUNcAJW.js → r-w6uG6mob.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-CtU4ws5a.js → razor-dz2oj2bd.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-J2zA6ceN.js → regexp-CfQdprnY.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-CMCvf-yZ.js → rst-DAOaX2tb.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-ChLBY_iR.js → ruby-QcCe4udB.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-cwn4x2VY.js → sas-BrM4lAAc.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-jSrJlzLI.js → scss-DVbJXoUb.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-D5nAd0gs.js → shellscript-09fP2hZf.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BZY4-Tdh.js → shellsession-Qgr8xjZF.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-COppPOKr.js → soy-DIYeNTuz.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-Blc_6dOA.js → sql-CzLR9msV.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-BYo2EZKy.js +11 -0
- package/dist/assets/web/dist/assets/{stata-DRv3OY-h.js → stata-zCY1ZzH0.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-qMv_eeJ6.js → surrealql-CJIjTRV4.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-d1Edb6Rz.js → svelte-C5OTPFZg.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-CUMSIOXG.js → templ-DYj-UICF.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-BGZQA-Ei.js → tex-xcie-3Gd.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-Z9ulxVsq.js → ts-tags-CoN86VTt.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-oI4bI3Vf.js → tsx-n3-No_Br.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-D_RXwBIX.js → twig-DPiJ7Vp1.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BgqQDYoN.js → typescript-BP6HBaOp.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-5MW-KRDd.js → typst-Do9ZjsVP.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-CZhHHsPm.js → vue-CzAeXNRY.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-DlYy2d8v.js → vue-html-D7iYmYjd.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-CJDnx-qn.js → vue-vine-DtrMRsJx.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-BRBDk65Q.js → xml-DGJHo1c0.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-BlCykPxo.js → xsl-Q00kvNgW.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-BqW48szf.js → yaml-F-UGKaiO.js} +1 -1
- package/dist/cli.js +5623 -2455
- package/package.json +1 -1
- package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.sh +0 -68
- package/dist/assets/web/dist/assets/HomePage-mSiqEEcN.js +0 -2
- package/dist/assets/web/dist/assets/RunFoldRow-CSo4-vld.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-9klVWhSF.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-BplMIgaw.js +0 -1
- package/dist/assets/web/dist/assets/budgets-BvWYKPsY.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-Bde9OZxG.js +0 -1
- package/dist/assets/web/dist/assets/main-Dkcbtu3u.js +0 -28
- package/dist/assets/web/dist/assets/main-zbP_dTjR.css +0 -1
- package/dist/assets/web/dist/assets/sseReplay-DPwdsaok.js +0 -9
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { FRICTION_CATEGORIES, type CategoryTotals, type FrictionCategory } from "./runFriction.js";
|
|
2
|
+
import { llmUsdOfUsage, type ModelPriceTable } from "./modelPricing.js";
|
|
3
|
+
import { emptyUsage, UNKNOWN_AGENT } from "./runUsage.js";
|
|
4
|
+
import { utf8ByteLength, type RunRecord } from "./runRecord.js";
|
|
5
|
+
|
|
6
|
+
// Run metrics (docs/reference/specs/run-metrics.md; docs/decisions/0063-every-finished-run-writes-one-metrics-point-and-a-metrics-page-reads-the-trend.md):
|
|
7
|
+
// one flat point per finished run, written to a Workers Analytics Engine
|
|
8
|
+
// dataset beside the record's commit. This file is the point's contract, shared
|
|
9
|
+
// by both sides of the wire — the bot's store/ledger clients compute the point
|
|
10
|
+
// (`pointOf`), the state Worker validates it (`isRunMetricsPoint`) and decides
|
|
11
|
+
// whether the row turned final (`pointTurnsFinal`) — so it is node-free like
|
|
12
|
+
// `runRecord.ts`: no Node built-ins, no I/O, no clock.
|
|
13
|
+
|
|
14
|
+
/** A point as the platform's `writeDataPoint` takes it: one index (the
|
|
15
|
+
* sampling key), positional string blobs and positional doubles. */
|
|
16
|
+
export interface RunMetricsPoint {
|
|
17
|
+
indexes: [string];
|
|
18
|
+
blobs: string[];
|
|
19
|
+
doubles: number[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The platform's byte cap per blob (and per index); `pointOf` truncates. */
|
|
23
|
+
export const MAX_POINT_BLOB_BYTES = 96;
|
|
24
|
+
|
|
25
|
+
/** The point's schema word, `blob1` — bump it when a position changes meaning. */
|
|
26
|
+
export const POINT_SCHEMA = "1";
|
|
27
|
+
|
|
28
|
+
const BLOB_NAMES = [
|
|
29
|
+
"schema",
|
|
30
|
+
"agent",
|
|
31
|
+
"preset",
|
|
32
|
+
"model",
|
|
33
|
+
"status",
|
|
34
|
+
"failure kind",
|
|
35
|
+
"dominant friction",
|
|
36
|
+
"channel",
|
|
37
|
+
"repository",
|
|
38
|
+
"machine class",
|
|
39
|
+
"route class",
|
|
40
|
+
"reply",
|
|
41
|
+
"lineage",
|
|
42
|
+
"requester",
|
|
43
|
+
"identity",
|
|
44
|
+
"run id",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
const DOUBLE_NAMES = [
|
|
48
|
+
"wall",
|
|
49
|
+
"getting ready",
|
|
50
|
+
"thinking",
|
|
51
|
+
"tools",
|
|
52
|
+
"finishing up",
|
|
53
|
+
"overhead",
|
|
54
|
+
"not recorded",
|
|
55
|
+
"not loaded",
|
|
56
|
+
"turns",
|
|
57
|
+
"input tokens",
|
|
58
|
+
"output tokens",
|
|
59
|
+
"cache read tokens",
|
|
60
|
+
"cache write tokens",
|
|
61
|
+
"dollars",
|
|
62
|
+
"steps",
|
|
63
|
+
"tool calls",
|
|
64
|
+
"events",
|
|
65
|
+
"unpriced tokens",
|
|
66
|
+
"minutes",
|
|
67
|
+
"finished at",
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
70
|
+
export type PointBlobName = (typeof BLOB_NAMES)[number];
|
|
71
|
+
export type PointDoubleName = (typeof DOUBLE_NAMES)[number];
|
|
72
|
+
|
|
73
|
+
/** The ONE ordered table naming every position once: `blobs[i]` is the meaning
|
|
74
|
+
* of `blob<i+1>`, `doubles[i]` of `double<i+1>`, `index[0]` of `index1`. The
|
|
75
|
+
* writer builds from it (`pointOf` fills a `Record<name, value>` and maps it
|
|
76
|
+
* through this order, so a position added here without a value fails to
|
|
77
|
+
* compile) and the reader's SQL aliases from it — a query names a column
|
|
78
|
+
* through its name here, never a magic number. */
|
|
79
|
+
export const POINT_COLUMNS = {
|
|
80
|
+
index: ["agent"] as const,
|
|
81
|
+
blobs: BLOB_NAMES,
|
|
82
|
+
doubles: DOUBLE_NAMES,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/** The 1-based platform column of a named blob: `"status"` → `"blob5"`. */
|
|
86
|
+
export function blobColumn(name: PointBlobName): string {
|
|
87
|
+
return `blob${POINT_COLUMNS.blobs.indexOf(name) + 1}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The 1-based platform column of a named double: `"dollars"` → `"double14"`. */
|
|
91
|
+
export function doubleColumn(name: PointDoubleName): string {
|
|
92
|
+
return `double${POINT_COLUMNS.doubles.indexOf(name) + 1}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A point's blob by its `POINT_COLUMNS` name — the tests' and readers' accessor. */
|
|
96
|
+
export function blobOf(point: RunMetricsPoint, name: PointBlobName): string {
|
|
97
|
+
return point.blobs[POINT_COLUMNS.blobs.indexOf(name)];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** A point's double by its `POINT_COLUMNS` name. */
|
|
101
|
+
export function doubleOf(point: RunMetricsPoint, name: PointDoubleName): number {
|
|
102
|
+
return point.doubles[POINT_COLUMNS.doubles.indexOf(name)];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Truncate to the platform's per-blob byte cap (multi-byte safe: cut by
|
|
106
|
+
* characters until the UTF-8 size fits — blob values are ids and enum words,
|
|
107
|
+
* so the loop is theoretical). */
|
|
108
|
+
function capBlob(s: string): string {
|
|
109
|
+
let out = s;
|
|
110
|
+
while (utf8ByteLength(out) > MAX_POINT_BLOB_BYTES) out = out.slice(0, -1);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isFinite_ = (v: unknown): v is number => typeof v === "number" && Number.isFinite(v);
|
|
115
|
+
|
|
116
|
+
/** The dominant friction category of a stored diagnosis: the `FrictionCategory`
|
|
117
|
+
* with the largest `durationMs` among those with a count (first in
|
|
118
|
+
* `FRICTION_CATEGORIES` order on a tie); empty when no category has a count. */
|
|
119
|
+
export function dominantFriction(byCategory: Partial<Record<FrictionCategory, CategoryTotals>>): FrictionCategory | "" {
|
|
120
|
+
let best: FrictionCategory | "" = "";
|
|
121
|
+
let bestMs = -1;
|
|
122
|
+
for (const c of FRICTION_CATEGORIES) {
|
|
123
|
+
const t = byCategory[c];
|
|
124
|
+
if (!t || t.count <= 0) continue;
|
|
125
|
+
if (t.durationMs > bestMs) {
|
|
126
|
+
best = c;
|
|
127
|
+
bestMs = t.durationMs;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return best;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The record's metrics point, or `undefined` for a record still in its
|
|
135
|
+
* provisional window (`provisional: true`) — and for nothing else: a final
|
|
136
|
+
* record whose `finishedAt` equals its `startedAt` is counted with a zero
|
|
137
|
+
* wall. Reads ONLY the record's typed dimensions and counters — never
|
|
138
|
+
* `activity`, `label`, `events`, `verdict`, `handoff`, `dispositions` or
|
|
139
|
+
* `diagnosis.verdict` — so no free text reaches the dataset. Dollars are
|
|
140
|
+
* priced at finish through the same table `RunsService` prices reads with
|
|
141
|
+
* (`llmUsdOfUsage`); an unpriced model's tokens land in `unpriced tokens`,
|
|
142
|
+
* never in `dollars` as $0.
|
|
143
|
+
*/
|
|
144
|
+
export function pointOf(record: RunRecord, prices?: ModelPriceTable): RunMetricsPoint | undefined {
|
|
145
|
+
if (record.provisional === true) return undefined;
|
|
146
|
+
const usage = record.usage ?? emptyUsage();
|
|
147
|
+
const priced = llmUsdOfUsage(usage, prices);
|
|
148
|
+
let inputTokens = 0;
|
|
149
|
+
let outputTokens = 0;
|
|
150
|
+
let cacheReadTokens = 0;
|
|
151
|
+
let cacheWriteTokens = 0;
|
|
152
|
+
for (const m of Object.values(usage.byModel)) {
|
|
153
|
+
inputTokens += m.inputTokens;
|
|
154
|
+
outputTokens += m.outputTokens;
|
|
155
|
+
cacheReadTokens += m.cacheReadTokens;
|
|
156
|
+
cacheWriteTokens += m.cacheWriteTokens;
|
|
157
|
+
}
|
|
158
|
+
const shape = record.diagnosis.shape;
|
|
159
|
+
const blobs: Record<PointBlobName, string> = {
|
|
160
|
+
schema: POINT_SCHEMA,
|
|
161
|
+
agent: record.agent ?? "",
|
|
162
|
+
preset: record.profile?.preset ?? "",
|
|
163
|
+
model: record.model ?? "",
|
|
164
|
+
status: record.status,
|
|
165
|
+
"failure kind": record.failure?.kind ?? "",
|
|
166
|
+
"dominant friction": dominantFriction(record.diagnosis.byCategory),
|
|
167
|
+
channel: record.channelId,
|
|
168
|
+
repository: record.repo ?? "",
|
|
169
|
+
"machine class": record.profile?.machine ?? "",
|
|
170
|
+
"route class": record.route !== undefined ? "routed" : "chosen",
|
|
171
|
+
reply: record.replyOk === true ? "ok" : record.replyOk === false ? "failed" : "none",
|
|
172
|
+
lineage: record.parentRunId !== undefined ? "child" : "root",
|
|
173
|
+
requester: record.userId,
|
|
174
|
+
identity: record.profile?.identity ?? "",
|
|
175
|
+
"run id": record.id,
|
|
176
|
+
};
|
|
177
|
+
const doubles: Record<PointDoubleName, number> = {
|
|
178
|
+
wall: record.finishedAt - (record.receivedAt ?? record.startedAt),
|
|
179
|
+
"getting ready": shape?.gettingReadyMs ?? 0,
|
|
180
|
+
thinking: shape?.thinkingMs ?? 0,
|
|
181
|
+
tools: shape?.toolsMs ?? 0,
|
|
182
|
+
"finishing up": shape?.finishingUpMs ?? 0,
|
|
183
|
+
overhead: shape?.overheadMs ?? 0,
|
|
184
|
+
"not recorded": shape?.notRecordedMs ?? 0,
|
|
185
|
+
"not loaded": shape?.notLoadedMs ?? 0,
|
|
186
|
+
turns: usage.turns,
|
|
187
|
+
"input tokens": inputTokens,
|
|
188
|
+
"output tokens": outputTokens,
|
|
189
|
+
"cache read tokens": cacheReadTokens,
|
|
190
|
+
"cache write tokens": cacheWriteTokens,
|
|
191
|
+
dollars: priced.usd,
|
|
192
|
+
steps: record.stepCount ?? 0,
|
|
193
|
+
"tool calls": isFinite_(record.diagnosis.toolCalls) ? record.diagnosis.toolCalls : 0,
|
|
194
|
+
events: record.eventCount,
|
|
195
|
+
"unpriced tokens": priced.unpricedTokens,
|
|
196
|
+
minutes: record.profile?.minutes ?? 0,
|
|
197
|
+
"finished at": record.finishedAt,
|
|
198
|
+
};
|
|
199
|
+
return {
|
|
200
|
+
indexes: [capBlob(record.agent ?? UNKNOWN_AGENT)],
|
|
201
|
+
blobs: POINT_COLUMNS.blobs.map((n) => capBlob(blobs[n])),
|
|
202
|
+
doubles: POINT_COLUMNS.doubles.map((n) => doubles[n]),
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Structural check on a point from outside the process (the `/runs/put` and
|
|
207
|
+
* `/runs/finish` bodies): exactly one index, exactly `POINT_COLUMNS.blobs.length`
|
|
208
|
+
* strings each at most `MAX_POINT_BLOB_BYTES` bytes, exactly
|
|
209
|
+
* `POINT_COLUMNS.doubles.length` finite numbers — and nothing else. */
|
|
210
|
+
export function isRunMetricsPoint(v: unknown): v is RunMetricsPoint {
|
|
211
|
+
if (typeof v !== "object" || v === null) return false;
|
|
212
|
+
const p = v as Record<string, unknown>;
|
|
213
|
+
if (!Array.isArray(p.indexes) || p.indexes.length !== 1) return false;
|
|
214
|
+
const index = p.indexes[0];
|
|
215
|
+
if (typeof index !== "string" || utf8ByteLength(index) > MAX_POINT_BLOB_BYTES) return false;
|
|
216
|
+
if (!Array.isArray(p.blobs) || p.blobs.length !== POINT_COLUMNS.blobs.length) return false;
|
|
217
|
+
if (!p.blobs.every((b) => typeof b === "string" && utf8ByteLength(b) <= MAX_POINT_BLOB_BYTES)) return false;
|
|
218
|
+
if (!Array.isArray(p.doubles) || p.doubles.length !== POINT_COLUMNS.doubles.length) return false;
|
|
219
|
+
return p.doubles.every(isFinite_);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The emission rule's one question, answered inside the store's transaction:
|
|
224
|
+
* did this write turn the run's row final? True exactly when the stored record
|
|
225
|
+
* is final (`provisional` absent) and the existing row was absent or itself
|
|
226
|
+
* provisional. There is deliberately no `finishedAt > startedAt` term: a plain
|
|
227
|
+
* `put` of an `interrupted` record over no row is a run counted once, and a
|
|
228
|
+
* final rewrite over a final row (the review artifact, an identical retry) is
|
|
229
|
+
* false however its stamps compare.
|
|
230
|
+
*/
|
|
231
|
+
export function pointTurnsFinal(
|
|
232
|
+
existing: { provisional?: boolean } | undefined,
|
|
233
|
+
stored: Pick<RunRecord, "provisional">,
|
|
234
|
+
): boolean {
|
|
235
|
+
if (stored.provisional === true) return false;
|
|
236
|
+
return existing === undefined || existing.provisional === true;
|
|
237
|
+
}
|
|
@@ -184,6 +184,13 @@ export interface RunRecord {
|
|
|
184
184
|
* reading the events. Absent for a preset a person, a scope or the default
|
|
185
185
|
* chose, and on records written before it existed. */
|
|
186
186
|
route?: RunRouteDecision;
|
|
187
|
+
/** The operator's shadow decision beside the routed request (record 0057;
|
|
188
|
+
* the one-door plan's operator unit; run-history item 60): what the operator
|
|
189
|
+
* bound for the same event, redacted like the receipt — never the message
|
|
190
|
+
* text — with the intake gate's verdict when the gate was present. The
|
|
191
|
+
* agreement row reads it from here; it follows the record's retention.
|
|
192
|
+
* Absent under `routing.operator: off` and on records written before it. */
|
|
193
|
+
operator?: RunOperatorDecision;
|
|
187
194
|
/** The effective profile the run was admitted with (docs/decisions/0026-capability-profiles-and-request-routing.md):
|
|
188
195
|
* the preset, its machine class and identity, the minutes it ran on and —
|
|
189
196
|
* when a boundary clipped the budget — the scope that did, so a reader can
|
|
@@ -404,6 +411,45 @@ export function routeOfEvents(events: readonly RunEvent[]): RunRouteDecision | u
|
|
|
404
411
|
};
|
|
405
412
|
}
|
|
406
413
|
|
|
414
|
+
/** The operator's decision as a record carries it — the `operator` event's
|
|
415
|
+
* fields less the stream bookkeeping (record 0057; run-history item 60). */
|
|
416
|
+
export interface RunOperatorDecision {
|
|
417
|
+
mode: "shadow" | "on";
|
|
418
|
+
outcome: "binds" | "question" | "refusal";
|
|
419
|
+
reason: string;
|
|
420
|
+
binds?: { line: string; reason: string }[];
|
|
421
|
+
question?: string;
|
|
422
|
+
/** A question's proposed line — what the next turn's "yes" binds. */
|
|
423
|
+
proposal?: string;
|
|
424
|
+
refusalCause?: string;
|
|
425
|
+
refusalText?: string;
|
|
426
|
+
intake?: { verdict: string; reason: string };
|
|
427
|
+
latencyMs?: number;
|
|
428
|
+
outputTokens?: number;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/** The operator's decision the run's events carry: the one `operator` event
|
|
432
|
+
* (record 0057), whatever the run's own agent source — the shadow decision
|
|
433
|
+
* rides routed and unrouted runs alike, or there would be no disagreement to
|
|
434
|
+
* count. Undefined on a run the operator never saw. */
|
|
435
|
+
export function operatorOfEvents(events: readonly RunEvent[]): RunOperatorDecision | undefined {
|
|
436
|
+
const e = events.find((e) => e.type === "operator");
|
|
437
|
+
if (e?.type !== "operator") return undefined;
|
|
438
|
+
return {
|
|
439
|
+
mode: e.mode,
|
|
440
|
+
outcome: e.outcome,
|
|
441
|
+
reason: e.reason,
|
|
442
|
+
...(e.binds ? { binds: e.binds.map((b) => ({ line: b.line, reason: b.reason })) } : {}),
|
|
443
|
+
...(e.question !== undefined ? { question: e.question } : {}),
|
|
444
|
+
...(e.proposal !== undefined ? { proposal: e.proposal } : {}),
|
|
445
|
+
...(e.refusalCause !== undefined ? { refusalCause: e.refusalCause } : {}),
|
|
446
|
+
...(e.refusalText !== undefined ? { refusalText: e.refusalText } : {}),
|
|
447
|
+
...(e.intake ? { intake: { verdict: e.intake.verdict, reason: e.intake.reason } } : {}),
|
|
448
|
+
...(e.latencyMs !== undefined ? { latencyMs: e.latencyMs } : {}),
|
|
449
|
+
...(e.outputTokens !== undefined ? { outputTokens: e.outputTokens } : {}),
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
407
453
|
function isRunRouteShape(v: unknown): v is RunRouteDecision {
|
|
408
454
|
if (typeof v !== "object" || v === null) return false;
|
|
409
455
|
const r = v as Record<string, unknown>;
|
|
@@ -23,6 +23,17 @@ export interface ModelUsage {
|
|
|
23
23
|
outputTokens: number;
|
|
24
24
|
cacheReadTokens: number;
|
|
25
25
|
cacheWriteTokens: number;
|
|
26
|
+
/** The model's dollars summed from its turns' own `usd` attrs (the proxy's
|
|
27
|
+
* meter row, model-proxy item 6): a number when every turn that counted
|
|
28
|
+
* tokens carried one, null when such a turn lacked it (unpriced — a sum
|
|
29
|
+
* that left counted tokens out would understate the model; a turn that
|
|
30
|
+
* counted none, an errored call or a retry, leaves nothing out), absent on
|
|
31
|
+
* a record whose turns predate the meter row (the price table then prices
|
|
32
|
+
* the tokens, costs.md item 4b). */
|
|
33
|
+
usd?: number | null;
|
|
34
|
+
/** The distinct `priceSource` words the turns carried, sorted — what the
|
|
35
|
+
* by-model view names as the source. Absent when no turn carried one. */
|
|
36
|
+
priceSources?: string[];
|
|
26
37
|
}
|
|
27
38
|
|
|
28
39
|
export interface RunUsage {
|
|
@@ -46,6 +57,14 @@ export const emptyUsage = (): RunUsage => ({ turns: 0, byModel: {} });
|
|
|
46
57
|
/** The run's usage from its events: one `model.turn` span end per provider call. */
|
|
47
58
|
export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
|
|
48
59
|
const usage = emptyUsage();
|
|
60
|
+
// The meter row per model (model-proxy item 6): the turns' own dollars and
|
|
61
|
+
// sources. `usd` sums only when every turn that counted tokens carried one —
|
|
62
|
+
// a turn that counted none (an upstream error, a retry the harness's SDK
|
|
63
|
+
// spent, a stream broken before its usage) has nothing a sum could leave
|
|
64
|
+
// out, so it never turns the model unpriced; a model none of whose turns
|
|
65
|
+
// carried price attrs stays without the fields (a record from before the
|
|
66
|
+
// meter row — the table prices it, costs.md item 4b).
|
|
67
|
+
const priced = new Map<string, { usd: number; pricedTurns: number; tokenTurns: number; sources: Set<string> }>();
|
|
49
68
|
for (const e of events) {
|
|
50
69
|
if (e.type !== "span_end" || e.name !== MODEL_TURN) continue;
|
|
51
70
|
const attrs = (e.attrs ?? {}) as Record<string, unknown>;
|
|
@@ -57,6 +76,8 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
|
|
|
57
76
|
cacheReadTokens: 0,
|
|
58
77
|
cacheWriteTokens: 0,
|
|
59
78
|
};
|
|
79
|
+
const counted =
|
|
80
|
+
num(attrs.inputTokens) + num(attrs.outputTokens) + num(attrs.cacheReadTokens) + num(attrs.cacheWriteTokens);
|
|
60
81
|
m.turns += 1;
|
|
61
82
|
m.inputTokens += num(attrs.inputTokens);
|
|
62
83
|
m.outputTokens += num(attrs.outputTokens);
|
|
@@ -64,38 +85,71 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
|
|
|
64
85
|
m.cacheWriteTokens += num(attrs.cacheWriteTokens);
|
|
65
86
|
usage.byModel[model] = m;
|
|
66
87
|
usage.turns += 1;
|
|
88
|
+
const hasUsd = typeof attrs.usd === "number" && Number.isFinite(attrs.usd);
|
|
89
|
+
const hasSource = typeof attrs.priceSource === "string" && attrs.priceSource !== "";
|
|
90
|
+
const p = priced.get(model) ?? { usd: 0, pricedTurns: 0, tokenTurns: 0, sources: new Set<string>() };
|
|
91
|
+
if (counted > 0) p.tokenTurns += 1;
|
|
92
|
+
if (hasUsd) {
|
|
93
|
+
p.usd += attrs.usd as number;
|
|
94
|
+
p.pricedTurns += 1;
|
|
95
|
+
}
|
|
96
|
+
if (hasSource) p.sources.add(attrs.priceSource as string);
|
|
97
|
+
priced.set(model, p);
|
|
98
|
+
}
|
|
99
|
+
for (const [model, p] of priced) {
|
|
100
|
+
if (p.pricedTurns === 0 && p.sources.size === 0) continue;
|
|
101
|
+
const m = usage.byModel[model]!;
|
|
102
|
+
m.usd = p.pricedTurns >= p.tokenTurns ? p.usd : null;
|
|
103
|
+
if (p.sources.size > 0) m.priceSources = [...p.sources].sort();
|
|
67
104
|
}
|
|
68
105
|
return usage;
|
|
69
106
|
}
|
|
70
107
|
|
|
108
|
+
/** The two sides' `usd` folded: a sum when both carry one, null when either
|
|
109
|
+
* has an unpriced turn, absent when either predates the meter row (the whole
|
|
110
|
+
* cell then prices from the table, never half a figure). */
|
|
111
|
+
const foldUsd = (a: ModelUsage["usd"], b: ModelUsage["usd"]): ModelUsage["usd"] =>
|
|
112
|
+
a === undefined || b === undefined ? undefined : a === null || b === null ? null : a + b;
|
|
113
|
+
|
|
71
114
|
export function addUsage(a: RunUsage, b: RunUsage): RunUsage {
|
|
72
115
|
const out: RunUsage = { turns: a.turns + b.turns, byModel: {} };
|
|
73
116
|
for (const src of [a.byModel, b.byModel]) {
|
|
74
117
|
for (const [model, m] of Object.entries(src)) {
|
|
75
|
-
const acc = out.byModel[model]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
cacheWriteTokens: 0,
|
|
81
|
-
};
|
|
118
|
+
const acc = out.byModel[model];
|
|
119
|
+
if (!acc) {
|
|
120
|
+
out.byModel[model] = { ...m, ...(m.priceSources ? { priceSources: [...m.priceSources] } : {}) };
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
82
123
|
acc.turns += m.turns;
|
|
83
124
|
acc.inputTokens += m.inputTokens;
|
|
84
125
|
acc.outputTokens += m.outputTokens;
|
|
85
126
|
acc.cacheReadTokens += m.cacheReadTokens;
|
|
86
127
|
acc.cacheWriteTokens += m.cacheWriteTokens;
|
|
87
|
-
|
|
128
|
+
const usd = foldUsd(acc.usd, m.usd);
|
|
129
|
+
if (usd === undefined) delete acc.usd;
|
|
130
|
+
else acc.usd = usd;
|
|
131
|
+
const sources = new Set([...(acc.priceSources ?? []), ...(m.priceSources ?? [])]);
|
|
132
|
+
if (sources.size > 0) acc.priceSources = [...sources].sort();
|
|
88
133
|
}
|
|
89
134
|
}
|
|
90
135
|
return out;
|
|
91
136
|
}
|
|
92
137
|
|
|
93
|
-
const isModelUsage = (v: unknown): v is ModelUsage =>
|
|
94
|
-
typeof v
|
|
95
|
-
v
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
const isModelUsage = (v: unknown): v is ModelUsage => {
|
|
139
|
+
if (typeof v !== "object" || v === null) return false;
|
|
140
|
+
const m = v as Record<string, unknown>;
|
|
141
|
+
if (
|
|
142
|
+
!["turns", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"].every(
|
|
143
|
+
(k) => typeof m[k] === "number",
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
return false;
|
|
147
|
+
if (m.usd !== undefined && m.usd !== null && typeof m.usd !== "number") return false;
|
|
148
|
+
return (
|
|
149
|
+
m.priceSources === undefined ||
|
|
150
|
+
(Array.isArray(m.priceSources) && m.priceSources.every((s) => typeof s === "string"))
|
|
98
151
|
);
|
|
152
|
+
};
|
|
99
153
|
|
|
100
154
|
export function isRunUsage(v: unknown): v is RunUsage {
|
|
101
155
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -379,6 +379,9 @@ export interface WatchdogSummary {
|
|
|
379
379
|
action?: unknown;
|
|
380
380
|
error?: string;
|
|
381
381
|
disk?: unknown;
|
|
382
|
+
/** The resident's last memory reading (`{usedBytes, capBytes, percent, …}`,
|
|
383
|
+
* resident-repos.md item 70) when it has one. */
|
|
384
|
+
memory?: unknown;
|
|
382
385
|
/** What the pass did about the resident's refresh Workflow instance: `{id, action, why}`. */
|
|
383
386
|
instance?: unknown;
|
|
384
387
|
}>;
|
|
@@ -439,28 +439,48 @@ export const TIMEOUT_ON_LONG_COMMANDS =
|
|
|
439
439
|
"State a timeout on any command you expect to run longer than a minute: a timeout that reaches past the loop's " +
|
|
440
440
|
"end is refused before the command runs, never cut midway.";
|
|
441
441
|
|
|
442
|
-
/** The fast gates a plan child runs before every push, named one by one
|
|
443
|
-
* (agent-ship item 13; agent-coding item 9).
|
|
444
|
-
* left the choice to the child, and children
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
*
|
|
448
|
-
*
|
|
442
|
+
/** The fast gates a plan child runs before every push, named one by one and
|
|
443
|
+
* each scoped to the changed set (agent-ship item 13; agent-coding item 9).
|
|
444
|
+
* "Its cheapest proving checks" left the choice to the child, and children
|
|
445
|
+
* chose wrong in both directions: prettier was reported clean while
|
|
446
|
+
* `format:check` was red, hygiene imprints reached CI that `hygiene:check`
|
|
447
|
+
* would have caught locally — and children ran the whole suite and the whole
|
|
448
|
+
* typecheck on the shared resident, minutes each call, time-sliced against
|
|
449
|
+
* every other run. So the gates are the changed-set forms, the full runs are
|
|
450
|
+
* said to be CI's alone in the same breath, and each gate is a receipt — the
|
|
451
|
+
* exit line goes into the PR description's validation table as the row's proof
|
|
452
|
+
* (the handoff has no verified list; parseHandoff carries deviations, followUps,
|
|
453
|
+
* unproven and landed), and a gate the child could not run goes under the
|
|
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
|
+
|
|
449
461
|
export const FAST_GATES_BEFORE_PUSH =
|
|
450
|
-
"The fast gates, before every push
|
|
451
|
-
|
|
452
|
-
"
|
|
453
|
-
"and
|
|
462
|
+
"The fast gates, before every push — each scoped to the changed set, never the whole project: " +
|
|
463
|
+
`${TOUCHED_TESTS_COMMAND} once (never \`--changed\`, never a directory: on a moving base that is most of the suite), ` +
|
|
464
|
+
"`tsc --noEmit -p` the touched tsconfig under `NODE_OPTIONS=--max-old-space-size=6144`, " +
|
|
465
|
+
"`npx prettier --check` on the changed files, `npm run hygiene:check` and `npm run specs:check` — " +
|
|
466
|
+
"then your judgement on what else this change needs, not a longer checklist. Every CI pipeline runs the " +
|
|
467
|
+
"tests, the types, the formatting and the full verification on your push, so you never run them again: " +
|
|
468
|
+
"you validate and fix your own change before pushing, at the changed-set scope. Passing the full test suite " +
|
|
469
|
+
"and the full typecheck is NOT part of your criteria: CI is that gate and the only place they run — on a " +
|
|
470
|
+
"shared resident they cost minutes that every other run pays for. " +
|
|
471
|
+
"Paste each command's exit line into the PR description's validation table as the row's proof; a gate you " +
|
|
472
|
+
"could not run goes under the handoff's unproven list and is never claimed clean.";
|
|
454
473
|
|
|
455
474
|
function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
|
|
456
475
|
const branch = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
|
|
457
476
|
const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
|
|
477
|
+
const gates = FAST_GATES_BEFORE_PUSH;
|
|
458
478
|
return (
|
|
459
479
|
`Rebase ${branch} onto ${onto} before any other work — the parent unit has merged and the base has moved; ` +
|
|
460
480
|
`the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
|
|
461
|
-
`Push the branch as soon as the change exists and the fast gates pass —
|
|
462
|
-
`
|
|
463
|
-
`survive the run's end. ${
|
|
481
|
+
`Push the branch as soon as the change exists and the fast gates pass — the project's full verification ` +
|
|
482
|
+
`is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not ` +
|
|
483
|
+
`survive the run's end. ${gates} Right before each push, fetch ${onto} again and rebase ` +
|
|
464
484
|
`once more if it moved while you worked, so the pull request is not born conflicting. At the wind-down note, ` +
|
|
465
485
|
`commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
|
|
466
486
|
);
|