instar 1.3.35 → 1.3.37

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.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +53 -3
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  6. package/dist/config/ConfigDefaults.js +15 -0
  7. package/dist/config/ConfigDefaults.js.map +1 -1
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +15 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/featureRolloutScan.d.ts +23 -0
  12. package/dist/core/featureRolloutScan.d.ts.map +1 -1
  13. package/dist/core/featureRolloutScan.js +102 -0
  14. package/dist/core/featureRolloutScan.js.map +1 -1
  15. package/dist/core/types.d.ts +45 -0
  16. package/dist/core/types.d.ts.map +1 -1
  17. package/dist/core/types.js.map +1 -1
  18. package/dist/messaging/AgentTelegramLedger.d.ts +65 -0
  19. package/dist/messaging/AgentTelegramLedger.d.ts.map +1 -0
  20. package/dist/messaging/AgentTelegramLedger.js +61 -0
  21. package/dist/messaging/AgentTelegramLedger.js.map +1 -0
  22. package/dist/messaging/ProcessedIdStore.d.ts +48 -0
  23. package/dist/messaging/ProcessedIdStore.d.ts.map +1 -0
  24. package/dist/messaging/ProcessedIdStore.js +111 -0
  25. package/dist/messaging/ProcessedIdStore.js.map +1 -0
  26. package/dist/monitoring/ReleaseReadinessSentinel.d.ts +172 -0
  27. package/dist/monitoring/ReleaseReadinessSentinel.d.ts.map +1 -0
  28. package/dist/monitoring/ReleaseReadinessSentinel.js +315 -0
  29. package/dist/monitoring/ReleaseReadinessSentinel.js.map +1 -0
  30. package/dist/monitoring/releaseReadinessWiring.d.ts +54 -0
  31. package/dist/monitoring/releaseReadinessWiring.d.ts.map +1 -0
  32. package/dist/monitoring/releaseReadinessWiring.js +241 -0
  33. package/dist/monitoring/releaseReadinessWiring.js.map +1 -0
  34. package/dist/scaffold/templates.d.ts.map +1 -1
  35. package/dist/scaffold/templates.js +5 -0
  36. package/dist/scaffold/templates.js.map +1 -1
  37. package/dist/server/AgentServer.d.ts +1 -0
  38. package/dist/server/AgentServer.d.ts.map +1 -1
  39. package/dist/server/AgentServer.js +1 -0
  40. package/dist/server/AgentServer.js.map +1 -1
  41. package/dist/server/CapabilityIndex.d.ts.map +1 -1
  42. package/dist/server/CapabilityIndex.js +14 -0
  43. package/dist/server/CapabilityIndex.js.map +1 -1
  44. package/dist/server/routes.d.ts +3 -0
  45. package/dist/server/routes.d.ts.map +1 -1
  46. package/dist/server/routes.js +55 -0
  47. package/dist/server/routes.js.map +1 -1
  48. package/package.json +1 -1
  49. package/scripts/analyze-release.js +221 -0
  50. package/scripts/upgrade-guide-validator.mjs +120 -0
  51. package/src/data/builtin-manifest.json +63 -63
  52. package/src/scaffold/templates/jobs/instar/release-readiness-check.md +24 -0
  53. package/src/scaffold/templates.ts +5 -0
  54. package/upgrades/1.3.37.md +59 -0
  55. package/upgrades/side-effects/a2a-ledger-and-processed-id-store.md +68 -0
  56. package/upgrades/side-effects/release-readiness-visibility-pr2a.md +27 -0
  57. package/upgrades/side-effects/release-readiness-visibility-pr2b.md +26 -0
  58. package/upgrades/side-effects/release-readiness-visibility-pr2c.md +37 -0
  59. package/upgrades/side-effects/release-readiness-visibility-pr3.md +33 -0
