instar 1.3.743 → 1.3.745

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.
@@ -18,6 +18,16 @@ export interface IntentClassifier {
18
18
  directed: boolean;
19
19
  }): Promise<RequestIntent>;
20
20
  }
21
+ /**
22
+ * Is this a harmless conversational self-post (a note / check-in / reminder / status
23
+ * update the bot would post into the CURRENT conversation)? Deterministic and
24
+ * conservative: it requires BOTH a self-post verb AND a conversational-content noun,
25
+ * and it disqualifies on ANY organizational-write / external / operational marker.
26
+ * Floor actions are already handled (and short-circuited) before this runs, so this
27
+ * only ever sees non-floor text. Requiring both a post-verb AND a content-noun keeps
28
+ * plain chatter ("just checking in, how's it going?") at T0 rather than promoting it.
29
+ */
30
+ export declare function isConversationalSelfPost(t: string): boolean;
21
31
  /** Does the text CLAIM an authorization from some named party ("X said it's fine")? */
22
32
  export declare function mentionsClaimedAuthority(text: string): boolean;
23
33
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"IntentClassifier.d.ts","sourceRoot":"","sources":["../../src/permissions/IntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgC,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5E;AAmBD,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI9D;AAYD;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAE1D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;CA6CjF"}
1
+ {"version":3,"file":"IntentClassifier.d.ts","sourceRoot":"","sources":["../../src/permissions/IntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAgC,MAAM,YAAY,CAAC;AAE9E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC5E;AA2CD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAI3D;AAED,uFAAuF;AACvF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI9D;AAaD;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAE1D,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;CAoDjF"}
@@ -27,14 +27,50 @@ const READ_VERB = /\b(summar\w*|what is|what's|look up|lookup|find out|explain|s
27
27
  const WRITE_VERB = /\b(post|create|file a ticket|open a ticket|schedule|add a|write up|note)\b/;
28
28
  const OP_VERB = /\b(run|trigger|kick off|start|rerun|re-run)\b/;
29
29
  const OP_NOUN = /\b(job|task|script|pipeline|staging|test|tests|build)\b/;
30
+ // ── Conversational self-post detection (member-seat false-positive fix) ──────────
31
+ // A "post a check-in note here in 5 minutes" is the bot AUTHORING conversational
32
+ // content into the CURRENT conversation — not an organizational write. It carries
33
+ // no floor signal (floor detection runs FIRST and always wins) and no org-write /
34
+ // external side-effect. Such a request is conversational (T1 read/draft level), so
35
+ // an ordinary member may direct it — the earlier blanket T2 low-write classification
36
+ // wrongly put it above the member ceiling and refused every member.
37
+ //
38
+ // A verb that FRAMES posting/scheduling conversational content...
39
+ const SELF_POST_VERB = /\b(post|drop|leave|put|write|schedule|set|send|remind|share)\b/;
40
+ // ...applied to a conversational-content noun (a note, check-in, reminder, update…).
41
+ const CONVERSATIONAL_NOUN = /\b(note|notes|check[- ]?in|checkin|reminder|reminders|heads[- ]?up|update|updates|standup|stand[- ]?up|message|ping|nudge)\b/;
42
+ // An organizational-write or EXTERNAL side-effect marker — its presence means this is
43
+ // NOT a harmless conversational post but a genuine low-write/external action that must
44
+ // keep its higher tier (a ticket, a record, another/named channel, a doc, a calendar
45
+ // hold, an email, an outside party). Any match disqualifies the conversational path.
46
+ const ORG_WRITE_OR_EXTERNAL_MARKER = /\b(ticket|tickets|jira|linear|asana|notion|confluence|wiki|calendar|invite|record|records|doc|docs|document|documents|announcement|announcements|another channel|other channel|email|e-mail|invoice|spreadsheet|sheet|client|clients|customer|customers|vendor|vendors|external|outside|partner|press|public)\b|#[a-z0-9][\w-]*/i;
47
+ // An operational marker — asking the bot to RUN/DEPLOY/execute something is never a
48
+ // conversational post, even if phrased as "post a note to run …". Disqualifies too.
49
+ const OPERATIONAL_MARKER = /\b(run|trigger|execute|kick off|deploy|rerun|re-run|migrat\w+|job|pipeline|build|script)\b/;
50
+ /**
51
+ * Is this a harmless conversational self-post (a note / check-in / reminder / status
52
+ * update the bot would post into the CURRENT conversation)? Deterministic and
53
+ * conservative: it requires BOTH a self-post verb AND a conversational-content noun,
54
+ * and it disqualifies on ANY organizational-write / external / operational marker.
55
+ * Floor actions are already handled (and short-circuited) before this runs, so this
56
+ * only ever sees non-floor text. Requiring both a post-verb AND a content-noun keeps
57
+ * plain chatter ("just checking in, how's it going?") at T0 rather than promoting it.
58
+ */
59
+ export function isConversationalSelfPost(t) {
60
+ if (ORG_WRITE_OR_EXTERNAL_MARKER.test(t))
61
+ return false;
62
+ if (OPERATIONAL_MARKER.test(t))
63
+ return false;
64
+ return SELF_POST_VERB.test(t) && CONVERSATIONAL_NOUN.test(t);
65
+ }
30
66
  /** Does the text CLAIM an authorization from some named party ("X said it's fine")? */
31
67
  export function mentionsClaimedAuthority(text) {
32
68
  const t = text.toLowerCase();
33
69
  return /\b(\w+\s+)?(said|told me|says|approved|authorized|okayed|gave the ok|signed off)\b/.test(t)
34
70
  && /\b(it'?s fine|approved|ok(ay)?|go ahead|do it|fine|allowed|permission)\b/.test(t);
35
71
  }
36
- function intent(action, tier, confidence, directed, floorAction) {
37
- return { action, tier, floorAction, confidence, directed };
72
+ function intent(action, tier, confidence, directed, floorAction, conversational) {
73
+ return { action, tier, floorAction, confidence, directed, conversational };
38
74
  }
39
75
  /**
40
76
  * Deterministic, conservative classifier. Floor detection is ordered first and
@@ -75,6 +111,13 @@ export class HeuristicIntentClassifier {
75
111
  if (OP_VERB.test(t) && OP_NOUN.test(t)) {
76
112
  return intent('operational', 3, 0.78, directed);
77
113
  }
114
+ // Harmless conversational self-post (note/check-in/reminder into the current
115
+ // conversation, no org/external/operational marker) → T1, not the T2 low-write
116
+ // that wrongly refused ordinary members. Checked BEFORE the generic WRITE_VERB
117
+ // branch so "post a check-in note here" isn't swept up as a low-write.
118
+ if (isConversationalSelfPost(t)) {
119
+ return intent('conversational-post', 1, 0.78, directed, undefined, true);
120
+ }
78
121
  if (WRITE_VERB.test(t)) {
79
122
  return intent('low-write', 2, 0.78, directed);
80
123
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/IntentClassifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"IntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/IntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,MAAM,WAAW,GAAG,sGAAsG,CAAC;AAC3H,MAAM,SAAS,GAAG,6CAA6C,CAAC;AAChE,MAAM,KAAK,GAAG,wEAAwE,CAAC;AACvF,MAAM,SAAS,GAAG,+EAA+E,CAAC;AAClG,MAAM,SAAS,GAAG,wEAAwE,CAAC;AAC3F,MAAM,gBAAgB,GAAG,iEAAiE,CAAC;AAC3F,MAAM,gBAAgB,GAAG,0FAA0F,CAAC;AACpH,MAAM,UAAU,GAAG,2CAA2C,CAAC;AAC/D,MAAM,UAAU,GAAG,2EAA2E,CAAC;AAC/F,MAAM,aAAa,GAAG,+CAA+C,CAAC;AACtE,MAAM,aAAa,GAAG,uGAAuG,CAAC;AAE9H,MAAM,SAAS,GAAG,iHAAiH,CAAC;AACpI,MAAM,UAAU,GAAG,4EAA4E,CAAC;AAChG,MAAM,OAAO,GAAG,+CAA+C,CAAC;AAChE,MAAM,OAAO,GAAG,yDAAyD,CAAC;AAE1E,oFAAoF;AACpF,iFAAiF;AACjF,kFAAkF;AAClF,kFAAkF;AAClF,mFAAmF;AACnF,qFAAqF;AACrF,oEAAoE;AACpE,EAAE;AACF,kEAAkE;AAClE,MAAM,cAAc,GAAG,gEAAgE,CAAC;AACxF,qFAAqF;AACrF,MAAM,mBAAmB,GACvB,8HAA8H,CAAC;AACjI,sFAAsF;AACtF,uFAAuF;AACvF,qFAAqF;AACrF,qFAAqF;AACrF,MAAM,4BAA4B,GAChC,kUAAkU,CAAC;AACrU,oFAAoF;AACpF,oFAAoF;AACpF,MAAM,kBAAkB,GACtB,4FAA4F,CAAC;AAE/F;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB,CAAC,CAAS;IAChD,IAAI,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC/D,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,wBAAwB,CAAC,IAAY;IACnD,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,oFAAoF,CAAC,IAAI,CAAC,CAAC,CAAC;WAC9F,0EAA0E,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED,SAAS,MAAM,CACb,MAAc,EACd,IAAqB,EACrB,UAAkB,EAClB,QAAiB,EACjB,WAAyB,EACzB,cAAwB;IAExB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,yBAAyB;IACpC,4DAA4D;IAC5D,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,GAA0B;QACrD,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAE9B,8DAA8D;QAC9D,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAClB,OAAO,MAAM,CAAC,gBAAgB,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QACtE,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,OAAO,MAAM,CAAC,mBAAmB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,MAAM,CAAC,kBAAkB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7C,OAAO,MAAM,CAAC,iBAAiB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;QACrE,CAAC;QAED,0FAA0F;QAC1F,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACxB,oFAAoF;YACpF,kFAAkF;YAClF,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC/C,CAAC;QAED,wBAAwB;QACxB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvC,OAAO,MAAM,CAAC,aAAa,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClD,CAAC;QACD,6EAA6E;QAC7E,+EAA+E;QAC/E,+EAA+E;QAC/E,uEAAuE;QACvE,IAAI,wBAAwB,CAAC,CAAC,CAAC,EAAE,CAAC;YAChC,OAAO,MAAM,CAAC,qBAAqB,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QAC3E,CAAC;QACD,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACvB,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACtB,OAAO,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC1C,CAAC;QAED,oCAAoC;QACpC,OAAO,MAAM,CAAC,YAAY,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IAChD,CAAC;CACF"}
@@ -42,7 +42,7 @@ import type { IntelligenceProvider } from '../core/types.js';
42
42
  import type { RequestIntent } from './types.js';
43
43
  import { type IntentClassifier } from './IntentClassifier.js';
44
44
  /** Observability hook reasons for why a classification fell back to the heuristic. */
45
- export type LlmIntentDegradeReason = 'no-intelligence' | 'error' | 'unparseable' | 'floor-deterministic';
45
+ export type LlmIntentDegradeReason = 'no-intelligence' | 'error' | 'unparseable' | 'floor-deterministic' | 'conversational-deterministic';
46
46
  export interface LlmIntentClassifierDeps {
47
47
  /**
48
48
  * The internal LLM provider (injected — never a direct framework import). When
@@ -1 +1 @@
1
- {"version":3,"file":"LlmIntentClassifier.d.ts","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,YAAY,CAAC;AACjE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,sFAAsF;AACtF,MAAM,MAAM,sBAAsB,GAC9B,iBAAiB,GACjB,OAAO,GACP,aAAa,GACb,qBAAqB,CAAC;AAE1B,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAYD,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA2C;gBAE1D,IAAI,GAAE,uBAA4B;IAOxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IA0DhF,OAAO,CAAC,MAAM;CAOf"}
1
+ {"version":3,"file":"LlmIntentClassifier.d.ts","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,YAAY,CAAC;AACjE,OAAO,EAA6B,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzF,sFAAsF;AACtF,MAAM,MAAM,sBAAsB,GAC9B,iBAAiB,GACjB,OAAO,GACP,aAAa,GACb,qBAAqB,GACrB,8BAA8B,CAAC;AAEnC,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,YAAY,CAAC,EAAE,oBAAoB,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAYD,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAuB;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmB;IAC7C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAA2C;gBAE1D,IAAI,GAAE,uBAA4B;IAOxC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAuEhF,OAAO,CAAC,MAAM;CAOf"}
@@ -64,6 +64,18 @@ export class LlmIntentClassifier {
64
64
  this.report('floor-deterministic');
65
65
  return floorRead;
66
66
  }
67
+ // Symmetric to the floor: a recognized-benign CONVERSATIONAL self-post (a
68
+ // note/check-in/reminder into the current conversation — deterministically
69
+ // cleared of any floor/org-write/external/operational marker) is returned AS-IS
70
+ // and the LLM is never consulted. This is the member-seat fix's load-bearing
71
+ // half: reconcile() only ever ESCALATES the tier (Math.max), so without this
72
+ // short-circuit the LLM would re-classify "post a note" up to T2 and re-refuse
73
+ // the member. The deterministic conversational read is the authority here, just
74
+ // as the deterministic floor read is above.
75
+ if (floorRead.conversational) {
76
+ this.report('conversational-deterministic');
77
+ return floorRead;
78
+ }
67
79
  // ── 2. No provider → fail closed to the heuristic ───────────────────────────
68
80
  if (!this.intelligence) {
69
81
  this.report('no-intelligence');
@@ -1 +1 @@
1
- {"version":3,"file":"LlmIntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LlmIntentClassifier.js","sourceRoot":"","sources":["../../src/permissions/LlmIntentClassifier.ts"],"names":[],"mappings":"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"}
@@ -63,6 +63,19 @@ export interface RequestIntent {
63
63
  * request is never authorized.
64
64
  */
65
65
  directed: boolean;
66
+ /**
67
+ * True iff this is a recognized HARMLESS CONVERSATIONAL self-post — the bot
68
+ * authoring a note / check-in / reminder / status update into the CURRENT
69
+ * conversation, carrying NO floor signal and NO organizational-write or external
70
+ * side-effect (a ticket, a record, another channel, a calendar hold, an email).
71
+ * Such a request is conversational, not an authority-requiring org action, so it
72
+ * is classified at the read/draft tier (T1) rather than the low-write tier (T2)
73
+ * — an ordinary member may direct it. This is set DETERMINISTICALLY by the
74
+ * heuristic (floor detection still runs first and always wins) and, like the
75
+ * floor, is NOT subject to upward re-classification by the LLM judgment band
76
+ * (see LlmIntentClassifier). Absent/false for everything else.
77
+ */
78
+ conversational?: boolean;
66
79
  }
67
80
  /** Relationship/behavioral anomaly assessment for this principal+request. */
68
81
  export interface AnomalyAssessment {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/permissions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1F,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAOvC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEhD,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB,gBAAgB,GAChB,aAAa,GACb,mBAAmB,GACnB,kBAAkB,GAClB,eAAe,GACf,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,IAAI,EAAE,OAAO,CAAC;IACd,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,eAAe,CAAC;IACtB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,qBAAqB;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,KAAK,EAAE,WAAW,GAAG,QAAQ,eAAe,EAAE,CAAC;IAC/C,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/permissions/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uDAAuD;AACvD,MAAM,MAAM,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC;AAE1F,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAOvC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEhD,+EAA+E;AAC/E,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E;;;;GAIG;AACH,MAAM,MAAM,WAAW,GACnB,gBAAgB,GAChB,aAAa,GACb,mBAAmB,GACnB,kBAAkB,GAClB,eAAe,GACf,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,IAAI,EAAE,OAAO,CAAC;IACd,+DAA+D;IAC/D,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,eAAe,CAAC;IACtB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,2FAA2F;IAC3F,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,6CAA6C;AAC7C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,CAAC;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mGAAmG;IACnG,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,qBAAqB;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,KAAK,EAAE,WAAW,GAAG,QAAQ,eAAe,EAAE,CAAC;IAC/C,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.743",
3
+ "version": "1.3.745",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,9 +28,11 @@
28
28
  "test:contract": "node scripts/run-contract-tests.js",
29
29
  "test:contract:raw": "vitest run --config vitest.contract.config.ts",
30
30
  "test:all": "vitest run && vitest run --config vitest.integration.config.ts && vitest run --config vitest.e2e.config.ts",
31
- "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js",
31
+ "lint": "tsc --noEmit && node scripts/lint-no-direct-destructive.js && node scripts/lint-no-direct-llm-http.js && node scripts/lint-no-direct-url-log.js && node scripts/lint-no-unfunneled-topic-creation.js && node scripts/lint-no-unfunneled-headless-launch.js && node scripts/lint-no-unbounded-llm-spawn.js && node scripts/lint-no-unfunneled-credential-write.js && node scripts/lint-state-registry.js && node scripts/lint-store-retention-declared.js && node scripts/lint-no-wholefile-sync-read.js && node scripts/lint-cas-emit-placement.js && node scripts/lint-journal-actuation-ban.js && node scripts/lint-no-blocking-process-scans.js && node scripts/lint-sync-subprocess-chokepoint.js && node scripts/lint-dev-agent-dark-gate.js && node scripts/lint-guard-manifest.js && node scripts/lint-llm-attribution.js && node scripts/lint-no-mainthread-cartographer-walk.js && node scripts/lint-scrape-fixture-realness.js && node scripts/check-codex-rule1-drift.js && node scripts/lint-routing-registry-freshness.js && node scripts/lint-no-opus-claude-cli-gating.js && node scripts/lint-model-registry-freshness.mjs",
32
32
  "lint:routing-registry": "node scripts/lint-routing-registry-freshness.js",
33
33
  "lint:no-opus-claude-cli-gating": "node scripts/lint-no-opus-claude-cli-gating.js",
34
+ "lint:model-freshness": "node scripts/lint-model-registry-freshness.mjs",
35
+ "lint:model-freshness:strict": "INSTAR_MODEL_FRESHNESS_STRICT=1 node scripts/lint-model-registry-freshness.mjs",
34
36
  "lint:no-unbounded-llm-spawn": "node scripts/lint-no-unbounded-llm-spawn.js",
35
37
  "lint:no-unbounded-llm-spawn:staged": "node scripts/lint-no-unbounded-llm-spawn.js --staged",
36
38
  "lint:destructive": "node scripts/lint-no-direct-destructive.js",
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * lint-model-registry-freshness.mjs — the model-registry FRESHNESS ratchet.
4
+ *
5
+ * THE PROBLEM (2026-07-03, operator directive): Instar's per-provider "capable/
6
+ * latest/frontier" model pins rot SILENTLY. Nothing forces anyone to re-check
7
+ * that (e.g.) the gemini `capable` tier still points at a current model — so
8
+ * `gemini-2.5-pro` kept routing spec-review/converge work long after
9
+ * Gemini 3-class shipped. A stale pin is invisible until it degrades output.
10
+ *
11
+ * THE GUARD (model-id-AGNOSTIC by construction): a deterministic check with
12
+ * TWO teeth, both driven by scripts/model-registry-freshness.manifest.json —
13
+ * the single human-edit surface:
14
+ *
15
+ * TOOTH 1 — STALENESS. `lastReviewedAt` must be within `stalenessWindowDays`
16
+ * of today. An un-reviewed list ages out and fails LOUDLY, forcing a
17
+ * periodic "is each pin still frontier?" review + a date bump. This is the
18
+ * anti-rot mechanism: it fails even if no id ever changes.
19
+ *
20
+ * TOOTH 2 — DRIFT. Every pinned capable/latest model id (extracted live from
21
+ * the real source files via each pin's regex) must be a member of that
22
+ * door's `frontierAllowlist` (the CURRENT_FRONTIER_MODELS set). A pin that
23
+ * names a model not in the maintained allowlist fails — either the pin is
24
+ * stale, or the allowlist wasn't updated. To go green a human must reconcile
25
+ * the two, which IS the review.
26
+ *
27
+ * Plus: `flaggedStale[]` entries (known-stale-pending-operator-confirmation) are
28
+ * always printed as WARN lines and, under strict enforcement, count as findings.
29
+ *
30
+ * The guard NEVER hard-codes what the "right" model id is — it only asserts the
31
+ * pins and the allowlist agree and that the review is fresh. Swapping to a new
32
+ * frontier id is a manifest edit (allowlist + date), never a code change here.
33
+ *
34
+ * ENFORCEMENT (dark/reversible): manifest `enforcement` field —
35
+ * "report" (default) — prints findings, ALWAYS exits 0 (non-gating). Safe to
36
+ * wire into CI today while the current list is known-stale: it stays VISIBLE
37
+ * in the lint log without breaking the build.
38
+ * "strict" — exits 1 on any finding (staleness, drift, or a flaggedStale row).
39
+ * Flip here once the flagged door swaps are operator-confirmed + applied.
40
+ * Env INSTAR_MODEL_FRESHNESS_STRICT=1 forces strict for a one-off run.
41
+ *
42
+ * Exit codes:
43
+ * 0 — no findings, OR findings under "report" enforcement (non-gating).
44
+ * 1 — findings under "strict" enforcement, or a manifest/parse error.
45
+ *
46
+ * Usage:
47
+ * node scripts/lint-model-registry-freshness.mjs # honor manifest enforcement
48
+ * INSTAR_MODEL_FRESHNESS_STRICT=1 node scripts/lint-model-registry-freshness.mjs
49
+ *
50
+ * Test overrides:
51
+ * INSTAR_MODEL_FRESHNESS_MANIFEST=<path> # point at a fixture manifest
52
+ * INSTAR_MODEL_FRESHNESS_ROOT=<path> # resolve pin files under this root
53
+ * INSTAR_MODEL_FRESHNESS_NOW=<ISO date> # inject the clock (staleness tests)
54
+ */
55
+
56
+ import fs from 'node:fs';
57
+ import path from 'node:path';
58
+ import { fileURLToPath } from 'node:url';
59
+
60
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
61
+ const REPO_ROOT = process.env.INSTAR_MODEL_FRESHNESS_ROOT
62
+ ? path.resolve(process.env.INSTAR_MODEL_FRESHNESS_ROOT)
63
+ : path.resolve(__dirname, '..');
64
+ const MANIFEST_PATH = process.env.INSTAR_MODEL_FRESHNESS_MANIFEST
65
+ ? path.resolve(process.env.INSTAR_MODEL_FRESHNESS_MANIFEST)
66
+ : path.join(__dirname, 'model-registry-freshness.manifest.json');
67
+
68
+ /**
69
+ * Run the freshness check. Pure over its inputs (manifest + files under root +
70
+ * the injected clock), so the unit test drives it directly with fixtures.
71
+ * @returns {{ findings: string[], warnings: string[], info: string[], strict: boolean, error: string|null }}
72
+ */
73
+ export function checkModelRegistryFreshness({
74
+ manifestPath = MANIFEST_PATH,
75
+ repoRoot = REPO_ROOT,
76
+ now = process.env.INSTAR_MODEL_FRESHNESS_NOW
77
+ ? new Date(process.env.INSTAR_MODEL_FRESHNESS_NOW)
78
+ : new Date(),
79
+ forceStrict = process.env.INSTAR_MODEL_FRESHNESS_STRICT === '1',
80
+ } = {}) {
81
+ const findings = [];
82
+ const warnings = [];
83
+ const info = [];
84
+
85
+ let manifest;
86
+ try {
87
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
88
+ } catch (e) {
89
+ return { findings: [], warnings: [], info: [], strict: true, error: `cannot read/parse manifest ${manifestPath}: ${e.message}` };
90
+ }
91
+
92
+ const strict = forceStrict || manifest.enforcement === 'strict';
93
+
94
+ // --- TOOTH 1: staleness ---
95
+ const reviewedRaw = manifest.lastReviewedAt;
96
+ const reviewed = reviewedRaw ? new Date(reviewedRaw) : null;
97
+ const windowDays = Number(manifest.stalenessWindowDays);
98
+ if (!reviewed || Number.isNaN(reviewed.getTime())) {
99
+ findings.push(`STALENESS: manifest.lastReviewedAt is missing or unparseable (got ${JSON.stringify(reviewedRaw)}).`);
100
+ } else if (!Number.isFinite(windowDays) || windowDays <= 0) {
101
+ findings.push(`STALENESS: manifest.stalenessWindowDays is missing or invalid (got ${JSON.stringify(manifest.stalenessWindowDays)}).`);
102
+ } else {
103
+ const ageDays = Math.floor((now.getTime() - reviewed.getTime()) / 86_400_000);
104
+ if (ageDays > windowDays) {
105
+ findings.push(
106
+ `STALENESS: model registry last reviewed ${reviewedRaw} (${ageDays}d ago) exceeds the ${windowDays}d window. ` +
107
+ `Re-review each capable/latest pin against current frontier, update frontierAllowlist if a model has moved, then bump lastReviewedAt.`
108
+ );
109
+ } else {
110
+ info.push(`Staleness OK: reviewed ${reviewedRaw} (${ageDays}d ago, window ${windowDays}d).`);
111
+ }
112
+ }
113
+
114
+ // --- TOOTH 2: drift (pin id must be in its door's frontier allowlist) ---
115
+ const allowlist = manifest.frontierAllowlist || {};
116
+ for (const pin of manifest.pins || []) {
117
+ const abs = path.join(repoRoot, pin.file);
118
+ let src;
119
+ try {
120
+ src = fs.readFileSync(abs, 'utf8');
121
+ } catch {
122
+ findings.push(`DRIFT: pin '${pin.id}' references ${pin.file} which is missing under ${repoRoot} (a pin site moved or was deleted — re-anchor it).`);
123
+ continue;
124
+ }
125
+ let re;
126
+ try {
127
+ re = new RegExp(pin.regex);
128
+ } catch (e) {
129
+ findings.push(`DRIFT: pin '${pin.id}' has an invalid regex (${e.message}).`);
130
+ continue;
131
+ }
132
+ const m = src.match(re);
133
+ if (!m) {
134
+ findings.push(`DRIFT: pin '${pin.id}' pattern did not match in ${pin.file} (the pinned site changed shape — re-anchor the regex).`);
135
+ continue;
136
+ }
137
+ // All capture groups are candidate model ids (e.g. default + escalated).
138
+ const ids = m.slice(1).filter(Boolean);
139
+ const doorAllow = allowlist[pin.door] || [];
140
+ for (const id of ids) {
141
+ if (!doorAllow.includes(id)) {
142
+ findings.push(
143
+ `DRIFT: pin '${pin.id}' (${pin.door}) pins '${id}' in ${pin.file}, which is NOT in frontierAllowlist['${pin.door}'] = [${doorAllow.join(', ')}]. ` +
144
+ `Either the pin is stale or the allowlist wasn't updated — reconcile the two (operator-confirm the frontier id).`
145
+ );
146
+ } else {
147
+ info.push(`Drift OK: ${pin.door} '${pin.id}' -> '${id}' (in allowlist).`);
148
+ }
149
+ }
150
+ }
151
+
152
+ // --- flaggedStale (known-stale-pending-confirmation): always WARN; strict counts them ---
153
+ for (const f of manifest.flaggedStale || []) {
154
+ const line =
155
+ `FLAGGED-STALE: ${f.door} pin '${f.pin}' currently '${f.currentId}' -> suspected frontier '${f.suspectedFrontier}'. ` +
156
+ `${f.evidence || ''} ${f.note || ''}`.trim();
157
+ warnings.push(line);
158
+ if (strict) findings.push(line);
159
+ }
160
+
161
+ return { findings, warnings, info, strict, error: null };
162
+ }
163
+
164
+ // --- CLI entry ---
165
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
166
+ if (isMain) {
167
+ const res = checkModelRegistryFreshness();
168
+ const B = '\x1b[1m', R = '\x1b[0m', Y = '\x1b[33m', RED = '\x1b[31m', G = '\x1b[32m';
169
+ console.log(`${B}[lint-model-registry-freshness]${R} enforcement=${res.strict ? 'strict' : 'report'}`);
170
+
171
+ if (res.error) {
172
+ console.error(`${RED}ERROR:${R} ${res.error}`);
173
+ process.exit(1);
174
+ }
175
+ for (const i of res.info) console.log(` ${G}ok${R} ${i}`);
176
+ for (const w of res.warnings) console.log(` ${Y}warn${R} ${w}`);
177
+ for (const f of res.findings) console.log(` ${RED}FIND${R} ${f}`);
178
+
179
+ if (res.findings.length === 0) {
180
+ console.log(`${G}PASS${R} — model registry pins fresh and in-allowlist.`);
181
+ process.exit(0);
182
+ }
183
+ if (res.strict) {
184
+ console.error(`${RED}FAIL${R} — ${res.findings.length} finding(s) under strict enforcement.`);
185
+ process.exit(1);
186
+ }
187
+ console.log(
188
+ `${Y}REPORT-ONLY${R} — ${res.findings.length} finding(s) surfaced but NOT gating (manifest enforcement="report"). ` +
189
+ `Resolve them, then flip enforcement to "strict".`
190
+ );
191
+ process.exit(0);
192
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "$comment": "Frontier-model allowlist + staleness gate for Instar's per-provider 'capable/latest' model pins. This file is the SINGLE human-edit surface that keeps the model registry from silently rotting. See docs/LLM-ROUTING-REGISTRY.md and scripts/lint-model-registry-freshness.mjs. Two teeth: (1) STALENESS — lastReviewedAt must be within stalenessWindowDays; (2) DRIFT — every pinned capable model id must be a member of frontierAllowlist[door]. To pass the check a human must confirm each capable pin is genuinely current frontier AND bump lastReviewedAt. An un-reviewed list fails loudly.",
3
+ "lastReviewedAt": "2026-07-03",
4
+ "stalenessWindowDays": 45,
5
+ "enforcement": "report",
6
+ "$enforcementNote": "'report' = non-gating (prints findings, always exits 0) — the dark/reversible default while the current list is known-stale. Flip to 'strict' (exits 1 on any finding) ONLY after the flaggedStale door swaps below are operator-confirmed and applied. Env INSTAR_MODEL_FRESHNESS_STRICT=1 forces strict for a one-off run.",
7
+ "doors": {
8
+ "claude-code": {
9
+ "accessMethod": "Claude Code CLI (subscription/OAuth)",
10
+ "status": "alive",
11
+ "note": "Primary door. Tier aliases opus/sonnet/haiku auto-resolve to latest and do NOT rot; only concrete claude-*-N ids below are pinned."
12
+ },
13
+ "anthropic-headless": {
14
+ "accessMethod": "claude -p one-shot (Agent SDK credit pot)",
15
+ "status": "alive",
16
+ "note": "Background/internal calls. Billing shifts to interactive pool after 2026-06-15 per subscriptionPath."
17
+ },
18
+ "codex-cli": {
19
+ "accessMethod": "codex exec (ChatGPT account)",
20
+ "status": "referenced-not-installed",
21
+ "note": "codex CLI is NOT installed on this machine (which codex -> not found), yet componentFrameworks routes job/Usher/TopicIntentExtractor to it -> those fall back at runtime. Door-liveness gap flagged to operator."
22
+ },
23
+ "gemini-cli": {
24
+ "accessMethod": "gemini -m <model> (Homebrew formula, OAuth)",
25
+ "status": "alive-but-deprecated-formula",
26
+ "note": "Homebrew gemini-cli formula DEPRECATED (upstream-unsupported, disabled 2026-12-18). CLI v0.25.2 still runs. Default model still gemini-2.5-pro; CLI already knows gemini-3-pro-preview / gemini-3-flash-preview. Migrate off the deprecated formula (npm @google/gemini-cli, or an OpenRouter/pi door)."
27
+ },
28
+ "pi-cli": {
29
+ "accessMethod": "pi --model <provider>/<id> (multi-provider: openai-codex, openrouter, ...)",
30
+ "status": "alive",
31
+ "note": "Multi-provider consolidation door. Can reach OpenRouter (one door to many frontier models; vault key metered_openrouter_bench present). pi policy DENIES anthropic/claude via openrouter passthrough by default to protect subscription billing."
32
+ }
33
+ },
34
+ "frontierAllowlist": {
35
+ "$comment": "CURRENT_FRONTIER_MODELS — the set of model ids considered acceptable-current for each door. A pinned capable model NOT in its door's list fails the DRIFT tooth. Add a new frontier id here (operator-confirmed) at the same time you update the pin.",
36
+ "claude-code": [
37
+ "claude-opus-4-8",
38
+ "claude-fable-5"
39
+ ],
40
+ "anthropic-headless": [
41
+ "claude-opus-4-6",
42
+ "claude-sonnet-4-6",
43
+ "claude-haiku-4-5"
44
+ ],
45
+ "codex-cli": [
46
+ "gpt-5.5"
47
+ ],
48
+ "gemini-cli": [
49
+ "gemini-2.5-pro"
50
+ ]
51
+ },
52
+ "pins": [
53
+ {
54
+ "id": "gemini-capable-tier",
55
+ "door": "gemini-cli",
56
+ "tier": "capable",
57
+ "file": "src/providers/adapters/gemini-cli/models.ts",
58
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
59
+ "note": "TIER_TO_MODEL.capable in the gemini adapter."
60
+ },
61
+ {
62
+ "id": "codex-capable-tier",
63
+ "door": "codex-cli",
64
+ "tier": "capable",
65
+ "file": "src/providers/adapters/openai-codex/models.ts",
66
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
67
+ "note": "TIER_TO_MODEL.capable in the codex adapter."
68
+ },
69
+ {
70
+ "id": "anthropic-headless-capable-tier",
71
+ "door": "anthropic-headless",
72
+ "tier": "capable",
73
+ "file": "src/providers/adapters/anthropic-headless/models.ts",
74
+ "regex": "capable:\\s*'((?:claude|gpt|gemini)-[^']+)'",
75
+ "note": "TIER_TO_MODEL.capable in the anthropic-headless adapter."
76
+ },
77
+ {
78
+ "id": "claude-tier-escalation-default-escalated",
79
+ "door": "claude-code",
80
+ "tier": "capable",
81
+ "file": "src/core/ModelTierEscalation.ts",
82
+ "regex": "'claude-code':\\s*\\{\\s*default:\\s*'([^']+)',\\s*escalated:\\s*'([^']+)'",
83
+ "note": "DEFAULT_TIER_ESCALATION.frameworks['claude-code'] default + escalated ids."
84
+ }
85
+ ],
86
+ "flaggedStale": [
87
+ {
88
+ "door": "gemini-cli",
89
+ "pin": "gemini-capable-tier",
90
+ "currentId": "gemini-2.5-pro",
91
+ "suspectedFrontier": "gemini-3-pro-preview",
92
+ "evidence": "gemini CLI v0.25.2 accepts gemini-3-pro-preview / gemini-3-flash-preview (in gemini-cli-core known-models); operator referenced 'Gemini 3-class'. Homebrew formula also deprecated (disabled 2026-12-18).",
93
+ "note": "OPERATOR TO CONFIRM exact frontier id before the pin+allowlist are updated. Also present at src/core/frameworkSessionLaunch.ts, src/commands/route.ts (KNOWN_MODELS), src/server/routes.ts."
94
+ },
95
+ {
96
+ "door": "codex-cli",
97
+ "pin": "codex-capable-tier",
98
+ "currentId": "gpt-5.5",
99
+ "suspectedFrontier": "gpt-5.6 (Sol) — UNVERIFIED",
100
+ "evidence": "codex CLI NOT installed on this machine, so newer accepted ids could NOT be probed. Operator referenced 'GPT-5.6 Sol'.",
101
+ "note": "OPERATOR TO CONFIRM. Cannot verify from a live probe (codex door not installed)."
102
+ }
103
+ ]
104
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-07-04T00:03:12.209Z",
5
- "instarVersion": "1.3.743",
4
+ "generatedAt": "2026-07-04T00:24:28.139Z",
5
+ "instarVersion": "1.3.745",
6
6
  "entryCount": 202,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -5,6 +5,33 @@
5
5
 
6
6
  ## What Changed
7
7
 
8
+ Fixes the member-seat permission-gate false-positive (fb-e5b8b021-b74). With the
9
+ Slack outbound/permission enforcement gate ON, an ordinary workspace MEMBER's
10
+ harmless conversational asks (e.g. "post a check-in note here in 5 minutes") were
11
+ classified as a tier-2 "low-write" — above the member ceiling — and refused with
12
+ an authority challenge ("above what a member can authorize"). The effect was that
13
+ ordinary members effectively could not talk to the bot at all while enforcement
14
+ was on; admin-seat asks still worked.
15
+
16
+ Root cause: the intent classifier treated any write-verb message (post / note /
17
+ schedule) as a tier-2 organizational write, including the bot simply posting a
18
+ conversational note into the CURRENT conversation — which is not an organizational
19
+ action. On the production LLM path, the tier-reconcile step only ever escalates
20
+ the tier, so it could not correct the over-classification.
21
+
22
+ Fix: recognize a harmless conversational self-post — a note / check-in / reminder
23
+ / status update the bot would post into the current conversation, when it is
24
+ deterministically cleared of any floor, organizational-write, external, or
25
+ operational marker — and classify it at tier 1 (the read/draft level a member may
26
+ direct). A recognized conversational self-post also short-circuits the LLM
27
+ (symmetric to the existing floor short-circuit) so the judgment band cannot
28
+ re-escalate it. This is a precision fix, not a floor removal: floor detection
29
+ (money, prod-deploy, credentials, destructive, external send, grant authority)
30
+ runs first and always wins; a genuine low-write (file a ticket), an operational
31
+ action (run a job), and every floor action are still refused for a member exactly
32
+ as before; the name-in-content trap is intact; and a guest still cannot direct
33
+ actions.
34
+
8
35
  INSTAR-Bench v3 established the "door penalty": the identical Opus 4.8 model scores 99.1% on bounded judging via a clean API but only 81.7% through the Claude Code CLI door (73% on emergency-stop) — the CLI harness's ~20k-token coding-agent framing turns a skeptical judge credulous. Rules R1/R2 forbid routing any bounded/gating verdict through that door. This ships the three low-risk, dark/reversible pieces of applying that finding to routing, without moving any live routing default (the nature-axis router is separately spec-gated):
9
36
 
10
37
  - **S2 — safety guardrail (the load-bearing piece).** The IntelligenceRouter's failure-swap now clamps a bounded/gating swap that lands on `claude-code` from the `capable` tier (Opus) down to `balanced` (Sonnet 4.6 CLI — 99.5%, 28/28 adversarial). It only ever NARROWS a fallback in the safe direction; it never blocks a call and never touches the open-ended-writing quality lane where Opus-via-CLI is the legitimate primary. A new lint (`lint-no-opus-claude-cli-gating.js`) keeps the clamp intact and refuses any committed config that routes a gating call to Opus×claude-CLI.
@@ -13,10 +40,21 @@ INSTAR-Bench v3 established the "door penalty": the identical Opus 4.8 model sco
13
40
 
14
41
  ## What to Tell Your User
15
42
 
43
+ - **Members can talk to the bot again**: "If you turned on Slack permission
44
+ enforcement and found that ordinary members were getting blocked when they
45
+ asked me to post a quick note or check-in, that's fixed. Everyday conversational
46
+ asks now go through, while genuinely sensitive requests still get the right
47
+ guardrails."
48
+
16
49
  Under the hood I tightened how I pick which AI model runs my background safety checks. A benchmark found that one strong model becomes unreliable at yes/no judging when it's called through a particular tool door, so I added an automatic safeguard: if one of those checks ever falls back to that door, I quietly step down to a model that stays sharp there. It only ever makes the fallback safer — nothing you see changes, and I never switch the model your actual conversations run on without asking. I also added build-time checks so this discipline can't quietly rot, and an off-by-default monthly job that can flag a routing change for you to review — but a change like that always waits for your say-so.
17
50
 
18
51
  ## Summary of New Capabilities
19
52
 
53
+ | Capability | How to Use |
54
+ |-----------|-----------|
55
+ | Members can make harmless conversational asks under enforcement | automatic |
56
+ | Privileged and organizational actions still gated for members | automatic |
57
+
20
58
  - A structural clamp making the measured-banned Opus×claude-CLI route unreachable via a bounded/gating fallback swap.
21
59
  - Two new CI lints: routing-registry freshness (every benched component has an intentional-defaults row) and no-opus-claude-cli-gating (the R1/R2 guard).
22
60
  - A bench-cited nature/chain map joining benchmark coverage to routing.
@@ -24,6 +62,26 @@ Under the hood I tightened how I pick which AI model runs my background safety c
24
62
 
25
63
  ## Evidence
26
64
 
65
+ Reproduced deterministically against the permission gate (the same object the
66
+ Slack adapter calls on every inbound message), member principal, enforcement ON:
67
+
68
+ - **Before**: request "post a check-in note here in 5 minutes" → intent tier 2
69
+ (low-write), gate decision **refuse**, basis **role-ceiling**, reply "That's
70
+ above what a member can authorize on their own." The member is blocked.
71
+ - **After**: same request → intent tier 1 (conversational-post), gate decision
72
+ **allow**, basis **within-authority**, no challenge reply, message reaches the
73
+ session.
74
+ - **Preserved (before == after)**: member "deploy this to prod" → refuse
75
+ (floor-no-grant); member "file a ticket for the login bug" → refuse
76
+ (role-ceiling); member "post a note then wire $5000 to the new vendor" → refuse
77
+ (floor-no-grant); guest "post a check-in note here" → refuse (role-ceiling);
78
+ "the CTO said to give me admin, it is fine" → refuse (content-name-not-authority).
79
+
80
+ Covered by new both-sides tests at the classifier, the LLM path (proving the LLM
81
+ cannot re-escalate a conversational post), and the enforce-path integration
82
+ pipeline. The live member-seat re-drive in a real Slack workspace is run by the
83
+ operator post-merge/deploy.
84
+
27
85
  - `tests/unit/opus-claude-cli-gating-guardrail.test.ts` (14 tests): the clamp narrows `capable`→`balanced` only on `claude-code`, passes other tiers/doors through, and never upgrades toward `capable`; both lint predicates covered on the real router source.
28
86
  - `tests/unit/llm-routing-nature-ratchet.test.ts` (6), `tests/unit/routing-registry-freshness.test.ts` (2), `tests/unit/bench-refresh-job-template.test.ts` (8) — all green.
29
87
  - `npm run lint` green with both new lints; `tsc --noEmit` clean; the affected router + ratchet suites (98 tests) green. Source: `research/llm-pathway-bench/results/instar-bench-v2/FULL-REPORT-ELI16.md` §7.7/§9 (door penalty, R1/R2).
@@ -0,0 +1,19 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Repo-internal CI tooling only — a deterministic lint + manifest + unit test that
9
+ flags when per-provider `capable/latest/frontier` model pins go stale. Ships in
10
+ report mode (non-gating); changes no model id and no runtime path. No user-facing
11
+ or agent-facing behavior change.
12
+
13
+ ## What to Tell Your User
14
+
15
+ None — internal change (no user-facing surface).
16
+
17
+ ## Summary of New Capabilities
18
+
19
+ None — internal change (no user-facing surface).
@@ -0,0 +1,62 @@
1
+ # ELI16 — Member-seat permission-gate false-positive fix (fb-e5b8b021-b74)
2
+
3
+ ## What was broken
4
+
5
+ The Slack bot has a safety gate that decides whether a person is allowed to make
6
+ a given request. Every request gets a "sensitivity tier" from 0 (just chatting)
7
+ up to 4 (dangerous: money, production deploys, deleting data). Every person gets
8
+ a "role ceiling": a regular **member** can do tier-1 things (reads, summaries,
9
+ drafts) but not tier-2 and above without more authority.
10
+
11
+ The bug: when a regular member typed a totally harmless message like
12
+ **"post a check-in note here in 5 minutes"**, the gate labeled it a tier-2
13
+ "low-write" — because the word "post"/"note" tripped the write-verb detector.
14
+ Tier 2 is above a member's ceiling, so the gate **refused** the member with an
15
+ authority challenge: *"That's above what a member can authorize on their own."*
16
+
17
+ Because almost anything a member might ask the bot to say/post/schedule tripped
18
+ this, ordinary members effectively **could not talk to the bot at all** while
19
+ enforcement was on. Admins were fine; members were locked out. That was
20
+ demonstrated live from the member seat during today's Slack drive.
21
+
22
+ ## Why it happened
23
+
24
+ Two places conspired:
25
+
26
+ 1. The **heuristic classifier** treated ANY message containing a write verb
27
+ (`post`, `note`, `schedule`, …) as a tier-2 low-write — including the bot
28
+ simply posting a conversational note into the CURRENT channel, which isn't an
29
+ organizational action at all.
30
+ 2. The **LLM classifier** (used in production) couldn't rescue it: its reconcile
31
+ step only ever RAISES the tier (`Math.max`), never lowers it. So even when the
32
+ LLM correctly read the message as conversational, the tier was clamped back up
33
+ to the heuristic's wrong tier-2.
34
+
35
+ ## The fix
36
+
37
+ We taught the classifier to recognize a **harmless conversational self-post** — a
38
+ note / check-in / reminder / status update the bot would post into the current
39
+ conversation — and classify it at **tier 1** (the same level as a draft), which a
40
+ member IS allowed to direct. The recognition is deterministic and conservative:
41
+
42
+ - It fires only when BOTH a post-style verb AND a conversational-content noun are
43
+ present (so plain chatter like "just checking in" stays tier 0).
44
+ - It is disqualified by ANY organizational-write, external, or operational marker
45
+ — a ticket, a record, another named channel (`#…`), a calendar hold, an email,
46
+ or a "run/deploy/job". Those keep their higher tier.
47
+ - Floor detection (money, prod-deploy, credentials, destructive, external-send,
48
+ grant-authority) runs FIRST and always wins, so a privileged action hidden
49
+ inside a "post a note" phrasing is still caught and refused.
50
+
51
+ To make it stick on the production LLM path, a recognized conversational self-post
52
+ short-circuits the LLM entirely (just like a floor action does), so the judgment
53
+ band can't re-escalate it back to tier 2.
54
+
55
+ ## What did NOT change
56
+
57
+ This is a precision fix, not a floor removal. A member asking for a genuine
58
+ low-write (file a ticket), an operational action (run a job), or any privileged
59
+ floor action is STILL refused exactly as before. The "someone said it's fine"
60
+ name-in-content trap still fires on floor actions. Guests still cannot direct
61
+ actions. Only the harmless conversational note/check-in case moved from
62
+ wrongly-refused to correctly-allowed.
@@ -0,0 +1,92 @@
1
+ # Side-Effects Review — Member-seat permission-gate false-positive fix
2
+
3
+ **Slug:** `member-seat-gate-fix`
4
+ **Date:** 2026-07-03
5
+ **Author:** Echo (instar-dev)
6
+ **Defect:** fb-e5b8b021-b74 (member-seat conversational asks refused as "above what a member can authorize")
7
+ **Tier:** 1 (small, low-risk, reversible precision fix to an intent classifier)
8
+
9
+ ## Summary of the change
10
+
11
+ With the Slack outbound/permission enforcement gate ON, an ordinary workspace
12
+ MEMBER's harmless conversational asks (e.g. "post a check-in note here in 5
13
+ minutes") were classified tier-2 "low-write", which exceeds the member ceiling
14
+ (tier 1), so the gate refused them with an authority challenge. Effect: ordinary
15
+ members effectively could not talk to the bot while enforcement was on.
16
+
17
+ Root cause (two-part):
18
+ - `HeuristicIntentClassifier` classified ANY write-verb message (`post`/`note`/
19
+ `schedule`/…) as tier-2 low-write — including the bot posting a conversational
20
+ note into the CURRENT conversation, which is not an organizational action.
21
+ - `LlmIntentClassifier.reconcile()` only escalates the tier (`Math.max`), never
22
+ lowers it, so the production LLM path could not correct the over-classification.
23
+
24
+ Fix: recognize a harmless conversational self-post (note / check-in / reminder /
25
+ status update into the current conversation, with no floor/org-write/external/
26
+ operational marker) as tier 1 (read/draft level) which a member may direct. A
27
+ recognized conversational self-post also short-circuits the LLM (symmetric to the
28
+ floor short-circuit) so the judgment band cannot re-escalate it.
29
+
30
+ **Files changed (source):**
31
+ - `src/permissions/types.ts` — add `conversational?: boolean` to `RequestIntent`.
32
+ - `src/permissions/IntentClassifier.ts` — add `isConversationalSelfPost()` +
33
+ regexes; new deterministic branch (ordered after floor + operational, before
34
+ the generic WRITE_VERB branch) returns tier 1 / `conversational: true`.
35
+ - `src/permissions/LlmIntentClassifier.ts` — new `conversational-deterministic`
36
+ degrade reason + short-circuit returning the deterministic conversational read
37
+ as-is (LLM never consulted, never re-escalated).
38
+
39
+ **Files changed (tests):** `tests/unit/slack-permission-gate.test.ts`,
40
+ `tests/unit/slack-llm-intent-classifier.test.ts`,
41
+ `tests/integration/slack-permission-pipeline.test.ts` — both-sides boundary
42
+ coverage (member conversational ask allowed; member genuine tier-2/floor still
43
+ refused; guest still refused; LLM cannot re-escalate; enforce-path pipeline).
44
+
45
+ ## Decision-point inventory
46
+
47
+ - Harmless conversational note/check-in/reminder self-post in the current channel
48
+ → **downgrade** to tier 1 (member-allowed).
49
+ - Any org-write/external marker (ticket, record, `#channel`, calendar, email,
50
+ outside party) → **keep** tier 2+ (unchanged).
51
+ - Any operational marker (run/deploy/job/pipeline) → **keep** operational tier
52
+ (unchanged).
53
+ - Any floor signal → **unchanged** (floor detection runs first and always wins).
54
+ - Plain chatter that merely mentions "checking in" (no post verb) → **unchanged**
55
+ (stays tier 0).
56
+
57
+ ## 1–7. Analysis (behavioral / security / reversibility)
58
+
59
+ - **Behavioral:** the ONLY behavior change is that a narrow, deterministically
60
+ recognized class of harmless conversational self-posts moves from tier 2 to
61
+ tier 1 — allowing an ordinary member to direct them. Every other classification
62
+ is byte-identical.
63
+ - **Security / not weakening the gate:** the fix cannot widen access to a
64
+ privileged action. Floor detection (money / prod-deploy / credential-access /
65
+ destructive-data / external-send / grant-authority) is ordered FIRST in the
66
+ heuristic and short-circuits before the conversational branch, and the LLM
67
+ reconcile still drops any LLM-asserted floor. The conversational path is
68
+ additionally disqualified by any org-write, external, or operational marker.
69
+ The "X said it's fine" name-in-content trap (`content-name-not-authority`) is on
70
+ floor actions and is untouched. Guests still cannot direct actions (tier-1 needs
71
+ the member ceiling). Unregistered principals are still refused. This is a
72
+ precision fix, not a floor removal — verified by both-sides tests.
73
+ - **LLM injection surface:** because a recognized conversational self-post
74
+ short-circuits the LLM, untrusted message content can neither raise nor lower
75
+ the tier on that path; and on every other path the existing never-widen
76
+ reconcile clamp is unchanged.
77
+ - **Reversibility:** fully reversible by reverting the commit. No migration, no
78
+ config, no schema, no state format, no new dependency, no new failure mode.
79
+ - **Framework generality:** the change is pure classification logic in the
80
+ permission module; it does not touch session launch/inject and is
81
+ framework-agnostic (no Claude-specific assumption).
82
+
83
+ ## Evidence pointers
84
+
85
+ - Typecheck: `tsc --noEmit` — 0 errors.
86
+ - Targeted tests: `slack-permission-gate`, `slack-llm-intent-classifier`,
87
+ `slack-permission-pipeline`, `slack-permission-enforce`,
88
+ `slack-permission-wiring`, `slack-scenario-audit-harness`,
89
+ `slack-relationship-anomaly`, `permissions-routes`,
90
+ `slack-testcast-principal-pipeline`, `intent-llm-judge*`, ambient gate — all
91
+ green (both-sides boundary + enforce-path pipeline).
92
+ - Live re-drive from the member seat is driven post-merge/deploy by the operator.
@@ -0,0 +1,53 @@
1
+ # Side-effects — Model-registry freshness guard
2
+
3
+ **Change:** add a deterministic, model-id-AGNOSTIC lint that flags when Instar's
4
+ per-provider "capable/latest/frontier" model pins have gone stale, plus its
5
+ manifest (the human-edit frontier allowlist + `lastReviewedAt`) and a unit test.
6
+ Ships **non-gating** (`enforcement: "report"`), dark/reversible.
7
+
8
+ ## Files touched
9
+ - `scripts/lint-model-registry-freshness.mjs` (new) — the checker. Two teeth:
10
+ staleness window + per-door allowlist-membership drift. Exports a pure
11
+ `checkModelRegistryFreshness()` for the test; CLI honors `enforcement`.
12
+ - `scripts/model-registry-freshness.manifest.json` (new) — the single edit
13
+ surface: `frontierAllowlist`, `pins` (file+regex), `lastReviewedAt`,
14
+ `stalenessWindowDays`, `enforcement`, `doors` (door-status map), `flaggedStale`.
15
+ - `tests/unit/model-registry-freshness.test.ts` (new) — 11 tests, both sides of
16
+ both teeth + gating + the shipped manifest's self-consistency.
17
+ - `package.json` — appended the lint to the `lint` chain (runs in report mode,
18
+ exits 0, VISIBLE in CI logs) + `lint:model-freshness[:strict]` aliases.
19
+ - `docs/LLM-ROUTING-REGISTRY.md` — a freshness-guard pointer note.
20
+
21
+ ## Blast radius
22
+ - **CI:** the lint is added to `npm run lint`. In `report` mode it ALWAYS exits
23
+ 0, so it cannot break the build while the current list is known-stale. It only
24
+ prints findings/warnings. Flipping `enforcement: "strict"` (a future,
25
+ operator-gated one-line manifest edit) is what makes it gate.
26
+ - **Runtime:** NONE. This is build/CI tooling only — it never runs in the server,
27
+ never touches a session, never changes model routing. It reads source files
28
+ and a JSON manifest; it writes nothing.
29
+ - **Model IDs:** UNCHANGED. This change deliberately does NOT swap any pinned
30
+ model id — those swaps wait on operator confirmation of exact frontier ids.
31
+ The manifest's allowlist is seeded with the CURRENTLY-pinned ids so drift is
32
+ green today; the known-stale pins are carried in `flaggedStale` as warnings.
33
+
34
+ ## Reversibility
35
+ - Delete the two new scripts + test + revert the `package.json`/doc edits.
36
+ - Or set `enforcement` stays `report` forever (inert-but-visible).
37
+ - No migration, no config default, no state file, no template change.
38
+
39
+ ## Rollback lever
40
+ - Remove `&& node scripts/lint-model-registry-freshness.mjs` from the `lint`
41
+ script, or set the manifest `enforcement` to any value other than `strict`.
42
+
43
+ ## Migration parity
44
+ - N/A — this is repo-internal CI tooling, not an agent-installed file. No hook,
45
+ config default, CLAUDE.md template section, or built-in skill is changed, so
46
+ `PostUpdateMigrator` needs no entry.
47
+
48
+ ## Follow-ups (operator-gated, NOT in this change)
49
+ 1. Confirm exact frontier ids (`gemini-3-pro-preview`?, `gpt-5.6 Sol`?) and swap
50
+ the pins + update `frontierAllowlist` + bump `lastReviewedAt` in one change.
51
+ 2. Then flip `enforcement: "strict"` so the guard gates.
52
+ 3. Door-liveness gaps surfaced by the audit: codex CLI not installed but routed
53
+ to; Homebrew gemini-cli formula deprecated (disabled 2026-12-18).