instar 1.3.1611 → 1.3.1613
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/dashboard/console/console.css +3 -1
- package/dashboard/console/console.js +18 -5
- package/dashboard/console/home-model.js +4 -1
- package/dashboard/console/messages-model.js +2 -1
- package/dashboard/console/messages.js +1 -1
- package/dashboard/console/pace-model.js +5 -0
- package/dashboard/console/pace.js +3 -1
- package/dashboard/console/plans-model.js +17 -1
- package/dashboard/console/promises.js +2 -1
- package/dashboard/console/secrets-model.js +11 -1
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +32 -8
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AppConversationStore.d.ts +2 -0
- package/dist/core/AppConversationStore.d.ts.map +1 -1
- package/dist/core/AppConversationStore.js +8 -1
- package/dist/core/AppConversationStore.js.map +1 -1
- package/dist/core/AppRecordSignatures.d.ts +23 -2
- package/dist/core/AppRecordSignatures.d.ts.map +1 -1
- package/dist/core/AppRecordSignatures.js +32 -5
- package/dist/core/AppRecordSignatures.js.map +1 -1
- package/dist/core/LocalAttentionItems.d.ts +49 -0
- package/dist/core/LocalAttentionItems.d.ts.map +1 -0
- package/dist/core/LocalAttentionItems.js +107 -0
- package/dist/core/LocalAttentionItems.js.map +1 -0
- package/dist/core/MacDesktopHost.d.ts +3 -0
- package/dist/core/MacDesktopHost.d.ts.map +1 -1
- package/dist/core/MacDesktopHost.js +10 -0
- package/dist/core/MacDesktopHost.js.map +1 -1
- package/dist/core/MachineLockStateReader.d.ts.map +1 -1
- package/dist/core/MachineLockStateReader.js +6 -1
- package/dist/core/MachineLockStateReader.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +6 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +59 -2
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/types.d.ts +12 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/data/standards-guard-index.json +1 -1
- package/dist/data/standards-guard-index.meta.json +2 -2
- package/dist/data/standards-registry.meta.json +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts +2 -0
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +28 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/localVerify/localVerifyWiring.d.ts +11 -0
- package/dist/localVerify/localVerifyWiring.d.ts.map +1 -1
- package/dist/localVerify/localVerifyWiring.js +9 -0
- package/dist/localVerify/localVerifyWiring.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +8 -0
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +46 -31
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/messaging/shared/MessageLogger.d.ts +2 -0
- package/dist/messaging/shared/MessageLogger.d.ts.map +1 -1
- package/dist/messaging/shared/MessageLogger.js.map +1 -1
- package/dist/messaging/slack/SlackAdapter.d.ts +19 -0
- package/dist/messaging/slack/SlackAdapter.d.ts.map +1 -1
- package/dist/messaging/slack/SlackAdapter.js +21 -0
- package/dist/messaging/slack/SlackAdapter.js.map +1 -1
- package/dist/messaging/telegram-origin/OriginForwardToHolder.d.ts +13 -0
- package/dist/messaging/telegram-origin/OriginForwardToHolder.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/OriginForwardToHolder.js +16 -0
- package/dist/messaging/telegram-origin/OriginForwardToHolder.js.map +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.d.ts +8 -0
- package/dist/messaging/telegram-origin/OriginLegacy.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/OriginLegacy.js +34 -6
- package/dist/messaging/telegram-origin/OriginLegacy.js.map +1 -1
- package/dist/messaging/telegram-origin/TelegramOriginBoot.d.ts +3 -0
- package/dist/messaging/telegram-origin/TelegramOriginBoot.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/TelegramOriginBoot.js +54 -10
- package/dist/messaging/telegram-origin/TelegramOriginBoot.js.map +1 -1
- package/dist/messaging/telegram-origin/types.d.ts +11 -0
- package/dist/messaging/telegram-origin/types.d.ts.map +1 -1
- package/dist/messaging/telegram-origin/types.js +15 -0
- package/dist/messaging/telegram-origin/types.js.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.d.ts.map +1 -1
- package/dist/monitoring/GreenPrAutoMerger.js +13 -0
- package/dist/monitoring/GreenPrAutoMerger.js.map +1 -1
- package/dist/monitoring/greenPrAutomergeWiring.d.ts.map +1 -1
- package/dist/monitoring/greenPrAutomergeWiring.js +3 -0
- package/dist/monitoring/greenPrAutomergeWiring.js.map +1 -1
- package/dist/monitoring/promptRelaySettle.d.ts +35 -0
- package/dist/monitoring/promptRelaySettle.d.ts.map +1 -0
- package/dist/monitoring/promptRelaySettle.js +58 -0
- package/dist/monitoring/promptRelaySettle.js.map +1 -0
- package/dist/native/instar-desktop-host +0 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/AgentServer.d.ts +15 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +78 -20
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.js +2 -2
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/mobileConversationRoutes.d.ts +16 -3
- package/dist/server/mobileConversationRoutes.d.ts.map +1 -1
- package/dist/server/mobileConversationRoutes.js +36 -8
- package/dist/server/mobileConversationRoutes.js.map +1 -1
- package/dist/server/mobilePairingRoutes.d.ts +1 -1
- package/dist/server/mobilePairingRoutes.d.ts.map +1 -1
- package/dist/server/mobilePairingRoutes.js +5 -2
- package/dist/server/mobilePairingRoutes.js.map +1 -1
- package/dist/server/mobileSlackRoutes.d.ts +162 -0
- package/dist/server/mobileSlackRoutes.d.ts.map +1 -0
- package/dist/server/mobileSlackRoutes.js +552 -0
- package/dist/server/mobileSlackRoutes.js.map +1 -0
- package/dist/server/routes.d.ts +2 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +85 -6
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/scripts/build-locked-proof.mjs +125 -0
- package/scripts/lib/locked-proof-build.mjs +71 -0
- package/scripts/locked-proof/configure-proof-agent.mjs +68 -0
- package/scripts/locked-proof/create-proof-user.sh +89 -0
- package/scripts/locked-proof/driver.mjs +457 -0
- package/scripts/locked-proof/lib.mjs +267 -0
- package/scripts/locked-proof/pty-feed.py +59 -0
- package/scripts/locked-proof/remove-proof-user.sh +18 -0
- package/scripts/locked-proof/reset-attempts.py +52 -0
- package/scripts/locked-proof/stage-proof-agents.sh +68 -0
- package/src/data/builtin-manifest.json +65 -65
- package/src/data/standards-guard-index.json +1 -1
- package/src/data/standards-guard-index.meta.json +2 -2
- package/src/data/standards-registry.meta.json +1 -1
- package/src/scaffold/templates.ts +3 -1
- package/upgrades/1.3.1612.md +72 -0
- package/upgrades/1.3.1613.md +108 -0
- package/upgrades/side-effects/blocker-text-is-the-message.eli16.md +5 -0
- package/upgrades/side-effects/blocker-text-is-the-message.md +38 -0
- package/upgrades/side-effects/console-needs-row-tidy.eli16.md +13 -0
- package/upgrades/side-effects/console-needs-row-tidy.md +24 -0
- package/upgrades/side-effects/console-partial-survives-reload.eli16.md +5 -0
- package/upgrades/side-effects/console-partial-survives-reload.md +34 -0
- package/upgrades/side-effects/console-secret-drop-names.eli16.md +5 -0
- package/upgrades/side-effects/console-secret-drop-names.md +34 -0
- package/upgrades/side-effects/desktop-tests-stop-touching-tcc.eli16.md +13 -0
- package/upgrades/side-effects/desktop-tests-stop-touching-tcc.md +65 -0
- package/upgrades/side-effects/green-pr-eligible-needs-passing-record.eli16.md +7 -0
- package/upgrades/side-effects/green-pr-eligible-needs-passing-record.md +39 -0
- package/upgrades/side-effects/green-pr-poke-unknown-mergeable.eli16.md +5 -0
- package/upgrades/side-effects/green-pr-poke-unknown-mergeable.md +41 -0
- package/upgrades/side-effects/held-expiry-one-row-per-topic.eli16.md +5 -0
- package/upgrades/side-effects/held-expiry-one-row-per-topic.md +37 -0
- package/upgrades/side-effects/locked-proof-driver.md +72 -0
- package/upgrades/side-effects/locked-proof-test-build.md +64 -0
- package/upgrades/side-effects/locked-security-item-no-telegram.md +78 -0
- package/upgrades/side-effects/mobile-native-conversations-inc4.md +115 -0
- package/upgrades/side-effects/origin-config-unknown-not-missing.eli16.md +16 -0
- package/upgrades/side-effects/origin-config-unknown-not-missing.md +38 -0
- package/upgrades/side-effects/pool-rows-use-local-machine-name.eli16.md +5 -0
- package/upgrades/side-effects/pool-rows-use-local-machine-name.md +34 -0
- package/upgrades/side-effects/prompt-gate-settle-before-relay.eli16.md +11 -0
- package/upgrades/side-effects/prompt-gate-settle-before-relay.md +57 -0
- package/upgrades/side-effects/topic-names-through-lifeline.eli16.md +9 -0
- package/upgrades/side-effects/topic-names-through-lifeline.md +39 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- Possible blockers that the deferral checker records now hold the message text, not the whole command that sent it, and link signatures or tokens are masked. A command that only writes text mentioning the reply script (code, docs) is no longer mistaken for a message. The dashboard's Plans page shows older entries the same tidy way.
|
|
9
|
+
|
|
10
|
+
- Home: "What needs you" rows no longer repeat the machine name or show the priority word, and empty card footers are hidden.
|
|
11
|
+
- Work: the Pace card keeps each dot beside its line on a phone and leaves out queues with nothing open, and a stalled pile now reads "nothing came in or got done this week; N still open" instead of "holding steady".
|
|
12
|
+
- Promises: the empty "Waiting on you" card no longer repeats the headline.
|
|
13
|
+
- Messages: the page leads with "Messages to you" instead of "No messages are held" when nothing is held.
|
|
14
|
+
- Menus: removed the outdated "opens in the dashboard, works exactly as before" line; the footer now just links the older dashboard.
|
|
15
|
+
|
|
16
|
+
- The dashboard's Secrets page shows saved Secret Drop submissions as one counted line instead of a long code per submission.
|
|
17
|
+
|
|
18
|
+
- The automatic merger only considers a pull request whose full test run passed. A failed run used to count as "verified", so a failed PR at the front of the line was attempted, refused and retried while ready PRs behind it waited.
|
|
19
|
+
|
|
20
|
+
- The automatic merger no longer skips a ready pull request just because GitHub's list says its mergeability is "unknown" (which happens after every merge). It reads that PR directly, which makes GitHub compute it, so the next round merges it.
|
|
21
|
+
|
|
22
|
+
- When the dashboard combines information from several machines, each machine is named from the list on the machine you are viewing, so the same machine no longer appears under two names (for example in the Machines page and in alerts).
|
|
23
|
+
|
|
24
|
+
- Topics you create or rename in Telegram now get their real name on the agent's server even when its lifeline does the Telegram polling. Before, the lifeline dropped the text-less "topic created/renamed" message, so those conversations showed as "Conversation name unavailable".
|
|
25
|
+
|
|
26
|
+
## What to Tell Your User
|
|
27
|
+
|
|
28
|
+
The "In the way" list on Plans now reads as plain sentences instead of shell commands, and it no longer shows private link signatures.
|
|
29
|
+
|
|
30
|
+
Small clean-ups on the home screen and menus, mostly visible on a phone.
|
|
31
|
+
|
|
32
|
+
The secrets list is easier to read: submissions you sent through a secure link appear as one line with a count.
|
|
33
|
+
|
|
34
|
+
Changes that passed their tests no longer wait behind one that failed.
|
|
35
|
+
|
|
36
|
+
Finished changes merge without someone having to open them first.
|
|
37
|
+
|
|
38
|
+
Each of your machines now shows under one name across the dashboard. A rename changes the name on the dashboard of the machine where you rename it.
|
|
39
|
+
|
|
40
|
+
New topics show their real names on the dashboard. Topics created recently pick up their name the next time a message is sent in them.
|
|
41
|
+
|
|
42
|
+
## Summary of New Capabilities
|
|
43
|
+
|
|
44
|
+
None.
|
|
45
|
+
|
|
46
|
+
None.
|
|
47
|
+
|
|
48
|
+
None.
|
|
49
|
+
|
|
50
|
+
None.
|
|
51
|
+
|
|
52
|
+
None.
|
|
53
|
+
|
|
54
|
+
None.
|
|
55
|
+
|
|
56
|
+
None. This fixes topic naming.
|
|
57
|
+
|
|
58
|
+
## Evidence
|
|
59
|
+
|
|
60
|
+
- On a live agent all 50 stored entries were whole reply commands, one with a signed private link, printed verbatim on the Plans page. Unit tests run the real rendered hook against a local server and check the posted text, and check the dashboard's cleanup of stored rows.
|
|
61
|
+
|
|
62
|
+
- Found on phone-width screenshots of the dashboard; rechecked after the change. A unit test covers the row sub-line.
|
|
63
|
+
|
|
64
|
+
- On a live agent the page listed several 64-character codes; a scan of every dashboard page flagged them. Unit test covers the grouping and that the codes are not shown.
|
|
65
|
+
|
|
66
|
+
- On a live agent the merger picked a PR whose test run had failed as the oldest ready one; it was refused, and the pass ended. Unit tests cover the passing-only rule and its wiring.
|
|
67
|
+
|
|
68
|
+
- On a live agent a verified PR was absent from every merge round until it was opened directly, and merged on the next round after that. Unit test covers the direct read for "unknown" and none for a real conflict.
|
|
69
|
+
|
|
70
|
+
- On a live agent one Mac appeared as "Mac M5 Pro" on the Machines page and as "Mac Lan" in its alerts. Integration test: a peer that calls itself differently is shown under this machine's name, including the alert title.
|
|
71
|
+
|
|
72
|
+
- On a live agent, four topics created that day showed as "Conversation name unavailable"; no machine had their names. Unit tests cover the lifeline forwarding a created/renamed name; route tests cover recording it and refusing an empty message that is not a name-only update.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
- The dashboard's home page keeps a slow machine's items for up to two minutes even across a page reload or reopening the app, instead of only while the page stays open.
|
|
9
|
+
|
|
10
|
+
Some desktop tests started the real packaged Mac desktop helper. macOS records a new Screen Recording / Accessibility entry for every path that runs it, so each test run from a checkout or a Local Verify clone left another "instar-desktop-host" row on the Mac running the tests, and one test asked macOS for permission, which showed real pop-ups.
|
|
11
|
+
|
|
12
|
+
- `MacDesktopHost` and the lock-state reader now refuse to start the real packaged helper inside a test runner (`VITEST`) unless `INSTAR_REAL_DESKTOP_HELPER_TESTS=1` is set. Fake test helpers have other names and still run.
|
|
13
|
+
- The tests that need the real helper run only with that opt-in.
|
|
14
|
+
|
|
15
|
+
Nothing changes outside a test runner.
|
|
16
|
+
|
|
17
|
+
- When held replies to one topic expire, they now add to a single alert for that topic (with a count) instead of creating one alert per message.
|
|
18
|
+
|
|
19
|
+
- New maintainer scripts in `scripts/locked-proof/` for the locked-Mac proof run (P12 crash tests and P15 Stop tests in `docs/specs/desktop-locked-screen.md`). Nothing runs against any Mac unless called with `--execute`; none of it ships as runtime behaviour.
|
|
20
|
+
- `driver.mjs`: the case table (27 P12 cases, 11 P15 cases), `expected` prints the expected-results table to commit before any run, `calibrate` measures the typing pace with one normal unlock, `run` drives each case through the real viewer page and records evidence plus a verdict to `results.jsonl`. The agent types the throwaway test user's password itself through the remote-desktop path, reading it once from its vault into memory; it is never printed, logged, written to results or put on a command line. The person is asked only for what software cannot do (local unlocks, a user switch, a login).
|
|
21
|
+
- `create-proof-user.sh`: generates the throwaway password, stores it in the vault first (`locked_proof_user_password`, plus Bitwarden; it refuses to run if the agent's `credential-store.mjs` still puts the encoded Bitwarden item on `bw`'s command line instead of stdin), creates the standard user `instarproof` by answering `sysadminctl`'s prompt through a pseudo-terminal (`pty-feed.py`), clears the user's account policies so macOS does not force a password change at first login, and checks the password authenticates.
|
|
22
|
+
- `stage-proof-agents.sh`, `configure-proof-agent.mjs`: install the `-lockedproof` test build in the user's own prefix and set up two proof agents (auto-update off, no start-on-login job, remote desktop on, locked access left off).
|
|
23
|
+
- Every signal is owner-checked (only the proof user's processes), a case that throws still resumes everything it stopped and relocks the test account, and an expectation that is not yet measured fails as "not measured" instead of passing.
|
|
24
|
+
- `reset-attempts.py`: resets the helper's attempt counter between batches under the helper's own lock, refused while a relock duty is open, without `--execute`, or for any store but the proof user's; every reset is logged.
|
|
25
|
+
- `remove-proof-user.sh`: tears the user down.
|
|
26
|
+
|
|
27
|
+
- New maintainer script `scripts/build-locked-proof.mjs`: the locked-screen test build the spec calls for (`docs/specs/desktop-locked-screen.md`, phase 3). It copies one committed revision into a scratch directory outside the checkout, adds a single `PROOF-RUN-IN-PROGRESS` row for one macOS major to that copy's proven-versions table, versions it `<release>-lockedproof.<n>`, builds it (the Swift helper is unchanged, so its source hash matches the release) and packs it, writing a manifest with the commit, macOS major, helper source hash and package checksum. The committed table is never touched and nothing is published. `--dry-run` shows the row and builds nothing.
|
|
28
|
+
- New test: the committed `PROVEN_MACOS_VERSIONS` table fails the unit suite if any row lacks an existing proof record under `docs/proofs/desktop-locked-screen/` for P12 or P15, or carries the test-build marker.
|
|
29
|
+
|
|
30
|
+
- An agent with no Telegram now has an attention list. Before, the remote-desktop security items ("A Mac may still be unlocked", "Remote desktop through the lock was turned off") had nowhere to go without Telegram: the server printed a warning and the owner was never told. They are now stored on disk (`state/local-attention-items.json`, one row per condition, bounded to 200 rows, open rows never dropped) and pushed to paired phones.
|
|
31
|
+
- `GET /attention`, `GET /attention/:id` and `PATCH /attention/:id` serve that local list when there is no Telegram (the list carries `surface: "local"`) instead of answering 503. The iPhone app's notification lookup reads it too. With Telegram, nothing changes.
|
|
32
|
+
- The relock record is only marked "reported" after the item is actually stored, so a failed write is retried by the next sweep.
|
|
33
|
+
- CLAUDE.md (new and existing agents): one bullet saying an agent without Telegram still has an attention list and where to read it.
|
|
34
|
+
|
|
35
|
+
- Slack conversations from the Instar iPhone app (native conversations increment 4). Where Slack is set up, the app's conversation list also shows Slack channels and threads, the phone can read their history, and a message typed in the app reaches the agent's Slack session exactly like a Slack message from the operator.
|
|
36
|
+
- Each message sent from the app into Slack is saved first, with the phone's signature and this machine's signed receipt (a new receipt version that also names the Slack conversation), so history can show whether it really came from the paired phone.
|
|
37
|
+
- A copy "📱 <owner> (from the app): …" is posted into the Slack channel or thread, with anything that looks like a password or key hidden.
|
|
38
|
+
- The agent's own Slack replies are now kept in the Slack message log, so the app shows both sides of the conversation.
|
|
39
|
+
- New optional setting `mobilePairing.slackOwnerUserId`: the operator's Slack user id. Until it is set, the app shows no Slack conversations. The agent proposes it and sets it only after the operator agrees.
|
|
40
|
+
- Existing agents get a new CLAUDE.md section about this on update.
|
|
41
|
+
|
|
42
|
+
- When a machine briefly cannot read its own messaging config (seen on a busy standby whose server stalled for tens of seconds), a message to a bug-group topic was refused with "this process has no messaging config issuesChatId", although the config holds it. The refusal now says what happened: the config could not be read. It is a retryable `503 origin-config-unavailable` with the reason (for example `origin-config-reader-timeout`).
|
|
43
|
+
- Messages refused this way, and the older "issues-chat-not-configured" and bare "origin-display-authority-unavailable" refusals already in the queue, were later marked "may already be delivered" and never sent. Both refusals happen before anything is sent, so recovery now replays them once config reads again, through the normal tone review, duplicate check and 6-hour deadline.
|
|
44
|
+
- The server log now records why a config refresh failed (`[telegram-origin] config snapshot unavailable: <reason>`) and when it recovered. Before, the reason was discarded.
|
|
45
|
+
|
|
46
|
+
Prompt Gate relayed every session prompt to Telegram the moment it saw one, even prompts that something else answered seconds later (an account swap after a usage limit, the permission-prompt floor, a respawn). The first relay in a topic also sent a separate "Prompt Gate is now active" message.
|
|
47
|
+
|
|
48
|
+
- A detected prompt now waits a short settle window (`promptGate.relaySettleMs`, default 45000 ms), then the live pane is read again. The prompt is relayed only if it is still on screen. A session that is gone, or a pane that no longer shows the prompt, means no message. An unreadable pane still relays.
|
|
49
|
+
- The one-time privacy note now rides on the first relayed prompt as a single line instead of arriving as its own message.
|
|
50
|
+
- `relaySettleMs: 0` restores the old immediate relay.
|
|
51
|
+
|
|
52
|
+
## What to Tell Your User
|
|
53
|
+
|
|
54
|
+
If one of your machines is slow when you reopen the app, its items no longer disappear from "what needs you".
|
|
55
|
+
|
|
56
|
+
If your Mac's Accessibility or Screen Recording list filled up with many "instar-desktop-host" entries, they came from automated test runs on that Mac and stop being added after this update. Old ones can be removed with the minus button; keep the one at ~/.instar/desktop-helper/instar-desktop-host switched on.
|
|
57
|
+
|
|
58
|
+
If replies to a conversation ever fail to go out, you'll see one alert for that conversation with a count, not dozens of copies, and the dashboard loads faster.
|
|
59
|
+
|
|
60
|
+
If you run me without Telegram and I ever can't confirm that a Mac relocked after a remote-desktop session, you'll now see it: it shows up in my attention list, in the Instar app, and as a phone notification. Before, that warning had nowhere to go.
|
|
61
|
+
|
|
62
|
+
You can now continue your Slack conversations from the Instar app. First I need to know which Slack account is yours: ask me to "link my Slack to the app" and I will propose your Slack user id for you to confirm. After that, your Slack channels show up in the app's conversation list, and what you type there reaches me as if you had typed it in Slack, with a copy posted in the channel so others see it. The app itself needs its next update to show the Slack rows.
|
|
63
|
+
|
|
64
|
+
If one of my machines is briefly too busy to read its own settings, messages I send from it to a bug topic now wait and arrive shortly after, instead of failing with a misleading "not configured" error and going missing.
|
|
65
|
+
|
|
66
|
+
You'll stop getting "your agent needs confirmation" messages for prompts that were already handled automatically. A prompt that really needs you arrives about 45 seconds after it appears.
|
|
67
|
+
|
|
68
|
+
## Summary of New Capabilities
|
|
69
|
+
|
|
70
|
+
None.
|
|
71
|
+
|
|
72
|
+
None. This removes a side effect of the test suite.
|
|
73
|
+
|
|
74
|
+
None.
|
|
75
|
+
|
|
76
|
+
- Attention list without Telegram: `GET /attention` / `PATCH /attention/:id` work on an agent with no Telegram, holding security items such as "A Mac may still be unlocked".
|
|
77
|
+
|
|
78
|
+
- Continue Slack conversations from the Instar app (list, history, send), with signed records and a copy in the channel. Dev-gated with native app conversations (`mobilePairing.native.enabled`); needs `mobilePairing.slackOwnerUserId`.
|
|
79
|
+
|
|
80
|
+
None. This fixes a misleading refusal and the lost messages behind it.
|
|
81
|
+
|
|
82
|
+
None. One new optional setting: `promptGate.relaySettleMs`.
|
|
83
|
+
|
|
84
|
+
## Evidence
|
|
85
|
+
|
|
86
|
+
- Live check: reloading Home while one machine's answer was missing dropped its row before this change and kept it after (preview).
|
|
87
|
+
|
|
88
|
+
Unit tests for the guard (both sides), plus the fake-helper suites passing with and without a built helper. Independent second-pass review concurred.
|
|
89
|
+
|
|
90
|
+
- On a live agent about 750 open "held reply expired" alerts (109 for one topic) made each pooled alert read 742 KB and the first read after a restart take 25 seconds. Unit test covers the shared per-topic condition and distinct per-message ids.
|
|
91
|
+
|
|
92
|
+
- Unit tests (no real user, lock screen or desktop helper is touched): case table coverage and selection; password shape and use of the random source; attempt reset refusal on an open duty; proof-target check; the runner against a fake Mac both passing and failing (Mac left unlocked) and never putting the password in evidence; every verdict reason; the user-creation script against stand-in commands (vault before user creation, password only via the prompt, policies cleared, verified, never printed); `pty-feed.py` both ways; `reset-attempts.py` both ways; dry runs do nothing.
|
|
93
|
+
|
|
94
|
+
- Unit tests: version naming, macOS major parsing, the rewrite against the real committed source (one row added, the rest of the file byte-identical), refusal of an already-proven version and of a missing declaration, the dry run leaving the committed table's bytes unchanged, refusal of an output directory inside the checkout, and the committed-table check on both sides (empty table passes; marker, stray path and missing record each fail).
|
|
95
|
+
|
|
96
|
+
- Unit: the store keeps one row per id, reopens on a re-raise, reports false when it cannot write, stays bounded without dropping open rows, and survives a corrupt file.
|
|
97
|
+
- Integration: the three attention routes serve, filter and resolve the local list without Telegram, and still answer 503 with neither Telegram nor a local list.
|
|
98
|
+
- E2E (production boot, no Telegram, remote desktop off so no real Mac helper starts): `GET /attention` answers 200, and the same method the locked-screen controller calls puts the security item on it.
|
|
99
|
+
|
|
100
|
+
- Unit tests: a Slack record verifies with the new receipt and fails when it is moved to another conversation, carries an old-style receipt, or was signed for another path; the store keeps the new receipt fields; the Slack bridge reads one channel or thread from the log and the in-memory history, and dispatches as the operator in the right thread; the CLAUDE.md section is added once.
|
|
101
|
+
- Integration tests (stub Slack adapter, real registry, request store and device signatures): list with and without the owner id, history merging Slack rows and app-sent rows, a send stored first and read back "verified", retry replay, refusals before anything is saved, a failed dispatch, a failed mirror, an emergency stop over the rate limit, invite removal, and recovery after the Slack log rotates.
|
|
102
|
+
- End-to-end test: a server built on the production path with a stub Slack adapter pairs a phone on the dashboard, lists the Slack channel, sends into it, the server's Slack dispatch receives it as the operator tagged signed, the mirror is posted, the agent's reply is logged, and the phone's history shows both.
|
|
103
|
+
|
|
104
|
+
- Mama PC (standby, 2026-11-02): sends to issue topic 10000000001 were refused at about 17:59, 20:03, 20:20 and 20:37 UTC, and the queued copies became `delivered-ambiguous` without being sent. Twenty fresh config reads there all succeeded in about 150 ms with `issuesChatId` present. The same log shows the server's event loop blocked (25 s drift bursts, "event-loop BLOCK 95% CPU") and the lifeline restarting it for being unresponsive, which is when a 2 s config read times out.
|
|
105
|
+
- Integration test: after boot, the config becomes unreadable; an issues-topic send is refused with `OriginConfigUnavailableError` (reason `origin-config-unparseable`), never "issues-chat-not-configured", and routes to the issues group again once the config reads.
|
|
106
|
+
- Unit test: both refusal bodies replay exactly once; a generic 500 and a relayed body stay uncertain.
|
|
107
|
+
|
|
108
|
+
Unit tests cover both sides of the decision (still on screen → relay; cleared, session gone → no relay; unreadable pane, nothing to match → relay; 0 disables the wait) plus wiring checks on the server relay path. Existing Prompt Gate tests pass.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# The "In the way" list shows what was said, not the command that said it
|
|
2
|
+
|
|
3
|
+
When the agent sends a message that sounds like it is stuck ("I can't…", "blocker"), a small checker writes it down as a possible blocker, so a real wall does not get forgotten. The trouble was what it wrote down. The agent sends messages by running a short shell command that wraps the text, and the checker saved that entire command: the wrapping, the topic number, and sometimes a private link with its secret signature still attached. The dashboard's Plans page then showed those lines exactly as saved, so the operator saw fifty rows of shell commands instead of readable sentences, and one of them exposed a signed link.
|
|
4
|
+
|
|
5
|
+
Now the checker saves only the message itself, and any part of a link that looks like a signature or token is replaced with "…". The dashboard also tidies the rows already saved the old way: it shows the first line of the message, masks the same link parts, and if there was no message at all it says that a command was flagged instead of printing the command. The checker also stops mistaking the agent's own code-writing for messages: before, a command that merely saved a file mentioning the message script was treated as a message and could create a bogus entry. Only commands that actually send a message are checked now.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Side-effects review: blocker entries record the message, not the shell command
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
- `src/core/PostUpdateMigrator.ts` (deferral-detector hook template): `blockerTextOf(command)` takes the heredoc body of a reply command and masks credential-looking query values (`sig`, `signature`, `token`, `key`, `auth`, `access_token`); the auto-opened blocker posts that instead of the whole command. With several heredocs it takes the one whose header line invokes the reply/send script.
|
|
5
|
+
- Same hook: a command counts as a message only when the reply/send script appears OUTSIDE heredoc bodies (`withoutHeredocBodies`). A command that writes a file whose text mentions the script (code, docs, tests) is no longer treated as a message; on 2026-11-02 two such code-writing commands opened new "Just spotted" rows within minutes.
|
|
6
|
+
- `dashboard/console/plans-model.js`: `blockerText()` applies the same reading to stored rows (first non-empty line, masked, capped at 220 chars); a command with no message text reads "Flagged in a command I ran (no message text)".
|
|
7
|
+
|
|
8
|
+
## Evidence
|
|
9
|
+
On the Studio (2026-11-02) all 50 `/blockers` entries were `candidate` rows from `deferral-detector` whose `detectedText` began `cat <<'EOF' | .claude/scripts/telegram-reply.sh <topic>`; the console Plans page printed them verbatim, including a private-view link with its `sig=` value.
|
|
10
|
+
|
|
11
|
+
## 1. Over-block
|
|
12
|
+
None added. The comm check is narrower, so fewer commands are checked at all.
|
|
13
|
+
|
|
14
|
+
## 2. Under-block
|
|
15
|
+
A message sent from inside a script that is itself a heredoc (e.g. `python3 - <<EOF` calling the reply script) is no longer checked by this hook; the outbound tone gate still reviews every message that is actually sent.
|
|
16
|
+
A credential not in a query parameter is not masked here; the outbound credential wall still governs what is sent. Stored rows keep their raw text in the ledger; only the display is cleaned.
|
|
17
|
+
|
|
18
|
+
## 3. Level-of-abstraction fit
|
|
19
|
+
The producer records the right thing; the display handles rows written before.
|
|
20
|
+
|
|
21
|
+
## 4. Signal vs authority
|
|
22
|
+
The hook stays signal-only (it never blocks). No authority added.
|
|
23
|
+
|
|
24
|
+
## 5. Interactions
|
|
25
|
+
None with the tone gate or the blocker state machine; only the stored text changes.
|
|
26
|
+
|
|
27
|
+
## 6. External surfaces
|
|
28
|
+
Dashboard Plans "In the way" rows read as sentences. Built-in hook, always overwritten on update (Migration Parity).
|
|
29
|
+
|
|
30
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
31
|
+
|
|
32
|
+
Plans "In the way" rows now lead with the plain sentence the agent wrote (first line, capped at 220 chars) instead of shell wrapping; no raw command, topic number or link signature is shown. A row with no message reads "Flagged in a command I ran (no message text)" rather than the command. Layout and actions are unchanged; checked on a 390px-wide phone view in the existing console tests' row model.
|
|
33
|
+
|
|
34
|
+
## 7. Multi-machine posture
|
|
35
|
+
Machine-local ledger, unchanged.
|
|
36
|
+
|
|
37
|
+
## 8. Rollback cost
|
|
38
|
+
Revert.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# A tidier home screen and menus
|
|
2
|
+
|
|
3
|
+
This is a set of small clean-ups found by looking at the dashboard on a phone-sized screen.
|
|
4
|
+
|
|
5
|
+
On the home screen, each item in "What needs you" said the machine's name twice: once in the title ("Mac Lan: Remote Desktop needs…") and again in the grey line underneath ("Mac Lan · high"). The grey line now only adds what the title does not already say, and it no longer shows the word "high", which is the queue's own bookkeeping rather than anything you need. The card also ended with an empty band and a divider line; empty footers are now hidden.
|
|
6
|
+
|
|
7
|
+
On the Work page, the Pace card listed each queue with its coloured dot on a line of its own above the text, which looked broken on a phone; the dot now sits beside its text. It also listed queues on machines where nothing is happening at all ("0 a day in, 0 done"); those with nothing open at all are left out. Where nothing is moving but items are still open, the line now says exactly that ("nothing came in or got done this week; 1271 still open") instead of calling it "holding steady", which hid a large stalled pile.
|
|
8
|
+
|
|
9
|
+
Finally, the menus said "each of these opens in the dashboard, where everything works exactly as before" and "everything is still in the legacy dashboard". Neither is true any more, because most pages now open inside the new console. The first sentence is gone, and the second now simply says the older dashboard is still available, with a link.
|
|
10
|
+
|
|
11
|
+
On the Promises page, "Nothing is waiting on you" appeared twice, once as the big headline and again as an empty card right underneath. The empty card is gone; it comes back as soon as something is actually waiting on you.
|
|
12
|
+
|
|
13
|
+
The Messages page used to open with "No messages are held." Held replies are a bug that should not happen, so a page that leads with them makes them sound like a normal feature. With nothing held, the page now simply says "Messages to you". If replies ever are held, it still says so plainly at the top.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Side-effects review: tidier Home and group pages
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
- Home "What needs you" rows: the sub-line no longer repeats the machine when the title already starts with "<machine>:", and no longer shows the queue's priority word ("high").
|
|
5
|
+
- Every Home card: a footer with nothing in it is hidden (it left a blank band with a divider under the rows).
|
|
6
|
+
- Work / Capacity / People / Settings pages: the subtitle "Each of these opens in the dashboard, where everything works exactly as before." is removed (most tiles now open native console pages), and the footer reads "The older dashboard is still available: open the legacy dashboard." instead of claiming everything is still there.
|
|
7
|
+
- Promises page: the empty "Waiting on you" card is not shown when nothing waits (the headline already says "Nothing is waiting on you.").
|
|
8
|
+
- Messages page: with nothing held, the headline is "Messages to you" (locked and unlocked views) instead of "No messages are held."; held replies are a fault to fix, not something the page should lead with. When some are held, the existing count headline still shows.
|
|
9
|
+
- Pace card (Work): each line's dot stays beside its text at phone width (grid instead of a wrapping flex row), and queues with nothing coming in, nothing done and nothing open are not listed. A queue with no flow but items still open no longer reads "0 a day in, 0 done — holding steady"; it says "nothing came in or got done this week; N still open" (on 2026-11-02, Mac Mini and Laptop held 93–100 open promises and 1,161–1,271 open self-improvement items with no movement).
|
|
10
|
+
|
|
11
|
+
## Evidence
|
|
12
|
+
Phone-width screenshots of the preview (2026-11-02): rows read "Mac Lan: Remote Desktop …" over "Mac Lan · high"; the needs card ended in an empty 44 px footer; Pace showed each dot on its own line and four rows of "0 a day in, 0 done"; group pages carried the outdated subtitle and footer.
|
|
13
|
+
|
|
14
|
+
## 1–5
|
|
15
|
+
Not a gate; no decision points. A queue with any activity or open items is still listed; the summary line is unchanged. Footer hiding only applies when it has no text and no link.
|
|
16
|
+
|
|
17
|
+
## 6. External surfaces / 6b. Operator-surface quality
|
|
18
|
+
Dashboard only. Rechecked on the preview at 390×844 after the change.
|
|
19
|
+
|
|
20
|
+
## 7. Multi-machine posture
|
|
21
|
+
Page code only.
|
|
22
|
+
|
|
23
|
+
## 8. Rollback cost
|
|
24
|
+
Revert.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Reopening the app no longer drops a slow machine's items
|
|
2
|
+
|
|
3
|
+
The dashboard's home page gathers "what needs you" from every one of the agent's machines. If one machine is slow to answer, the page keeps showing that machine's items from the last complete answer for up to two minutes, instead of dropping them or saying part of the page could not be checked. That memory only lived inside the open page, though. Reloading the page, or reopening the iPhone app (which reloads it), wiped it. If a machine happened to be slow at that moment, its items vanished from the list.
|
|
4
|
+
|
|
5
|
+
Now the last complete answer is also kept in the browser's session storage, which survives a reload of the same tab. After a reload, the page uses it under the same two-minute rule. If the browser does not allow storage, or it is full, the page behaves exactly as before. Nothing new is shown, and nothing older than two minutes is used.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Side-effects review: the last complete pooled answer survives a page reload
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
`dashboard/console/console.js`: `rememberComplete()` also writes the last complete pooled answer (attention items, sessions) to `sessionStorage` under `console.lastComplete.<key>`; `fillFromLastComplete()` reads it when page memory has none. Every read/write is in try/catch: with storage absent, full or unreadable it is page memory only, as before. The two-minute window (`PARTIAL_GRACE_MS` in `fillPartial`) and all fill rules are unchanged.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
Live check 2026-11-02 against 1.3.1611: Home loaded with all machines, then a page reload whose pooled attention read dropped the Mac Mini (simulated `pool.failed`). The Mac Mini's needs-you row disappeared, because the remembered answer lived in page memory, which a reload clears. Reopening the iPhone app is a reload. Same check on the preview with this change: the Mac Mini's row stays after the reload.
|
|
8
|
+
|
|
9
|
+
## 1. Over-block
|
|
10
|
+
None.
|
|
11
|
+
|
|
12
|
+
## 2. Under-block
|
|
13
|
+
After two minutes the stored answer is ignored, exactly as in memory. A new browser tab has its own session storage.
|
|
14
|
+
|
|
15
|
+
## 3. Level-of-abstraction fit
|
|
16
|
+
Same place the memory copy is kept.
|
|
17
|
+
|
|
18
|
+
## 4. Signal vs authority
|
|
19
|
+
Display only.
|
|
20
|
+
|
|
21
|
+
## 5. Interactions
|
|
22
|
+
None; `fillPartial` is unchanged.
|
|
23
|
+
|
|
24
|
+
## 6. External surfaces
|
|
25
|
+
Browser session storage holds up to two answers (attention items ~380 KB, sessions) for the tab's lifetime. They contain what the page already displays.
|
|
26
|
+
|
|
27
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
28
|
+
Reopening the app while one machine is slow keeps that machine's needs-you rows and shows no "could not be checked" for the first two minutes, the same as navigating inside the app.
|
|
29
|
+
|
|
30
|
+
## 7. Multi-machine posture
|
|
31
|
+
Client-side only.
|
|
32
|
+
|
|
33
|
+
## 8. Rollback cost
|
|
34
|
+
Revert.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Saved secrets from Secret Drop show as one line, not long codes
|
|
2
|
+
|
|
3
|
+
The Secrets page in the dashboard lists the names of the secrets the agent keeps, never their values, so the operator can see what it already has. Most names are readable, like "github_token". But when someone sends a secret through a Secret Drop link, the agent keeps a durable copy under a name made from a long random code, sixty-four letters and numbers. Each of those showed up on the page as its own line of gibberish, which on a phone wraps across the screen and tells the operator nothing.
|
|
4
|
+
|
|
5
|
+
Now the page counts those entries and shows one line instead, such as "Saved Secret Drop submissions (2)". Every other name is listed exactly as before, and nothing about how secrets are stored or used changes. The list the agent reads for itself when it starts a session still has the full names, because the agent may need them; only what the operator sees on the page is tidied.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Side-effects review: Secrets page groups saved Secret Drop entries
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
`dashboard/console/secrets-model.js` `buildKeys()`: names matching `secretDrops.<16+ hex>` (a saved Secret Drop's storage token, often with "(+N nested)") are replaced by one counted line, "Saved Secret Drop submissions (N)". Other names unchanged. Console display only; the session-start key list (`BootSelfKnowledge`) is untouched.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
On the Studio (2026-11-02) the Secrets page listed several `secretDrops.<64-hex> (+5 nested)` rows; the raw-id scan of every console page flagged them as long hex ids.
|
|
8
|
+
|
|
9
|
+
## 1. Over-block
|
|
10
|
+
A real vault key literally named `secretDrops.<hex>` would be counted rather than listed; none exists by convention.
|
|
11
|
+
|
|
12
|
+
## 2. Under-block
|
|
13
|
+
None.
|
|
14
|
+
|
|
15
|
+
## 3. Level-of-abstraction fit
|
|
16
|
+
Display-only; the agent still needs the full names at session start.
|
|
17
|
+
|
|
18
|
+
## 4. Signal vs authority
|
|
19
|
+
No decision logic.
|
|
20
|
+
|
|
21
|
+
## 5. Interactions
|
|
22
|
+
None.
|
|
23
|
+
|
|
24
|
+
## 6. External surfaces
|
|
25
|
+
Secrets page list reads as names.
|
|
26
|
+
|
|
27
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
28
|
+
The "keys I hold" list no longer shows 64-character tokens that wrap on a phone; saved drops are one line with a count. No values or lengths are shown, as before.
|
|
29
|
+
|
|
30
|
+
## 7. Multi-machine posture
|
|
31
|
+
Per-machine read, unchanged.
|
|
32
|
+
|
|
33
|
+
## 8. Rollback cost
|
|
34
|
+
Revert.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# ELI16 — why tests were filling the Mac's Accessibility list, and the fix
|
|
2
|
+
|
|
3
|
+
Macs keep a list of programs allowed to control the computer (Accessibility) and to see the screen (Screen Recording). The list remembers each program by where it sits on disk. Instar's remote-desktop helper is one such program, and the one that matters lives in a fixed place in your home folder.
|
|
4
|
+
|
|
5
|
+
Our automated tests started the real helper too. They ran it from a fresh copy of the code each time, often a throwaway copy used to check a pull request. Each copy sits somewhere new on disk, so each test run added another "instar-desktop-host" line to the list, all switched off. One test even asked macOS for permission, which made pop-ups appear on the screen of the person using that Mac. On the Mac Studio this produced dozens of lines and kept adding more after Justin removed them.
|
|
6
|
+
|
|
7
|
+
The fix has two parts. First, the code that starts the helper now refuses to start the real one when it is running inside the test tool, unless someone sets a specific switch meaning "this Mac is set aside for these tests". Second, the handful of tests that genuinely need the real helper now run only with that switch on. Tests that use a pretend helper, which is most of them, keep running as before.
|
|
8
|
+
|
|
9
|
+
Nothing changes for an agent running normally: the check only applies inside the test tool. The old lines already on a Mac stay until someone removes them with the minus button. The one at ~/.instar/desktop-helper/instar-desktop-host must stay on.
|
|
10
|
+
|
|
11
|
+
The decision for a reader: whether real-helper tests should ever run on an operator's everyday Mac. This change says no, by default.
|
|
12
|
+
|
|
13
|
+
One small fix came with it: the page that reports whether locked-screen access is set up now answers "cannot read it here" straight away when the helper cannot be checked, instead of waiting until the request times out.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Side-Effects Review — test runs stop touching the Mac's privacy lists
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `desktop-tests-stop-touching-tcc`
|
|
4
|
+
**Date:** `2026-11-02`
|
|
5
|
+
**Author:** `Echo`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent (see below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Operator report 2026-11-02 10:37 (topic 134088, with a photo): "instar-desktop-host" entries kept appearing in the Studio's Accessibility list after he removed them, plus a "tmux would like to access data from other apps" pop-up. Cause: desktop tests ran the real packaged helper (`dist/native/instar-desktop-host`) from each checkout / Local Verify clone. macOS keys privacy entries by path, so each run added a row; `desktop-own-identity` also runs `request-permission`, which raises real prompts.
|
|
11
|
+
|
|
12
|
+
Changes:
|
|
13
|
+
1. `src/core/MacDesktopHost.ts`: `packagedBinary()` throws `mac-desktop-host-test-guard` when `VITEST` is set, `INSTAR_REAL_DESKTOP_HELPER_TESTS` is not `1`, and the resolved binary is the real packaged helper. Logic in the exported pure function `realHelperBlockedUnderTest`.
|
|
14
|
+
2. `tests/e2e/desktop-own-identity.test.ts`, `tests/e2e/desktop-helper-retention-lifecycle.test.ts`: skip unless the opt-in is set (they spawn the binary directly or build their own signed copies, so the host guard alone does not cover them).
|
|
15
|
+
3. `tests/e2e/desktop-boot-lifecycle.test.ts`: the three live-Mac-target cases skip unless the opt-in is set. `tests/unit/desktop-helper-state-op.test.ts` (spawns the real binary to decide whether to skip) gets the same gate.
|
|
16
|
+
5. `src/core/MachineLockStateReader.ts`: its default probe, the one other direct spawn of the real helper, applies the same rule and reports "cannot run" (null) under a test runner.
|
|
17
|
+
4. `tests/unit/desktop-real-helper-test-guard.test.ts`: both sides of the guard.
|
|
18
|
+
|
|
19
|
+
6. `src/server/routes.ts` `GET /desktop/locked/status`: when the helper's locked state cannot be read (helper missing, refused, invalid), it answers 503 with a reason code instead of leaving the request hanging. Found by Local Verify: with the guard, the e2e test's request hung for 30 s (408). The real-helper case of `desktop-locked-lifecycle` is now opt-in; a new non-opt-in case checks the route answers.
|
|
20
|
+
|
|
21
|
+
## Decision-point inventory
|
|
22
|
+
|
|
23
|
+
- `realHelperBlockedUnderTest` — add — invariant: a test runner never starts the real packaged helper without an explicit opt-in. Deterministic by design: the harm (a permanent privacy-list row on the operator's Mac, real pop-ups) lands the moment the binary runs.
|
|
24
|
+
|
|
25
|
+
## 1. Over-block
|
|
26
|
+
|
|
27
|
+
Under a test runner, any code path that starts the real packaged helper now fails with `mac-desktop-host-test-guard` instead of running. The comparison uses the expected path, not a resolved one, so fake helpers in `dist/native` still run on a Mac where the real helper was never built (the second-pass review caught an earlier draft that resolved the real path and failed when it was missing). Tests that relied on the real helper are exactly the ones now opt-in gated; route-wiring assertions in the other desktop e2e files tolerate an unavailable probe (they already run on Linux CI where the helper does not exist). Production (no `VITEST`) is unaffected.
|
|
28
|
+
|
|
29
|
+
## 2. Under-block
|
|
30
|
+
|
|
31
|
+
A test that copies the helper to a new name and runs it (as the retention test does) is not caught by the host guard; that test is gated by the env opt-in instead. A future test doing the same would not be caught structurally; the unit test documents the rule, and the opt-in name is greppable.
|
|
32
|
+
|
|
33
|
+
Coverage that now runs only with the opt-in: the own-identity re-exec check, the signed kept-copy retention lifecycle, the live Mac target on the production route, and the helper state verb. These were already Mac-only (never on Linux CI). They need a deliberate opt-in run on a Mac set aside for it; tracked as commitment CMT-1241 (run them on the Laptop test user during the locked-Mac proof sitting).
|
|
34
|
+
|
|
35
|
+
## 3. Level-of-abstraction fit
|
|
36
|
+
|
|
37
|
+
The host's `packagedBinary()` is the single place every real-helper spawn resolves through (probe, capture, input, indicator, relock guard), so one check covers all verbs.
|
|
38
|
+
|
|
39
|
+
## 4. Signal vs authority compliance
|
|
40
|
+
|
|
41
|
+
The guard is a hard refusal, but only inside a test runner and only for one exact path; it gates no user-facing behaviour. Reference: docs/signal-vs-authority.md.
|
|
42
|
+
|
|
43
|
+
## 5. Interactions
|
|
44
|
+
|
|
45
|
+
`DesktopHelperRetention` is already disabled under a test runner, so no kept copy is used in tests; the guard covers the packaged fallback. No interaction with Local Verify beyond fewer rows on the verifying Mac.
|
|
46
|
+
|
|
47
|
+
## 6. External surfaces
|
|
48
|
+
|
|
49
|
+
Removes an external side effect of the test suite (privacy-list rows, permission prompts on the operator's Mac). No runtime surface changes.
|
|
50
|
+
|
|
51
|
+
## 7. Multi-machine posture
|
|
52
|
+
|
|
53
|
+
Machine-local by design (`hardware-bound-resource`: a Mac's privacy database is per machine). Every Mac that runs tests benefits independently.
|
|
54
|
+
|
|
55
|
+
## 8. Rollback cost
|
|
56
|
+
|
|
57
|
+
Revert the commit. Rows would start appearing again on test-running Macs; no data or state to repair.
|
|
58
|
+
|
|
59
|
+
## Second-pass review
|
|
60
|
+
|
|
61
|
+
**Reviewer:** independent reviewer subagent (two rounds, 2026-11-02)
|
|
62
|
+
|
|
63
|
+
Round 1 raised a concern: the guard resolved the default helper path on disk, so every fake-helper test failed on a Mac without a built helper (43 of 70), and `desktop-helper-state-op.test.ts` plus `MachineLockStateReader`'s default probe still ran the real helper. All three were fixed. Round 2: "Concur with the review" — fake-helper and guard tests 70/70 with and without a built helper; state-op, lock-state and watch-only tests 27 passed, 6 skipped (opt-in). Noted, no action: a symlink at the default path pointing to another name would pass the guard; nothing creates one.
|
|
64
|
+
|
|
65
|
+
Follow-up (route fix, item 6): independent reviewer — Concur with the review. Only `status()` is inside the try; the error passes through only when it matches the `mac-desktop-host-*` code shape, else a fixed code; success and pool paths unchanged; the test split is sound (the new case proves the route answers, not that a 503 happens, which is its job).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# The automatic merger only lines up changes that passed their tests
|
|
2
|
+
|
|
3
|
+
Before a change is merged automatically, it is tested in full on one of the agent's machines, and the result is kept as a record. The automatic merger then looks for changes that "have a record" and merges the oldest one.
|
|
4
|
+
|
|
5
|
+
The flaw: a change whose tests had failed also "had a record". So the merger would pick a failed change, try to merge it, get refused (the final safety check never lets a failed change through), wait, and try again later. Because it always starts with the oldest change, a failed one at the front of the line held up every working change behind it. That happened today.
|
|
6
|
+
|
|
7
|
+
Now only a record of a passing test run counts. A change whose tests failed simply waits until someone fixes it and it is tested again, and the changes behind it are merged as soon as they pass. Nothing that could not be merged before can be merged now; the merger just stops wasting its turns on changes that are certain to be refused.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Side-effects review: only a passing verify record makes a PR merge-eligible
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
`src/localVerify/localVerifyWiring.ts` adds `hasPassingRecord(svc, pr, head, base)` (true only for `outcome === 'success'`); `src/commands/server.ts` wires the green-PR watcher's `hasRecord` through it. Before, `!!svc.getRecord(...)` counted a FAILED record as "has a record".
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
On the Studio (2026-11-02 22:13 UTC) the watcher's acting pass picked #2567, whose local verify had failed, as the oldest eligible PR; safe-merge refused it (`refused:verify-failed`), the tick ended, and the PR entered the retry ladder at the front of the line. Under the local floor safe-merge already refuses any non-success record, so every such attempt was a guaranteed refusal that cost a tick and an episode attempt.
|
|
8
|
+
|
|
9
|
+
## 1. Over-block
|
|
10
|
+
None: a PR with a failed record could never merge (safe-merge refuses it). It now waits as `no-verify-record` until a new head is verified.
|
|
11
|
+
|
|
12
|
+
## 2. Under-block
|
|
13
|
+
None new.
|
|
14
|
+
|
|
15
|
+
## 3. Level-of-abstraction fit
|
|
16
|
+
The watcher's eligibility read is where "verified" is decided; safe-merge stays the act-time authority.
|
|
17
|
+
|
|
18
|
+
## 4. Signal vs authority
|
|
19
|
+
Narrows candidacy only; never widens what can merge.
|
|
20
|
+
|
|
21
|
+
## 5. Interactions
|
|
22
|
+
`requestVerifies` still runs over all candidates, so a failed PR's new head still gets verified. For an unchanged failed head it now asks every tick; `LocalVerifyService.request` returns the existing failure record (`recorded`) without queuing anything, so the cost is three GitHub reads and one `verify-requested` audit row per failed PR per tick, and no test run. The red-PR watchdog reads GitHub check state, not this flag.
|
|
23
|
+
|
|
24
|
+
## 6. External surfaces
|
|
25
|
+
Fewer refused merge attempts in the audit log.
|
|
26
|
+
|
|
27
|
+
## 7. Multi-machine posture
|
|
28
|
+
Runs on the lease holder, as before.
|
|
29
|
+
|
|
30
|
+
## 8. Rollback cost
|
|
31
|
+
Revert.
|
|
32
|
+
|
|
33
|
+
## Class closure
|
|
34
|
+
unbounded-self-action: n/a. Narrows an existing selection; adds no action.
|
|
35
|
+
|
|
36
|
+
## Second-pass review
|
|
37
|
+
**Reviewer:** independent Claude Opus 5.5 subagent (general-purpose), given the diff and the write-up, not the conclusion.
|
|
38
|
+
|
|
39
|
+
Concur with the review. `hasRecord` is consumed only by `GreenPrAutoMerger.recordFor` (acting pass, hand-off, not-agent-owned audit label, `requestVerifies`); nothing relies on a failed record counting as true. The change only narrows (`classifyCandidate` requires `hasRecord === true`; safe-merge already refused failed records). No re-verify loop: a non-explicit request on a failed same-head record returns `recorded` early. The per-tick read cost noted in section 5 is from this review.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Ready changes are no longer invisible to the automatic merger
|
|
2
|
+
|
|
3
|
+
To decide what to merge, the automatic merger asks GitHub for the list of open changes and whether each one can be merged cleanly. GitHub works that out lazily: in a list it often answers "unknown" until someone opens the change itself, and every time something merges, it forgets the answer for all the others. The merger treated "unknown" as "can't merge" and moved on without a word, so a change that had passed all its tests could sit there unseen, round after round. Today that happened to one of the fixes until it was opened by hand, after which it merged on the very next round.
|
|
4
|
+
|
|
5
|
+
Now, when a change has passed its tests and GitHub says "unknown", the merger looks at that one change directly. That makes GitHub work out the answer, so on the next round the change is seen as mergeable and goes in. It also writes a short note in its log saying it is waiting for that answer. Changes that genuinely conflict are left alone, and nothing that could not be merged before can be merged now.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Side-effects review: a verified PR with UNKNOWN mergeability is read directly
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
`src/monitoring/GreenPrAutoMerger.ts` (`gather`): when a candidate is skipped as `not-mergeable` because the PR list reported `mergeable: UNKNOWN`, and its head is verified (a passing local-verify record under the local floor; any candidate under the actions floor), the watcher logs `waiting: mergeability-unknown` and reads that PR once with the existing `refetchPr` (a direct single-PR read), whose field list (`REFETCH_PR_JSON_FIELDS`, `greenPrAutomergeWiring.ts`) now also requests `mergeable`, pinned by the wiring test. Under the actions floor the nudge also requires green checks (`statusRollup === 'SUCCESS'`). GitHub computes mergeability on a direct read, so the next tick lists it as MERGEABLE. A real `CONFLICTING` PR is not read.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
On the Studio (2026-11-02) after #2602, #2609 and #2606 merged, #2611 (verified at 22:00) was absent from the watcher's candidates on every pass and a manual pass answered `no-candidate`; a direct `gh pr view` returned MERGEABLE, and the very next pass merged it (22:30). Earlier the same day #2602, #2605, #2609 and #2611 all read MERGEABLE only after being viewed directly.
|
|
8
|
+
|
|
9
|
+
Live check 2026-11-02 22:44 UTC: PR #2457 listed UNKNOWN; after one direct read the list returned MERGEABLE.
|
|
10
|
+
|
|
11
|
+
## 1. Over-block
|
|
12
|
+
None.
|
|
13
|
+
|
|
14
|
+
## 2. Under-block
|
|
15
|
+
Merging still waits one tick after the nudge (the list is re-read next tick).
|
|
16
|
+
|
|
17
|
+
## 3. Level-of-abstraction fit
|
|
18
|
+
`gather` is where list fields decide candidacy; the read reuses the existing dependency.
|
|
19
|
+
|
|
20
|
+
## 4. Signal vs authority
|
|
21
|
+
No change to what may merge; safe-merge stays the authority.
|
|
22
|
+
|
|
23
|
+
## 5. Interactions
|
|
24
|
+
At most one extra GitHub read per verified-but-UNKNOWN PR per tick (a handful). The read's errors are swallowed (it is only a nudge). `act()` still refetches before any attempt.
|
|
25
|
+
|
|
26
|
+
## 6. External surfaces
|
|
27
|
+
Fewer stalled ready PRs; one new audit event class.
|
|
28
|
+
|
|
29
|
+
## 7. Multi-machine posture
|
|
30
|
+
Lease holder only, as before.
|
|
31
|
+
|
|
32
|
+
## 8. Rollback cost
|
|
33
|
+
Revert.
|
|
34
|
+
|
|
35
|
+
## Class closure
|
|
36
|
+
unbounded-self-action: n/a. One bounded read per verified PR per tick; no new loop or trigger.
|
|
37
|
+
|
|
38
|
+
## Second-pass review
|
|
39
|
+
**Reviewer:** independent Claude Opus 5.5 subagent (general-purpose), given the diff and the write-up, not the conclusion.
|
|
40
|
+
|
|
41
|
+
First pass raised: the production read did not request `mergeable`; no wiring pin; the actions-floor case nudged PRs with unfinished checks. All three fixed (field added and pinned, `statusRollup === 'SUCCESS'` required, a PENDING case in the unit test). Second look: "Concur with the review." `refetchPr` is a plain read-only `gh pr view`; `recordFor` uses the same lookup as the classifier; warm-up and dry-run ticks make the read harmlessly; awaiting in the loop keeps rate use small.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# A topic's lost replies show as one alert, not one per message
|
|
2
|
+
|
|
3
|
+
When one of the agent's machines writes a reply but is not the machine allowed to send to Telegram right then, the reply is held and handed to the machine that is. If that hand-over does not happen within six hours, the reply expires and the agent raises an alert saying a reply to that topic could not be delivered. Each expired reply raised its own alert. Over two weeks the machines piled up about 750 of these open alerts, 109 for a single topic. They are kept out of the operator's alert topic, but every time the dashboard asks all the machines for open alerts, it downloads all of them, about three quarters of a megabyte, which is why the first load after a restart took 25 seconds.
|
|
4
|
+
|
|
5
|
+
Now all expiries for the same topic share one alert. The first one opens it; later ones add to its count and refresh its text, so a topic shows one row saying how many replies were lost instead of a wall of identical rows. Every expiry is still recorded in the delivery log with its own identifier, so nothing is hidden. Alerts created before this change are not merged, but they close on their own within two weeks.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Side-effects review: held-reply expiry alerts are one row per topic
|
|
2
|
+
|
|
3
|
+
## Change
|
|
4
|
+
`src/messaging/telegram-origin/OriginForwardToHolder.ts`: new `heldExpiryItem(row)` builds the attention item raised when a held reply expires. The id stays per operation; the new `conditionKey` is `telegram-origin-held:<topic>:expired` (or `unknown`). `src/commands/server.ts` `onHeldForwardExpired` raises that item (same category, priority, title, wording, source, `hubOnly`). With the attention lifecycle on, a later expiry for the same topic lands on the existing open row (occurrences +1, alias recorded, summary refreshed) instead of a new row.
|
|
5
|
+
|
|
6
|
+
## Evidence
|
|
7
|
+
On 2026-11-02 the pooled read `GET /attention?scope=pool&status=OPEN` returned 978 open items (742 KB); 744 were keyless "Held reply to topic N expired" rows (109 for topic 33890 alone), mostly from the Mac Mini, Laptop and Mama PC. The lifecycle could not merge them: their derived keys differ by operation id. The first read after a restart took 25 s, and the console showed "Checking in…" for that long. The held-reply logs (Mini, Laptop) show the expiries were mostly automated job output and stopped after 11-01.
|
|
8
|
+
|
|
9
|
+
## 1. Over-block
|
|
10
|
+
None; nothing is suppressed. Every expiry is still reported once and written to the held-reply audit log with its operation id.
|
|
11
|
+
|
|
12
|
+
## 2. Under-block
|
|
13
|
+
Rows raised before this change (no conditionKey) do not merge with the new keyed row; they age out under the existing 14-day automated window.
|
|
14
|
+
|
|
15
|
+
## 3. Level-of-abstraction fit
|
|
16
|
+
The producer names its condition; the lifecycle's one-row-per-condition rule does the merging, as documented for recurring items.
|
|
17
|
+
|
|
18
|
+
## 4. Signal vs authority
|
|
19
|
+
No decision logic; a notification's grouping only.
|
|
20
|
+
|
|
21
|
+
## 5. Interactions
|
|
22
|
+
`hubOnly` and priority unchanged. The aggregate/held-active items (`HeldForwardItemCollapser`) keep their own ids and keys. The `telegram-origin-held` resolver is unchanged.
|
|
23
|
+
|
|
24
|
+
## 6. External surfaces
|
|
25
|
+
The Attention list shows one expiry row per topic with a count instead of one row per lost message.
|
|
26
|
+
|
|
27
|
+
## 6b. Operator-surface quality (Operator-Surface Quality standard)
|
|
28
|
+
A topic that loses several replies reads as one row with its count, not a wall of identical rows; pooled reads stay small, so Home answers promptly instead of "Checking in…".
|
|
29
|
+
|
|
30
|
+
## 7. Multi-machine posture
|
|
31
|
+
Machine-local attention store per machine, unchanged; pooled read merges them.
|
|
32
|
+
|
|
33
|
+
## 8. Rollback cost
|
|
34
|
+
Revert.
|
|
35
|
+
|
|
36
|
+
## Class closure
|
|
37
|
+
unbounded-self-action: n/a. Changes the grouping key of an existing notification; adds no action, loop or trigger.
|