@@ -0,0 +1,61 @@
1
+ /**
2
+ * AgentTelegramLedger — append-only audit trail for the agent-to-agent Telegram comms
3
+ * primitive (spec MENTOR-LIVE-READINESS §Fix 2a "Round-trip audit ledger" + Codey's
4
+ * round-2 design point: separate sent + received JSONLs so Stage-B forensics can
5
+ * reconstruct the round trip from the ledger files alone, OR from Telegram chat history
6
+ * alone via the visible `corr=` field).
7
+ *
8
+ * Conventions:
9
+ * - Append-only JSONL (atomic at line granularity on POSIX for `<PIPE_BUF` writes —
10
+ * the same rationale the original mentor-outbox used; documented so a future reviewer
11
+ * doesn't "fix" it to atomicWriteFileSync, which is wrong for append).
12
+ * - **No secrets in any row** (round-2 adversarial F5 — and the SendAuditRow shape
13
+ * explicitly excludes the bot token; the ReceiveAuditRow only captures routing-level
14
+ * marker fields, never the body).
15
+ * - Default paths: `{stateDir}/a2a-sent.jsonl` + `{stateDir}/a2a-received.jsonl`. The
16
+ * caller may override (per-agent isolation, tests).
17
+ *
18
+ * I/O is the only side-effect; everything else is pure data.
19
+ */
20
+ import fs from 'node:fs';
21
+ import path from 'node:path';
22
+ export function defaultLedgerPaths(stateDir) {
23
+ return {
24
+ sentPath: path.join(stateDir, 'a2a-sent.jsonl'),
25
+ receivedPath: path.join(stateDir, 'a2a-received.jsonl'),
26
+ };
27
+ }
28
+ /**
29
+ * Append-only JSONL audit ledger. Both `appendSent` and `appendReceived` are synchronous
30
+ * + best-effort: they NEVER throw (an audit-write failure must not crash a tick). Writes
31
+ * use `fs.appendFileSync` because atomic-write-replace is wrong for append-only files
32
+ * (see the documented rationale in the module header).
33
+ */
34
+ export class AgentTelegramLedger {
35
+ sentPath;
36
+ receivedPath;
37
+ constructor(paths) {
38
+ this.sentPath = paths.sentPath;
39
+ this.receivedPath = paths.receivedPath;
40
+ }
41
+ appendSent(row) {
42
+ this.appendLine(this.sentPath, row);
43
+ }
44
+ appendReceived(row) {
45
+ this.appendLine(this.receivedPath, row);
46
+ }
47
+ appendLine(target, row) {
48
+ try {
49
+ fs.mkdirSync(path.dirname(target), { recursive: true });
50
+ fs.appendFileSync(target, JSON.stringify(row) + '\n', 'utf-8');
51
+ }
52
+ catch (err) {
53
+ // Best-effort audit: an audit-write failure must not crash a tick. Log but swallow.
54
+ // (Spec §Fix 2a "every drop path writes an audit row" — silent drops make Stage-B
55
+ // forensics painful, but a CRASHED tick is worse. We accept this trade-off.)
56
+ // eslint-disable-next-line no-console
57
+ console.warn(`[a2a-ledger] append failed (non-fatal) at ${target}:`, err instanceof Error ? err.message : String(err));
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=AgentTelegramLedger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AgentTelegramLedger.js","sourceRoot":"","sources":["../../src/messaging/AgentTelegramLedger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAkC7B,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC;QAC/C,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC;KACxD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,mBAAmB;IACb,QAAQ,CAAS;IACjB,YAAY,CAAS;IAEtC,YAAY,KAA+B;QACzC,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;IACzC,CAAC;IAED,UAAU,CAAC,GAAiB;QAC1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;IAED,cAAc,CAAC,GAAoB;QACjC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IAEO,UAAU,CAAC,MAAc,EAAE,GAAY;QAC7C,IAAI,CAAC;YACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACxD,EAAE,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,oFAAoF;YACpF,kFAAkF;YAClF,6EAA6E;YAC7E,sCAAsC;YACtC,OAAO,CAAC,IAAI,CAAC,6CAA6C,MAAM,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACzH,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * ProcessedIdStore — small persistent set of recently-processed a2a marker `id`s, so
3
+ * Telegram retries / adapter restarts can't double-inject the same prompt (spec
4
+ * MENTOR-LIVE-READINESS §Fix 2a "Processed-id ledger" + Codey's round-2 design point).
5
+ *
6
+ * Bounded by `maxEntries` (default 10_000) AND `maxAgeMs` (default 30 days) — whichever
7
+ * evicts first. **Bounded eviction is the same surface adversarial F2 worried about**:
8
+ * an `id` that ages out is no longer "duplicate" and could be re-injected by a replayer
9
+ * who captured it. The marker's `ts` + skew-window (24h default) is the defense at the
10
+ * primitive layer; this store's eviction policy is set generously so legitimate retries
11
+ * within hours/days are still de-duped.
12
+ *
13
+ * Convention note: instar prefers SQLite for durable dedup ledgers (MessageProcessingLedger,
14
+ * CommitmentTracker). This store uses a small JSON file because the working set is small
15
+ * (per-mentee processed-ids over 30d), the file is rewritten atomically on every mark, and
16
+ * there's no concurrent-writer story to need WAL. A future bump to SQLite is a drop-in
17
+ * replacement at the class boundary.
18
+ */
19
+ export interface ProcessedIdStoreOptions {
20
+ /** Absolute path to the JSON file backing the store. */
21
+ filePath: string;
22
+ /** Max entries to retain before evicting the oldest (default 10_000). */
23
+ maxEntries?: number;
24
+ /** Max age (ms) before an entry is evicted (default 30 days). */
25
+ maxAgeMs?: number;
26
+ /** Injected for testability. Default: Date.now. */
27
+ now?: () => number;
28
+ }
29
+ export declare class ProcessedIdStore {
30
+ private readonly filePath;
31
+ private readonly maxEntries;
32
+ private readonly maxAgeMs;
33
+ private readonly now;
34
+ private entries;
35
+ constructor(opts: ProcessedIdStoreOptions);
36
+ /** True if the id has been marked + has not been evicted. */
37
+ hasProcessed(id: string): boolean;
38
+ /** Mark this id processed at now() and persist. Idempotent (re-marking is a no-op
39
+ * beyond refreshing the persisted snapshot). */
40
+ markProcessed(id: string): void;
41
+ /** Test helper: current entry count. */
42
+ size(): number;
43
+ private load;
44
+ private persist;
45
+ private evictExpired;
46
+ private evictOverflow;
47
+ }
48
+ //# sourceMappingURL=ProcessedIdStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProcessedIdStore.d.ts","sourceRoot":"","sources":["../../src/messaging/ProcessedIdStore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,MAAM,WAAW,uBAAuB;IACtC,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAgBD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,OAAO,CAA+B;gBAElC,IAAI,EAAE,uBAAuB;IASzC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAKjC;qDACiD;IACjD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAS/B,wCAAwC;IACxC,IAAI,IAAI,MAAM;IAKd,OAAO,CAAC,IAAI;IAmBZ,OAAO,CAAC,OAAO;IAef,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,aAAa;CAStB"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * ProcessedIdStore — small persistent set of recently-processed a2a marker `id`s, so
3
+ * Telegram retries / adapter restarts can't double-inject the same prompt (spec
4
+ * MENTOR-LIVE-READINESS §Fix 2a "Processed-id ledger" + Codey's round-2 design point).
5
+ *
6
+ * Bounded by `maxEntries` (default 10_000) AND `maxAgeMs` (default 30 days) — whichever
7
+ * evicts first. **Bounded eviction is the same surface adversarial F2 worried about**:
8
+ * an `id` that ages out is no longer "duplicate" and could be re-injected by a replayer
9
+ * who captured it. The marker's `ts` + skew-window (24h default) is the defense at the
10
+ * primitive layer; this store's eviction policy is set generously so legitimate retries
11
+ * within hours/days are still de-duped.
12
+ *
13
+ * Convention note: instar prefers SQLite for durable dedup ledgers (MessageProcessingLedger,
14
+ * CommitmentTracker). This store uses a small JSON file because the working set is small
15
+ * (per-mentee processed-ids over 30d), the file is rewritten atomically on every mark, and
16
+ * there's no concurrent-writer story to need WAL. A future bump to SQLite is a drop-in
17
+ * replacement at the class boundary.
18
+ */
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
22
+ const DEFAULT_MAX_ENTRIES = 10_000;
23
+ const DEFAULT_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
24
+ export class ProcessedIdStore {
25
+ filePath;
26
+ maxEntries;
27
+ maxAgeMs;
28
+ now;
29
+ entries;
30
+ constructor(opts) {
31
+ this.filePath = opts.filePath;
32
+ this.maxEntries = opts.maxEntries ?? DEFAULT_MAX_ENTRIES;
33
+ this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
34
+ this.now = opts.now ?? Date.now;
35
+ this.entries = new Map();
36
+ this.load();
37
+ }
38
+ /** True if the id has been marked + has not been evicted. */
39
+ hasProcessed(id) {
40
+ this.evictExpired();
41
+ return this.entries.has(id);
42
+ }
43
+ /** Mark this id processed at now() and persist. Idempotent (re-marking is a no-op
44
+ * beyond refreshing the persisted snapshot). */
45
+ markProcessed(id) {
46
+ if (!this.entries.has(id)) {
47
+ this.entries.set(id, { ts: this.now() });
48
+ }
49
+ this.evictExpired();
50
+ this.evictOverflow();
51
+ this.persist();
52
+ }
53
+ /** Test helper: current entry count. */
54
+ size() {
55
+ this.evictExpired();
56
+ return this.entries.size;
57
+ }
58
+ load() {
59
+ if (!fs.existsSync(this.filePath))
60
+ return;
61
+ try {
62
+ const raw = fs.readFileSync(this.filePath, 'utf-8');
63
+ const parsed = JSON.parse(raw);
64
+ if (parsed && parsed.v === 1 && parsed.entries && typeof parsed.entries === 'object') {
65
+ for (const [id, ts] of Object.entries(parsed.entries)) {
66
+ if (typeof ts === 'number' && Number.isFinite(ts)) {
67
+ this.entries.set(id, { ts });
68
+ }
69
+ }
70
+ }
71
+ }
72
+ catch {
73
+ // Corrupted store → start fresh (full re-allow). Better than crashing the recipient
74
+ // — duplicate prompts are recoverable; a dead recipient isn't.
75
+ this.entries = new Map();
76
+ }
77
+ }
78
+ persist() {
79
+ const file = {
80
+ v: 1,
81
+ entries: Object.fromEntries([...this.entries].map(([id, r]) => [id, r.ts])),
82
+ };
83
+ try {
84
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
85
+ SafeFsExecutor.atomicWriteJsonSync(this.filePath, file, { operation: 'ProcessedIdStore.persist' });
86
+ }
87
+ catch (err) {
88
+ // Best-effort persistence; on the next mark we retry.
89
+ // eslint-disable-next-line no-console
90
+ console.warn(`[a2a-processed-id] persist failed (non-fatal) at ${this.filePath}:`, err instanceof Error ? err.message : String(err));
91
+ }
92
+ }
93
+ evictExpired() {
94
+ const cutoff = this.now() - this.maxAgeMs;
95
+ for (const [id, r] of this.entries) {
96
+ if (r.ts < cutoff)
97
+ this.entries.delete(id);
98
+ }
99
+ }
100
+ evictOverflow() {
101
+ if (this.entries.size <= this.maxEntries)
102
+ return;
103
+ // Drop oldest first.
104
+ const sorted = [...this.entries].sort((a, b) => a[1].ts - b[1].ts);
105
+ const dropCount = this.entries.size - this.maxEntries;
106
+ for (let i = 0; i < dropCount; i++) {
107
+ this.entries.delete(sorted[i][0]);
108
+ }
109
+ }
110
+ }
111
+ //# sourceMappingURL=ProcessedIdStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProcessedIdStore.js","sourceRoot":"","sources":["../../src/messaging/ProcessedIdStore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * ReleaseReadinessSentinel — Layer B of the release-readiness-visibility spec.
3
+ *
4
+ * Watches whether a release is blocked/stalling and makes it impossible to miss.
5
+ * The original incident (2026-05-27): npm publishing silently stalled since
6
+ * v1.3.26 — green merges, green CI, no published release, no alert. The publish
7
+ * workflow's NEXT.md gate is a *silent skip*: a stalled release is
8
+ * indistinguishable from "nothing to publish". This sentinel is the missing
9
+ * visibility — a recurring, near-silent check that surfaces a stuck release as
10
+ * a single, deduped, escalating Attention item.
11
+ *
12
+ * Design (spec §4.2):
13
+ * - Reads canonical `main` (FETCH_HEAD), not the local checkout, so a stale
14
+ * dev branch can't produce a false "all clear".
15
+ * - "Blocked" is decoupled from NEXT.md state (so Layer A's auto-draft can't
16
+ * silence the alarm by clearing the file): unreleased feature/fix commits
17
+ * exist AND the guide can't publish (missing/template/unreviewed), OR the
18
+ * analyzer reports critical/high coverage gaps.
19
+ * - Near-silent: silent below threshold (state + log only); ONE Attention
20
+ * item per stall episode above it, keyed on the OLDEST unreleased commit
21
+ * SHA (stable across ticks — not a resettable per-tick id), priority scaled
22
+ * by backlog age, 12h hysteresis on re-raise after an auto-resolve.
23
+ * - Fail-loud: any evaluation failure (fetch error, analyzer error) raises a
24
+ * low-priority Attention item — never a silent catch (that would re-create
25
+ * the exact bug this fixes).
26
+ * - Lifecycle owner: detect → surface → auto-resolve → reap, with
27
+ * resolveEpisodesInRange consulted by the publish-finalize path.
28
+ * - Repo-gated: needs an analyzable instar git repo (dev/maintainer env). On
29
+ * an npm-installed agent with no such repo the deps make the check inert.
30
+ * - Supervision: Tier 0 (mechanical computation + fixed-template wording).
31
+ */
32
+ import { EventEmitter } from 'node:events';
33
+ /** Parsed subset of `analyze-release.js --json` the sentinel needs. */
34
+ export interface AnalyzerReport {
35
+ lastTag: string;
36
+ commitCount: number;
37
+ analysis: {
38
+ commitClassification: {
39
+ features: number;
40
+ fixes: number;
41
+ };
42
+ };
43
+ guideCoverage: {
44
+ criticalGaps: number;
45
+ highGaps: number;
46
+ };
47
+ }
48
+ export interface OldestCommit {
49
+ sha: string;
50
+ dateMs: number;
51
+ }
52
+ export interface ReadinessEpisode {
53
+ /** Stable key: SHA of the oldest unreleased commit at first detection. */
54
+ oldestSha: string;
55
+ firstDetectedMs: number;
56
+ /** Set when an Attention item was raised (age crossed the threshold). */
57
+ openedMs?: number;
58
+ attentionId?: string;
59
+ lastPriority?: ReadinessPriority;
60
+ resolvedMs?: number;
61
+ resolvedReason?: 'published' | 'stale' | 'rolled-back' | 'cleared';
62
+ }
63
+ export interface ReadinessState {
64
+ episodes: ReadinessEpisode[];
65
+ /** Episodes resolved recently — used for the re-raise hysteresis window. */
66
+ recentResolves: Array<{
67
+ oldestSha: string;
68
+ resolvedMs: number;
69
+ }>;
70
+ /** Last failure-episode key we signaled, to dedupe fail-loud notices. */
71
+ lastFailureKey?: string;
72
+ lastTickAt?: number;
73
+ lastSignalAt?: number;
74
+ cacheHeadSha?: string;
75
+ canonicalRemoteOverridden?: boolean;
76
+ /** Runtime kill-switch set by POST /release-readiness/rollback. When true,
77
+ * tick() no-ops without evaluating. Cleared by /release-readiness/enable. */
78
+ disabled?: boolean;
79
+ rollbackHistory?: Array<{
80
+ ts: number;
81
+ sessionId?: string;
82
+ sourceIp?: string;
83
+ reason?: string;
84
+ }>;
85
+ }
86
+ export type ReadinessPriority = 'LOW' | 'MEDIUM' | 'HIGH';
87
+ export interface AttentionItem {
88
+ id: string;
89
+ title: string;
90
+ summary: string;
91
+ category?: string;
92
+ priority?: string;
93
+ }
94
+ export interface ReleaseReadinessSentinelDeps {
95
+ /** Fetch canonical main; resolve FETCH_HEAD sha. ok:false ⇒ unreachable. */
96
+ fetchCanonical(): Promise<{
97
+ ok: boolean;
98
+ headSha?: string;
99
+ }>;
100
+ /** Run analyze-release.js --json --ref against the canonical ref. null ⇒ error. */
101
+ runAnalyzer(ref: string): Promise<AnalyzerReport | null>;
102
+ /** Oldest unreleased feature/fix commit relative to lastTag..ref. */
103
+ oldestUnreleasedCommit(lastTag: string, ref: string): Promise<OldestCommit | null>;
104
+ /** True when the publish gate would refuse the guide (missing/template/unreviewed). */
105
+ guideBlocksPublish(): Promise<boolean>;
106
+ /** Best-effort: seed NEXT.md via Layer A (analyze-release.js --draft-guide). */
107
+ draftGuide(ref: string): Promise<void>;
108
+ /** Post an Attention item; resolves to true on delivery. */
109
+ postAttention(item: AttentionItem): Promise<boolean>;
110
+ /** Resolve an open Attention item by id. */
111
+ resolveAttention(id: string, reason: string): Promise<boolean>;
112
+ /** Load persisted state (or a fresh empty state). */
113
+ loadState(): ReadinessState;
114
+ /** Persist state atomically. */
115
+ saveState(state: ReadinessState): void;
116
+ /** Is `sha` an ancestor of `ref`? (for resolveEpisodesInRange) */
117
+ isAncestor(sha: string, ref: string): Promise<boolean>;
118
+ /** Append a structured audit line (sentinel-events.jsonl). */
119
+ audit(event: Record<string, unknown>): void;
120
+ now(): number;
121
+ }
122
+ export interface ReleaseReadinessSentinelConfig {
123
+ enabled?: boolean;
124
+ tickIntervalMs?: number;
125
+ backlogAgeDaysSilent?: number;
126
+ backlogAgeDaysLow?: number;
127
+ backlogAgeDaysMedium?: number;
128
+ backlogAgeDaysHigh?: number;
129
+ hysteresisHours?: number;
130
+ staleEpisodeTtlDays?: number;
131
+ }
132
+ export declare class ReleaseReadinessSentinel extends EventEmitter {
133
+ private readonly deps;
134
+ private readonly cfg;
135
+ private tickHandle;
136
+ constructor(deps: ReleaseReadinessSentinelDeps, cfg?: ReleaseReadinessSentinelConfig);
137
+ start(): void;
138
+ stop(): void;
139
+ /** Priority for a given backlog age, or null when below the silent threshold. */
140
+ priorityForAge(ageDays: number): ReadinessPriority | null;
141
+ /** The cron entry point. Top-level catch converts any error into a loud signal. */
142
+ tick(): Promise<void>;
143
+ /** Read-only snapshot of current state (for GET /release-readiness). */
144
+ snapshot(): ReadinessState;
145
+ /**
146
+ * Runtime kill-switch (POST /release-readiness/rollback). Disables future
147
+ * ticks, resolves all open Attention items, and is ITSELF loud — raises a
148
+ * HIGH-priority Attention item + audits — so the rollback can never be a
149
+ * silent way to mute the alarm (iter-3 Adversarial V5).
150
+ */
151
+ rollback(meta?: {
152
+ sessionId?: string;
153
+ sourceIp?: string;
154
+ }): Promise<void>;
155
+ /** Re-arm after a rollback. */
156
+ enable(): void;
157
+ private evaluate;
158
+ private isBlocked;
159
+ private raiseGapOnly;
160
+ private failLoud;
161
+ private resolveAll;
162
+ /**
163
+ * Called by the publish-finalize path (and the /rollback route via 'rolled-back').
164
+ * Resolves every open episode whose oldestSha is an ancestor of newTagSha —
165
+ * correct even when the oldest-unreleased SHA churned during the open window.
166
+ */
167
+ resolveEpisodesInRange(newTagSha: string, reason?: ReadinessEpisode['resolvedReason']): Promise<void>;
168
+ /** TTL-reap episodes/resolves so state doesn't grow unbounded. */
169
+ private reap;
170
+ static emptyState(): ReadinessState;
171
+ }
172
+ //# sourceMappingURL=ReleaseReadinessSentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReleaseReadinessSentinel.d.ts","sourceRoot":"","sources":["../../src/monitoring/ReleaseReadinessSentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE;QACR,oBAAoB,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;KAC3D,CAAC;IACF,aAAa,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3D;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,iBAAiB,CAAC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;CACpE;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,4EAA4E;IAC5E,cAAc,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC;kFAC8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjG;AAED,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,4BAA4B;IAC3C,4EAA4E;IAC5E,cAAc,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,mFAAmF;IACnF,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IACzD,qEAAqE;IACrE,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACnF,uFAAuF;IACvF,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,gFAAgF;IAChF,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,4DAA4D;IAC5D,aAAa,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,qDAAqD;IACrD,SAAS,IAAI,cAAc,CAAC;IAC5B,gCAAgC;IAChC,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,CAAC;IACvC,kEAAkE;IAClE,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,8DAA8D;IAC9D,KAAK,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC5C,GAAG,IAAI,MAAM,CAAC;CACf;AAED,MAAM,WAAW,8BAA8B;IAC7C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAgBD,qBAAa,wBAAyB,SAAQ,YAAY;IAKtD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA2C;IAC/D,OAAO,CAAC,UAAU,CAA+C;gBAG9C,IAAI,EAAE,4BAA4B,EACnD,GAAG,GAAE,8BAAmC;IAM1C,KAAK,IAAI,IAAI;IAQb,IAAI,IAAI,IAAI;IAOZ,iFAAiF;IACjF,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI;IAOzD,mFAAmF;IAC7E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B,wEAAwE;IACxE,QAAQ,IAAI,cAAc;IAI1B;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBnF,+BAA+B;IAC/B,MAAM,IAAI,IAAI;YAOA,QAAQ;IAsFtB,OAAO,CAAC,SAAS;YAQH,YAAY;YAuBZ,QAAQ;YAgBR,UAAU;IAWxB;;;;OAIG;IACG,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,gBAAgB,CAAC,gBAAgB,CAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBxH,kEAAkE;IAClE,OAAO,CAAC,IAAI;IAmBZ,MAAM,CAAC,UAAU,IAAI,cAAc;CAGpC"}