instar 1.3.1665 → 1.3.1666

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.
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
5
- "packageVersion": "1.3.1665",
5
+ "packageVersion": "1.3.1666",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "fa0889edea392faf22e071678dfc99ab29a790b3018cd8226791bff29c72eda5",
2
+ "sha256": "f58de796ebb089dbbc16199d4d4e7adb68bc81b328fb344e26b66d36ff78cf75",
3
3
  "registrySha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
4
- "packageVersion": "1.3.1665"
4
+ "packageVersion": "1.3.1666"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
3
3
  "articleCount": 95,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1665"
5
+ "packageVersion": "1.3.1666"
6
6
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1665",
3
+ "version": "1.3.1666",
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",
@@ -13,16 +13,20 @@
13
13
  * evidence to the helper the fleet will run.
14
14
  *
15
15
  * Usage:
16
- * node scripts/build-locked-proof.mjs [--os-major N] [--n N] [--ref REF] [--out DIR] [--dry-run]
16
+ * node scripts/build-locked-proof.mjs [--os-major N] [--n N] [--ref REF] [--out DIR] [--helper-dir NAME] [--dry-run]
17
17
  *
18
18
  * --os-major macOS major to admit (default: this Mac's `sw_vers -productVersion`)
19
19
  * --n build number for the version suffix (default 1)
20
20
  * --ref git revision to build (default HEAD; committed content only)
21
21
  * --out where the scratch tree, .tgz and manifest go (default $TMPDIR/instar-locked-proof)
22
+ * --helper-dir keep the helper in ~/.instar/NAME (desktop-helper-<name>) in this build only. For a proof Mac
23
+ * whose macOS permission row at the default folder names an older build: that row can be neither
24
+ * seen nor cleared, and a new path has none. The helper source, and so its hash, stays the same.
22
25
  * --dry-run stop after the rewrite: print the row and the diff, build nothing
23
26
  *
24
27
  * Output: `<out>/instar-<version>.tgz` and `<out>/locked-proof-manifest-<version>.json`
25
- * ({ packageVersion, commit, osMajor, helperSourceHash, tgz, tgzSha256, createdAt }).
28
+ * ({ packageVersion, commit, osMajor, helperSourceHash, helperDir, tgz, tgzSha256, createdAt }).
29
+ * On a Mac the helper is universal: only a universal helper may take the kept copy.
26
30
  * Install only into a throwaway macOS user's own prefix, with auto-update off.
27
31
  */
28
32
  import { execFileSync } from 'node:child_process';
@@ -31,10 +35,12 @@ import fs from 'node:fs';
31
35
  import os from 'node:os';
32
36
  import path from 'node:path';
33
37
  import { fileURLToPath } from 'node:url';
34
- import { parseOsMajor, proofVersion, rewriteProvenTable } from './lib/locked-proof-build.mjs';
38
+ import { parseOsMajor, proofVersion, rewriteHelperRoot, rewriteProvenTable } from './lib/locked-proof-build.mjs';
35
39
 
36
40
  const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
37
41
  const TABLE_FILE = 'src/core/DesktopProvenVersions.ts';
42
+ const RETENTION_FILE = 'src/core/DesktopHelperRetention.ts';
43
+ const FAT_MAGIC = 0xcafebabe;
38
44
 
39
45
  function arg(name) {
40
46
  const i = process.argv.indexOf(`--${name}`);
@@ -63,6 +69,9 @@ function main() {
63
69
  const basePkg = JSON.parse(git(['show', `${commit}:package.json`]));
64
70
  const version = proofVersion(basePkg.version, n);
65
71
  const rewritten = rewriteProvenTable(committedTable, { osMajor, version });
72
+ const helperDir = arg('helper-dir');
73
+ const rewrittenRetention = helperDir === undefined ? null
74
+ : rewriteHelperRoot(git(['show', `${commit}:${RETENTION_FILE}`]), helperDir);
66
75
 
67
76
  const tableHashBefore = fs.existsSync(path.join(ROOT, TABLE_FILE))
68
77
  ? crypto.createHash('sha256').update(fs.readFileSync(path.join(ROOT, TABLE_FILE))).digest('hex') : null;
@@ -70,6 +79,7 @@ function main() {
70
79
  if (dryRun) {
71
80
  console.log(`[locked-proof] dry run: ${version} at ${commit.slice(0, 12)} admits macOS ${osMajor}`);
72
81
  console.log(rewritten.split('\n').filter(line => line.includes('PROVEN_MACOS_VERSIONS')).join('\n'));
82
+ if (rewrittenRetention) console.log(rewrittenRetention.split('\n').filter(line => line.includes(helperDir)).join('\n'));
73
83
  return;
74
84
  }
75
85
 
@@ -82,6 +92,7 @@ function main() {
82
92
  execFileSync('tar', ['-x', '-C', scratch], { input: archive, maxBuffer: 1 << 30 });
83
93
 
84
94
  fs.writeFileSync(path.join(scratch, TABLE_FILE), rewritten);
95
+ if (rewrittenRetention) fs.writeFileSync(path.join(scratch, RETENTION_FILE), rewrittenRetention);
85
96
  const pkg = JSON.parse(fs.readFileSync(path.join(scratch, 'package.json'), 'utf8'));
86
97
  pkg.version = version;
87
98
  fs.writeFileSync(path.join(scratch, 'package.json'), `${JSON.stringify(pkg, null, 2)}\n`);
@@ -95,6 +106,16 @@ function main() {
95
106
  delete buildEnv.INSTAR_RELEASE_PRIVATE_KEY_PEM_PATH;
96
107
  delete buildEnv.INSTAR_RELEASE_BUILD;
97
108
  execFileSync('npm', ['run', 'build'], { cwd: scratch, stdio: 'inherit', env: buildEnv });
109
+ if (process.platform === 'darwin') {
110
+ // Retention keeps only a universal helper, and a plain build is thin. Only this native step sees the flag, so
111
+ // the lock-file signer above still ran without it.
112
+ execFileSync('node', ['scripts/build-native-desktop-host.mjs'], { cwd: scratch, stdio: 'inherit', env: { ...buildEnv, INSTAR_RELEASE_BUILD: '1' } });
113
+ const head = Buffer.alloc(4);
114
+ const handle = fs.openSync(path.join(scratch, 'dist/native/instar-desktop-host'), 'r');
115
+ fs.readSync(handle, head, 0, 4, 0);
116
+ fs.closeSync(handle);
117
+ if (head.readUInt32BE(0) !== FAT_MAGIC) throw new Error('the helper did not come out universal, so it could never be kept');
118
+ }
98
119
  // The symlink must not be packed; npm pack ignores node_modules, but drop it to be sure.
99
120
  fs.unlinkSync(path.join(scratch, 'node_modules'));
100
121
  const packed = execFileSync('npm', ['pack', '--ignore-scripts', '--pack-destination', out], { cwd: scratch, encoding: 'utf8' })
@@ -103,7 +124,7 @@ function main() {
103
124
 
104
125
  const helper = JSON.parse(fs.readFileSync(path.join(scratch, 'dist/native/instar-desktop-host.json'), 'utf8'));
105
126
  const manifest = {
106
- packageVersion: version, commit, osMajor, helperSourceHash: helper.sourceHash,
127
+ packageVersion: version, commit, osMajor, helperSourceHash: helper.sourceHash, helperDir: helperDir ?? 'desktop-helper',
107
128
  tgz, tgzSha256: crypto.createHash('sha256').update(fs.readFileSync(tgz)).digest('hex'),
108
129
  createdAt: new Date().toISOString(),
109
130
  };
@@ -50,6 +50,27 @@ export function rewriteProvenTable(source, { osMajor, version }) {
50
50
  return source.replace(whole, next);
51
51
  }
52
52
 
53
+ /** The exact default the retained helper's folder is declared with. */
54
+ const HELPER_ROOT_DEFAULT = "path.join(os.homedir(), '.instar', 'desktop-helper')";
55
+
56
+ /**
57
+ * Return the source of DesktopHelperRetention.ts with the kept helper's default
58
+ * folder moved to `~/.instar/<dir>`. macOS keeps a permission row per path, and
59
+ * neither Settings nor tccutil can clear a hidden row for a bare program, so a
60
+ * proof Mac whose row names an older build needs a path with no row. The Swift
61
+ * source is untouched, so the helper source hash that binds the proof is too.
62
+ * Refuses when the default is not found exactly once, or for a name that could
63
+ * land inside the locked store or outside `~/.instar`.
64
+ */
65
+ export function rewriteHelperRoot(source, dir) {
66
+ if (typeof dir !== 'string' || !/^desktop-helper-[a-z0-9-]+$/.test(dir)) {
67
+ throw new Error(`helper folder "${dir}" must look like desktop-helper-<name>`);
68
+ }
69
+ const found = source.split(HELPER_ROOT_DEFAULT).length - 1;
70
+ if (found !== 1) throw new Error(`expected exactly one default helper folder, found ${found}`);
71
+ return source.replace(HELPER_ROOT_DEFAULT, `path.join(os.homedir(), '.instar', '${dir}')`);
72
+ }
73
+
53
74
  /**
54
75
  * Check a table's rows against the proof records on disk. A committed row must
55
76
  * cite, for p12 and p15, a file under docs/proofs/desktop-locked-screen/ that
@@ -72,6 +72,20 @@ export function setupGaps(agents) {
72
72
  /** The login name owning a pid, or '' when it is gone. */
73
73
  export function ownerOf(pid) { return sh('ps', ['-o', 'user=', '-p', String(pid)], { allowFail: true }).trim(); }
74
74
 
75
+ /**
76
+ * Sends one person step and writes down whether it went. A refused send used to
77
+ * vanish, so a person was never told and the case simply timed out; now the
78
+ * refusal is in person-steps.jsonl and on stderr for whoever is watching the run.
79
+ */
80
+ export function recordPersonStep(o, text, send) {
81
+ const result = o.notifyTopic ? send() : null;
82
+ const sent = result ? result.status === 0 : false;
83
+ const row = { at: new Date().toISOString(), text, topic: o.notifyTopic ?? null, sent };
84
+ fs.appendFileSync(path.join(o.out, 'person-steps.jsonl'), `${JSON.stringify(row)}\n`);
85
+ if (o.notifyTopic && !sent) process.stderr.write(`[locked-proof] person step NOT delivered to topic ${o.notifyTopic}: ${text}\n`);
86
+ return row;
87
+ }
88
+
75
89
  /** The proof Mac, the proof user, its two agents and the real viewer page. */
76
90
  export class RealTarget {
77
91
  constructor(o) {
@@ -363,10 +377,9 @@ export class RealTarget {
363
377
 
364
378
  async askPerson(text) {
365
379
  process.stdout.write(`\n>>> PERSON STEP: ${text}\n\n`);
366
- if (this.o.notifyTopic) {
367
- sh('bash', [path.join(this.o.agentHome, '.instar/scripts/telegram-reply.sh'), String(this.o.notifyTopic)],
368
- { input: `Locked-Mac proof: ${text}`, allowFail: true });
369
- }
380
+ recordPersonStep(this.o, text, () => spawnSync('bash',
381
+ [path.join(this.o.agentHome, '.instar/scripts/telegram-reply.sh'), String(this.o.notifyTopic)],
382
+ { encoding: 'utf8', input: `Locked-Mac proof: ${text}`, timeout: 30_000 }));
370
383
  }
371
384
 
372
385
  async extraEvidence(c, ev) {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "$schema": "./builtin-manifest.schema.json",
3
3
  "schemaVersion": 1,
4
- "generatedAt": "2026-11-05T04:47:26.740Z",
5
- "instarVersion": "1.3.1665",
4
+ "generatedAt": "2026-11-05T05:26:43.534Z",
5
+ "instarVersion": "1.3.1666",
6
6
  "entryCount": 206,
7
7
  "entries": {
8
8
  "hook:session-start": {
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
4
  "registrySha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
5
- "packageVersion": "1.3.1665",
5
+ "packageVersion": "1.3.1666",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "fa0889edea392faf22e071678dfc99ab29a790b3018cd8226791bff29c72eda5",
2
+ "sha256": "f58de796ebb089dbbc16199d4d4e7adb68bc81b328fb344e26b66d36ff78cf75",
3
3
  "registrySha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
4
- "packageVersion": "1.3.1665"
4
+ "packageVersion": "1.3.1666"
5
5
  }
@@ -2,5 +2,5 @@
2
2
  "sha256": "f91273af3f6f22eb6bb3161c35b109469d8457f1f93b2ec90eec9e58f62ead53",
3
3
  "articleCount": 95,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1665"
5
+ "packageVersion": "1.3.1666"
6
6
  }
@@ -0,0 +1,16 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The locked-Mac proof build can keep its helper in a separate folder for one test build, so a hidden macOS permission record at the usual folder no longer blocks a proof. The proof build now always makes a helper that can be kept, and the proof driver records whether each person step was delivered. Nothing that ships to agents changes.
9
+
10
+ ## What to Tell Your User
11
+
12
+ None — internal change (no user-facing surface).
13
+
14
+ ## Summary of New Capabilities
15
+
16
+ None — internal change (no user-facing surface).
@@ -0,0 +1,11 @@
1
+ # Moving the test helper to a clean spot (plain version)
2
+
3
+ We are proving that Remote Desktop can open a locked Mac. The test on the Mac Mini is stuck for a boring reason. macOS remembers permissions by the program's location, and the memory at the current location describes an older copy of the helper. Nobody can see that record in System Settings, and the usual tool for clearing records refuses programs like ours.
4
+
5
+ What is new: the script that makes the test build can now put the helper in a different folder, for that one test build only. A folder macOS has never seen has no old record to trip over. The helper program itself does not change, so what we prove still applies to the helper we ship.
6
+
7
+ Two small repairs ride along. The test build now always makes the kind of helper that can be kept, which used to need a manual step. And the test driver now writes down whether each "please do this at the Mac" message was really delivered, because a refused message used to disappear silently while the test waited.
8
+
9
+ Nothing here changes what ships to any agent. The real product problem, that a changed helper can leave a hidden record behind, is still open and tracked in the feedback queue. This is the way past it for the proof, and it is also the way we find out whether a clean location really works.
10
+
11
+ There is nothing for anyone to decide here. If it turns out wrong, reverting the commit puts everything back.
@@ -0,0 +1,52 @@
1
+ # Side-Effects Review: locked-proof build can move the helper folder
2
+
3
+ **Version / slug:** `locked-proof-helper-dir`
4
+ **Date:** `2026-11-04`
5
+ **Author:** `Echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Tier 1, test harness only: 5 files, about 90 lines, one review round, nothing under `src/` changes.
11
+
12
+ The locked-Mac proof is stuck on the Mac Mini. macOS keeps a permission row per program path, and the row at the kept helper's path names an older build. The row is invisible in System Settings, the plus button leaves its old requirement in place, and `tccutil` refuses a bare program. So:
13
+
14
+ 1. `scripts/build-locked-proof.mjs --helper-dir desktop-helper-<name>` moves the kept helper's default folder, in the scratch copy of one test build only. The manifest records `helperDir`.
15
+ 2. The same script now always makes a universal helper on a Mac. Retention keeps only a universal helper, and the last three proof builds needed a hand-run extra step for this.
16
+ 3. `driver.mjs` writes every person step to `person-steps.jsonl` with whether it was delivered, and says so on stderr when it was not. A refused send used to vanish while the case timed out.
17
+
18
+ ## Decision-point inventory
19
+
20
+ None added to any shipped path. `rewriteHelperRoot` refuses bad input at build time on a developer's Mac. `recordPersonStep` records a fact and blocks nothing.
21
+
22
+ ## 1. Over-block
23
+
24
+ `--helper-dir` accepts only `desktop-helper-<lowercase name>`. That is deliberate, and no legitimate use needs more.
25
+
26
+ ## 2. Under-block
27
+
28
+ A build moved to a new folder still needs one person trip for the grant. If macOS writes a stale requirement at a fresh path too, this does not help; the proof sitting measures that. The shipped product still has the stale-row dead end after a helper source change. That is tracked in feedback fb-a67570d1-f1d and fb-f21f34ff-491 and is not fixed here.
29
+
30
+ ## 3. Level-of-abstraction fit
31
+
32
+ Right layer. The build script already rewrites a scratch copy for the proven-versions table. A config key would add a shipped option that only a proof needs.
33
+
34
+ ## 4. Signal vs authority compliance
35
+
36
+ No blocking authority is added. The person-step record is a pure signal. Reference: `docs/signal-vs-authority.md`.
37
+
38
+ ## 5. Interactions
39
+
40
+ The locked store stays at `~/.instar/desktop-helper/locked` (`DesktopLockedController`), and `driver.mjs` and `reset-attempts.py` pin that store, so they keep working. `resolve()` treats a new folder as a first grant.
41
+
42
+ ## 6. External surfaces
43
+
44
+ None for users or other agents. Only `-lockedproof` builds, which are never published, can carry the moved folder.
45
+
46
+ ## 7. Multi-machine posture
47
+
48
+ Machine-local: one build for one proof Mac. Nothing is replicated.
49
+
50
+ ## 8. Rollback cost
51
+
52
+ Revert the commit. Without the flag the build output is as before, except that the helper is universal on a Mac.