instar 1.3.636 → 1.3.638
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/storage/JsonlStore.d.ts +54 -0
- package/dist/core/storage/JsonlStore.d.ts.map +1 -0
- package/dist/core/storage/JsonlStore.js +81 -0
- package/dist/core/storage/JsonlStore.js.map +1 -0
- package/dist/core/types.d.ts +14 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/monitoring/TokenLedger.d.ts +44 -0
- package/dist/monitoring/TokenLedger.d.ts.map +1 -1
- package/dist/monitoring/TokenLedger.js +76 -0
- package/dist/monitoring/TokenLedger.js.map +1 -1
- package/dist/monitoring/TokenLedgerPoller.d.ts +24 -0
- package/dist/monitoring/TokenLedgerPoller.d.ts.map +1 -1
- package/dist/monitoring/TokenLedgerPoller.js +37 -0
- package/dist/monitoring/TokenLedgerPoller.js.map +1 -1
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +3 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/utils/jsonl-rotation.d.ts +37 -0
- package/dist/utils/jsonl-rotation.d.ts.map +1 -1
- package/dist/utils/jsonl-rotation.js +86 -0
- package/dist/utils/jsonl-rotation.js.map +1 -1
- package/package.json +2 -2
- package/scripts/bounded-accumulation-retention-baseline.json +81 -0
- package/scripts/bounded-accumulation-wholefile-read-baseline.json +5 -0
- package/scripts/lint-no-wholefile-sync-read.js +128 -0
- package/scripts/lint-store-retention-declared.js +93 -0
- package/src/data/builtin-manifest.json +3 -3
- package/src/data/state-coherence-registry.json +71 -10
- package/upgrades/1.3.637.md +41 -0
- package/upgrades/1.3.638.md +48 -0
- package/upgrades/side-effects/bounded-accumulation-increment-1.md +103 -0
- package/upgrades/side-effects/bounded-accumulation-increment-2-token-ledger.md +97 -0
|
@@ -12,15 +12,27 @@
|
|
|
12
12
|
* - Lazy rotation — called before/after append, no background timers
|
|
13
13
|
*/
|
|
14
14
|
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
15
16
|
import { SafeFsExecutor } from '../core/SafeFsExecutor.js';
|
|
16
17
|
// ── Constants ────────────────────────────────────────────────────────
|
|
17
18
|
const DEFAULT_MAX_BYTES = 10 * 1024 * 1024; // 10MB
|
|
18
19
|
const DEFAULT_KEEP_RATIO = 0.75;
|
|
20
|
+
const DEFAULT_KEEP_SEGMENTS = 4;
|
|
21
|
+
function escapeRegExp(s) {
|
|
22
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
23
|
+
}
|
|
19
24
|
// ── Public API ───────────────────────────────────────────────────────
|
|
20
25
|
/**
|
|
21
26
|
* Check if a JSONL file exceeds its size limit, and if so, rotate it
|
|
22
27
|
* by keeping only the most recent lines.
|
|
23
28
|
*
|
|
29
|
+
* NON-CONFORMANT to the Bounded Accumulation standard (§3.5): this rotates by
|
|
30
|
+
* `readFileSync` (whole file) + `split` + `writeFileSync`, which blocks the event
|
|
31
|
+
* loop for hundreds of ms on a multi-MB file — the exact stall the standard exists
|
|
32
|
+
* to kill. Prefer {@link maybeRotateJsonlSegment} (constant-time rename) for any
|
|
33
|
+
* store registered `access: 'streamed'`. Retained for back-compat callers pending
|
|
34
|
+
* their migration (Bounded Accumulation Increment 2).
|
|
35
|
+
*
|
|
24
36
|
* @returns true if rotation occurred, false otherwise
|
|
25
37
|
*/
|
|
26
38
|
export function maybeRotateJsonl(filePath, options) {
|
|
@@ -61,4 +73,78 @@ export function maybeRotateJsonl(filePath, options) {
|
|
|
61
73
|
return false;
|
|
62
74
|
}
|
|
63
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Segment-based JSONL rotation — the event-loop-SAFE alternative to
|
|
78
|
+
* {@link maybeRotateJsonl}'s read-filter-rewrite (Bounded Accumulation standard §3.5).
|
|
79
|
+
*
|
|
80
|
+
* When the active file exceeds `maxBytes`, the active file is RENAMED to a numbered
|
|
81
|
+
* segment `<name>.<seq>` (a constant-time metadata op — NO file read, NO whole-file
|
|
82
|
+
* rewrite) and a fresh empty active file is opened. Oldest segments beyond
|
|
83
|
+
* `keepSegments` are unlinked — UNLESS `archive: true`, which retains every segment
|
|
84
|
+
* (compliance/audit trails that must never drop their oldest entries).
|
|
85
|
+
*
|
|
86
|
+
* Why it exists: rotating a 14MB hot log via the old read+split+rewrite path froze the
|
|
87
|
+
* event loop for hundreds of ms on the append path; renaming a segment is O(1). Callers
|
|
88
|
+
* SHOULD gate the size-check behind a cached byte-counter (see `JsonlStore`) so even the
|
|
89
|
+
* O(1) `statSync` is not paid on every append.
|
|
90
|
+
*
|
|
91
|
+
* @returns true if a segment was cut, false otherwise. Never throws.
|
|
92
|
+
*/
|
|
93
|
+
export function maybeRotateJsonlSegment(filePath, options) {
|
|
94
|
+
const maxBytes = options?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
95
|
+
const keepSegments = Math.max(1, options?.keepSegments ?? DEFAULT_KEEP_SEGMENTS);
|
|
96
|
+
const archive = options?.archive ?? false;
|
|
97
|
+
try {
|
|
98
|
+
let size;
|
|
99
|
+
try {
|
|
100
|
+
size = fs.statSync(filePath).size; // O(1) — no file read
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return false; // no active file yet → nothing to rotate
|
|
104
|
+
}
|
|
105
|
+
if (size <= maxBytes)
|
|
106
|
+
return false;
|
|
107
|
+
const dir = path.dirname(filePath);
|
|
108
|
+
const base = path.basename(filePath);
|
|
109
|
+
// Rotated segments are "<base>.<seq>" (optionally ".gz"); seq is monotonic.
|
|
110
|
+
const segRe = new RegExp('^' + escapeRegExp(base) + '\\.(\\d+)(?:\\.gz)?$');
|
|
111
|
+
const segments = [];
|
|
112
|
+
let maxSeq = 0;
|
|
113
|
+
for (const f of fs.readdirSync(dir)) {
|
|
114
|
+
const m = f.match(segRe);
|
|
115
|
+
if (!m)
|
|
116
|
+
continue;
|
|
117
|
+
const seq = parseInt(m[1], 10);
|
|
118
|
+
if (!Number.isFinite(seq))
|
|
119
|
+
continue;
|
|
120
|
+
segments.push({ seq, name: f });
|
|
121
|
+
if (seq > maxSeq)
|
|
122
|
+
maxSeq = seq;
|
|
123
|
+
}
|
|
124
|
+
const nextSeq = maxSeq + 1;
|
|
125
|
+
// Cut the segment: rename active → segment (constant-time), open a fresh active.
|
|
126
|
+
fs.renameSync(filePath, path.join(dir, base + '.' + nextSeq));
|
|
127
|
+
fs.writeFileSync(filePath, '');
|
|
128
|
+
// Prune oldest segments beyond keepSegments — NEVER in archive (compliance) mode.
|
|
129
|
+
if (!archive) {
|
|
130
|
+
const cutoff = nextSeq - keepSegments; // segments with seq <= cutoff are dropped
|
|
131
|
+
for (const s of segments) {
|
|
132
|
+
if (s.seq <= cutoff) {
|
|
133
|
+
try {
|
|
134
|
+
SafeFsExecutor.safeUnlinkSync(path.join(dir, s.name), {
|
|
135
|
+
operation: 'src/utils/jsonl-rotation.ts:maybeRotateJsonlSegment',
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// best effort — a failed unlink just leaves an extra old segment
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
64
150
|
//# sourceMappingURL=jsonl-rotation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonl-rotation.js","sourceRoot":"","sources":["../../src/utils/jsonl-rotation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"jsonl-rotation.js","sourceRoot":"","sources":["../../src/utils/jsonl-rotation.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instar",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.638",
|
|
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,7 +28,7 @@
|
|
|
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-unfunneled-credential-write.js && node scripts/lint-state-registry.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-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",
|
|
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-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-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",
|
|
32
32
|
"lint:destructive": "node scripts/lint-no-direct-destructive.js",
|
|
33
33
|
"lint:dev-agent-dark-gate": "node scripts/lint-dev-agent-dark-gate.js",
|
|
34
34
|
"lint:guard-manifest": "node scripts/lint-guard-manifest.js",
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"note": "FROZEN baseline of registry categories without a retention policy at Bounded Accumulation Increment 1. The lint forbids NEW categories without retention; this set may only SHRINK (D6 set-monotonicity). Giving a baselined category retention counts the backlog down.",
|
|
3
|
+
"frozenAt": "2026-06-21",
|
|
4
|
+
"categories": [
|
|
5
|
+
"attention-queue",
|
|
6
|
+
"audit-a2a-messages",
|
|
7
|
+
"audit-activity",
|
|
8
|
+
"audit-apprenticeship-decisions",
|
|
9
|
+
"audit-decision-journal",
|
|
10
|
+
"audit-operation-log",
|
|
11
|
+
"audit-prompt-gate",
|
|
12
|
+
"audit-reap-log",
|
|
13
|
+
"audit-reaper",
|
|
14
|
+
"audit-recovery-events",
|
|
15
|
+
"audit-sentinel-events",
|
|
16
|
+
"audit-skill-telemetry",
|
|
17
|
+
"audit-trust-chain",
|
|
18
|
+
"audit-watchdog-interventions",
|
|
19
|
+
"autonomous-working-artifacts",
|
|
20
|
+
"commitment-opkeys",
|
|
21
|
+
"commitment-pending-mutations",
|
|
22
|
+
"commitment-replicas",
|
|
23
|
+
"commitments",
|
|
24
|
+
"conversation-live-tail",
|
|
25
|
+
"derived-discovery",
|
|
26
|
+
"derived-docs-code-sync",
|
|
27
|
+
"derived-framework-model-preferences",
|
|
28
|
+
"derived-memory-index",
|
|
29
|
+
"derived-project-map",
|
|
30
|
+
"derived-projects-digest",
|
|
31
|
+
"derived-topic-memory",
|
|
32
|
+
"ephemeral-caches",
|
|
33
|
+
"fleet-config",
|
|
34
|
+
"guard-posture-episodes",
|
|
35
|
+
"guard-posture-peers",
|
|
36
|
+
"guard-posture-snapshot",
|
|
37
|
+
"job-definitions",
|
|
38
|
+
"job-run-state",
|
|
39
|
+
"learned-preferences",
|
|
40
|
+
"lease-coordination-state",
|
|
41
|
+
"legacy-singletons",
|
|
42
|
+
"listener-daemon-internals",
|
|
43
|
+
"machine-identity-keys",
|
|
44
|
+
"message-store-and-threads",
|
|
45
|
+
"messaging-adapter-state",
|
|
46
|
+
"nonce-sequence-watermarks",
|
|
47
|
+
"pending-pulls",
|
|
48
|
+
"per-session-state",
|
|
49
|
+
"platform-attachment-caches",
|
|
50
|
+
"process-restart-coordination",
|
|
51
|
+
"projects-registry",
|
|
52
|
+
"pull-opkeys",
|
|
53
|
+
"quota-state",
|
|
54
|
+
"relationships",
|
|
55
|
+
"relay-delivery-queues",
|
|
56
|
+
"remote-ack-queue",
|
|
57
|
+
"remote-close-audit",
|
|
58
|
+
"resume-queue",
|
|
59
|
+
"secret-vault",
|
|
60
|
+
"session-monitor-ctx-notified",
|
|
61
|
+
"slack-pending-registrations",
|
|
62
|
+
"slack-permission-decisions",
|
|
63
|
+
"slack-relationship-baselines",
|
|
64
|
+
"soul-identity-documents",
|
|
65
|
+
"stream-tickets",
|
|
66
|
+
"threadline-conversation-state",
|
|
67
|
+
"threadline-pairing-result",
|
|
68
|
+
"threadline-transport-internals",
|
|
69
|
+
"threadline-trust-profiles",
|
|
70
|
+
"topic-ownership-current",
|
|
71
|
+
"topic-project-bindings",
|
|
72
|
+
"trust-elevation-incidents",
|
|
73
|
+
"uncertain-correction-capture-backlog",
|
|
74
|
+
"uncertain-instructions-tracking",
|
|
75
|
+
"uncertain-project-round-worktrees",
|
|
76
|
+
"uncertain-shared-state-ledger",
|
|
77
|
+
"uncertain-topic-intent-store",
|
|
78
|
+
"users-registry",
|
|
79
|
+
"visibility-guard"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lint-no-wholefile-sync-read.js — Bounded Accumulation §3c (Lint 2).
|
|
4
|
+
*
|
|
5
|
+
* Forbids a whole-file SYNCHRONOUS read of a store the registry marks
|
|
6
|
+
* `access: 'streamed'` (a store that can exceed 8MB): `JSON.parse(fs.readFileSync(p))`
|
|
7
|
+
* or `fs.readFileSync(p, ...).split(...)`. Reading a multi-MB file whole on the event
|
|
8
|
+
* loop is the stall this standard exists to kill (#1239; the cartographer index,
|
|
9
|
+
* instar#1069). Such stores must be read by streaming / segment / SQLite.
|
|
10
|
+
*
|
|
11
|
+
* COVERAGE HONESTY (No Silent Degradation): this is a static guardrail, NOT complete.
|
|
12
|
+
* It resolves a `streamed` store by its path BASENAME appearing as a literal near the
|
|
13
|
+
* read. A read via a variable path (`fs.readFileSync(this.logPath)`) is NOT statically
|
|
14
|
+
* resolvable and is NOT caught here — that gap is closed by the accessor funnel (route
|
|
15
|
+
* all reads through src/core/storage/, Bounded Accumulation Increment 2). The complete
|
|
16
|
+
* runtime check is the growth-burst test; this lint is the cheap forward ratchet that
|
|
17
|
+
* stops a NEW literal whole-file read of a bounded store.
|
|
18
|
+
*
|
|
19
|
+
* Ships WARN-then-ratchet: a FROZEN baseline grandfathers today's literal hits; a NEW
|
|
20
|
+
* hit fails. Exit codes: 0 pass · 1 new violation · 2 cannot-read.
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
28
|
+
// --registry / --baseline / --root override the defaults (for tests + alternate checkouts).
|
|
29
|
+
function argVal(flag) {
|
|
30
|
+
const i = process.argv.indexOf(flag);
|
|
31
|
+
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : null;
|
|
32
|
+
}
|
|
33
|
+
const REGISTRY = path.resolve(argVal('--registry') || path.join(ROOT, 'src', 'data', 'state-coherence-registry.json'));
|
|
34
|
+
const BASELINE = path.resolve(argVal('--baseline') || path.join(ROOT, 'scripts', 'bounded-accumulation-wholefile-read-baseline.json'));
|
|
35
|
+
const SRC = path.resolve(argVal('--root') || path.join(ROOT, 'src'));
|
|
36
|
+
|
|
37
|
+
// Files exempt: the accessor + rotation definitions, and the standard's own machinery.
|
|
38
|
+
const EXEMPT = [/\/core\/storage\//, /\/utils\/jsonl-rotation\.ts$/];
|
|
39
|
+
|
|
40
|
+
function streamedBasenames() {
|
|
41
|
+
const reg = JSON.parse(fs.readFileSync(REGISTRY, 'utf8'));
|
|
42
|
+
const names = new Set();
|
|
43
|
+
for (const e of reg.entries || []) {
|
|
44
|
+
if (e.retention && (e.retention.access === 'streamed')) {
|
|
45
|
+
for (const p of e.paths || []) {
|
|
46
|
+
const b = path.basename(p).replace(/\*/g, '');
|
|
47
|
+
if (b && b.endsWith('.jsonl')) names.add(b);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return names;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function walk(dir, out) {
|
|
55
|
+
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
56
|
+
const fp = path.join(dir, f.name);
|
|
57
|
+
if (f.isDirectory()) walk(fp, out);
|
|
58
|
+
else if (f.name.endsWith('.ts') && !f.name.endsWith('.test.ts')) out.push(fp);
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// A whole-file sync read: JSON.parse(...readFileSync...) OR readFileSync(...).split
|
|
64
|
+
const WHOLE_READ = /(JSON\.parse\([^)]*readFileSync|readFileSync\s*\([^)]*\)\s*\.\s*split)/;
|
|
65
|
+
|
|
66
|
+
let registryBasenames;
|
|
67
|
+
try {
|
|
68
|
+
registryBasenames = streamedBasenames();
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error('lint-no-wholefile-sync-read: cannot read registry: ' + e.message);
|
|
71
|
+
process.exit(2);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let baseline = { violations: [] };
|
|
75
|
+
try {
|
|
76
|
+
baseline = JSON.parse(fs.readFileSync(BASELINE, 'utf8'));
|
|
77
|
+
} catch {
|
|
78
|
+
// No baseline yet → treat as empty (first run records nothing as grandfathered).
|
|
79
|
+
}
|
|
80
|
+
const baselineSet = new Set((baseline.violations || []).map((v) => v.file + '::' + v.basename));
|
|
81
|
+
|
|
82
|
+
const found = [];
|
|
83
|
+
for (const file of walk(SRC, [])) {
|
|
84
|
+
if (EXEMPT.some((re) => re.test(file))) continue;
|
|
85
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n');
|
|
86
|
+
for (let i = 0; i < lines.length; i++) {
|
|
87
|
+
const line = lines[i];
|
|
88
|
+
if (!WHOLE_READ.test(line)) continue;
|
|
89
|
+
// Resolve to a streamed store by a basename literal on this or the 2 lines above.
|
|
90
|
+
const ctx = (lines[i - 2] || '') + '\n' + (lines[i - 1] || '') + '\n' + line;
|
|
91
|
+
for (const b of registryBasenames) {
|
|
92
|
+
if (ctx.includes(b)) {
|
|
93
|
+
found.push({ file: path.relative(ROOT, file), basename: b, line: i + 1 });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Generate-baseline mode: record current hits as grandfathered.
|
|
100
|
+
if (process.argv.includes('--write-baseline')) {
|
|
101
|
+
fs.writeFileSync(
|
|
102
|
+
BASELINE,
|
|
103
|
+
JSON.stringify(
|
|
104
|
+
{
|
|
105
|
+
note: 'FROZEN baseline of literal whole-file-sync reads of streamed stores at Bounded Accumulation Increment 1. May only shrink. New hits fail the lint.',
|
|
106
|
+
frozenAt: '2026-06-21',
|
|
107
|
+
violations: found.map((f) => ({ file: f.file, basename: f.basename })),
|
|
108
|
+
},
|
|
109
|
+
null,
|
|
110
|
+
2,
|
|
111
|
+
) + '\n',
|
|
112
|
+
);
|
|
113
|
+
console.log(`lint-no-wholefile-sync-read: wrote baseline with ${found.length} grandfathered hit(s).`);
|
|
114
|
+
process.exit(0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const newViolations = found.filter((f) => !baselineSet.has(f.file + '::' + f.basename));
|
|
118
|
+
if (newViolations.length) {
|
|
119
|
+
console.error('lint-no-wholefile-sync-read: FAIL — new whole-file sync read(s) of a streamed store:');
|
|
120
|
+
for (const v of newViolations) {
|
|
121
|
+
console.error(` • ${v.file}:${v.line} reads ${v.basename} whole. Use streaming / segment / SQLite, or route through src/core/storage/.`);
|
|
122
|
+
}
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
console.log(
|
|
126
|
+
`lint-no-wholefile-sync-read: OK — ${found.length} literal hit(s), all grandfathered (${baselineSet.size} baselined).`,
|
|
127
|
+
);
|
|
128
|
+
process.exit(0);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* lint-store-retention-declared.js — Bounded Accumulation §3b (Lint 1).
|
|
4
|
+
*
|
|
5
|
+
* Every persistent store (a category in state-coherence-registry.json) MUST declare a
|
|
6
|
+
* retention policy. This lint is the RATCHET: it FAILS on a NEW category that has no
|
|
7
|
+
* `retention`, while grandfathering the frozen legacy backlog (the §4 retrofit counts
|
|
8
|
+
* that backlog down). It also enforces D6 set-monotonicity — the frozen baseline may
|
|
9
|
+
* only SHRINK: growing it (gaming the ratchet by adding a new store to the allowlist
|
|
10
|
+
* instead of giving it retention) fails.
|
|
11
|
+
*
|
|
12
|
+
* Pairs with lint-state-registry.js (which forces a write-site to be REGISTERED at all);
|
|
13
|
+
* this lint forces a registered store to also be BOUNDED.
|
|
14
|
+
*
|
|
15
|
+
* Exit codes: 0 pass · 1 violation · 2 cannot-read (fail-loud, never silently skip).
|
|
16
|
+
*/
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
23
|
+
// --registry / --baseline override the defaults (for tests + alternate checkouts).
|
|
24
|
+
function argVal(flag) {
|
|
25
|
+
const i = process.argv.indexOf(flag);
|
|
26
|
+
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : null;
|
|
27
|
+
}
|
|
28
|
+
const REGISTRY = path.resolve(argVal('--registry') || path.join(ROOT, 'src', 'data', 'state-coherence-registry.json'));
|
|
29
|
+
const BASELINE = path.resolve(argVal('--baseline') || path.join(ROOT, 'scripts', 'bounded-accumulation-retention-baseline.json'));
|
|
30
|
+
|
|
31
|
+
// The category count of the frozen baseline at Increment 1. The baseline may only
|
|
32
|
+
// shrink; a larger set means the allowlist was grown to dodge the ratchet (D6).
|
|
33
|
+
const FROZEN_BASELINE_COUNT = 75;
|
|
34
|
+
|
|
35
|
+
function hasRetention(e) {
|
|
36
|
+
return !!(e && e.retention && typeof e.retention === 'object' && Object.keys(e.retention).length > 0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let registry, baseline;
|
|
40
|
+
try {
|
|
41
|
+
registry = JSON.parse(fs.readFileSync(REGISTRY, 'utf8'));
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error('lint-store-retention-declared: cannot read registry: ' + e.message);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
baseline = JSON.parse(fs.readFileSync(BASELINE, 'utf8'));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error('lint-store-retention-declared: cannot read frozen baseline: ' + e.message);
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const baselineSet = new Set(baseline.categories || []);
|
|
54
|
+
const errors = [];
|
|
55
|
+
|
|
56
|
+
// D6: the frozen baseline may only shrink.
|
|
57
|
+
if ((baseline.categories || []).length > FROZEN_BASELINE_COUNT) {
|
|
58
|
+
errors.push(
|
|
59
|
+
`Retention baseline GREW (${baseline.categories.length} > frozen ${FROZEN_BASELINE_COUNT}). ` +
|
|
60
|
+
`The baseline may only shrink (D6 set-monotonicity) — declare a retention policy on the new ` +
|
|
61
|
+
`store instead of adding it to the grandfathered allowlist.`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Every no-retention category must be in the frozen baseline (grandfathered legacy).
|
|
66
|
+
for (const e of registry.entries || []) {
|
|
67
|
+
if (hasRetention(e)) continue;
|
|
68
|
+
if (!baselineSet.has(e.category)) {
|
|
69
|
+
errors.push(
|
|
70
|
+
`Store category "${e.category}" has NO retention policy and is not in the frozen baseline. ` +
|
|
71
|
+
`Every persistent store must declare a retention policy (Bounded Accumulation §2). Add a ` +
|
|
72
|
+
`"retention" field to its state-coherence-registry.json entry — one of: ` +
|
|
73
|
+
`{class:'A',access:'streamed',maxBytes,keepSegments} (rotating log), ` +
|
|
74
|
+
`{class:'C',access:'streamed',complianceHold:true} (audit/forensic — archive, never drop), ` +
|
|
75
|
+
`{class:'sqlite',access:'sqlite',maxAgeMs} (indexed store), ` +
|
|
76
|
+
`{class:'R',access:'protocol-reader',boundedBy:'replication-protocol'} (replication substrate), or ` +
|
|
77
|
+
`{class:'resolution',boundedByResolution:true,maxOpenItems} (actionable queue).`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (errors.length) {
|
|
83
|
+
console.error('lint-store-retention-declared: FAIL');
|
|
84
|
+
for (const e of errors) console.error(' • ' + e);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const retentioned = (registry.entries || []).filter(hasRetention).length;
|
|
89
|
+
console.log(
|
|
90
|
+
`lint-store-retention-declared: OK — ${retentioned} stores retentioned, ` +
|
|
91
|
+
`${baselineSet.size} grandfathered (retrofit backlog, may only shrink).`,
|
|
92
|
+
);
|
|
93
|
+
process.exit(0);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "./builtin-manifest.schema.json",
|
|
3
3
|
"schemaVersion": 1,
|
|
4
|
-
"generatedAt": "2026-06-
|
|
5
|
-
"instarVersion": "1.3.
|
|
4
|
+
"generatedAt": "2026-06-22T00:51:13.440Z",
|
|
5
|
+
"instarVersion": "1.3.638",
|
|
6
6
|
"entryCount": 202,
|
|
7
7
|
"entries": {
|
|
8
8
|
"hook:session-start": {
|
|
@@ -1530,7 +1530,7 @@
|
|
|
1530
1530
|
"type": "subsystem",
|
|
1531
1531
|
"domain": "server",
|
|
1532
1532
|
"sourcePath": "src/server/AgentServer.ts",
|
|
1533
|
-
"contentHash": "
|
|
1533
|
+
"contentHash": "945b9253690012a195bdad396d909e82e6415e061f89281865ef0decaf5d7ed6",
|
|
1534
1534
|
"since": "2025-01-01"
|
|
1535
1535
|
},
|
|
1536
1536
|
"subsystem:session-manager": {
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
"paths": [
|
|
15
15
|
"state/coherence-journal/"
|
|
16
16
|
],
|
|
17
|
-
"grandfathered": false
|
|
17
|
+
"grandfathered": false,
|
|
18
|
+
"retention": {
|
|
19
|
+
"class": "R",
|
|
20
|
+
"access": "protocol-reader",
|
|
21
|
+
"boundedBy": "replication-protocol",
|
|
22
|
+
"note": "CARVED OUT — bounded inside CoherenceJournal via KindRetention + the new seq-floor prune guard; never externally rotated"
|
|
23
|
+
}
|
|
18
24
|
},
|
|
19
25
|
{
|
|
20
26
|
"category": "pending-pulls",
|
|
@@ -171,7 +177,13 @@
|
|
|
171
177
|
"paths": [
|
|
172
178
|
"state/coherence-journal/*.topic-placement.jsonl"
|
|
173
179
|
],
|
|
174
|
-
"grandfathered": false
|
|
180
|
+
"grandfathered": false,
|
|
181
|
+
"retention": {
|
|
182
|
+
"class": "R",
|
|
183
|
+
"access": "protocol-reader",
|
|
184
|
+
"boundedBy": "replication-protocol",
|
|
185
|
+
"note": "CARVED OUT — replication substrate"
|
|
186
|
+
}
|
|
175
187
|
},
|
|
176
188
|
{
|
|
177
189
|
"category": "autonomous-working-artifacts",
|
|
@@ -432,7 +444,12 @@
|
|
|
432
444
|
"state/resource-ledger.db",
|
|
433
445
|
"state/resources.db"
|
|
434
446
|
],
|
|
435
|
-
"grandfathered": false
|
|
447
|
+
"grandfathered": false,
|
|
448
|
+
"retention": {
|
|
449
|
+
"class": "sqlite",
|
|
450
|
+
"access": "sqlite",
|
|
451
|
+
"maxAgeMs": 2592000000
|
|
452
|
+
}
|
|
436
453
|
},
|
|
437
454
|
{
|
|
438
455
|
"category": "lease-coordination-state",
|
|
@@ -608,7 +625,14 @@
|
|
|
608
625
|
"state/destructive-ops.jsonl",
|
|
609
626
|
"logs/destructive-ops.jsonl"
|
|
610
627
|
],
|
|
611
|
-
"grandfathered": false
|
|
628
|
+
"grandfathered": false,
|
|
629
|
+
"retention": {
|
|
630
|
+
"class": "C",
|
|
631
|
+
"access": "streamed",
|
|
632
|
+
"complianceHold": true,
|
|
633
|
+
"keepSegments": 0,
|
|
634
|
+
"note": "forensic trail — archive, never drop-delete (replaces SafeGitExecutor.maybeRotateAuditLog drop-rotate)"
|
|
635
|
+
}
|
|
612
636
|
},
|
|
613
637
|
{
|
|
614
638
|
"category": "audit-job-runs",
|
|
@@ -621,7 +645,13 @@
|
|
|
621
645
|
"state/job-runs.jsonl",
|
|
622
646
|
"logs/job-runs.jsonl"
|
|
623
647
|
],
|
|
624
|
-
"grandfathered": false
|
|
648
|
+
"grandfathered": false,
|
|
649
|
+
"retention": {
|
|
650
|
+
"class": "A",
|
|
651
|
+
"access": "streamed",
|
|
652
|
+
"maxBytes": 33554432,
|
|
653
|
+
"keepSegments": 4
|
|
654
|
+
}
|
|
625
655
|
},
|
|
626
656
|
{
|
|
627
657
|
"category": "audit-reaper",
|
|
@@ -671,7 +701,14 @@
|
|
|
671
701
|
"state/security.jsonl",
|
|
672
702
|
"logs/security.jsonl"
|
|
673
703
|
],
|
|
674
|
-
"grandfathered": false
|
|
704
|
+
"grandfathered": false,
|
|
705
|
+
"retention": {
|
|
706
|
+
"class": "C",
|
|
707
|
+
"access": "streamed",
|
|
708
|
+
"complianceHold": true,
|
|
709
|
+
"keepSegments": 0,
|
|
710
|
+
"note": "forensic trail — archive, never drop-delete (replaces SecurityLog maybeRotateJsonl keep-ratio trim)"
|
|
711
|
+
}
|
|
675
712
|
},
|
|
676
713
|
{
|
|
677
714
|
"category": "audit-decision-journal",
|
|
@@ -749,7 +786,13 @@
|
|
|
749
786
|
"state/telegram-messages.jsonl",
|
|
750
787
|
"logs/telegram-messages.jsonl"
|
|
751
788
|
],
|
|
752
|
-
"grandfathered": false
|
|
789
|
+
"grandfathered": false,
|
|
790
|
+
"retention": {
|
|
791
|
+
"class": "A",
|
|
792
|
+
"access": "streamed",
|
|
793
|
+
"maxBytes": 33554432,
|
|
794
|
+
"keepSegments": 4
|
|
795
|
+
}
|
|
753
796
|
},
|
|
754
797
|
{
|
|
755
798
|
"category": "audit-a2a-messages",
|
|
@@ -814,7 +857,13 @@
|
|
|
814
857
|
"state/feedback.jsonl",
|
|
815
858
|
"logs/feedback.jsonl"
|
|
816
859
|
],
|
|
817
|
-
"grandfathered": false
|
|
860
|
+
"grandfathered": false,
|
|
861
|
+
"retention": {
|
|
862
|
+
"class": "A",
|
|
863
|
+
"access": "streamed",
|
|
864
|
+
"maxBytes": 33554432,
|
|
865
|
+
"keepSegments": 4
|
|
866
|
+
}
|
|
818
867
|
},
|
|
819
868
|
{
|
|
820
869
|
"category": "audit-apprenticeship-decisions",
|
|
@@ -840,7 +889,13 @@
|
|
|
840
889
|
"state/token-ledger.db",
|
|
841
890
|
"state/tokens.db"
|
|
842
891
|
],
|
|
843
|
-
"grandfathered": false
|
|
892
|
+
"grandfathered": false,
|
|
893
|
+
"retention": {
|
|
894
|
+
"class": "sqlite",
|
|
895
|
+
"access": "sqlite",
|
|
896
|
+
"maxAgeMs": 2592000000,
|
|
897
|
+
"note": "incremental-vacuum + batched delete off-thread (Increment 2)"
|
|
898
|
+
}
|
|
844
899
|
},
|
|
845
900
|
{
|
|
846
901
|
"category": "derived-semantic-memory",
|
|
@@ -856,7 +911,13 @@
|
|
|
856
911
|
"state/episodes/",
|
|
857
912
|
"memory/semantic.jsonl"
|
|
858
913
|
],
|
|
859
|
-
"grandfathered": false
|
|
914
|
+
"grandfathered": false,
|
|
915
|
+
"retention": {
|
|
916
|
+
"class": "sqlite",
|
|
917
|
+
"access": "sqlite",
|
|
918
|
+
"maxAgeMs": 7776000000,
|
|
919
|
+
"note": "mixed .db + memory/semantic.jsonl; jsonl arm is A-class streamed in retrofit"
|
|
920
|
+
}
|
|
860
921
|
},
|
|
861
922
|
{
|
|
862
923
|
"category": "derived-topic-memory",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Instar gains a new constitutional standard — **Bounded Accumulation: every persistent store must
|
|
9
|
+
declare a ceiling and stay under it** — and the first slice of its enforcement. This is the
|
|
10
|
+
storage-dimension twin of the existing "No Unbounded Loops" and "Bounded Notification Surface"
|
|
11
|
+
standards: it exists because an agent that runs for months accumulates data in append-only logs and
|
|
12
|
+
databases that only ever grow, and reading one of those multi-MB files all at once freezes the
|
|
13
|
+
single-threaded event loop (the cause of recurring health-check failures and restarts).
|
|
14
|
+
|
|
15
|
+
This increment is non-behavioral — it adds the substrate without changing how any current store
|
|
16
|
+
behaves: a registry that records each store's retention policy, an event-loop-safe segment-rotation
|
|
17
|
+
primitive (rename, never read-and-rewrite), a `JsonlStore` accessor that all JSONL persistence will
|
|
18
|
+
route through, a growth-burst test that proves retention actually bounds a store on disk, and two
|
|
19
|
+
build-time lints that ratchet new violations (a new store must declare a ceiling; a new whole-file
|
|
20
|
+
synchronous read of a large store is rejected) while grandfathering the existing tree.
|
|
21
|
+
|
|
22
|
+
## What to Tell Your User
|
|
23
|
+
|
|
24
|
+
Nothing changes today in how your agent behaves. This lays the foundation that stops your agent's
|
|
25
|
+
on-disk data from growing without bound and stops the kind of large-file reads that briefly freeze
|
|
26
|
+
it. The actual trimming of existing oversized files, and the one-time cleanup of data that has
|
|
27
|
+
already accumulated, come in the next increments (the cleanup is operator-gated — it asks before
|
|
28
|
+
deleting any history).
|
|
29
|
+
|
|
30
|
+
## Summary of New Capabilities
|
|
31
|
+
|
|
32
|
+
- A retention policy field on every store in the state-coherence registry (10 stores declared,
|
|
33
|
+
75 legacy categories tracked as a shrink-only backlog).
|
|
34
|
+
- `maybeRotateJsonlSegment` (event-loop-safe rename rotation) + `JsonlStore` (the accessor funnel
|
|
35
|
+
with an amortized size-check) in `src/core/storage/`.
|
|
36
|
+
- Two CI lints — `lint-store-retention-declared` (a new store must declare a retention policy) and
|
|
37
|
+
`lint-no-wholefile-sync-read` (no new literal whole-file synchronous read of a streamed store) —
|
|
38
|
+
both wired into `npm run lint` over a frozen baseline (only NEW violations fail).
|
|
39
|
+
- A growth-burst invariant test that floods a registered store and asserts the on-disk footprint
|
|
40
|
+
stays bounded by the declared policy (and that compliance-hold audit stores never drop their
|
|
41
|
+
oldest segment).
|