@integrity-labs/agt-cli 0.28.773 → 0.28.775
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/bin/agt.js +5 -5
- package/dist/{chunk-75UAJFJJ.js → chunk-E33WDZNL.js} +3851 -3783
- package/dist/chunk-E33WDZNL.js.map +1 -0
- package/dist/{chunk-UVA7VXG4.js → chunk-FHOU7LRX.js} +9187 -9156
- package/dist/chunk-FHOU7LRX.js.map +1 -0
- package/dist/{chunk-UL2W5NY6.js → chunk-VCAWCBMT.js} +13 -13
- package/dist/{claude-pair-runtime-5ULX4UVC.js → claude-pair-runtime-QQBNL5WW.js} +2 -2
- package/dist/lib/manager-worker.js +13 -13
- package/dist/mcp/direct-chat-channel.js +68 -0
- package/dist/mcp/index.js +160 -13
- package/dist/mcp/origami.js +68 -0
- package/dist/mcp/slack-channel.js +68 -0
- package/dist/mcp/telegram-channel.js +68 -0
- package/dist/{persistent-session-E4AHQHRA.js → persistent-session-UEQ7A6QP.js} +3 -3
- package/dist/{responsiveness-probe-EN6IOLBG.js → responsiveness-probe-CKU7IQK6.js} +3 -3
- package/dist/{session-auth-dead-LNYMXEHY.js → session-auth-dead-ZP5YGGW3.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-75UAJFJJ.js.map +0 -1
- package/dist/chunk-UVA7VXG4.js.map +0 -1
- /package/dist/{chunk-UL2W5NY6.js.map → chunk-VCAWCBMT.js.map} +0 -0
- /package/dist/{claude-pair-runtime-5ULX4UVC.js.map → claude-pair-runtime-QQBNL5WW.js.map} +0 -0
- /package/dist/{persistent-session-E4AHQHRA.js.map → persistent-session-UEQ7A6QP.js.map} +0 -0
- /package/dist/{responsiveness-probe-EN6IOLBG.js.map → responsiveness-probe-CKU7IQK6.js.map} +0 -0
- /package/dist/{session-auth-dead-LNYMXEHY.js.map → session-auth-dead-ZP5YGGW3.js.map} +0 -0
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
rotateDailySession,
|
|
19
19
|
sessionFileExists,
|
|
20
20
|
todayLocalIso
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-E33WDZNL.js";
|
|
22
22
|
import {
|
|
23
23
|
reapOrphanChannelMcps
|
|
24
24
|
} from "./chunk-XWVM4KPK.js";
|
|
@@ -5050,11 +5050,12 @@ function getProjectDir(codeName) {
|
|
|
5050
5050
|
}
|
|
5051
5051
|
|
|
5052
5052
|
export {
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5053
|
+
agentRuntimeKey,
|
|
5054
|
+
hardBoundedExecFile,
|
|
5055
|
+
hardBoundedCapture,
|
|
5056
|
+
LATE_BOUND_VARS,
|
|
5057
|
+
expandTemplateVars,
|
|
5058
|
+
parseEnvIntegrations,
|
|
5058
5059
|
shellQuote,
|
|
5059
5060
|
CHANNEL_SECRET_ENV_KEYS,
|
|
5060
5061
|
parseEnvFileEntries,
|
|
@@ -5062,13 +5063,11 @@ export {
|
|
|
5062
5063
|
collectChannelSpawnSecrets,
|
|
5063
5064
|
toOpencodeModel,
|
|
5064
5065
|
mergeOpencodeConfigArtifact,
|
|
5066
|
+
buildRemoteMcpEntry,
|
|
5067
|
+
buildLiveHeaderRemoteMcpProxyEntry,
|
|
5068
|
+
buildOAuthRemoteMcpProxyEntry,
|
|
5069
|
+
buildNativeMcpEntry,
|
|
5065
5070
|
readChannelServerEnv,
|
|
5066
|
-
hardBoundedExecFile,
|
|
5067
|
-
hardBoundedCapture,
|
|
5068
|
-
LATE_BOUND_VARS,
|
|
5069
|
-
expandTemplateVars,
|
|
5070
|
-
parseEnvIntegrations,
|
|
5071
|
-
agentRuntimeKey,
|
|
5072
5071
|
log,
|
|
5073
5072
|
sha256,
|
|
5074
5073
|
hashFile,
|
|
@@ -5084,6 +5083,7 @@ export {
|
|
|
5084
5083
|
getOpencodeSessionState,
|
|
5085
5084
|
getOpencodeTurnHealth,
|
|
5086
5085
|
getOpencodeActivityAgeSeconds,
|
|
5086
|
+
agentTmpDirFor,
|
|
5087
5087
|
checkChannelInputs,
|
|
5088
5088
|
takeWatchdogGiveUpCount,
|
|
5089
5089
|
creditWatchdogGiveUpCount,
|
|
@@ -5141,4 +5141,4 @@ export {
|
|
|
5141
5141
|
stopAllSessionsAndWait,
|
|
5142
5142
|
getProjectDir
|
|
5143
5143
|
};
|
|
5144
|
-
//# sourceMappingURL=chunk-
|
|
5144
|
+
//# sourceMappingURL=chunk-VCAWCBMT.js.map
|
|
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
|
|
|
100
100
|
return { ok: true };
|
|
101
101
|
} catch {
|
|
102
102
|
}
|
|
103
|
-
const { resolveClaudeBinary } = await import("./persistent-session-
|
|
103
|
+
const { resolveClaudeBinary } = await import("./persistent-session-UEQ7A6QP.js");
|
|
104
104
|
const claudeBin = resolveClaudeBinary();
|
|
105
105
|
const pairEnv = {
|
|
106
106
|
...process.env,
|
|
@@ -373,4 +373,4 @@ export {
|
|
|
373
373
|
startClaudePair,
|
|
374
374
|
submitClaudePairCode
|
|
375
375
|
};
|
|
376
|
-
//# sourceMappingURL=claude-pair-runtime-
|
|
376
|
+
//# sourceMappingURL=claude-pair-runtime-QQBNL5WW.js.map
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
safeWriteJsonAtomic,
|
|
59
59
|
setConfigHash,
|
|
60
60
|
tripClass
|
|
61
|
-
} from "../chunk-
|
|
61
|
+
} from "../chunk-FHOU7LRX.js";
|
|
62
62
|
import {
|
|
63
63
|
getProjectDir as getProjectDir2,
|
|
64
64
|
getReadyTasks,
|
|
@@ -120,7 +120,7 @@ import {
|
|
|
120
120
|
takeZombieDetection,
|
|
121
121
|
toOpencodeModel,
|
|
122
122
|
writeEgressAllowlist
|
|
123
|
-
} from "../chunk-
|
|
123
|
+
} from "../chunk-VCAWCBMT.js";
|
|
124
124
|
import {
|
|
125
125
|
ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
|
|
126
126
|
AnchorSessionClient,
|
|
@@ -209,7 +209,7 @@ import {
|
|
|
209
209
|
subagentActivityAgeSeconds,
|
|
210
210
|
sumTranscriptUsageInWindow,
|
|
211
211
|
transcriptActivityAgeSeconds
|
|
212
|
-
} from "../chunk-
|
|
212
|
+
} from "../chunk-E33WDZNL.js";
|
|
213
213
|
import {
|
|
214
214
|
reapOrphanChannelMcps
|
|
215
215
|
} from "../chunk-XWVM4KPK.js";
|
|
@@ -12870,7 +12870,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
|
|
|
12870
12870
|
var dayRolloverInboundHold = /* @__PURE__ */ new Map();
|
|
12871
12871
|
var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
|
|
12872
12872
|
async function channelInboundActivityAgeSecondsFor(codeName) {
|
|
12873
|
-
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-
|
|
12873
|
+
const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-CKU7IQK6.js");
|
|
12874
12874
|
const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
|
|
12875
12875
|
if (newest === null) return null;
|
|
12876
12876
|
return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
|
|
@@ -13530,7 +13530,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
|
|
|
13530
13530
|
var lastVersionCheckAt = 0;
|
|
13531
13531
|
var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
|
|
13532
13532
|
var lastResponsivenessProbeAt = 0;
|
|
13533
|
-
var agtCliVersion = true ? "0.28.
|
|
13533
|
+
var agtCliVersion = true ? "0.28.775" : "dev";
|
|
13534
13534
|
function resolveBrewPath(execFileSync2) {
|
|
13535
13535
|
try {
|
|
13536
13536
|
const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
|
|
@@ -15074,7 +15074,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
|
|
|
15074
15074
|
if (codeNames.length === 0) return;
|
|
15075
15075
|
void (async () => {
|
|
15076
15076
|
try {
|
|
15077
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15077
|
+
const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
|
|
15078
15078
|
await api.post("/host/heartbeat", {
|
|
15079
15079
|
host_id: hostId,
|
|
15080
15080
|
agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -15215,7 +15215,7 @@ async function pollCycleInner() {
|
|
|
15215
15215
|
}
|
|
15216
15216
|
try {
|
|
15217
15217
|
const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
|
|
15218
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
15218
|
+
const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
|
|
15219
15219
|
const diagCodeNames = [...agentState.persistentSessionAgents];
|
|
15220
15220
|
const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
|
|
15221
15221
|
let tailscaleHostname;
|
|
@@ -15384,7 +15384,7 @@ async function pollCycleInner() {
|
|
|
15384
15384
|
collectPanelessActivityProbes,
|
|
15385
15385
|
getResponsivenessIntervalMs,
|
|
15386
15386
|
occupancyQualificationClassifications
|
|
15387
|
-
} = await import("../responsiveness-probe-
|
|
15387
|
+
} = await import("../responsiveness-probe-CKU7IQK6.js");
|
|
15388
15388
|
const probeIntervalMs = getResponsivenessIntervalMs();
|
|
15389
15389
|
if (now - lastResponsivenessProbeAt > probeIntervalMs) {
|
|
15390
15390
|
const probeCodeNames = [...agentState.persistentSessionAgents];
|
|
@@ -15494,7 +15494,7 @@ async function pollCycleInner() {
|
|
|
15494
15494
|
collectResponsivenessProbes,
|
|
15495
15495
|
livePendingInboundOldestAgeSeconds,
|
|
15496
15496
|
parkPendingInbound
|
|
15497
|
-
} = await import("../responsiveness-probe-
|
|
15497
|
+
} = await import("../responsiveness-probe-CKU7IQK6.js");
|
|
15498
15498
|
const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
|
|
15499
15499
|
const wedgeNow = /* @__PURE__ */ new Date();
|
|
15500
15500
|
const liveAgents = agentState.persistentSessionAgents;
|
|
@@ -15650,7 +15650,7 @@ async function pollCycleInner() {
|
|
|
15650
15650
|
}
|
|
15651
15651
|
try {
|
|
15652
15652
|
const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
|
|
15653
|
-
const { probeSessionAuth } = await import("../session-auth-dead-
|
|
15653
|
+
const { probeSessionAuth } = await import("../session-auth-dead-ZP5YGGW3.js");
|
|
15654
15654
|
const observations = [];
|
|
15655
15655
|
const pendingCacheCommits = [];
|
|
15656
15656
|
const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
|
|
@@ -19690,7 +19690,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
|
|
|
19690
19690
|
void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
|
|
19691
19691
|
void (async () => {
|
|
19692
19692
|
try {
|
|
19693
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
19693
|
+
const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
|
|
19694
19694
|
await api.post("/host/heartbeat", {
|
|
19695
19695
|
host_id: hostId,
|
|
19696
19696
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -19736,7 +19736,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
|
|
|
19736
19736
|
}
|
|
19737
19737
|
try {
|
|
19738
19738
|
const hostId = await getHostId();
|
|
19739
|
-
const { collectDiagnostics } = await import("../persistent-session-
|
|
19739
|
+
const { collectDiagnostics } = await import("../persistent-session-UEQ7A6QP.js");
|
|
19740
19740
|
await api.post("/host/heartbeat", {
|
|
19741
19741
|
host_id: hostId,
|
|
19742
19742
|
agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
|
|
@@ -20372,7 +20372,7 @@ async function processClaudePairSessions(agents) {
|
|
|
20372
20372
|
killPairSession,
|
|
20373
20373
|
pairTmuxSession,
|
|
20374
20374
|
finalizeClaudePairOnboarding
|
|
20375
|
-
} = await import("../claude-pair-runtime-
|
|
20375
|
+
} = await import("../claude-pair-runtime-QQBNL5WW.js");
|
|
20376
20376
|
for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
|
|
20377
20377
|
log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
|
|
20378
20378
|
const killed = await killPairSession(pairTmuxSession(pairId));
|
|
@@ -37139,6 +37139,74 @@ var FLAG_REGISTRY = [
|
|
|
37139
37139
|
// dominant callers. This is precisely the deliberate, audited flip ADR-0022
|
|
37140
37140
|
// §4 reserves confirmation for.
|
|
37141
37141
|
sensitive: true
|
|
37142
|
+
},
|
|
37143
|
+
{
|
|
37144
|
+
key: "artefact-export-jobs-serve",
|
|
37145
|
+
description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
|
|
37146
|
+
flagType: "boolean",
|
|
37147
|
+
// ON is the no-op default here, which looks like the wrong direction until
|
|
37148
|
+
// you read it with the start lever: serving an empty table is
|
|
37149
|
+
// indistinguishable from not serving at all. Shipping it OFF would instead
|
|
37150
|
+
// mean that arming async later takes TWO flips, and that the second one is
|
|
37151
|
+
// the easy one to forget — leaving a fleet that creates jobs it refuses to
|
|
37152
|
+
// serve, i.e. every slow export failing while the flag page reads "async: on".
|
|
37153
|
+
defaultValue: true
|
|
37154
|
+
},
|
|
37155
|
+
{
|
|
37156
|
+
key: "artefact-export-jobs-start",
|
|
37157
|
+
description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
|
|
37158
|
+
flagType: "boolean",
|
|
37159
|
+
// Dark. The client half of this change publishes with the same merge, and
|
|
37160
|
+
// the fleet has not converged on it at merge time by construction — see the
|
|
37161
|
+
// runbook. Arming is an operator action taken against a measurement.
|
|
37162
|
+
defaultValue: false,
|
|
37163
|
+
// Arming this changes the RESPONSE SHAPE of a route whose callers include a
|
|
37164
|
+
// binary we do not control the version of. ADR-0022 section 4 reserves
|
|
37165
|
+
// confirmation for exactly this: a flip that a single unguarded write should
|
|
37166
|
+
// not be able to make.
|
|
37167
|
+
sensitive: true,
|
|
37168
|
+
// `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
|
|
37169
|
+
//
|
|
37170
|
+
// The field's docblock says it records the CLI whose build first shipped
|
|
37171
|
+
// this flag's HOST-SIDE READER, and this flag has none — it is evaluated
|
|
37172
|
+
// server-side in the export route, so every host "honors" a flip the instant
|
|
37173
|
+
// it is made. Read literally, `since` should therefore be undefined.
|
|
37174
|
+
//
|
|
37175
|
+
// Undefined is the dangerous answer. `classifyHostReach` returns `honors`
|
|
37176
|
+
// for every host when `since` is unset, so the flip-reach modal — the screen
|
|
37177
|
+
// an operator is looking at AT THE MOMENT OF THE FLIP — would report full
|
|
37178
|
+
// reach for the one flag whose entire purpose is "do not flip this until you
|
|
37179
|
+
// have measured the fleet". It would be telling the truth about the
|
|
37180
|
+
// mechanism (the flip does reach every host immediately) while giving
|
|
37181
|
+
// exactly the wrong answer to the question being asked (can those hosts
|
|
37182
|
+
// survive it). The dependency is real; it is on the CLI's ability to SURVIVE
|
|
37183
|
+
// the flag, not to READ it, and the modal cannot tell those apart.
|
|
37184
|
+
//
|
|
37185
|
+
// Pinned ABOVE every published release on purpose, the `agt-bin-path`
|
|
37186
|
+
// precedent: it classifies EVERY host as `stale`, so the modal reports no
|
|
37187
|
+
// reach rather than reach it has no basis for. A guessed number would be
|
|
37188
|
+
// worse than a placeholder, and the two directions are not symmetric —
|
|
37189
|
+
// too HIGH over-warns (annoying), too LOW promises reach to a build that
|
|
37190
|
+
// answers a job id with a TypeError inside file-write logic (a broken export
|
|
37191
|
+
// reported to the agent as "download failed").
|
|
37192
|
+
//
|
|
37193
|
+
// The exact version cannot be known here: the CLI auto-publishes ON MERGE of
|
|
37194
|
+
// this change, patch-bumping from whatever is current, so any pre-merge
|
|
37195
|
+
// value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
|
|
37196
|
+
// trust a version produced by re-running a failed publish job (ENG-9647: a
|
|
37197
|
+
// re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
|
|
37198
|
+
// string would claim a capability the bytes do not carry).
|
|
37199
|
+
//
|
|
37200
|
+
// AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
|
|
37201
|
+
// the flip-reach modal. Nothing in the evaluation or override-write path
|
|
37202
|
+
// consults it, so this changes what an operator is TOLD and does not stop a
|
|
37203
|
+
// flip on an under-`since` host. What holds the line is `defaultValue:
|
|
37204
|
+
// false` + `sensitive: true` + the runbook.
|
|
37205
|
+
//
|
|
37206
|
+
// REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
|
|
37207
|
+
// docs/runbooks/async-export-rollout.md, so the placeholder has a named
|
|
37208
|
+
// owner and a named moment rather than being a deferral nobody holds.
|
|
37209
|
+
since: "0.29.0"
|
|
37142
37210
|
}
|
|
37143
37211
|
];
|
|
37144
37212
|
var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
|
package/dist/mcp/index.js
CHANGED
|
@@ -36790,6 +36790,60 @@ function jsonSchemaPropertyToZod(prop) {
|
|
|
36790
36790
|
return external_exports.unknown();
|
|
36791
36791
|
}
|
|
36792
36792
|
|
|
36793
|
+
// src/artefact-export-response.ts
|
|
36794
|
+
var DEFAULT_EXPORT_RETRY_MS = 1e4;
|
|
36795
|
+
function classifyExportResponse(body) {
|
|
36796
|
+
if (typeof body !== "object" || body === null) {
|
|
36797
|
+
throw new Error("export response was not an object");
|
|
36798
|
+
}
|
|
36799
|
+
const candidate = body;
|
|
36800
|
+
if (typeof candidate.job_id === "string" && candidate.job_id.length > 0) {
|
|
36801
|
+
const retry = typeof candidate.retry_after_ms === "number" && Number.isFinite(candidate.retry_after_ms) ? Math.max(0, candidate.retry_after_ms) : null;
|
|
36802
|
+
return {
|
|
36803
|
+
kind: "pending",
|
|
36804
|
+
jobId: candidate.job_id,
|
|
36805
|
+
status: typeof candidate.status === "string" ? candidate.status : null,
|
|
36806
|
+
retryAfterMs: retry
|
|
36807
|
+
};
|
|
36808
|
+
}
|
|
36809
|
+
if (typeof candidate.base64 !== "string" || candidate.base64.length === 0) {
|
|
36810
|
+
throw new Error("export response carried neither rendered bytes nor a job id");
|
|
36811
|
+
}
|
|
36812
|
+
return {
|
|
36813
|
+
kind: "bytes",
|
|
36814
|
+
body: {
|
|
36815
|
+
base64: candidate.base64,
|
|
36816
|
+
content_type: typeof candidate.content_type === "string" ? candidate.content_type : "",
|
|
36817
|
+
filename: typeof candidate.filename === "string" ? candidate.filename : "",
|
|
36818
|
+
format: typeof candidate.format === "string" ? candidate.format : "",
|
|
36819
|
+
version: typeof candidate.version === "number" ? candidate.version : 0
|
|
36820
|
+
}
|
|
36821
|
+
};
|
|
36822
|
+
}
|
|
36823
|
+
function pendingExportText(n) {
|
|
36824
|
+
const wait = Math.max(1, Math.round((n.retryAfterMs ?? DEFAULT_EXPORT_RETRY_MS) / 1e3));
|
|
36825
|
+
const fmt = n.format.toUpperCase();
|
|
36826
|
+
const retryLine = `Call artefacts_download again with the SAME arguments in about ${wait}s. The retry attaches to the render already in progress rather than starting a new one, and items that have already finished come back immediately.`;
|
|
36827
|
+
if (n.totalItems <= 1) {
|
|
36828
|
+
return [
|
|
36829
|
+
`The ${fmt} export is still rendering (job ${n.jobId}); no file was saved yet.`,
|
|
36830
|
+
retryLine,
|
|
36831
|
+
"Nothing has failed \u2014 this document simply takes longer than the export request waits."
|
|
36832
|
+
].join("\n");
|
|
36833
|
+
}
|
|
36834
|
+
const header = n.saved.length > 0 ? `Rendered ${n.saved.length} of ${n.totalItems} items; item ${n.pendingPosition} is still rendering (job ${n.jobId}), so this is a PARTIAL export.` : `None of the ${n.totalItems} items finished in time; item ${n.pendingPosition} is still rendering (job ${n.jobId}).`;
|
|
36835
|
+
const lines = [header];
|
|
36836
|
+
if (n.saved.length > 0) {
|
|
36837
|
+
lines.push(`Saved so far (items 1-${n.saved.length}):`);
|
|
36838
|
+
lines.push(...n.saved.map((p2) => `- ${p2}`));
|
|
36839
|
+
lines.push(
|
|
36840
|
+
"These files are complete and usable, but they are NOT the whole document \u2014 do not send them as if they were."
|
|
36841
|
+
);
|
|
36842
|
+
}
|
|
36843
|
+
lines.push(retryLine);
|
|
36844
|
+
return lines.join("\n");
|
|
36845
|
+
}
|
|
36846
|
+
|
|
36793
36847
|
// src/mutation-commit-state.ts
|
|
36794
36848
|
var API_GATEWAY_INTEGRATION_TIMEOUT_MS = 29e3;
|
|
36795
36849
|
var GATEWAY_TIMEOUT_MARGIN_MS = 3e3;
|
|
@@ -44134,6 +44188,74 @@ var FLAG_REGISTRY = [
|
|
|
44134
44188
|
// dominant callers. This is precisely the deliberate, audited flip ADR-0022
|
|
44135
44189
|
// §4 reserves confirmation for.
|
|
44136
44190
|
sensitive: true
|
|
44191
|
+
},
|
|
44192
|
+
{
|
|
44193
|
+
key: "artefact-export-jobs-serve",
|
|
44194
|
+
description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
|
|
44195
|
+
flagType: "boolean",
|
|
44196
|
+
// ON is the no-op default here, which looks like the wrong direction until
|
|
44197
|
+
// you read it with the start lever: serving an empty table is
|
|
44198
|
+
// indistinguishable from not serving at all. Shipping it OFF would instead
|
|
44199
|
+
// mean that arming async later takes TWO flips, and that the second one is
|
|
44200
|
+
// the easy one to forget — leaving a fleet that creates jobs it refuses to
|
|
44201
|
+
// serve, i.e. every slow export failing while the flag page reads "async: on".
|
|
44202
|
+
defaultValue: true
|
|
44203
|
+
},
|
|
44204
|
+
{
|
|
44205
|
+
key: "artefact-export-jobs-start",
|
|
44206
|
+
description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
|
|
44207
|
+
flagType: "boolean",
|
|
44208
|
+
// Dark. The client half of this change publishes with the same merge, and
|
|
44209
|
+
// the fleet has not converged on it at merge time by construction — see the
|
|
44210
|
+
// runbook. Arming is an operator action taken against a measurement.
|
|
44211
|
+
defaultValue: false,
|
|
44212
|
+
// Arming this changes the RESPONSE SHAPE of a route whose callers include a
|
|
44213
|
+
// binary we do not control the version of. ADR-0022 section 4 reserves
|
|
44214
|
+
// confirmation for exactly this: a flip that a single unguarded write should
|
|
44215
|
+
// not be able to make.
|
|
44216
|
+
sensitive: true,
|
|
44217
|
+
// `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
|
|
44218
|
+
//
|
|
44219
|
+
// The field's docblock says it records the CLI whose build first shipped
|
|
44220
|
+
// this flag's HOST-SIDE READER, and this flag has none — it is evaluated
|
|
44221
|
+
// server-side in the export route, so every host "honors" a flip the instant
|
|
44222
|
+
// it is made. Read literally, `since` should therefore be undefined.
|
|
44223
|
+
//
|
|
44224
|
+
// Undefined is the dangerous answer. `classifyHostReach` returns `honors`
|
|
44225
|
+
// for every host when `since` is unset, so the flip-reach modal — the screen
|
|
44226
|
+
// an operator is looking at AT THE MOMENT OF THE FLIP — would report full
|
|
44227
|
+
// reach for the one flag whose entire purpose is "do not flip this until you
|
|
44228
|
+
// have measured the fleet". It would be telling the truth about the
|
|
44229
|
+
// mechanism (the flip does reach every host immediately) while giving
|
|
44230
|
+
// exactly the wrong answer to the question being asked (can those hosts
|
|
44231
|
+
// survive it). The dependency is real; it is on the CLI's ability to SURVIVE
|
|
44232
|
+
// the flag, not to READ it, and the modal cannot tell those apart.
|
|
44233
|
+
//
|
|
44234
|
+
// Pinned ABOVE every published release on purpose, the `agt-bin-path`
|
|
44235
|
+
// precedent: it classifies EVERY host as `stale`, so the modal reports no
|
|
44236
|
+
// reach rather than reach it has no basis for. A guessed number would be
|
|
44237
|
+
// worse than a placeholder, and the two directions are not symmetric —
|
|
44238
|
+
// too HIGH over-warns (annoying), too LOW promises reach to a build that
|
|
44239
|
+
// answers a job id with a TypeError inside file-write logic (a broken export
|
|
44240
|
+
// reported to the agent as "download failed").
|
|
44241
|
+
//
|
|
44242
|
+
// The exact version cannot be known here: the CLI auto-publishes ON MERGE of
|
|
44243
|
+
// this change, patch-bumping from whatever is current, so any pre-merge
|
|
44244
|
+
// value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
|
|
44245
|
+
// trust a version produced by re-running a failed publish job (ENG-9647: a
|
|
44246
|
+
// re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
|
|
44247
|
+
// string would claim a capability the bytes do not carry).
|
|
44248
|
+
//
|
|
44249
|
+
// AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
|
|
44250
|
+
// the flip-reach modal. Nothing in the evaluation or override-write path
|
|
44251
|
+
// consults it, so this changes what an operator is TOLD and does not stop a
|
|
44252
|
+
// flip on an under-`since` host. What holds the line is `defaultValue:
|
|
44253
|
+
// false` + `sensitive: true` + the runbook.
|
|
44254
|
+
//
|
|
44255
|
+
// REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
|
|
44256
|
+
// docs/runbooks/async-export-rollout.md, so the placeholder has a named
|
|
44257
|
+
// owner and a named moment rather than being a deferral nobody holds.
|
|
44258
|
+
since: "0.29.0"
|
|
44137
44259
|
}
|
|
44138
44260
|
];
|
|
44139
44261
|
var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
|
|
@@ -47451,21 +47573,36 @@ async function exportArtefactToFile(dir, artefactId, format, item) {
|
|
|
47451
47573
|
// it, the gateway replaces our authored error with its opaque envelope and
|
|
47452
47574
|
// we lose the ability to say whether the render landed.
|
|
47453
47575
|
//
|
|
47454
|
-
//
|
|
47455
|
-
//
|
|
47456
|
-
//
|
|
47457
|
-
//
|
|
47458
|
-
//
|
|
47459
|
-
//
|
|
47460
|
-
//
|
|
47461
|
-
//
|
|
47576
|
+
// ENG-9729 closed the readiness gap this comment used to describe: the
|
|
47577
|
+
// response is now classified before anything reads `base64`, so a `{ job_id }`
|
|
47578
|
+
// body is handled rather than thrown on. The budget above is what makes the
|
|
47579
|
+
// server's ~20s grace window reachable from here at all.
|
|
47580
|
+
//
|
|
47581
|
+
// WHAT IS STILL NOT TRUE: a published CLI version is not convergence. This
|
|
47582
|
+
// build can read a job body; the fleet is a mixed-version population and
|
|
47583
|
+
// hosts update overnight, some never. The server's start lever
|
|
47584
|
+
// (`artefact-export-jobs-start`) must therefore stay off until
|
|
47585
|
+
// hosts.agt_version has been MEASURED — docs/runbooks/async-export-rollout.md
|
|
47586
|
+
// — because arming it against an older CLI does not degrade an export, it
|
|
47587
|
+
// throws a TypeError inside file-write logic, which is strictly worse than
|
|
47588
|
+
// the 503 + Retry-After that build already understands.
|
|
47462
47589
|
FORWARD_TOOL_CALL_TIMEOUT_MS
|
|
47463
47590
|
);
|
|
47591
|
+
const outcome = classifyExportResponse(data);
|
|
47592
|
+
if (outcome.kind === "pending") {
|
|
47593
|
+
return { kind: "pending", jobId: outcome.jobId, retryAfterMs: outcome.retryAfterMs };
|
|
47594
|
+
}
|
|
47464
47595
|
const fallback = `${artefactId}${item !== void 0 ? `-item-${item + 1}` : ""}.${format}`;
|
|
47465
|
-
const filePath = join4(dir, safeDownloadName(
|
|
47466
|
-
const buf = Buffer.from(
|
|
47596
|
+
const filePath = join4(dir, safeDownloadName(outcome.body.filename, fallback));
|
|
47597
|
+
const buf = Buffer.from(outcome.body.base64, "base64");
|
|
47467
47598
|
await writeFile(filePath, buf);
|
|
47468
|
-
return {
|
|
47599
|
+
return {
|
|
47600
|
+
kind: "file",
|
|
47601
|
+
path: filePath,
|
|
47602
|
+
bytes: buf.length,
|
|
47603
|
+
contentType: outcome.body.content_type,
|
|
47604
|
+
version: outcome.body.version
|
|
47605
|
+
};
|
|
47469
47606
|
}
|
|
47470
47607
|
async function resolveItemCountForPng(artefactId) {
|
|
47471
47608
|
try {
|
|
@@ -47498,8 +47635,18 @@ async function downloadArtefactText(params) {
|
|
|
47498
47635
|
const saved = [];
|
|
47499
47636
|
let contentType = "";
|
|
47500
47637
|
let version2 = 0;
|
|
47501
|
-
for (
|
|
47502
|
-
const r = await exportArtefactToFile(dir, params.artefact_id, params.format,
|
|
47638
|
+
for (let i = 0; i < items.length; i++) {
|
|
47639
|
+
const r = await exportArtefactToFile(dir, params.artefact_id, params.format, items[i]);
|
|
47640
|
+
if (r.kind === "pending") {
|
|
47641
|
+
return pendingExportText({
|
|
47642
|
+
format: params.format,
|
|
47643
|
+
saved: saved.map((f) => f.path),
|
|
47644
|
+
totalItems: items.length,
|
|
47645
|
+
pendingPosition: i + 1,
|
|
47646
|
+
jobId: r.jobId,
|
|
47647
|
+
retryAfterMs: r.retryAfterMs
|
|
47648
|
+
});
|
|
47649
|
+
}
|
|
47503
47650
|
saved.push({ path: r.path, bytes: r.bytes });
|
|
47504
47651
|
contentType = r.contentType;
|
|
47505
47652
|
version2 = r.version;
|
package/dist/mcp/origami.js
CHANGED
|
@@ -42863,6 +42863,74 @@ var FLAG_REGISTRY = [
|
|
|
42863
42863
|
// dominant callers. This is precisely the deliberate, audited flip ADR-0022
|
|
42864
42864
|
// §4 reserves confirmation for.
|
|
42865
42865
|
sensitive: true
|
|
42866
|
+
},
|
|
42867
|
+
{
|
|
42868
|
+
key: "artefact-export-jobs-serve",
|
|
42869
|
+
description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
|
|
42870
|
+
flagType: "boolean",
|
|
42871
|
+
// ON is the no-op default here, which looks like the wrong direction until
|
|
42872
|
+
// you read it with the start lever: serving an empty table is
|
|
42873
|
+
// indistinguishable from not serving at all. Shipping it OFF would instead
|
|
42874
|
+
// mean that arming async later takes TWO flips, and that the second one is
|
|
42875
|
+
// the easy one to forget — leaving a fleet that creates jobs it refuses to
|
|
42876
|
+
// serve, i.e. every slow export failing while the flag page reads "async: on".
|
|
42877
|
+
defaultValue: true
|
|
42878
|
+
},
|
|
42879
|
+
{
|
|
42880
|
+
key: "artefact-export-jobs-start",
|
|
42881
|
+
description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
|
|
42882
|
+
flagType: "boolean",
|
|
42883
|
+
// Dark. The client half of this change publishes with the same merge, and
|
|
42884
|
+
// the fleet has not converged on it at merge time by construction — see the
|
|
42885
|
+
// runbook. Arming is an operator action taken against a measurement.
|
|
42886
|
+
defaultValue: false,
|
|
42887
|
+
// Arming this changes the RESPONSE SHAPE of a route whose callers include a
|
|
42888
|
+
// binary we do not control the version of. ADR-0022 section 4 reserves
|
|
42889
|
+
// confirmation for exactly this: a flip that a single unguarded write should
|
|
42890
|
+
// not be able to make.
|
|
42891
|
+
sensitive: true,
|
|
42892
|
+
// `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
|
|
42893
|
+
//
|
|
42894
|
+
// The field's docblock says it records the CLI whose build first shipped
|
|
42895
|
+
// this flag's HOST-SIDE READER, and this flag has none — it is evaluated
|
|
42896
|
+
// server-side in the export route, so every host "honors" a flip the instant
|
|
42897
|
+
// it is made. Read literally, `since` should therefore be undefined.
|
|
42898
|
+
//
|
|
42899
|
+
// Undefined is the dangerous answer. `classifyHostReach` returns `honors`
|
|
42900
|
+
// for every host when `since` is unset, so the flip-reach modal — the screen
|
|
42901
|
+
// an operator is looking at AT THE MOMENT OF THE FLIP — would report full
|
|
42902
|
+
// reach for the one flag whose entire purpose is "do not flip this until you
|
|
42903
|
+
// have measured the fleet". It would be telling the truth about the
|
|
42904
|
+
// mechanism (the flip does reach every host immediately) while giving
|
|
42905
|
+
// exactly the wrong answer to the question being asked (can those hosts
|
|
42906
|
+
// survive it). The dependency is real; it is on the CLI's ability to SURVIVE
|
|
42907
|
+
// the flag, not to READ it, and the modal cannot tell those apart.
|
|
42908
|
+
//
|
|
42909
|
+
// Pinned ABOVE every published release on purpose, the `agt-bin-path`
|
|
42910
|
+
// precedent: it classifies EVERY host as `stale`, so the modal reports no
|
|
42911
|
+
// reach rather than reach it has no basis for. A guessed number would be
|
|
42912
|
+
// worse than a placeholder, and the two directions are not symmetric —
|
|
42913
|
+
// too HIGH over-warns (annoying), too LOW promises reach to a build that
|
|
42914
|
+
// answers a job id with a TypeError inside file-write logic (a broken export
|
|
42915
|
+
// reported to the agent as "download failed").
|
|
42916
|
+
//
|
|
42917
|
+
// The exact version cannot be known here: the CLI auto-publishes ON MERGE of
|
|
42918
|
+
// this change, patch-bumping from whatever is current, so any pre-merge
|
|
42919
|
+
// value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
|
|
42920
|
+
// trust a version produced by re-running a failed publish job (ENG-9647: a
|
|
42921
|
+
// re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
|
|
42922
|
+
// string would claim a capability the bytes do not carry).
|
|
42923
|
+
//
|
|
42924
|
+
// AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
|
|
42925
|
+
// the flip-reach modal. Nothing in the evaluation or override-write path
|
|
42926
|
+
// consults it, so this changes what an operator is TOLD and does not stop a
|
|
42927
|
+
// flip on an under-`since` host. What holds the line is `defaultValue:
|
|
42928
|
+
// false` + `sensitive: true` + the runbook.
|
|
42929
|
+
//
|
|
42930
|
+
// REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
|
|
42931
|
+
// docs/runbooks/async-export-rollout.md, so the placeholder has a named
|
|
42932
|
+
// owner and a named moment rather than being a deferral nobody holds.
|
|
42933
|
+
since: "0.29.0"
|
|
42866
42934
|
}
|
|
42867
42935
|
];
|
|
42868
42936
|
var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
|
|
@@ -37386,6 +37386,74 @@ var FLAG_REGISTRY = [
|
|
|
37386
37386
|
// dominant callers. This is precisely the deliberate, audited flip ADR-0022
|
|
37387
37387
|
// §4 reserves confirmation for.
|
|
37388
37388
|
sensitive: true
|
|
37389
|
+
},
|
|
37390
|
+
{
|
|
37391
|
+
key: "artefact-export-jobs-serve",
|
|
37392
|
+
description: 'ADR-0077 / ENG-9729 \u2014 the MASTER switch for the async artefact-export lane. ON (default) = a route may resolve an export request to an artefact_export_jobs row: attach to one that is already rendering, or hand back the bytes of one that has completed. OFF = the job lane is invisible and every export renders synchronously, which is the pre-ADR-0077 behaviour. DEFAULT true DOES NOT MEAN ASYNC IS ON. Nothing CREATES a job unless artefact-export-jobs-start is also on, so with the shipped defaults this flag governs an empty table and no caller can tell it exists. WHY THE LANE IS TWO FLAGS AND NOT ONE. Disarming the start lever while renders are in flight must not strand the callers holding their job ids: those renders keep running (a Lambda invocation nobody awaits still runs to completion), and their bytes land in the private bucket regardless. A single flag would take the only door to them away at the moment it was most needed. So "stop creating new jobs" and "keep serving the ones that exist" are separate, and this is the second one. READ-ONLY BY CONSTRUCTION: with start off, the route looks up a REUSABLE row and nothing more \u2014 it never inserts, never requeues a terminal row, and never dispatches a renderer. A requeue mints an attempt and fires an invoke, so a serve lever that requeued would start new renders under a disarmed start lever, which is the exact thing disarming it was meant to stop. Evaluated server-side per request (org-scoped), so a flip applies within the evaluation cache TTL with no deploy and no host update. Deliberately NO envVar: the consumer is the API Lambda, whose environment block is at the AWS 4KB cap (ENG-7972), and a literal in sst.config.ts would pin every stage and make the admin control inert (ENG-8303).',
|
|
37393
|
+
flagType: "boolean",
|
|
37394
|
+
// ON is the no-op default here, which looks like the wrong direction until
|
|
37395
|
+
// you read it with the start lever: serving an empty table is
|
|
37396
|
+
// indistinguishable from not serving at all. Shipping it OFF would instead
|
|
37397
|
+
// mean that arming async later takes TWO flips, and that the second one is
|
|
37398
|
+
// the easy one to forget — leaving a fleet that creates jobs it refuses to
|
|
37399
|
+
// serve, i.e. every slow export failing while the flag page reads "async: on".
|
|
37400
|
+
defaultValue: true
|
|
37401
|
+
},
|
|
37402
|
+
{
|
|
37403
|
+
key: "artefact-export-jobs-start",
|
|
37404
|
+
description: "ADR-0077 / ENG-9729 \u2014 whether a route may CREATE an async artefact-export job: insert or requeue an artefact_export_jobs row, dispatch the worker, and answer a caller with a job id instead of bytes when the ~20s grace window is exceeded. OFF (default) = today's synchronous render on every export. THIS IS THE ROLLOUT GATE, AND IT IS ORDERED AGAINST A CLIENT, NOT A DEPLOY. The agent caller is `artefacts_download`, which ships inside the agt CLI to a permanently mixed-version fleet \u2014 hosts self-update overnight and some never do. Arming this against a CLI that cannot read a job-id body does not degrade an export, it BREAKS one: the pre-ENG-9729 tool does Buffer.from(data.base64) with no branch for any other shape, so a job id arrives as a TypeError deep in file-write logic. That is strictly worse than the 503 + Retry-After it would replace, which the tool already understands. A published CLI version is NOT evidence of convergence \u2014 measure hosts.agt_version across recently-seen hosts first. The procedure is docs/runbooks/async-export-rollout.md; arm this only after running it. Turning it back OFF stops new jobs and leaves artefact-export-jobs-serve delivering the in-flight ones, which is why the two are separate. Org-scoped, so it can be armed for one tenant at a time. Deliberately NO envVar (ENG-7972 4KB cap, ENG-8303 pin).",
|
|
37405
|
+
flagType: "boolean",
|
|
37406
|
+
// Dark. The client half of this change publishes with the same merge, and
|
|
37407
|
+
// the fleet has not converged on it at merge time by construction — see the
|
|
37408
|
+
// runbook. Arming is an operator action taken against a measurement.
|
|
37409
|
+
defaultValue: false,
|
|
37410
|
+
// Arming this changes the RESPONSE SHAPE of a route whose callers include a
|
|
37411
|
+
// binary we do not control the version of. ADR-0022 section 4 reserves
|
|
37412
|
+
// confirmation for exactly this: a flip that a single unguarded write should
|
|
37413
|
+
// not be able to make.
|
|
37414
|
+
sensitive: true,
|
|
37415
|
+
// `since` USED SLIGHTLY OFF-LABEL, AND THAT IS THE POINT.
|
|
37416
|
+
//
|
|
37417
|
+
// The field's docblock says it records the CLI whose build first shipped
|
|
37418
|
+
// this flag's HOST-SIDE READER, and this flag has none — it is evaluated
|
|
37419
|
+
// server-side in the export route, so every host "honors" a flip the instant
|
|
37420
|
+
// it is made. Read literally, `since` should therefore be undefined.
|
|
37421
|
+
//
|
|
37422
|
+
// Undefined is the dangerous answer. `classifyHostReach` returns `honors`
|
|
37423
|
+
// for every host when `since` is unset, so the flip-reach modal — the screen
|
|
37424
|
+
// an operator is looking at AT THE MOMENT OF THE FLIP — would report full
|
|
37425
|
+
// reach for the one flag whose entire purpose is "do not flip this until you
|
|
37426
|
+
// have measured the fleet". It would be telling the truth about the
|
|
37427
|
+
// mechanism (the flip does reach every host immediately) while giving
|
|
37428
|
+
// exactly the wrong answer to the question being asked (can those hosts
|
|
37429
|
+
// survive it). The dependency is real; it is on the CLI's ability to SURVIVE
|
|
37430
|
+
// the flag, not to READ it, and the modal cannot tell those apart.
|
|
37431
|
+
//
|
|
37432
|
+
// Pinned ABOVE every published release on purpose, the `agt-bin-path`
|
|
37433
|
+
// precedent: it classifies EVERY host as `stale`, so the modal reports no
|
|
37434
|
+
// reach rather than reach it has no basis for. A guessed number would be
|
|
37435
|
+
// worse than a placeholder, and the two directions are not symmetric —
|
|
37436
|
+
// too HIGH over-warns (annoying), too LOW promises reach to a build that
|
|
37437
|
+
// answers a job id with a TypeError inside file-write logic (a broken export
|
|
37438
|
+
// reported to the agent as "download failed").
|
|
37439
|
+
//
|
|
37440
|
+
// The exact version cannot be known here: the CLI auto-publishes ON MERGE of
|
|
37441
|
+
// this change, patch-bumping from whatever is current, so any pre-merge
|
|
37442
|
+
// value is a guess. Do NOT read it off `apps/cli/package.json`, and do NOT
|
|
37443
|
+
// trust a version produced by re-running a failed publish job (ENG-9647: a
|
|
37444
|
+
// re-run rebuilds the ORIGINAL tree under a HIGHER number, so the version
|
|
37445
|
+
// string would claim a capability the bytes do not carry).
|
|
37446
|
+
//
|
|
37447
|
+
// AND `since` IS ADVISORY, NOT A GUARD. It is read in exactly one place —
|
|
37448
|
+
// the flip-reach modal. Nothing in the evaluation or override-write path
|
|
37449
|
+
// consults it, so this changes what an operator is TOLD and does not stop a
|
|
37450
|
+
// flip on an under-`since` host. What holds the line is `defaultValue:
|
|
37451
|
+
// false` + `sensitive: true` + the runbook.
|
|
37452
|
+
//
|
|
37453
|
+
// REPLACING IT IS STEP 1 OF THE ARMING PROCEDURE in
|
|
37454
|
+
// docs/runbooks/async-export-rollout.md, so the placeholder has a named
|
|
37455
|
+
// owner and a named moment rather than being a deferral nobody holds.
|
|
37456
|
+
since: "0.29.0"
|
|
37389
37457
|
}
|
|
37390
37458
|
];
|
|
37391
37459
|
var REGISTRY_BY_KEY = new Map(FLAG_REGISTRY.map((definition) => [definition.key, definition]));
|