staysfixed 0.6.0 → 0.6.2
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/CHANGELOG.md +11 -0
- package/README.md +9 -0
- package/package.json +1 -1
- package/src/v2/adapters/android-driver.js +8 -1
- package/src/v2/adapters/android.js +36 -4
- package/src/v2/watch/events.js +1087 -0
- package/src/v2/watch/index.js +382 -0
- package/src/v2/watch/panel.js +1660 -0
- package/src/v2/watch/window.js +1671 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to this project are recorded here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the version
|
|
5
5
|
numbers follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.6.1] — 2026-08-30
|
|
8
|
+
|
|
9
|
+
Documentation only, and it is a correction rather than a polish. The README
|
|
10
|
+
described where journeys come from in the words of the design — read the code,
|
|
11
|
+
then harvest the project's own test suite, then recorded sessions — and only the
|
|
12
|
+
first of those is wired into `staysfixed check`. The suite harvest, session
|
|
13
|
+
recording and the flake register are written and tested in `src/v2/journeys/` and
|
|
14
|
+
nothing on the check path calls them. Both places that implied otherwise now say
|
|
15
|
+
so plainly. A feature that exists in the repository and not in the run is not a
|
|
16
|
+
feature you have.
|
|
17
|
+
|
|
7
18
|
## [0.6.0] — 2026-08-30
|
|
8
19
|
|
|
9
20
|
The second sweep for silences, done the same way as the first: read all of
|
package/README.md
CHANGED
|
@@ -148,6 +148,7 @@ pretend otherwise.
|
|
|
148
148
|
| The reference cut when you ship, sealed intents, the waiver budget, and escalations in your closing summary | **Works.** This page describes what it actually does. |
|
|
149
149
|
| The coverage ledger — every door counted, the unopened ones named, and the sentence saying so on every reply | **Works.** See [what it did not check](#what-it-did-not-check). |
|
|
150
150
|
| Aiming a check at one kind of product, and refusing by name rather than checking something else | **Works.** |
|
|
151
|
+
| Steps taken from your own test suite, from a recorded session, or rejected at birth for not repeating twice | **Written, not wired.** The code is in `src/v2/journeys/` with tests around it, and nothing on the check path calls it yet. Journeys today come from what each adapter reads out of your source, plus any `--journeys` file you name. |
|
|
151
152
|
| Android APKs on an emulator | **The adapter is here.** It reads everything the APK declares with nothing installed and no Java, and where there is an emulator it installs one build at a time and walks it. Whether *this* machine can run one is a separate question, and `doctor` asks the adapter itself rather than keeping a second opinion — most of what it wants installs with a command; accepting Google's licence, once, needs a person. Two emulator snapshots restoring byte-identically is unproven, so Android compares against the stored record and says which mode it used. |
|
|
152
153
|
| The iOS simulator | **The adapter is here.** It reads what the app bundle declares with nothing running, and where Xcode and a simulator runtime are present it installs one build at a time, boots it and reads what is on the screen. It is new. Paired running costs two `xcodebuild` passes, so it is for before a release rather than for every edit, and like Android it compares against the stored record and says which mode it used. Ask `doctor` what it is actually covering on your machine before trusting a clean run. |
|
|
153
154
|
| Native Windows GUI (a real Win32 app, not an Electron one) | **The probe is here**, driven over ssh to any machine that reaches a Windows desktop — a WSL shell on one counts, and nothing is installed on it. Windows shows one desktop, so two builds can never run at once: the comparison is genuinely weaker here than anywhere else. |
|
|
@@ -232,6 +233,14 @@ under instrumentation → recorded real sessions → the agent exploring one nam
|
|
|
232
233
|
gap and freezing it into a replayable file → never a person clicking through an
|
|
233
234
|
app.
|
|
234
235
|
|
|
236
|
+
What is actually wired into `staysfixed check` today is the first of those and a
|
|
237
|
+
journeys file you point it at: each adapter reads your source and offers the
|
|
238
|
+
journeys it can walk — routes, commands, screens, message channels — and
|
|
239
|
+
`--journeys <file>` names steps by hand. The suite harvest, recorded sessions and
|
|
240
|
+
the flake register are written and tested in `src/v2/journeys/`, and **nothing on
|
|
241
|
+
the check path calls them yet**. Saying so is the point: a feature that exists in
|
|
242
|
+
the repository and not in the run is not a feature you have.
|
|
243
|
+
|
|
235
244
|
## Keeping it quiet
|
|
236
245
|
|
|
237
246
|
Four layers before anything reaches the agent: **normalise** volatile shapes by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "staysfixed",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Prove that what already worked still works after an agent changed the code. Picture checks, guards for fixed bugs, a pre-release walkthrough, and known-good markers — as a CLI and as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1183,7 +1183,14 @@ export function looksIrreversible(method, url) {
|
|
|
1183
1183
|
* in the background is filed separately, and this limit is stated in the adapter's own
|
|
1184
1184
|
* description rather than hidden here.
|
|
1185
1185
|
*/
|
|
1186
|
-
|
|
1186
|
+
/*
|
|
1187
|
+
* Every one of these was seen leaving a Google APIs emulator on 2026-08-30 while Terminal
|
|
1188
|
+
* Deck — an app that talks to one relay and nothing else — sat on its first screen. The
|
|
1189
|
+
* ones added that day are on the second line: `googleadservices` and `youtube` were being
|
|
1190
|
+
* filed as calls THE APP made, which reads as "your change made the app phone YouTube" and
|
|
1191
|
+
* is the most alarming false alarm this adapter is capable of producing.
|
|
1192
|
+
*/
|
|
1193
|
+
const DEVICE_HOSTS = /(^|\.)(google|googleapis|gstatic|android|gvt1|gvt2|doubleclick|crashlytics|firebaseinstallations)\.com$|(^|\.)(googleadservices|googlesyndication|googletagmanager|googleusercontent|google-analytics|youtube|ytimg|ggpht|appspot)\.com$|(^|\.)google\.[a-z.]+$/i;
|
|
1187
1194
|
|
|
1188
1195
|
/**
|
|
1189
1196
|
* @param {string} host
|
|
@@ -953,22 +953,54 @@ export const androidAdapter = defineAdapter({
|
|
|
953
953
|
const key = `${call.method} ${call.host}${call.route}`;
|
|
954
954
|
byCall.set(key, (byCall.get(key) ?? 0) + 1);
|
|
955
955
|
}
|
|
956
|
+
/**
|
|
957
|
+
* The phone's own background chatter is COUNTED, never given an address of its own.
|
|
958
|
+
*
|
|
959
|
+
* A device-wide proxy sees the whole phone, and on a Google APIs emulator most of what
|
|
960
|
+
* it sees is Play services: checking connectivity, fetching fonts, asking about digital
|
|
961
|
+
* asset links, phoning gmscompliance. None of it belongs to the app being checked, none
|
|
962
|
+
* of it is the same twice, and every one of those hosts given an address of its own is
|
|
963
|
+
* an address that appears in one run and is gone in the next.
|
|
964
|
+
*
|
|
965
|
+
* Measured on Terminal Deck on 2026-08-30, on a build with NOTHING changed in it: eight
|
|
966
|
+
* findings, seven of them a Google host that came or went. Running the build twice
|
|
967
|
+
* subtracted fifteen more and still could not subtract those, because this traffic is
|
|
968
|
+
* not merely unstable — it is episodic, so a run pair can agree with itself and still
|
|
969
|
+
* disagree with the run pair before it. Left as it was, an Android check reports noise
|
|
970
|
+
* on a build nobody touched, and the first thing anybody does with a tool that cries
|
|
971
|
+
* wolf is switch it off.
|
|
972
|
+
*
|
|
973
|
+
* So: the app's own calls keep one address each, which is the signal worth having, and
|
|
974
|
+
* the phone's own are reduced to a count that sits on one rung of a coarse ladder. What
|
|
975
|
+
* was seen is still said in plain English, and the ledger still records that nothing
|
|
976
|
+
* inside those requests was ever opened.
|
|
977
|
+
*/
|
|
978
|
+
let fromThePhone = 0;
|
|
956
979
|
for (const [key, times] of [...byCall.entries()].sort()) {
|
|
957
980
|
const call = calls.find((c) => `${c.method} ${c.host}${c.route}` === key);
|
|
958
981
|
if (!call) continue;
|
|
959
|
-
|
|
960
|
-
|
|
982
|
+
if (isDeviceHost(call.host)) {
|
|
983
|
+
fromThePhone += 1;
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
fromTheApp += 1;
|
|
961
987
|
out.push(observation({
|
|
962
988
|
channel: 'effects',
|
|
963
|
-
path: joinPath('net', journey.name,
|
|
989
|
+
path: joinPath('net', journey.name, 'the app', key),
|
|
964
990
|
value: { asked: countBucket(times), reached: call.allowed ? 'let through' : 'stopped here' },
|
|
965
|
-
says:
|
|
991
|
+
says: `the app tried to call ${key}${times > 1 ? ` ${times} times` : ''} — ${call.why}`,
|
|
966
992
|
covered: call.how === 'encrypted' ? false : undefined,
|
|
967
993
|
reason: call.how === 'encrypted' ? 'not supported here' : undefined,
|
|
968
994
|
journey: journey.name,
|
|
969
995
|
surface: 'android',
|
|
970
996
|
}));
|
|
971
997
|
}
|
|
998
|
+
out.push(notCovered({
|
|
999
|
+
channel: 'effects',
|
|
1000
|
+
path: joinPath('net', journey.name, 'the phone itself'),
|
|
1001
|
+
reason: 'not supported here',
|
|
1002
|
+
says: `${fromThePhone === 0 ? 'nothing else on the phone' : `${fromThePhone} other thing${fromThePhone === 1 ? '' : 's'} on the phone`} reached out while this ran — Android\'s own services, not this app. It is watched and stopped here like everything else, and it is not compared, because a device-wide proxy cannot tell which program made a call and Google\'s background traffic is different every single run.`,
|
|
1003
|
+
}));
|
|
972
1004
|
out.push(observation({
|
|
973
1005
|
channel: 'counters',
|
|
974
1006
|
path: joinPath('count', journey.name, 'calls the app made'),
|