instar 1.3.310 → 1.3.311
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/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +32 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +17 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/BootSelfKnowledge.d.ts +121 -0
- package/dist/core/BootSelfKnowledge.d.ts.map +1 -0
- package/dist/core/BootSelfKnowledge.js +285 -0
- package/dist/core/BootSelfKnowledge.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +104 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +26 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +8 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +135 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +72 -64
- package/src/scaffold/templates.ts +8 -0
- package/src/templates/scripts/secret-get.mjs +143 -0
- package/upgrades/1.3.311.md +20 -0
- package/upgrades/side-effects/session-boot-self-knowledge.md +78 -0
package/dist/server/routes.js
CHANGED
|
@@ -17,6 +17,7 @@ import { planTransferByNickname } from '../core/TransferByNickname.js';
|
|
|
17
17
|
import { IntelligenceRouter } from '../core/IntelligenceRouter.js';
|
|
18
18
|
import { knownComponents } from '../core/componentCategories.js';
|
|
19
19
|
import { SecretStore } from '../core/SecretStore.js';
|
|
20
|
+
import { writeConfigAtomic, readSelfKnowledgeFlags } from '../core/BootSelfKnowledge.js';
|
|
20
21
|
import { rateLimiter, signViewPath } from './middleware.js';
|
|
21
22
|
import { writeLifelineRestartSignal } from '../core/version-skew.js';
|
|
22
23
|
import { readSessionClocks } from '../core/SessionClockReader.js';
|
|
@@ -11225,6 +11226,140 @@ export function createRoutes(ctx) {
|
|
|
11225
11226
|
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to read preferences' });
|
|
11226
11227
|
}
|
|
11227
11228
|
});
|
|
11229
|
+
// ── Session Boot Self-Knowledge (spec: session-boot-self-knowledge.md) ──
|
|
11230
|
+
//
|
|
11231
|
+
// The "what I already have" block the session-start hook injects at boot:
|
|
11232
|
+
// vault secret NAMES (never values; the same secretKeyPaths derivation as
|
|
11233
|
+
// /secrets/sync-status, presented depth-capped) + self-asserted operational
|
|
11234
|
+
// facts. Deterministic config/capability discovery — SIGNAL-ONLY, gates
|
|
11235
|
+
// nothing. NOTE: independent of the SelfKnowledgeTree (search/validate/
|
|
11236
|
+
// health above) — both are self-knowledge surfaces; this one is the
|
|
11237
|
+
// deterministic boot inventory.
|
|
11238
|
+
//
|
|
11239
|
+
// Availability rides the developmentAgent gate (`enabled ?? !!developmentAgent`
|
|
11240
|
+
// — dark fleet / live dev-agent; the live-fleet flip is a tracked follow-up,
|
|
11241
|
+
// spec §Availability). Flags + facts are read FRESH from disk per request
|
|
11242
|
+
// (BootSelfKnowledge re-reads config.json) so enable/disable + fact edits
|
|
11243
|
+
// take effect without a server restart.
|
|
11244
|
+
//
|
|
11245
|
+
// A decrypt failure is a 200 with `vaultState:'decrypt-failed'` and the
|
|
11246
|
+
// hands-off warning block — NEVER a 500 (the hook's `curl -sf` swallows 5xx,
|
|
11247
|
+
// which would silently hide the exact warning the honesty rule exists to
|
|
11248
|
+
// deliver).
|
|
11249
|
+
router.get('/self-knowledge/session-context', async (req, res) => {
|
|
11250
|
+
try {
|
|
11251
|
+
const configPath = path.join(ctx.config.projectDir, '.instar', 'config.json');
|
|
11252
|
+
const { BootSelfKnowledge, DEFAULT_MAX_BYTES } = await import('../core/BootSelfKnowledge.js');
|
|
11253
|
+
const freshFlags = readSelfKnowledgeFlags(configPath);
|
|
11254
|
+
const enabled = freshFlags.enabled ?? Boolean(ctx.config.developmentAgent);
|
|
11255
|
+
if (!enabled) {
|
|
11256
|
+
res.status(503).json({ error: 'self-knowledge session-context disabled' });
|
|
11257
|
+
return;
|
|
11258
|
+
}
|
|
11259
|
+
const maxBytes = typeof freshFlags.maxInjectedBytes === 'number' && freshFlags.maxInjectedBytes > 0
|
|
11260
|
+
? freshFlags.maxInjectedBytes
|
|
11261
|
+
: DEFAULT_MAX_BYTES;
|
|
11262
|
+
const full = String(req.query.full ?? '') === '1';
|
|
11263
|
+
const bsk = new BootSelfKnowledge({ stateDir: ctx.config.stateDir, configPath });
|
|
11264
|
+
const result = bsk.sessionContext(maxBytes, { full });
|
|
11265
|
+
console.log(`[boot-self-knowledge] served names=${result.names.length} facts=${result.factCount} vaultState=${result.vaultState} bytes=${Buffer.byteLength(result.block, 'utf8')}`);
|
|
11266
|
+
if (result.vaultState === 'decrypt-failed') {
|
|
11267
|
+
console.warn('[boot-self-knowledge] vault DECRYPT-FAILED — served hands-off warning block (not an empty vault)');
|
|
11268
|
+
}
|
|
11269
|
+
res.json(result);
|
|
11270
|
+
}
|
|
11271
|
+
catch (err) {
|
|
11272
|
+
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to build boot self-knowledge' });
|
|
11273
|
+
}
|
|
11274
|
+
});
|
|
11275
|
+
// Operational-facts writer (No-Manual-Work: nobody hand-edits config.json).
|
|
11276
|
+
// POST appends a fact (stamped {fact, updatedAt, machine}); DELETE removes by
|
|
11277
|
+
// {match} (preferred; 409 when ambiguous) or {index, expect} (409 on
|
|
11278
|
+
// mismatch — closes the read-then-delete index race). Both write through
|
|
11279
|
+
// writeConfigAtomic() (re-read → mutate → temp+rename; last-writer-wins
|
|
11280
|
+
// semantics vs the other, pre-existing non-atomic config writers).
|
|
11281
|
+
router.post('/self-knowledge/facts', async (req, res) => {
|
|
11282
|
+
try {
|
|
11283
|
+
const { MAX_FACT_CHARS, MAX_FACTS_STORED } = await import('../core/BootSelfKnowledge.js');
|
|
11284
|
+
const fact = typeof req.body?.fact === 'string' ? req.body.fact.trim() : '';
|
|
11285
|
+
if (!fact) {
|
|
11286
|
+
res.status(400).json({ error: 'fact must be a non-empty string' });
|
|
11287
|
+
return;
|
|
11288
|
+
}
|
|
11289
|
+
if (fact.length > MAX_FACT_CHARS) {
|
|
11290
|
+
res.status(400).json({ error: `fact exceeds ${MAX_FACT_CHARS} chars` });
|
|
11291
|
+
return;
|
|
11292
|
+
}
|
|
11293
|
+
const configPath = path.join(ctx.config.projectDir, '.instar', 'config.json');
|
|
11294
|
+
const outcome = writeConfigAtomic(configPath, (cfg) => {
|
|
11295
|
+
const sk = (cfg.selfKnowledge ??= {});
|
|
11296
|
+
const facts = (sk.operationalFacts ??= []);
|
|
11297
|
+
const existing = facts.map((f) => (typeof f === 'string' ? f : f?.fact));
|
|
11298
|
+
if (existing.includes(fact))
|
|
11299
|
+
return { error: { status: 409, message: 'duplicate fact' } };
|
|
11300
|
+
if (facts.length >= MAX_FACTS_STORED) {
|
|
11301
|
+
return { error: { status: 409, message: `fact cap reached (${MAX_FACTS_STORED}) — remove one first` } };
|
|
11302
|
+
}
|
|
11303
|
+
facts.push({ fact, updatedAt: new Date().toISOString(), machine: os.hostname() });
|
|
11304
|
+
return { value: facts };
|
|
11305
|
+
});
|
|
11306
|
+
if (outcome.error) {
|
|
11307
|
+
res.status(outcome.error.status).json({ error: outcome.error.message });
|
|
11308
|
+
return;
|
|
11309
|
+
}
|
|
11310
|
+
console.log(`[boot-self-knowledge] fact-added (${fact.slice(0, 60)}${fact.length > 60 ? '…' : ''})`);
|
|
11311
|
+
res.json({ success: true, facts: outcome.value });
|
|
11312
|
+
}
|
|
11313
|
+
catch (err) {
|
|
11314
|
+
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to add fact' });
|
|
11315
|
+
}
|
|
11316
|
+
});
|
|
11317
|
+
router.delete('/self-knowledge/facts', (req, res) => {
|
|
11318
|
+
try {
|
|
11319
|
+
const match = typeof req.body?.match === 'string' ? req.body.match : null;
|
|
11320
|
+
const index = typeof req.body?.index === 'number' ? req.body.index : null;
|
|
11321
|
+
const expect = typeof req.body?.expect === 'string' ? req.body.expect : null;
|
|
11322
|
+
if (!match && index === null) {
|
|
11323
|
+
res.status(400).json({ error: 'provide {match} or {index, expect}' });
|
|
11324
|
+
return;
|
|
11325
|
+
}
|
|
11326
|
+
const configPath = path.join(ctx.config.projectDir, '.instar', 'config.json');
|
|
11327
|
+
const outcome = writeConfigAtomic(configPath, (cfg) => {
|
|
11328
|
+
const sk = cfg.selfKnowledge;
|
|
11329
|
+
const facts = sk?.operationalFacts ?? [];
|
|
11330
|
+
const text = (f) => (typeof f === 'string' ? f : (f?.fact ?? ''));
|
|
11331
|
+
let removeAt = -1;
|
|
11332
|
+
if (match) {
|
|
11333
|
+
const hits = facts.map((f, i) => [text(f), i]).filter(([t]) => t.includes(match));
|
|
11334
|
+
if (hits.length === 0)
|
|
11335
|
+
return { error: { status: 404, message: 'no fact matches' } };
|
|
11336
|
+
if (hits.length > 1)
|
|
11337
|
+
return { error: { status: 409, message: `ambiguous match (${hits.length} facts) — narrow it` } };
|
|
11338
|
+
removeAt = hits[0][1];
|
|
11339
|
+
}
|
|
11340
|
+
else {
|
|
11341
|
+
if (index === null || index < 0 || index >= facts.length) {
|
|
11342
|
+
return { error: { status: 404, message: 'index out of range' } };
|
|
11343
|
+
}
|
|
11344
|
+
if (!expect || text(facts[index]) !== expect) {
|
|
11345
|
+
return { error: { status: 409, message: 'expect does not match the fact at that index (it may have moved) — re-read and retry' } };
|
|
11346
|
+
}
|
|
11347
|
+
removeAt = index;
|
|
11348
|
+
}
|
|
11349
|
+
const [removed] = facts.splice(removeAt, 1);
|
|
11350
|
+
return { value: { removed: text(removed), facts } };
|
|
11351
|
+
});
|
|
11352
|
+
if (outcome.error) {
|
|
11353
|
+
res.status(outcome.error.status).json({ error: outcome.error.message });
|
|
11354
|
+
return;
|
|
11355
|
+
}
|
|
11356
|
+
console.log(`[boot-self-knowledge] fact-removed (${String(outcome.value?.removed ?? '').slice(0, 60)})`);
|
|
11357
|
+
res.json({ success: true, ...outcome.value });
|
|
11358
|
+
}
|
|
11359
|
+
catch (err) {
|
|
11360
|
+
res.status(500).json({ error: err instanceof Error ? err.message : 'Failed to remove fact' });
|
|
11361
|
+
}
|
|
11362
|
+
});
|
|
11228
11363
|
// ── Corrections (Correction & Preference Learning Sentinel, Slice 1b) ──
|
|
11229
11364
|
//
|
|
11230
11365
|
// Read surface over the CorrectionLedger — distilled, scrubbed correction /
|