instar 1.3.475 → 1.3.476

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.475",
3
+ "version": "1.3.476",
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",
@@ -0,0 +1,150 @@
1
+ /**
2
+ * throwaway-identity.mjs — mint genuinely-distinct, readable throwaway email
3
+ * identities for live-integration test harnesses (Slack, Discord, …).
4
+ *
5
+ * Backed by the mail.tm public disposable-mailbox API. Each minted inbox is a
6
+ * real, distinct, readable address — so a live test can use N of them as N
7
+ * GENUINELY DISTINCT principals (distinct addresses → distinct provider user
8
+ * IDs) with ZERO real accounts. This is the autonomous half of the
9
+ * test-identity provisioning the Live Integration Security-Test Harness needs;
10
+ * the only step it does NOT cover is an anti-bot signup CAPTCHA at workspace/
11
+ * account creation, which is a deliberate human-verification control (see
12
+ * docs/specs/JUDGMENT-PERMISSION-LIVE-RUN-RUNBOOK.md).
13
+ *
14
+ * Pure helpers (extractCode/extractLink/matchMessage) and HTTP helpers with an
15
+ * injectable `fetchImpl` so the unit test runs fully hermetic (no network).
16
+ *
17
+ * CLI: scripts/throwaway-identity.mjs (mint | wait). This file is the importable
18
+ * library; the CLI is a thin wrapper.
19
+ */
20
+
21
+ const DEFAULT_API_BASE = 'https://api.mail.tm';
22
+
23
+ /** Extract the first verification code (default: a 6-digit run) from a message body. */
24
+ export function extractCode(text, { pattern = /\b(\d{6})\b/ } = {}) {
25
+ if (typeof text !== 'string') return null;
26
+ const m = text.match(pattern);
27
+ return m ? (m[1] ?? m[0]) : null;
28
+ }
29
+
30
+ /** Extract the first URL (optionally matching a substring/regex) from a message body. */
31
+ export function extractLink(text, { match } = {}) {
32
+ if (typeof text !== 'string') return null;
33
+ const urls = text.match(/https?:\/\/[^\s"'<>)\]]+/g) || [];
34
+ if (!match) return urls[0] ?? null;
35
+ const test = match instanceof RegExp ? (u) => match.test(u) : (u) => u.includes(match);
36
+ return urls.find(test) ?? null;
37
+ }
38
+
39
+ /** Does a mail.tm message summary match the caller's filter (subject/from substring or regex)? */
40
+ export function matchMessage(msg, { subject, from } = {}) {
41
+ if (!msg) return false;
42
+ const subjOk = matchField(msg.subject, subject);
43
+ const fromAddr = (msg.from && (msg.from.address || msg.from.name)) || '';
44
+ const fromOk = matchField(fromAddr, from);
45
+ return subjOk && fromOk;
46
+ }
47
+
48
+ function matchField(value, filter) {
49
+ if (filter === undefined || filter === null) return true;
50
+ const v = String(value ?? '');
51
+ return filter instanceof RegExp ? filter.test(v) : v.toLowerCase().includes(String(filter).toLowerCase());
52
+ }
53
+
54
+ function memberArray(json) {
55
+ // mail.tm is a Hydra/JSON-LD API: collections live under "hydra:member".
56
+ if (Array.isArray(json)) return json;
57
+ if (json && Array.isArray(json['hydra:member'])) return json['hydra:member'];
58
+ return [];
59
+ }
60
+
61
+ async function jsonFetch(fetchImpl, url, opts) {
62
+ const res = await fetchImpl(url, opts);
63
+ const text = await res.text();
64
+ let body = null;
65
+ try { body = text ? JSON.parse(text) : null; } catch { body = null; }
66
+ if (!res.ok) {
67
+ const detail = (body && (body['hydra:description'] || body.message || body.detail)) || text.slice(0, 200);
68
+ throw new Error(`mail.tm ${opts?.method || 'GET'} ${url} → ${res.status}: ${detail}`);
69
+ }
70
+ return body;
71
+ }
72
+
73
+ /**
74
+ * Mint a fresh throwaway inbox: pick a domain, create the account, get a token.
75
+ * Returns { address, password, token, accountId }. `rand` is injectable so the
76
+ * test is deterministic and the script never calls Math.random in a workflow.
77
+ */
78
+ export async function createInbox({
79
+ fetchImpl = fetch,
80
+ apiBase = DEFAULT_API_BASE,
81
+ localPart,
82
+ rand = () => Math.floor(Math.random() * 1e10).toString(36),
83
+ } = {}) {
84
+ const domains = memberArray(await jsonFetch(fetchImpl, `${apiBase}/domains`));
85
+ const domain = domains.find((d) => d.isActive !== false)?.domain || domains[0]?.domain;
86
+ if (!domain) throw new Error('mail.tm: no available domain');
87
+ const local = localPart || `echo-${rand()}`;
88
+ const address = `${local}@${domain}`;
89
+ const password = `Echo!${rand()}A9`;
90
+ const account = await jsonFetch(fetchImpl, `${apiBase}/accounts`, {
91
+ method: 'POST',
92
+ headers: { 'Content-Type': 'application/json' },
93
+ body: JSON.stringify({ address, password }),
94
+ });
95
+ const tokenResp = await jsonFetch(fetchImpl, `${apiBase}/token`, {
96
+ method: 'POST',
97
+ headers: { 'Content-Type': 'application/json' },
98
+ body: JSON.stringify({ address, password }),
99
+ });
100
+ if (!tokenResp?.token) throw new Error('mail.tm: token request returned no token');
101
+ return { address, password, token: tokenResp.token, accountId: account?.id ?? null };
102
+ }
103
+
104
+ /** List message summaries in the inbox (most-recent first, per mail.tm). */
105
+ export async function listMessages(token, { fetchImpl = fetch, apiBase = DEFAULT_API_BASE } = {}) {
106
+ const body = await jsonFetch(fetchImpl, `${apiBase}/messages`, {
107
+ headers: { Authorization: `Bearer ${token}` },
108
+ });
109
+ return memberArray(body);
110
+ }
111
+
112
+ /** Fetch one message's full body (text + html). */
113
+ export async function getMessage(token, id, { fetchImpl = fetch, apiBase = DEFAULT_API_BASE } = {}) {
114
+ return jsonFetch(fetchImpl, `${apiBase}/messages/${id}`, {
115
+ headers: { Authorization: `Bearer ${token}` },
116
+ });
117
+ }
118
+
119
+ /**
120
+ * Poll the inbox until a message matches the filter, then return its full body.
121
+ * `sleep` and `now` are injectable so the test runs without real timers/clock.
122
+ * Throws on timeout. Returns the full message (with .text / .html / .subject).
123
+ */
124
+ export async function waitForMessage(token, {
125
+ subject,
126
+ from,
127
+ timeoutMs = 120_000,
128
+ intervalMs = 3_000,
129
+ fetchImpl = fetch,
130
+ apiBase = DEFAULT_API_BASE,
131
+ sleep = (ms) => new Promise((r) => setTimeout(r, ms)),
132
+ now = () => Date.now(),
133
+ } = {}) {
134
+ const deadline = now() + timeoutMs;
135
+ // First iteration runs immediately (no upfront sleep).
136
+ for (let first = true; ; first = false) {
137
+ if (!first) {
138
+ if (now() >= deadline) {
139
+ throw new Error(`waitForMessage: timed out after ${timeoutMs}ms (subject=${subject ?? '*'} from=${from ?? '*'})`);
140
+ }
141
+ await sleep(intervalMs);
142
+ }
143
+ const summaries = await listMessages(token, { fetchImpl, apiBase });
144
+ const hit = summaries.find((m) => matchMessage(m, { subject, from }));
145
+ if (hit) return getMessage(token, hit.id, { fetchImpl, apiBase });
146
+ if (first && now() >= deadline) {
147
+ throw new Error(`waitForMessage: timed out after ${timeoutMs}ms (subject=${subject ?? '*'} from=${from ?? '*'})`);
148
+ }
149
+ }
150
+ }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * throwaway-identity.mjs — CLI for the disposable-identity helper (lib/throwaway-identity.mjs).
4
+ *
5
+ * node scripts/throwaway-identity.mjs mint
6
+ * → mints a fresh inbox; prints JSON { address, password, token, accountId }.
7
+ *
8
+ * node scripts/throwaway-identity.mjs wait <token> [--subject S] [--from F]
9
+ * [--code | --link [MATCH]] [--timeout MS]
10
+ * → polls the inbox until a matching message arrives, then prints either
11
+ * the full message JSON, or (with --code/--link) just the extracted value.
12
+ *
13
+ * Use for live-integration test-identity provisioning (the autonomous half — an
14
+ * anti-bot signup CAPTCHA at account creation is the human handoff).
15
+ */
16
+ import {
17
+ createInbox,
18
+ waitForMessage,
19
+ extractCode,
20
+ extractLink,
21
+ } from './lib/throwaway-identity.mjs';
22
+
23
+ function parseFlags(args) {
24
+ const out = { _: [] };
25
+ for (let i = 0; i < args.length; i++) {
26
+ const a = args[i];
27
+ if (a === '--code') out.code = true;
28
+ else if (a === '--link') { out.link = true; if (args[i + 1] && !args[i + 1].startsWith('--')) out.linkMatch = args[++i]; }
29
+ else if (a === '--subject') out.subject = args[++i];
30
+ else if (a === '--from') out.from = args[++i];
31
+ else if (a === '--timeout') out.timeout = parseInt(args[++i], 10);
32
+ else out._.push(a);
33
+ }
34
+ return out;
35
+ }
36
+
37
+ async function main() {
38
+ const [cmd, ...rest] = process.argv.slice(2);
39
+ const flags = parseFlags(rest);
40
+
41
+ if (cmd === 'mint') {
42
+ const inbox = await createInbox();
43
+ process.stdout.write(JSON.stringify(inbox) + '\n');
44
+ return;
45
+ }
46
+
47
+ if (cmd === 'wait') {
48
+ const token = flags._[0];
49
+ if (!token) { console.error('usage: throwaway-identity.mjs wait <token> [--subject S] [--from F] [--code|--link [MATCH]] [--timeout MS]'); process.exit(1); }
50
+ const msg = await waitForMessage(token, {
51
+ subject: flags.subject,
52
+ from: flags.from,
53
+ timeoutMs: flags.timeout ?? 120_000,
54
+ });
55
+ const text = msg.text || msg.html?.join?.('\n') || (Array.isArray(msg.html) ? msg.html.join('\n') : msg.html) || '';
56
+ if (flags.code) {
57
+ const code = extractCode(text);
58
+ if (!code) { console.error('no verification code found in message'); process.exit(1); }
59
+ process.stdout.write(code + '\n');
60
+ } else if (flags.link) {
61
+ const link = extractLink(text, flags.linkMatch ? { match: flags.linkMatch } : {});
62
+ if (!link) { console.error('no link found in message'); process.exit(1); }
63
+ process.stdout.write(link + '\n');
64
+ } else {
65
+ process.stdout.write(JSON.stringify({ subject: msg.subject, from: msg.from, text }) + '\n');
66
+ }
67
+ return;
68
+ }
69
+
70
+ console.error('usage: throwaway-identity.mjs <mint | wait>');
71
+ process.exit(1);
72
+ }
73
+
74
+ import { pathToFileURL } from 'node:url';
75
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
76
+ main().catch((err) => { console.error(err instanceof Error ? err.message : String(err)); process.exit(1); });
77
+ }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-06-10T18:44:18.495Z",
5
- "instarVersion": "1.3.475",
4
+ "generatedAt": "2026-06-10T19:09:40.028Z",
5
+ "instarVersion": "1.3.476",
6
6
  "entryCount": 201,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -0,0 +1,33 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ Added a standalone test utility — `scripts/throwaway-identity.mjs` (+ `scripts/lib/`) — that
9
+ mints genuinely-distinct, readable throwaway email inboxes via the mail.tm public
10
+ disposable-mailbox API and extracts verification codes/links from them. It's the autonomous
11
+ half of provisioning distinct test identities for the Live Integration Security-Test Harness
12
+ (Slack today, any integration later): N distinct inboxes → N genuinely-distinct principals,
13
+ zero real accounts. No runtime code, gate, or config is touched.
14
+
15
+ ## What to Tell Your User
16
+
17
+ Nothing changes in how the agent runs. This is a developer/test tool. It lets the agent set
18
+ up several genuinely-different throwaway test users (and read their email) on its own, so a
19
+ live integration test can use real distinct identities without anyone hand-creating email
20
+ accounts. The only step it intentionally leaves to a human is passing an anti-bot CAPTCHA at
21
+ workspace creation.
22
+
23
+ ## Summary of New Capabilities
24
+
25
+ - `scripts/throwaway-identity.mjs mint` — create a fresh readable throwaway inbox.
26
+ - `scripts/throwaway-identity.mjs wait <token>` — await an email and extract its code/link.
27
+ - `scripts/lib/throwaway-identity.mjs` — importable helper (HTTP injectable for hermetic tests).
28
+
29
+ ## Evidence
30
+
31
+ - 15 hermetic unit tests (`tests/unit/throwaway-identity.test.ts`) — pure extractors + the
32
+ mint / poll-until-match / timeout flow, with fetch + clock injected (no network).
33
+ - Live CLI smoke minted a real inbox + token. `tsc --noEmit` clean.
@@ -0,0 +1,48 @@
1
+ # Side-Effects Review — throwaway-identity helper
2
+
3
+ **Version / slug:** `throwaway-identity-helper`
4
+ **Date:** `2026-06-10`
5
+ **Author:** `echo`
6
+ **Tier:** `1` (standalone test-tooling script + lib + hermetic test; no runtime/gate/config wiring)
7
+ **Second-pass reviewer:** `not required`
8
+
9
+ ## Summary of the change
10
+
11
+ Adds `scripts/lib/throwaway-identity.mjs` (importable) + `scripts/throwaway-identity.mjs`
12
+ (CLI) — mints genuinely-distinct, readable throwaway email inboxes via the mail.tm public
13
+ disposable-mailbox API, and polls/extracts codes+links from them. The autonomous half of
14
+ test-identity provisioning for live-integration test harnesses (Slack/Discord/…). + a fully
15
+ hermetic unit test (injected fetch + clock, no network) and an ELI16.
16
+
17
+ ## Decision-point inventory
18
+
19
+ None in runtime — it's standalone tooling, never imported by `src/` or wired into a gate.
20
+ Internal branch points (domain selection, message-match filter, timeout) are pure functions
21
+ covered by the test.
22
+
23
+ ## 1. Over-block
24
+
25
+ Nothing is rejected at runtime. The tool only calls a public disposable-mail API and reads
26
+ inboxes it just created. It is not on any agent code path.
27
+
28
+ ## 2. Under-block
29
+
30
+ It deliberately does NOT cover the anti-bot signup CAPTCHA at workspace/account creation —
31
+ that is a human-verification control and remains a ~30s human handoff (documented in the
32
+ live-run runbook). It is the email half only.
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ Right layer: a `scripts/` + `scripts/lib/` test utility beside the other dev/test scripts,
37
+ with the HTTP injected so the test is hermetic. Reusable by any integration's live harness,
38
+ not coupled to the permission gate.
39
+
40
+ ## Migration / rollback
41
+
42
+ No migration (standalone tooling). Rollback = delete the two scripts + the test.
43
+
44
+ ## Testing-integrity note
45
+
46
+ 15 hermetic unit tests (pure extractors + the full mint / poll-until-match / timeout flow,
47
+ HTTP + clock injected). A separate live CLI smoke confirmed it mints a real inbox
48
+ (`echo-…@web-library.net` + token). `tsc --noEmit` clean.