instar 1.3.670 → 1.3.672

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.
@@ -34,6 +34,8 @@ import { PlaywrightProfileRegistry, PlaywrightRegistryError, PlaywrightRegistryC
34
34
  import { writeConfigAtomic, readSelfKnowledgeFlags } from '../core/BootSelfKnowledge.js';
35
35
  import { rateLimiter, signViewPath, OUTBOUND_GATE_REVIEW_BUDGET_MS } from './middleware.js';
36
36
  import { reviewWithinBudget } from './outboundGateBudget.js';
37
+ import { resolveToneRecipientClass } from './toneRecipientClass.js';
38
+ import { buildDegradedToneResult } from '../core/MessagingToneGate.js';
37
39
  import { writeLifelineRestartSignal } from '../core/version-skew.js';
38
40
  import { readSessionClocks } from '../core/SessionClockReader.js';
39
41
  import { P13_PROTOCOL_VERSION } from '../core/CompletionEvaluator.js';
@@ -965,6 +967,31 @@ export function createRoutes(ctx) {
965
967
  // clock-read error must never block an outbound message.
966
968
  agentState = undefined;
967
969
  }
970
+ // operator-channel-sacred (outbound, spec: outbound-gate-tiered-fail-direction):
971
+ // resolve the recipient class STRUCTURALLY (verified operator binding +
972
+ // single-human-operator), and decide whether an availability failure should
973
+ // DELIVER (operator's own channel, 'tiered' mode, not dryRun) vs HOLD.
974
+ const recipientClass = resolveToneRecipientClass(ctx.topicOperatorStore, options.topicId);
975
+ const _toneCfg = ctx.config.messaging?.toneGate;
976
+ const _toneMode = _toneCfg?.failClosedMode ?? (_toneCfg?.failClosedOnExhaustion === false ? 'never' : 'always');
977
+ const operatorTierDeliver = _toneMode === 'tiered' && recipientClass === 'operator' && _toneCfg?.toneTierDryRun !== true;
978
+ // §Design 6 + tone-gate-graceful-degradation F4. The slow-review timeout is
979
+ // the SLOW manifestation of the same rate-limit outage as a provider throw,
980
+ // so it mirrors `review()`'s disposition. Precedence (highest first):
981
+ // - operator-tier deliver (operator's own channel, 'tiered', not dryRun) → fail-OPEN, tagged
982
+ // - per-call opt-out (automated/fixed-template send) → fail-OPEN (stay available)
983
+ // - failClosedOnExhaustion:false (operator kill-switch) → fail-OPEN
984
+ // - failClosedOnExhaustion:true (operator strict) → pure-HOLD (GATE_TIMEOUT)
985
+ // - default (unset) → DEGRADE to the
986
+ // deterministic leak floor (clean SENDS, leak HOLDS) — closes the F4 gap
987
+ // for the slow path, not just the fast throw.
988
+ const perCallAllowsClosed = options.failClosedOnBudgetTimeout ?? true;
989
+ const globalFailClosed = _toneCfg?.failClosedOnExhaustion;
990
+ // Operator-tier delivery suppresses both fail-closed and degrade (we want OPEN).
991
+ const budgetFailClosed = !operatorTierDeliver && perCallAllowsClosed && globalFailClosed !== false;
992
+ const budgetDegrade = budgetFailClosed && globalFailClosed !== true
993
+ ? (latencyMs) => buildDegradedToneResult(text, latencyMs, 'budget-timeout')
994
+ : undefined;
968
995
  const result = await reviewWithinBudget(ctx.messagingToneGate.review(text, {
969
996
  channel,
970
997
  recentMessages,
@@ -972,16 +999,15 @@ export function createRoutes(ctx) {
972
999
  targetStyle: ctx.config.messagingStyle,
973
1000
  messageKind: options.messageKind,
974
1001
  agentState,
1002
+ recipientClass,
975
1003
  }), gateBudgetMs, undefined, undefined,
976
- // §Design 6: the slow-review timeout fails CLOSED by default (the safe
977
- // direction). Two independent ways to fail OPEN instead: (a) the per-call
978
- // opt-out an automated/fixed-template send (post-update) that must stay
979
- // available passes failClosedOnBudgetTimeout:false; (b) the global operator
980
- // kill-switch messaging.toneGate.failClosedOnExhaustion:false (reverts every
981
- // path live, no deploy). Both must allow fail-closed for it to engage.
982
- ((options.failClosedOnBudgetTimeout ?? true) &&
983
- (ctx.config.messaging?.toneGate
984
- ?.failClosedOnExhaustion) !== false));
1004
+ // §Design 6 + F4: the slow-review timeout disposition mirrors review()'s.
1005
+ // `budgetFailClosed` already folds in (a) the per-call opt-out, (b) the
1006
+ // global kill-switch, and (c) operator-tier delivery (which suppresses
1007
+ // fail-closed so the operator is never sealed out under load). When the
1008
+ // disposition is the default (config unset), `budgetDegrade` is defined and
1009
+ // takes precedence in reviewWithinBudget degrading to the leak floor.
1010
+ budgetFailClosed, operatorTierDeliver, budgetDegrade);
985
1011
  // Structured observability: log every decision the authority made. This is
986
1012
  // the "why I blocked" log — over-block audits read this. Invalid-rule
987
1013
  // citations (authority drift) and budgetExceeded fail-opens are logged so
@@ -1343,6 +1369,10 @@ export function createRoutes(ctx) {
1343
1369
  failedOpen: entry.result.failedOpen || false,
1344
1370
  invalidRule: entry.result.invalidRule || false,
1345
1371
  budgetExceeded: entry.result.budgetExceeded || false,
1372
+ // operator-channel-sacred (outbound): surface the deliver-on-availability-
1373
+ // failure so it is AUDITED, never silent (the No-Silent-Degradation
1374
+ // reconciliation rests on this being observable).
1375
+ failedOpenOperatorChannel: entry.result.failedOpenOperatorChannel || false,
1346
1376
  latencyMs: entry.result.latencyMs,
1347
1377
  signals: {
1348
1378
  junk: entry.signals.junk?.detected ?? null,