@oxygen-agent/cli 1.685.12 → 1.691.5
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/README.md +1 -1
- package/dist/index.js +24 -2
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +108 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +153 -0
- package/node_modules/@oxygen/shared/dist/index.d.ts +1 -0
- package/node_modules/@oxygen/shared/dist/index.js +1 -0
- package/node_modules/@oxygen/shared/dist/sequence-template.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
- package/node_modules/@oxygen/shared/dist/sql-error.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/sql-error.js +13 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1169,6 +1169,15 @@ function buildCrmPhotosBody(options) {
|
|
|
1169
1169
|
...(limit ? { limit: Number.parseInt(limit, 10) } : {}),
|
|
1170
1170
|
};
|
|
1171
1171
|
}
|
|
1172
|
+
function buildCrmFieldsBody(options) {
|
|
1173
|
+
const object = readOption(options.object);
|
|
1174
|
+
const limit = readOption(options.limit);
|
|
1175
|
+
return {
|
|
1176
|
+
mode: resolveLiveDryRunMode(options),
|
|
1177
|
+
...(object ? { objects: object } : {}),
|
|
1178
|
+
...(limit ? { limit: Number.parseInt(limit, 10) } : {}),
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1172
1181
|
function buildCrmRollupBody(options) {
|
|
1173
1182
|
const object = readOption(options.object);
|
|
1174
1183
|
const row = readOption(options.row);
|
|
@@ -3758,6 +3767,19 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
3758
3767
|
method: "POST",
|
|
3759
3768
|
body: buildCrmPhotosBody(options),
|
|
3760
3769
|
}));
|
|
3770
|
+
}))
|
|
3771
|
+
.addCommand(new Command("fields")
|
|
3772
|
+
.description("Fill the stored CRM fields (Industry, Employees, HQ, and their siblings) from enrichment data this workspace already holds. These are ordinary text/number fields you can edit \u2014 the fill only ever writes an EMPTY cell, so a value you typed is never overwritten. Free: no provider calls, no credits. Run this once on a workspace whose records were enriched before those fields became editable. Defaults to dry-run.")
|
|
3773
|
+
.option("--object <object>", "Limit the fill to one CRM object (companies, people, deals). Defaults to all.")
|
|
3774
|
+
.option("--limit <n>", "Maximum rows to scan per object. Defaults to 2000.")
|
|
3775
|
+
.option("--dry-run", "Preview how many cells would be filled, without writing.")
|
|
3776
|
+
.option("--live", "Write the extracted values onto the records.")
|
|
3777
|
+
.option("--json", "Print a JSON envelope.")
|
|
3778
|
+
.action(async (options) => {
|
|
3779
|
+
await handleAsyncAction("crm fields", options, () => requestOxygen("/api/cli/crm/fields", {
|
|
3780
|
+
method: "POST",
|
|
3781
|
+
body: buildCrmFieldsBody(options),
|
|
3782
|
+
}));
|
|
3761
3783
|
}))
|
|
3762
3784
|
.addCommand(new Command("rollup")
|
|
3763
3785
|
.description("Recompute the Open Deal column on companies (and the copy stamped on their people) from this workspace's deals. It normally converges on every deal write; run this to repair after a bulk import, or on a workspace whose deals predate the column. Free: no provider calls, no credits. Defaults to dry-run.")
|
|
@@ -11789,9 +11811,9 @@ fixed scan; watermark_cursor starts a later bounded replay. Consumers must retai
|
|
|
11789
11811
|
});
|
|
11790
11812
|
}))
|
|
11791
11813
|
.addCommand(new Command("start")
|
|
11792
|
-
.description("Preview or start a sequence.
|
|
11814
|
+
.description("Preview or start a sequence. Without --approved, preview scans pending LinkedIn copy and returns exact rendered recipient samples, character counts/limits, copy blockers, per-kind scope, sender/mailbox capacity, CRM readiness, and safety caps. Live start requires --approved; --max-credits is optional. Email/WhatsApp/CRM-task journeys additionally require --max-live-sends because their external actions cost 0 Oxygen credits. Use --dry-run to simulate without any send, provider campaign, CRM task, or credits. The first live start also switches on the standing reply → CRM automation for the workspace.")
|
|
11793
11815
|
.argument("<sequence>", "Sequence id or slug.")
|
|
11794
|
-
.option("--approved", "Approve and activate live. Without this flag, returns a preview only.")
|
|
11816
|
+
.option("--approved", "Approve and activate live. Without this flag, returns a preview only, including copy_preview rendered samples and blockers.")
|
|
11795
11817
|
.option("--max-credits <n>", "Optional credit ceiling for the LinkedIn track (omit for an unbounded run).")
|
|
11796
11818
|
.option("--max-live-sends <n>", "Live external-action ceiling (positive integer). Required for email, WhatsApp, or crm_task steps.")
|
|
11797
11819
|
.option("--dry-run", "Activate in dry-run mode: advance every step with simulated actions, no sends, CRM writes, provider calls, or credits.")
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
type FeatureGateEnv = Record<string, string | undefined>;
|
|
2
|
+
/** Where the effective value came from. Precedence is env → remote → default. */
|
|
3
|
+
export type FeatureGateSource = "env_override" | "remote" | "safe_default";
|
|
4
|
+
export type FeatureGateDefinition = {
|
|
5
|
+
/** PostHog feature-flag key. Lowercase kebab-case by convention. */
|
|
6
|
+
key: string;
|
|
7
|
+
/**
|
|
8
|
+
* The value used whenever the provider has no answer. Pick the value that is
|
|
9
|
+
* safe to serve indefinitely, not the value you expect: this is what ships
|
|
10
|
+
* during a vendor outage, and on Vercel it is also what every cold
|
|
11
|
+
* invocation serves until flag definitions finish loading.
|
|
12
|
+
*/
|
|
13
|
+
safeDefault: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Optional break-glass env var. Set it to force the gate on/off without
|
|
16
|
+
* touching the flag service — the only lever that still works when the flag
|
|
17
|
+
* service is the thing that is broken. Must not name a NEVER_FLAGGABLE key.
|
|
18
|
+
*/
|
|
19
|
+
envOverride?: string;
|
|
20
|
+
};
|
|
21
|
+
export type FeatureGateResolution = {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
source: FeatureGateSource;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Env gates that must NEVER become remotely-flippable feature flags, and stay
|
|
27
|
+
* read directly from `process.env` (Doppler) at their call sites.
|
|
28
|
+
*
|
|
29
|
+
* Two disqualifying properties, each fatal on its own:
|
|
30
|
+
*
|
|
31
|
+
* (a) CIRCULARITY — a kill switch for a telemetry vendor cannot be resolved
|
|
32
|
+
* through a telemetry vendor. You reach for it precisely when that path is
|
|
33
|
+
* misbehaving, so a switch that needs the vendor reachable to be read is
|
|
34
|
+
* not a kill switch at all.
|
|
35
|
+
* (b) AUTHORIZATION — spend enforcement and external-write enablement. A
|
|
36
|
+
* remotely-flippable value means a vendor outage, a mis-click in someone
|
|
37
|
+
* else's UI, or a compromised vendor account can uncap credit spend or
|
|
38
|
+
* enable public side effects. Hard rules keep those in Doppler, where the
|
|
39
|
+
* change is an auditable secret change.
|
|
40
|
+
*
|
|
41
|
+
* Per-key rationale (call sites read 2026-08, verified against this base):
|
|
42
|
+
*
|
|
43
|
+
* - OXYGEN_TELEMETRY_ENABLED — (a). The master telemetry kill switch, read by
|
|
44
|
+
* `apps/web/src/lib/axiom-log-shipper.ts` and `apps/worker/src/telemetry.ts` /
|
|
45
|
+
* `log-shipper.ts`. Resolving it remotely makes turning telemetry OFF depend
|
|
46
|
+
* on a telemetry vendor being up.
|
|
47
|
+
* - OXYGEN_LOG_SHIPPING_ENABLED — (a). Same circularity, narrower blast radius
|
|
48
|
+
* (log shipping only; `axiom-log-shipper.ts`, `apps/worker/src/log-shipper.ts`).
|
|
49
|
+
* - OXYGEN_LLM_TRACING_ENABLED — (a) + (b). `packages/shared/src/langfuse.ts`
|
|
50
|
+
* fails CLOSED on it because it authorizes egress of full prompt/completion
|
|
51
|
+
* payloads to Langfuse (ADR 0014). Where prompt payloads may travel is a data
|
|
52
|
+
* boundary, not a rollout. It also lives in this transport-free package, so
|
|
53
|
+
* remote resolution would drag a flag client into the published CLI.
|
|
54
|
+
* - OXYGEN_TRIGGER_DEFAULT_CAPS — (b). Selects log-only vs enforce for the
|
|
55
|
+
* plan-tier default per-delivery ceiling on autonomous triggers
|
|
56
|
+
* (`apps/worker/src/workflow-spend-cap.ts`). Remote control over spend
|
|
57
|
+
* ENFORCEMENT means an outage can uncap autonomous credit spend everywhere.
|
|
58
|
+
* - OXYGEN_BYOK_DAILY_CAPS — (b). Same, for BYOK per-provider daily caps
|
|
59
|
+
* (`packages/providers/src/provider-fetch.ts`) — external money on the
|
|
60
|
+
* customer's own keys, and likewise in a package the CLI can reach.
|
|
61
|
+
* - OXYGEN_PUBLISHING_ENABLED — (b). Fail-closed production switch for posting
|
|
62
|
+
* to real provider accounts (`apps/web/src/app/(app)/(dashboard)/publishing/
|
|
63
|
+
* data.ts`). Wrong "on" = public posts from customer accounts.
|
|
64
|
+
* - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
|
|
65
|
+
* tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
|
|
66
|
+
* `apps/worker/src/agent-cycle.ts`). Same class as publishing.
|
|
67
|
+
*
|
|
68
|
+
* The list is a guard, not a to-do: none of these is migrated, and
|
|
69
|
+
* `findFeatureGateRegistryViolations` fails the build's tests if one ever is.
|
|
70
|
+
*/
|
|
71
|
+
export declare const NEVER_FLAGGABLE: readonly string[];
|
|
72
|
+
/**
|
|
73
|
+
* Every server-side gate resolved through PostHog.
|
|
74
|
+
*
|
|
75
|
+
* Intentionally empty: introducing the mechanism is not a reason to move
|
|
76
|
+
* existing switches onto it. The seven env gates above stay in Doppler, and a
|
|
77
|
+
* new entry belongs here only when its worst wrong value is a cosmetic or
|
|
78
|
+
* reversible product difference — never spend, external writes, or telemetry
|
|
79
|
+
* egress.
|
|
80
|
+
*/
|
|
81
|
+
export declare const FEATURE_GATES: readonly FeatureGateDefinition[];
|
|
82
|
+
/**
|
|
83
|
+
* Tri-state env parse: `true` / `false` / `null` for "unset or unparseable".
|
|
84
|
+
*
|
|
85
|
+
* The null case is load-bearing — an override that is merely absent has to fall
|
|
86
|
+
* through to the remote value, while an override explicitly set to `false` has
|
|
87
|
+
* to win over it. Collapsing the two into a boolean would make every gate
|
|
88
|
+
* without an override permanently forced off.
|
|
89
|
+
*/
|
|
90
|
+
export declare function parseFeatureGateBoolean(value: string | undefined): boolean | null;
|
|
91
|
+
/**
|
|
92
|
+
* Resolve one gate. Pure: the caller supplies the provider's answer (or
|
|
93
|
+
* `undefined` when it had none) and the env to read.
|
|
94
|
+
*
|
|
95
|
+
* Env override wins over the remote value on purpose. It is the break-glass for
|
|
96
|
+
* "the flag service is serving the wrong answer", which is worthless if the
|
|
97
|
+
* flag service outranks it.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveFeatureGate(definition: FeatureGateDefinition, remote?: boolean | undefined, env?: FeatureGateEnv): FeatureGateResolution;
|
|
100
|
+
export declare function isNeverFlaggable(name: string): boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Registry lint, asserted by tests rather than thrown at import time (a gate
|
|
103
|
+
* registry must never be able to crash a boot). Returns one human-readable
|
|
104
|
+
* violation per problem; an empty array means the registry is clean.
|
|
105
|
+
*/
|
|
106
|
+
export declare function findFeatureGateRegistryViolations(gates?: readonly FeatureGateDefinition[]): string[];
|
|
107
|
+
export declare function findFeatureGate(key: string): FeatureGateDefinition | undefined;
|
|
108
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Server-side feature gates (ADR 0021: PostHog owns server-side feature flags).
|
|
2
|
+
//
|
|
3
|
+
// This module is the CONTRACT ONLY — gate definitions, the unflaggable list, and
|
|
4
|
+
// the pure resolver. It is deliberately TRANSPORT-FREE: no `posthog-node`, no
|
|
5
|
+
// fetch, no client, no I/O of any kind. `packages/cli` is published to npm as
|
|
6
|
+
// `@oxygen-agent/cli` and depends on `@oxygen/shared` unbundled, so every import
|
|
7
|
+
// added here becomes a runtime dependency of every customer's CLI install. The
|
|
8
|
+
// evaluation transport lives in `apps/web/src/lib/feature-gate-provider.ts`,
|
|
9
|
+
// which nothing outside the web app imports.
|
|
10
|
+
//
|
|
11
|
+
// The resolver's one invariant: an absent provider answer is not an answer. A
|
|
12
|
+
// flag-service outage, a quota-limited project, a cold lambda whose definitions
|
|
13
|
+
// have not loaded yet, and a flag that simply does not exist all arrive here as
|
|
14
|
+
// `undefined`, and all of them resolve to the gate's `safeDefault`. A gate never
|
|
15
|
+
// flips because a vendor is unreachable.
|
|
16
|
+
/**
|
|
17
|
+
* Env gates that must NEVER become remotely-flippable feature flags, and stay
|
|
18
|
+
* read directly from `process.env` (Doppler) at their call sites.
|
|
19
|
+
*
|
|
20
|
+
* Two disqualifying properties, each fatal on its own:
|
|
21
|
+
*
|
|
22
|
+
* (a) CIRCULARITY — a kill switch for a telemetry vendor cannot be resolved
|
|
23
|
+
* through a telemetry vendor. You reach for it precisely when that path is
|
|
24
|
+
* misbehaving, so a switch that needs the vendor reachable to be read is
|
|
25
|
+
* not a kill switch at all.
|
|
26
|
+
* (b) AUTHORIZATION — spend enforcement and external-write enablement. A
|
|
27
|
+
* remotely-flippable value means a vendor outage, a mis-click in someone
|
|
28
|
+
* else's UI, or a compromised vendor account can uncap credit spend or
|
|
29
|
+
* enable public side effects. Hard rules keep those in Doppler, where the
|
|
30
|
+
* change is an auditable secret change.
|
|
31
|
+
*
|
|
32
|
+
* Per-key rationale (call sites read 2026-08, verified against this base):
|
|
33
|
+
*
|
|
34
|
+
* - OXYGEN_TELEMETRY_ENABLED — (a). The master telemetry kill switch, read by
|
|
35
|
+
* `apps/web/src/lib/axiom-log-shipper.ts` and `apps/worker/src/telemetry.ts` /
|
|
36
|
+
* `log-shipper.ts`. Resolving it remotely makes turning telemetry OFF depend
|
|
37
|
+
* on a telemetry vendor being up.
|
|
38
|
+
* - OXYGEN_LOG_SHIPPING_ENABLED — (a). Same circularity, narrower blast radius
|
|
39
|
+
* (log shipping only; `axiom-log-shipper.ts`, `apps/worker/src/log-shipper.ts`).
|
|
40
|
+
* - OXYGEN_LLM_TRACING_ENABLED — (a) + (b). `packages/shared/src/langfuse.ts`
|
|
41
|
+
* fails CLOSED on it because it authorizes egress of full prompt/completion
|
|
42
|
+
* payloads to Langfuse (ADR 0014). Where prompt payloads may travel is a data
|
|
43
|
+
* boundary, not a rollout. It also lives in this transport-free package, so
|
|
44
|
+
* remote resolution would drag a flag client into the published CLI.
|
|
45
|
+
* - OXYGEN_TRIGGER_DEFAULT_CAPS — (b). Selects log-only vs enforce for the
|
|
46
|
+
* plan-tier default per-delivery ceiling on autonomous triggers
|
|
47
|
+
* (`apps/worker/src/workflow-spend-cap.ts`). Remote control over spend
|
|
48
|
+
* ENFORCEMENT means an outage can uncap autonomous credit spend everywhere.
|
|
49
|
+
* - OXYGEN_BYOK_DAILY_CAPS — (b). Same, for BYOK per-provider daily caps
|
|
50
|
+
* (`packages/providers/src/provider-fetch.ts`) — external money on the
|
|
51
|
+
* customer's own keys, and likewise in a package the CLI can reach.
|
|
52
|
+
* - OXYGEN_PUBLISHING_ENABLED — (b). Fail-closed production switch for posting
|
|
53
|
+
* to real provider accounts (`apps/web/src/app/(app)/(dashboard)/publishing/
|
|
54
|
+
* data.ts`). Wrong "on" = public posts from customer accounts.
|
|
55
|
+
* - OXYGEN_AGENTS_ENABLED — (b). Authorizes autonomous Agent run execution —
|
|
56
|
+
* tool authority AND credit spend (`apps/web/src/lib/agent-runtime.ts`,
|
|
57
|
+
* `apps/worker/src/agent-cycle.ts`). Same class as publishing.
|
|
58
|
+
*
|
|
59
|
+
* The list is a guard, not a to-do: none of these is migrated, and
|
|
60
|
+
* `findFeatureGateRegistryViolations` fails the build's tests if one ever is.
|
|
61
|
+
*/
|
|
62
|
+
export const NEVER_FLAGGABLE = [
|
|
63
|
+
"OXYGEN_PUBLISHING_ENABLED",
|
|
64
|
+
"OXYGEN_AGENTS_ENABLED",
|
|
65
|
+
"OXYGEN_TELEMETRY_ENABLED",
|
|
66
|
+
"OXYGEN_LOG_SHIPPING_ENABLED",
|
|
67
|
+
"OXYGEN_LLM_TRACING_ENABLED",
|
|
68
|
+
"OXYGEN_TRIGGER_DEFAULT_CAPS",
|
|
69
|
+
"OXYGEN_BYOK_DAILY_CAPS",
|
|
70
|
+
];
|
|
71
|
+
/**
|
|
72
|
+
* Every server-side gate resolved through PostHog.
|
|
73
|
+
*
|
|
74
|
+
* Intentionally empty: introducing the mechanism is not a reason to move
|
|
75
|
+
* existing switches onto it. The seven env gates above stay in Doppler, and a
|
|
76
|
+
* new entry belongs here only when its worst wrong value is a cosmetic or
|
|
77
|
+
* reversible product difference — never spend, external writes, or telemetry
|
|
78
|
+
* egress.
|
|
79
|
+
*/
|
|
80
|
+
export const FEATURE_GATES = [];
|
|
81
|
+
/**
|
|
82
|
+
* Tri-state env parse: `true` / `false` / `null` for "unset or unparseable".
|
|
83
|
+
*
|
|
84
|
+
* The null case is load-bearing — an override that is merely absent has to fall
|
|
85
|
+
* through to the remote value, while an override explicitly set to `false` has
|
|
86
|
+
* to win over it. Collapsing the two into a boolean would make every gate
|
|
87
|
+
* without an override permanently forced off.
|
|
88
|
+
*/
|
|
89
|
+
export function parseFeatureGateBoolean(value) {
|
|
90
|
+
const normalized = value?.trim().toLowerCase();
|
|
91
|
+
if (!normalized)
|
|
92
|
+
return null;
|
|
93
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes")
|
|
94
|
+
return true;
|
|
95
|
+
if (normalized === "false" || normalized === "0" || normalized === "no")
|
|
96
|
+
return false;
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve one gate. Pure: the caller supplies the provider's answer (or
|
|
101
|
+
* `undefined` when it had none) and the env to read.
|
|
102
|
+
*
|
|
103
|
+
* Env override wins over the remote value on purpose. It is the break-glass for
|
|
104
|
+
* "the flag service is serving the wrong answer", which is worthless if the
|
|
105
|
+
* flag service outranks it.
|
|
106
|
+
*/
|
|
107
|
+
export function resolveFeatureGate(definition, remote, env = process.env) {
|
|
108
|
+
const override = definition.envOverride
|
|
109
|
+
? parseFeatureGateBoolean(env[definition.envOverride])
|
|
110
|
+
: null;
|
|
111
|
+
if (override !== null)
|
|
112
|
+
return { enabled: override, source: "env_override" };
|
|
113
|
+
if (typeof remote === "boolean")
|
|
114
|
+
return { enabled: remote, source: "remote" };
|
|
115
|
+
return { enabled: definition.safeDefault, source: "safe_default" };
|
|
116
|
+
}
|
|
117
|
+
// Compare env-var names and flag keys in one space, so `oxygen-agents-enabled`
|
|
118
|
+
// as a flag key is caught by a list written in SCREAMING_SNAKE env style.
|
|
119
|
+
function normalizeGateName(name) {
|
|
120
|
+
return name.trim().toUpperCase().replace(/-/g, "_");
|
|
121
|
+
}
|
|
122
|
+
export function isNeverFlaggable(name) {
|
|
123
|
+
const normalized = normalizeGateName(name);
|
|
124
|
+
return NEVER_FLAGGABLE.some((entry) => normalizeGateName(entry) === normalized);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Registry lint, asserted by tests rather than thrown at import time (a gate
|
|
128
|
+
* registry must never be able to crash a boot). Returns one human-readable
|
|
129
|
+
* violation per problem; an empty array means the registry is clean.
|
|
130
|
+
*/
|
|
131
|
+
export function findFeatureGateRegistryViolations(gates = FEATURE_GATES) {
|
|
132
|
+
const violations = [];
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
for (const gate of gates) {
|
|
135
|
+
if (!gate.key.trim()) {
|
|
136
|
+
violations.push("A feature gate has an empty key.");
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (seen.has(gate.key))
|
|
140
|
+
violations.push(`Duplicate feature gate key: ${gate.key}`);
|
|
141
|
+
seen.add(gate.key);
|
|
142
|
+
if (isNeverFlaggable(gate.key)) {
|
|
143
|
+
violations.push(`Feature gate "${gate.key}" names a NEVER_FLAGGABLE switch — it must stay an env gate.`);
|
|
144
|
+
}
|
|
145
|
+
if (gate.envOverride && isNeverFlaggable(gate.envOverride)) {
|
|
146
|
+
violations.push(`Feature gate "${gate.key}" uses NEVER_FLAGGABLE env var "${gate.envOverride}" as its override.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return violations;
|
|
150
|
+
}
|
|
151
|
+
export function findFeatureGate(key) {
|
|
152
|
+
return FEATURE_GATES.find((gate) => gate.key === key);
|
|
153
|
+
}
|
|
@@ -22,6 +22,7 @@ export * from "./directory.js";
|
|
|
22
22
|
export * from "./email-tracking-token.js";
|
|
23
23
|
export * from "./email-unsubscribe-token.js";
|
|
24
24
|
export * from "./error-redaction.js";
|
|
25
|
+
export * from "./feature-gates.js";
|
|
25
26
|
export * from "./hosted-ai.js";
|
|
26
27
|
export * from "./identifiers.js";
|
|
27
28
|
export * from "./knowledge-constants.js";
|
|
@@ -22,6 +22,7 @@ export * from "./directory.js";
|
|
|
22
22
|
export * from "./email-tracking-token.js";
|
|
23
23
|
export * from "./email-unsubscribe-token.js";
|
|
24
24
|
export * from "./error-redaction.js";
|
|
25
|
+
export * from "./feature-gates.js";
|
|
25
26
|
export * from "./hosted-ai.js";
|
|
26
27
|
export * from "./identifiers.js";
|
|
27
28
|
export * from "./knowledge-constants.js";
|
|
@@ -66,6 +66,12 @@ export declare const MAX_SPINTAX_DEPTH = 10;
|
|
|
66
66
|
* `{{column}}` placeholders render empty; a malformed placeholder is left literal.
|
|
67
67
|
*/
|
|
68
68
|
export declare function renderTemplate(template: string, values: Record<string, unknown>, options?: RenderTemplateOptions): string;
|
|
69
|
+
/**
|
|
70
|
+
* Bare variables that are blank in the branch this exact render selected.
|
|
71
|
+
* Untaken conditional/spintax branches are deliberately ignored; fallback-bearing
|
|
72
|
+
* tokens are optional, while bare tokens selected inside a fallback are checked.
|
|
73
|
+
*/
|
|
74
|
+
export declare function renderedTemplateMissingVariables(template: string, values: Record<string, unknown>, options?: RenderTemplateOptions): string[];
|
|
69
75
|
/**
|
|
70
76
|
* Every column key a template references — across bare `{{column}}` substitutions,
|
|
71
77
|
* `{{column|fallback}}` fallbacks, `{{RANDOM|…}}` spintax options, and both the
|
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Normalized failure category, derived from SQLSTATE first, then errno/message. */
|
|
2
|
-
export type SqlErrorCause = "connect_timeout" | "connection" | "statement_timeout" | "admin_shutdown" | "too_many_connections" | "insufficient_resources" | "auth" | "schema_drift" | "data_exception" | "integrity_constraint" | "serialization" | "deadlock" | "transaction_rollback" | "syntax_or_access" | "internal" | "unknown";
|
|
2
|
+
export type SqlErrorCause = "connect_timeout" | "connection" | "statement_timeout" | "admin_shutdown" | "too_many_connections" | "insufficient_resources" | "auth" | "schema_drift" | "data_exception" | "integrity_constraint" | "serialization" | "deadlock" | "transaction_rollback" | "read_only" | "syntax_or_access" | "internal" | "unknown";
|
|
3
3
|
export type SqlErrorAttribution = {
|
|
4
4
|
/** Postgres SQLSTATE (5 chars) when present; null for connection-level errnos. */
|
|
5
5
|
pgCode: string | null;
|
|
@@ -62,6 +62,12 @@ const TRANSIENT_CAUSES = new Set([
|
|
|
62
62
|
"serialization",
|
|
63
63
|
"deadlock",
|
|
64
64
|
"transaction_rollback",
|
|
65
|
+
// A Neon compute waking from autostop (or briefly in hot-standby/recovery)
|
|
66
|
+
// rejects writes with 25006 read_only_sql_transaction until the primary is
|
|
67
|
+
// writable again; the next poll tick succeeds. The dev worker's control-queue
|
|
68
|
+
// drain produced ~hourly 25006 bursts that self-healed within one wake cycle
|
|
69
|
+
// (OXY-5984).
|
|
70
|
+
"read_only",
|
|
65
71
|
]);
|
|
66
72
|
/**
|
|
67
73
|
* Classifies a pg/drizzle error into structured, non-secret attribution.
|
|
@@ -215,6 +221,13 @@ const EXACT_SQLSTATE_CAUSES = new Map([
|
|
|
215
221
|
["53300", "too_many_connections"],
|
|
216
222
|
["40001", "serialization"],
|
|
217
223
|
["40P01", "deadlock"],
|
|
224
|
+
// 25006 read_only_sql_transaction: a write hit a compute that is (still)
|
|
225
|
+
// read-only — a Neon dev branch mid-autostart or in hot-standby/recovery.
|
|
226
|
+
// Class 25 has no blanket mapping on purpose: its other members (25P02
|
|
227
|
+
// in_failed_sql_transaction, 25001 active_sql_transaction, ...) are
|
|
228
|
+
// transaction-protocol misuse, not a transient infra state. Only 25006 is
|
|
229
|
+
// the "retry on the next tick" case (OXY-5984).
|
|
230
|
+
["25006", "read_only"],
|
|
218
231
|
]);
|
|
219
232
|
// SQLSTATE class (first two chars) → cause, applied only after the exact and
|
|
220
233
|
// schema-drift lookups above so the precise codes keep their dedicated cause.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const OXYGEN_VERSION = "1.
|
|
1
|
+
export const OXYGEN_VERSION = "1.691.5";
|
|
2
2
|
// The GLOBAL CLI compatibility floor: the oldest CLI allowed to call any
|
|
3
3
|
// operational route. Raising it hard-rejects every older CLI from the entire
|
|
4
4
|
// product, so it obeys one law, enforced by scripts/ci/cli-min-version-gate.mjs:
|