@hasna/recordings 0.2.13 → 0.3.1

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.
Files changed (120) hide show
  1. package/Dockerfile.package +4 -5
  2. package/README.md +113 -8
  3. package/bun.lock +2 -2
  4. package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
  5. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
  6. package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
  7. package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
  9. package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
  10. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
  11. package/dist/__tests__/preload.d.ts +1 -4
  12. package/dist/__tests__/preload.d.ts.map +1 -1
  13. package/dist/cli/desktop-snapshot.d.ts +20 -0
  14. package/dist/cli/desktop-snapshot.d.ts.map +1 -0
  15. package/dist/cli/index.js +2078 -277
  16. package/dist/cli/macos-permissions.d.ts +72 -0
  17. package/dist/cli/macos-permissions.d.ts.map +1 -1
  18. package/dist/cli/macos-shortcut.d.ts +139 -0
  19. package/dist/cli/macos-shortcut.d.ts.map +1 -0
  20. package/dist/cli/trigger-probe.d.ts +172 -0
  21. package/dist/cli/trigger-probe.d.ts.map +1 -0
  22. package/dist/db/database.d.ts +9 -0
  23. package/dist/db/database.d.ts.map +1 -1
  24. package/dist/http/client.d.ts +10 -7
  25. package/dist/http/client.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +634 -49
  29. package/dist/lib/capture-probe.d.ts +216 -0
  30. package/dist/lib/capture-probe.d.ts.map +1 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/macos-bundle.d.ts +50 -0
  33. package/dist/lib/macos-bundle.d.ts.map +1 -0
  34. package/dist/lib/persistence-probe.d.ts +208 -0
  35. package/dist/lib/persistence-probe.d.ts.map +1 -0
  36. package/dist/lib/recorder.d.ts.map +1 -1
  37. package/dist/lib/retired-deployment-modes.d.ts +24 -0
  38. package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
  39. package/dist/lib/transcriber.d.ts +60 -0
  40. package/dist/lib/transcriber.d.ts.map +1 -1
  41. package/dist/mcp/index.js +140 -56
  42. package/dist/server/cloud-config.d.ts +25 -3
  43. package/dist/server/cloud-config.d.ts.map +1 -1
  44. package/dist/server/cloud-readiness.d.ts.map +1 -1
  45. package/dist/server/cloud.d.ts +8 -7
  46. package/dist/server/cloud.d.ts.map +1 -1
  47. package/dist/server/index.js +187 -79
  48. package/dist/server/migrate-command.d.ts.map +1 -1
  49. package/dist/server/serve.d.ts.map +1 -1
  50. package/dist/storage.d.ts +2 -2
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +108 -49
  53. package/dist/store.d.ts +1 -1
  54. package/dist/store.d.ts.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/package.json +25 -6
  58. package/scripts/build_native_fs_guard.sh +15 -2
  59. package/scripts/ci-linux-suite.ts +403 -0
  60. package/scripts/ci-native-build.ts +402 -0
  61. package/scripts/enforce_identity_migration.sh +110 -0
  62. package/scripts/generate-sdk.ts +1 -1
  63. package/scripts/install_macos_app.sh +138 -24
  64. package/scripts/macos_artifact.ts +205 -33
  65. package/scripts/native/realtime-settle-probe/README.md +39 -0
  66. package/scripts/native/realtime-settle-probe/main.swift +135 -0
  67. package/scripts/policy/local-only-approved-targets.txt +21 -0
  68. package/scripts/read_local_only_targets.sh +203 -0
  69. package/scripts/scan-artifact.ts +40 -0
  70. package/scripts/set-version.ts +145 -0
  71. package/scripts/vacuity-manifest-gen.ts +96 -0
  72. package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
  73. package/scripts/vacuity-manifests/install-chain.tsv +13 -0
  74. package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
  75. package/scripts/vacuity-manifests/reorder.tsv +4 -0
  76. package/scripts/vacuity-manifests/source-side.tsv +15 -0
  77. package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
  78. package/scripts/vacuity-manifests/version-sites.tsv +61 -0
  79. package/scripts/vacuity-mutation-battery.ts +266 -0
  80. package/src/native/Recordings/App/MenuBarStatusView.swift +14 -7
  81. package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
  82. package/src/native/Recordings/App/SidebarView.swift +1 -1
  83. package/src/native/Recordings/Package.swift +24 -5
  84. package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
  85. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
  86. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  87. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
  88. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
  89. package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
  90. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
  91. package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
  92. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
  93. package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
  94. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
  95. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
  96. package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
  97. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
  98. package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
  99. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
  100. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
  101. package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
  102. package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
  103. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
  104. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
  105. package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
  106. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
  107. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
  108. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
  109. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
  110. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
  111. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
  112. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
  113. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
  114. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
  115. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
  116. package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
  117. package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
  118. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
  119. package/src/native/Recordings/build.sh +75 -9
  120. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
@@ -1,15 +1,14 @@
1
1
  # syntax=docker/dockerfile:1
2
2
  # Image built from the published @hasna/recordings npm tarball by the ECR image
3
- # builder (CodeBuild, ARM64). Runs recordings-serve (PURE REMOTE, Amendment A1):
4
- # the serve process reads/writes the shared cloud Postgres directly with
5
- # @hasna/contracts API-key auth. No local sqlite/sync in the service.
3
+ # builder (CodeBuild, ARM64). Runs recordings-serve with the `postgresql` data
4
+ # backend: the serve process reads/writes the shared Postgres directly, selected
5
+ # by the presence of HASNA_RECORDINGS_DATABASE_URL. No on-box sqlite/sync in the
6
+ # service.
6
7
 
7
8
  FROM oven/bun:1.3.13-alpine AS runtime
8
9
 
9
10
  ENV NODE_ENV=production \
10
11
  HASNA_APP_NAME=recordings \
11
- HASNA_APP_MODE=self_hosted \
12
- HASNA_RECORDINGS_STORAGE_MODE=remote \
13
12
  HOST=0.0.0.0 \
14
13
  PORT=8874 \
15
14
  NODE_EXTRA_CA_CERTS=/etc/ssl/certs/rds-global-bundle.pem \
package/README.md CHANGED
@@ -26,7 +26,7 @@ provides recording controls and access to the main window while it is in the bac
26
26
  the CLI and MCP write), searchable and filterable by project, mode, and machine, with a
27
27
  detail pane (copy, paste-into-front-app, audio playback, metadata).
28
28
  - **Projects** — app projects are registered through the same canonical Store before a
29
- recording can reference them, preserving referential integrity in local and remote modes.
29
+ recording can reference them, preserving referential integrity on either store.
30
30
  - **Settings** (⌘,) — OpenAI key, language, recording shortcut, permissions, projects,
31
31
  and voice shortcuts.
32
32
 
@@ -59,6 +59,9 @@ recordings app install \
59
59
 
60
60
  recordings app open # launch it
61
61
  recordings app status # show install state
62
+ recordings app snapshot # write ./desktop-snapshot.png for local UI debugging
63
+ # From this repository, optionally choosing another output path:
64
+ bun run desktop:snapshot -- /tmp/recordings-desktop.png
62
65
  /Applications/Recordings.app/Contents/Helpers/recordings-update-client status
63
66
 
64
67
  # Release builds run only as the isolated _recordingsbuild account. Provision these first:
@@ -115,7 +118,10 @@ RECORDINGS_RELEASE_COMPATIBLE_COHORT_MANIFEST="/Library/Application Support/Hasn
115
118
  ./build.sh release app-update
116
119
 
117
120
  # Explicit local-only alternative when Developer ID credentials are unavailable.
118
- # Build on a Mac other than the approved target; this does not replace a release:
121
+ # Build on a Mac other than the approved target; this does not replace a release.
122
+ # The approved targets are declared once in
123
+ # scripts/policy/local-only-approved-targets.txt, which the builder, the installer,
124
+ # and the artifact tool all read; add a Mac there rather than in any guard:
119
125
  RECORDINGS_LOCAL_APPROVED_TARGET="station06" \
120
126
  RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_KIND="tailscale_node_id_sha256" \
121
127
  RECORDINGS_LOCAL_APPROVED_TARGET_IDENTITY_SHA256="AUTHENTICATED_TAILSCALE_NODE_ID_SHA256" \
@@ -135,6 +141,27 @@ recordings app install \
135
141
  --approved-target-identity-sha256 AUTHENTICATED_TAILSCALE_NODE_ID_SHA256 \
136
142
  --acknowledge-local-signing-and-permissions \
137
143
  --launch
144
+
145
+ # Reinstalling or repairing an already-installed local-only app additionally needs
146
+ # --allow-adhoc-identity-migration. Local-only builds are ad-hoc signed, so every rebuild
147
+ # produces a new CDHash; replacing the installed app is therefore a real identity migration
148
+ # and the installer refuses it with exit 1 until you approve it once, explicitly. Approving
149
+ # it voids the Microphone and Accessibility grants held by the replaced app -- macOS keys
150
+ # those to code identity and the installer cannot restore them -- so expect to grant both
151
+ # again afterwards. Run `scripts/install_macos_app.sh --help` for the full argument list.
152
+ recordings app install \
153
+ --artifact /path/to/Recordings-0.2.13-macos-station06-local-only.zip \
154
+ --manifest /path/to/Recordings-0.2.13-macos-station06-local-only.manifest.json \
155
+ --manifest-sha256 AUTHENTICATED_MANIFEST_SHA256 \
156
+ --expected-source-sha APPROVED_40_CHARACTER_COMMIT_SHA \
157
+ --expected-version 0.2.13 \
158
+ --artifact-policy local-only \
159
+ --approved-target station06 \
160
+ --approved-target-identity-kind tailscale_node_id_sha256 \
161
+ --approved-target-identity-sha256 AUTHENTICATED_TAILSCALE_NODE_ID_SHA256 \
162
+ --acknowledge-local-signing-and-permissions \
163
+ --allow-adhoc-identity-migration \
164
+ --launch
138
165
  swift test # run the native test suite
139
166
  ```
140
167
 
@@ -236,7 +263,9 @@ signature check. The mutable `/Applications` path is never executed, caller stat
236
263
  not inherited, and all snapshot paths are removed by normal installer cleanup. The builder applies
237
264
  the same checks in its private build directory and removes the snapshot with the rest of the build
238
265
  workspace. The status parser then
239
- requires online `Self` with hostname `station06`,
266
+ requires online `Self` whose hostname equals the caller's `--expected-hostname` — the
267
+ approved target when the installer verifies the target, and the builder's own host when
268
+ the builder verifies itself, which `build.sh` requires to differ from the target —
240
269
  requires the single nonempty `Self.ID` to contain no whitespace or NUL, hashes its exact decoded
241
270
  bytes without a newline, and compares the digest. Neither raw node ID is written to the manifest,
242
271
  build log, or installer log. Older schema-v3 artifacts without an
@@ -264,6 +293,31 @@ The native app uses OpenAI realtime transcription for the stop-and-paste path: s
264
293
  unsettled, or cannot be saved. Raw and processed transcript fields are still stored
265
294
  separately, so cleanup instructions never replace the verbatim transcript.
266
295
 
296
+ ### What the app reads to confirm a paste
297
+
298
+ Posting a Cmd-V keystroke returns no delivery receipt — `CGEvent.post` returns `Void` — so
299
+ the only way to know whether a transcript actually landed is to look. Around each paste the
300
+ app therefore **reads the text value of whatever field is focused in the target app**, via
301
+ Accessibility, once before the keystroke and once after, and compares them.
302
+
303
+ State this plainly because it is a real change in what a dictation app can see:
304
+
305
+ - The read covers the focused field's full value and its current selection, not only the
306
+ pasted fragment, so text you did not dictate is inside the app's process during the
307
+ comparison.
308
+ - It is **never logged and never persisted.** The comparison happens in memory and only its
309
+ verdict (`pasted` / `not observed` / `unverified` plus a reason) reaches the log.
310
+ - Fields longer than 20,000 characters are reported unverifiable rather than copied and
311
+ scanned.
312
+ - A field that publishes no Accessibility value — terminals, canvas editors, some Electron
313
+ apps — is reported as unverified, never as a success.
314
+ - Reading requires the Accessibility permission the app already needs to post the keystroke.
315
+ No additional permission is requested, which is exactly why this is worth writing down.
316
+
317
+ One known limit, in the safe direction: pasting text identical to the selection it replaces
318
+ leaves the field's value and the occurrence count unchanged, so a genuinely successful paste
319
+ is reported as **not observed**. The app under-claims rather than over-claims.
320
+
267
321
  ## CLI Usage
268
322
 
269
323
  ```bash
@@ -378,9 +432,23 @@ Endpoints: `GET /health` → `{"status":"ok","name":"recordings"}`, MCP at `/mcp
378
432
 
379
433
  ## HTTP API (`recordings-serve`)
380
434
 
381
- `recordings-serve` is the self-hosted HTTP API. In cloud mode it is PURE REMOTE
382
- (Amendment A1): the process reads/writes the shared cloud Postgres directly with
383
- API-key auth via [`@hasna/contracts`](https://www.npmjs.com/package/@hasna/contracts).
435
+ `recordings-serve` is the HTTP API. Its data backend is a single two-value
436
+ switch, `HASNA_RECORDINGS_STORAGE_MODE=sqlite | postgresql` (a
437
+ `HASNA_RECORDINGS_DATABASE_URL` on its own implies `postgresql`). On
438
+ `postgresql` the process reads/writes that database directly with API-key auth
439
+ via [`@hasna/contracts`](https://www.npmjs.com/package/@hasna/contracts).
440
+
441
+ There are no deployment modes. `local`, `self-hosted`, `cloud`, `remote` and
442
+ `hybrid` no longer select anything, and passing one is an error that names the
443
+ variable and value to use instead. Where the server runs, and who operates it,
444
+ never changed how it stored data.
445
+
446
+ The CLI and MCP client are a separate, two-value switch:
447
+ `HASNA_RECORDINGS_CLIENT_STORE=sqlite | http`. Setting
448
+ `HASNA_RECORDINGS_API_URL` + `HASNA_RECORDINGS_API_KEY` selects `http` on its
449
+ own; `HASNA_RECORDINGS_CLIENT_STORE=sqlite` forces the on-box file even when
450
+ both are set. The client never opens Postgres — it reaches the shared dataset
451
+ only through this API.
384
452
 
385
453
  ```bash
386
454
  recordings-serve --port 8874 # start the API
@@ -401,8 +469,9 @@ Versioned API (`/v1/*`, API-key auth via `x-api-key` or `Authorization: Bearer`)
401
469
  | GET/POST | `/v1/agents` · GET `/v1/agents/:id` | `recordings:read` / `recordings:write` |
402
470
  | GET/POST | `/v1/projects` · GET `/v1/projects/:id` | `recordings:read` / `recordings:write` |
403
471
 
404
- Env: `HASNA_RECORDINGS_DATABASE_URL` (remote Postgres DSN — enables cloud `/v1`)
405
- and `HASNA_RECORDINGS_API_SIGNING_KEY` (HMAC signing secret for API-key auth).
472
+ Env: `HASNA_RECORDINGS_STORAGE_MODE` (`sqlite` | `postgresql`),
473
+ `HASNA_RECORDINGS_DATABASE_URL` (PostgreSQL DSN implies `postgresql`) and
474
+ `HASNA_RECORDINGS_API_SIGNING_KEY` (HMAC signing secret for API-key auth).
406
475
 
407
476
  ## SDK
408
477
 
@@ -433,6 +502,42 @@ context, `transcriber_prompt` for cleanup instructions, and `post_processing_mod
433
502
  `off`, `auto`, or `always`. Tool results preserve `raw_text` and return `processed_text`
434
503
  only when post-processing actually produced enhanced output.
435
504
 
505
+ ## Releasing
506
+
507
+ The release version is hand-maintained in four places: `package.json`, `src/version.ts`, and both
508
+ `CFBundleShortVersionString` and `CFBundleVersion` in
509
+ `src/native/Recordings/RecordingsLib/Info.plist`. Bump them together, never by hand:
510
+
511
+ ```bash
512
+ bun run version:set 0.3.0 # rewrites every hand-maintained site
513
+ bun run generate:sdk # restamps the generated SDK's header
514
+ bun run version:check # exits 1 if any site disagrees with package.json
515
+ ```
516
+
517
+ A fifth copy is **generated**, not written: `src/server/openapi.ts` stamps `VERSION` into the
518
+ OpenAPI document and `bun run generate:sdk` bakes it into the `// Source: …` header of
519
+ `src/sdk/v1.generated.ts`. `version:set` leaves that file alone — the generator owns it, and
520
+ patching the stamp by hand would hide real regeneration drift — so regenerate after every bump.
521
+ `src/__tests__/version-site-guard.test.ts` fails when the committed header disagrees with
522
+ `package.json`.
523
+
524
+ `package.json` is the authority because `scripts/build_companion_cli.sh` compares the
525
+ compiled CLI's `--version` against it and exits 1 on a mismatch. That abort propagates
526
+ through `src/native/Recordings/build.sh` (`set -euo pipefail`), so a partial bump does
527
+ not just fail an assertion -- the native app cannot be built at all, and the whole
528
+ `native-app-companion-contract` suite aborts on its first test.
529
+
530
+ Three things enforce it. `.github/workflows/ci.yml` runs the whole TypeScript suite on
531
+ every push, which is what makes the two guards below actually block a branch rather than
532
+ wait for someone to run them locally. `prepack` runs `build:native-fs-guard` first (the
533
+ fail-closed macOS gate), then `version:check`, so a partial bump stops before the build
534
+ rather than deep inside it. And `prepublishOnly` runs `bun test`, which covers the sites
535
+ through `src/__tests__/native-bundle-version.test.ts` and
536
+ `src/__tests__/version-site-guard.test.ts`.
537
+
538
+ The Swift side is not covered: no reachable machine currently runs the Swift suite, so a
539
+ version claim about the app bundle is only as verified as the last macOS build.
540
+
436
541
  ## Data Directory
437
542
 
438
543
  Data is stored in `~/.hasna/recordings/`.
package/bun.lock CHANGED
@@ -5,7 +5,7 @@
5
5
  "": {
6
6
  "name": "@hasna/recordings",
7
7
  "dependencies": {
8
- "@hasna/contracts": "^0.4.2",
8
+ "@hasna/contracts": "^0.8.4",
9
9
  "@hasna/events": "^0.1.11",
10
10
  "@modelcontextprotocol/sdk": "^1.12.1",
11
11
  "chalk": "^5.4.1",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  },
25
25
  "packages": {
26
- "@hasna/contracts": ["@hasna/contracts@0.4.2", "", { "dependencies": { "commander": "^13.1.0", "zod": "^3.25.76" }, "bin": { "contracts": "dist/cli/index.js", "contracts-cli": "dist/cli/index.js" } }, "sha512-xD7ZWQXR+AjYAJVpaMsQ+8V0SABiJSKVeI0B1WbPByYeGz6daDtgyZ6c3q0c2S6uzIEtUxlGHPundr675636ew=="],
26
+ "@hasna/contracts": ["@hasna/contracts@0.8.4", "", { "dependencies": { "commander": "^13.1.0", "zod": "^3.25.76" }, "bin": { "contracts": "dist/cli/index.js", "contracts-cli": "dist/cli/contracts-cli.js" } }, "sha512-a/flLzdu8cbCUrScNQT3VBMl2HgwbjUYWrmwQ45x19X6F8FPiOf2gbb43MT0vlKTLUPEonh5jSoEPdjp44nAGw=="],
27
27
 
28
28
  "@hasna/events": ["@hasna/events@0.1.11", "", { "dependencies": { "commander": "^13.1.0" }, "bin": { "events": "dist/cli/index.js", "hasna-events": "dist/cli/index.js" } }, "sha512-X36W9TB8mKxtS5UeW1n2IlvW6nPUcoYCQWNMMV+/JM4YNzlnnk1FRm8Aay02tt1LjQOGICCHF3hz/l//EHEe+Q=="],
29
29
 
@@ -0,0 +1,22 @@
1
+ export declare const EXISTING_IDENTITY_SHA256: string;
2
+ export declare const CANDIDATE_IDENTITY_SHA256: string;
3
+ export declare const MANIFEST_SHA256: string;
4
+ export type ArtifactPolicy = "local-only" | "release";
5
+ export type IncompatibleDirection = "installed-requirement-vs-candidate" | "candidate-requirement-vs-installed" | "both";
6
+ export type GuardExecutionOptions = {
7
+ identityMigration: boolean;
8
+ incompatibleDirection?: IncompatibleDirection;
9
+ artifactPolicy?: ArtifactPolicy;
10
+ extraArguments?: string[];
11
+ };
12
+ export type GuardExecutionResult = {
13
+ exitCode: number;
14
+ stdout: string;
15
+ stderr: string;
16
+ reachedTransaction: boolean;
17
+ codesignInvocations: string[];
18
+ bunInvocations: string[];
19
+ unstubbedInvocations: string[];
20
+ };
21
+ export declare function runInstallerToIdentityGuard(options: GuardExecutionOptions): GuardExecutionResult;
22
+ //# sourceMappingURL=installer-guard-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer-guard-execution.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-guard-execution.ts"],"names":[],"mappings":"AA+DA,eAAO,MAAM,wBAAwB,QAAiB,CAAC;AACvD,eAAO,MAAM,yBAAyB,QAAiB,CAAC;AACxD,eAAO,MAAM,eAAe,QAAiB,CAAC;AAsB9C,MAAM,MAAM,cAAc,GAAG,YAAY,GAAG,SAAS,CAAC;AAuFtD,MAAM,MAAM,qBAAqB,GAE7B,oCAAoC,GAGpC,oCAAoC,GAEpC,MAAM,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG;IAKlC,iBAAiB,EAAE,OAAO,CAAC;IAG3B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAK9C,cAAc,CAAC,EAAE,cAAc,CAAC;IAEhC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IAKf,kBAAkB,EAAE,OAAO,CAAC;IAG5B,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAG9B,cAAc,EAAE,MAAM,EAAE,CAAC;IAMzB,oBAAoB,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,qBAAqB,GAC7B,oBAAoB,CAwStB"}
@@ -0,0 +1,22 @@
1
+ export declare const POLICY_RELATIVE_PATH = "scripts/policy/local-only-approved-targets.txt";
2
+ export declare const READER_RELATIVE_PATH = "scripts/read_local_only_targets.sh";
3
+ export declare const IDENTITY_GUARD_RELATIVE_PATH = "scripts/enforce_identity_migration.sh";
4
+ export declare const readRepositoryFile: (relativePath: string) => string;
5
+ export type InstallerPreflightOptions = {
6
+ artifactPolicy?: "release" | "local-only";
7
+ approvedTarget?: string;
8
+ hostname?: string;
9
+ extraArguments?: string[];
10
+ policyContents?: string | null;
11
+ removeReader?: boolean;
12
+ removeIdentityGuard?: boolean;
13
+ identityGuardContents?: string;
14
+ symlinkIdentityGuard?: boolean;
15
+ environment?: Record<string, string>;
16
+ };
17
+ export declare function runInstallerPreflight(options?: InstallerPreflightOptions): {
18
+ exitCode: number;
19
+ stdout: string;
20
+ stderr: string;
21
+ };
22
+ //# sourceMappingURL=installer-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer-preflight.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/installer-preflight.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,oBAAoB,mDAAmD,CAAC;AACrF,eAAO,MAAM,oBAAoB,uCAAuC,CAAC;AACzE,eAAO,MAAM,4BAA4B,0CAA0C,CAAC;AAEpF,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,KAAG,MACA,CAAC;AAc3D,MAAM,MAAM,yBAAyB,GAAG;IACtC,cAAc,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IAC1C,cAAc,CAAC,EAAE,MAAM,CAAC;IAGxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAG9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAG/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAG/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAEF,wBAAgB,qBAAqB,CACnC,OAAO,GAAE,yBAA8B,GACtC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CA8GtD"}
@@ -1 +1 @@
1
- {"version":3,"file":"native-fs-guard.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/native-fs-guard.ts"],"names":[],"mappings":"AAKA,wBAAgB,wBAAwB,CAAC,cAAc,SAAuC,GAAG,MAAM,CAsCtG"}
1
+ {"version":3,"file":"native-fs-guard.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/native-fs-guard.ts"],"names":[],"mappings":"AA2CA,wBAAgB,wBAAwB,CAAC,cAAc,SAAuC,GAAG,MAAM,CActG"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Shared assertions for contract tests that read source text.
3
+ *
4
+ * These live here rather than inside one suite because the defect they exist to prevent is
5
+ * repo-wide: a sweep of every `*.test.ts` found 40 ordering assertions written as
6
+ * `indexOf(a) < indexOf(b)`, across 11 files. `indexOf` answers -1 when the needle is absent and
7
+ * `-1 < anything` is true, so such an assertion PASSES when the thing being ordered is DELETED.
8
+ * The same hole exists in `.slice(indexOf(...), indexOf(...))` region bounds, where a -1 silently
9
+ * slices from the end of the file or to its start — a region assertion over the wrong text, or over
10
+ * none of it, reads exactly like a satisfied one.
11
+ *
12
+ * ---------------------------------------------------------------------------------------------
13
+ * BEFORE YOU BUILD A MUTATION BATTERY: three suites are RED ON A CONTENDED STATION, not on Linux.
14
+ *
15
+ * A mutation battery is evidence only when its clean control is GREEN. Include a suite that was
16
+ * already failing and the run was non-zero before you changed anything, so every mutation "looks
17
+ * caught" and every verdict is manufactured. That already produced one wrong all-clear here.
18
+ *
19
+ * These are the three suites it happens to. NO PASS/FAIL SPLIT IS RECORDED FOR THEM ON PURPOSE —
20
+ * see below. `macos-app-lifecycle.test.ts` has 140 tests, `native-app-companion-contract.test.ts`
21
+ * has 14, `config.test.ts` has 44, and how many of those fail is a property of the MACHINE:
22
+ *
23
+ * src/__tests__/macos-app-lifecycle.test.ts
24
+ * src/__tests__/native-app-companion-contract.test.ts
25
+ * src/__tests__/config.test.ts
26
+ *
27
+ * CORRECTED 2026-07-27, and the correction is the useful part. This block used to call them
28
+ * "PERMANENTLY RED on Linux" and attribute fixed environmental causes — BSD `stat -f`, a fixture
29
+ * port reading `NaN`, a `getDataDir` HOME-ancestor assumption. The first CI run this repository
30
+ * ever had (run 30302342895, ubuntu-24.04) re-ran all three on a clean single-tenant runner and
31
+ * every one of them PASSED:
32
+ *
33
+ * macos-app-lifecycle 140 pass / 0 fail (358.85s)
34
+ * native-app-companion-contract 14 pass / 0 fail ( 4.50s)
35
+ * config 44 pass / 0 fail ( 0.10s)
36
+ *
37
+ * So the cause is not the platform. Measure on a quiet machine, or in CI, before recording a suite
38
+ * as red. There were TWO independent station-local causes, and every earlier version of this comment
39
+ * named only one of them. In a 92-fail run of `macos-app-lifecycle.test.ts` on this station the
40
+ * failure messages broke down as 38 × `Home ancestor has an unexpected owner.`, 22 × FIFO
41
+ * synchronisation timeout, 24 × ENOENT on a fixture marker. The first cause is now fixed in the
42
+ * fixture and only the second remains; do not collapse them into one, because a quiet machine with
43
+ * `FORCE_COLOR` set still showed all 38 aborts, so "measure on a quiet machine" was necessary and
44
+ * not sufficient:
45
+ *
46
+ * 1. `FORCE_COLOR` — **FIXED IN THE FIXTURE, no longer a live cause.** The `stat` stub answered
47
+ * `%u` by shelling out to `bun -e '… console.log(statSync(…).uid)'`
48
+ * (`macos-app-lifecycle.test.ts:217`) and spread `...Bun.env` into the installer, so with
49
+ * `FORCE_COLOR` set Bun COLOURED the number: the installer compared `\e[0m\e[33m1000\e[0m`
50
+ * against `id -u`'s `1000` at `install_macos_app.sh:143` and aborted before reaching any gate.
51
+ * `NO_COLOR=1` did NOT help — FORCE_COLOR wins in Bun. The stub now writes bare integers with
52
+ * `process.stdout.write` and `unset FORCE_COLOR`s its own children, which takes that message
53
+ * from 38 to 0 in place, positive-controlled (the same grep still finds 38 in the pre-fix log).
54
+ * Nothing about ancestor MODE was ever involved: `verify_secure_parent` and
55
+ * `verify_safe_home_ancestor` each `stat` only the one path handed to them, the sole call is
56
+ * `verify_safe_home_ancestor "$HOME"`, and the stub hardcodes every `%Lp` answer anyway.
57
+ * Kept here rather than deleted because it is the reason this file's split moved, and because
58
+ * the same trap recurs in any stub that parses `console.log` of a NUMBER: only strings are
59
+ * left uncoloured.
60
+ * 2. CONTENTION. The station routinely runs several full recordings suites at once out of
61
+ * different worktrees, and this suite scans a shared /tmp — the hazard this very comment warns
62
+ * about below. Those were FIFO timeouts at an internal 5000ms budget; the FIFO helper now uses
63
+ * the suite's configured timeout too, so a raised test budget can distinguish load sensitivity
64
+ * from a regression. With `FORCE_COLOR` unset the residual failures scaled with load: 132 pass /
65
+ * 8 fail at load ~20, 114 pass / 26 fail at load 44-60 on the same commit, which is why no split
66
+ * belongs here either. GitHub Actions sets neither `FORCE_COLOR` nor a competing suite, which
67
+ * is why both causes were absent from the only clean measurement.
68
+ *
69
+ * WHY NO SPLIT IS RECORDED. Every split ever written here has gone stale, including two written as
70
+ * corrections. On one unchanged tree, three consecutive runs measured 48/92, 48/92, 49/91; the
71
+ * single test that flips is `runtime smoke timeout does not wait forever on a live open process`
72
+ * (`macos-app-lifecycle.test.ts:3594`), which races a hardcoded internal `Bun.sleep(2_000)` that no
73
+ * `--timeout` flag reaches either. So a count comparison across two trees shows a phantom delta
74
+ * from this file alone — which is the concrete reason for the rule below: compare failing test
75
+ * NAMES, never counts. Two trees whose failing NAME SETS are identical are identical regardless of
76
+ * what the totals say.
77
+ *
78
+ * `@hasna/events` MUST resolve 0.1.11, as `bun.lock` pins it. A plain `bun install` pulls 0.1.14,
79
+ * which dropped a shipped CLI command inside the patch range and fails `cli.test.ts` — and it can
80
+ * drift back mid-session, so re-check it before quoting any cross-tree comparison.
81
+ *
82
+ * Corollary, also corrected: this repo NOW HAS CI. `.github/workflows/ci.yml` gates the whole
83
+ * suite with no exemptions on every pull request, so these suites are no longer gated only by
84
+ * somebody remembering to run them. What CI does NOT cover is the Swift/C half, which does not
85
+ * currently compile; see `.github/native-known-errors.txt`.
86
+ * Compare failing test NAMES, never counts — the suite is nondeterministic at the margin.
87
+ * ---------------------------------------------------------------------------------------------
88
+ */
89
+ /**
90
+ * Assert `first` appears before `second`, requiring BOTH to exist.
91
+ *
92
+ * Use this instead of comparing two `indexOf` results directly. `firstMatch: "last"` selects
93
+ * `lastIndexOf` for the first operand, which has the identical -1 hole.
94
+ */
95
+ export declare function expectOrder(haystack: string, first: string, second: string, options?: {
96
+ firstMatch?: "first" | "last";
97
+ }): void;
98
+ /**
99
+ * Slice between two markers, requiring both to exist and the region to be non-trivial.
100
+ *
101
+ * The length floor matters as much as the -1 checks: `slice(-1)` yields a ONE-CHARACTER string, not
102
+ * an empty one, so a `expect(region.length).toBeGreaterThan(0)` control passes on a region that
103
+ * contains nothing worth asserting about. Any `not.toContain` over such a region is vacuous.
104
+ */
105
+ export declare function sliceBetween(source: string, open: string, close: string, options?: {
106
+ minimumLength?: number;
107
+ }): string;
108
+ /**
109
+ * Assert a marker occurs exactly once before slicing on it.
110
+ *
111
+ * A duplicated end marker silently extends a region: `let myPID = ProcessInfo…` occurs twice in
112
+ * `RecordingEngine.swift`, so a region bounded by it could stretch ~127 KB and be satisfied by
113
+ * copies of the needle from an unrelated function.
114
+ */
115
+ export declare function sliceBetweenUnique(source: string, open: string, close: string): string;
116
+ /** Strip Swift line comments so an assertion about code is not defeated by prose. */
117
+ export declare function withoutComments(source: string): string;
118
+ /**
119
+ * Index just past the `close` matching the `open` at `openIndex`, skipping comments and literals.
120
+ *
121
+ * Needed because `lastIndexOf("}")` is not brace matching, and the difference is a live defect: an
122
+ * early exit written between a decision and its use —
123
+ *
124
+ * } // the decision table closes here
125
+ * guard stillOwnsChangeCount else { return }
126
+ * if shouldRestore { … }
127
+ *
128
+ * — puts a NEARER `}` (the `else` block's) between the two, so a "nothing between them" check
129
+ * measured from the last brace saw only whitespace and passed. Counting braces naively fails the
130
+ * other way: one `}` inside a string literal, such as `log("settlement }")`, cancels a real opener.
131
+ * Both were measured surviving at EXIT=0.
132
+ */
133
+ export declare function matchingDelimiterIndex(source: string, openIndex: number, open: string, close: string): number;
134
+ export declare function withoutAnyComments(source: string): string;
135
+ /**
136
+ * The arms of a Swift `switch` body, as a mapping from each matched case to its expression.
137
+ *
138
+ * Order- and grouping-independent, which matters because pinning an arm by its exact TEXT gets both
139
+ * directions wrong. It false-positives on a pure reorder — `.deliveredUnverified, .deliveryNotObserved`
140
+ * is the same table and failed — and it misses a real defect: splitting one outcome out of a group
141
+ * into its own arm with a different expression leaves the pinned needle intact, so
142
+ * `.targetUnavailable` could be given `false` while the six-outcome needle still matched.
143
+ *
144
+ * `body` is the text between the switch's braces, comments already stripped by the caller.
145
+ */
146
+ export declare function switchArmsByOutcome(body: string): Map<string, string>;
147
+ /**
148
+ * Evaluate a Swift boolean condition for a given binding of its identifiers.
149
+ *
150
+ * Folded in from PR #43, which pinned the clipboard-restore guard by evaluating it for BOTH values
151
+ * of the decision rather than comparing its text. That is the half worth keeping: an exact
152
+ * `toBe("shouldRestore")` on a captured condition kills the inversion, but it also fails on
153
+ * `(shouldRestore)` and on a trailing comment — reporting refactors as defects while proving
154
+ * nothing about behaviour.
155
+ *
156
+ * Deliberately narrow and fail-CLOSED: `true`, `false`, `!x`, whole-expression parentheses, and
157
+ * identifiers present in `env`. Anything else throws. An added disjunct
158
+ * (`shouldRestore || stillOwnsChangeCount`) is an unevaluatable expression, not a passing one —
159
+ * which is the behaviour that matters, because that disjunct is exactly how the
160
+ * transcript-destroying defect gets reintroduced. Extend the evaluator when a new shape is
161
+ * legitimate; do not loosen the assertion.
162
+ */
163
+ export declare function evaluateSwiftCondition(condition: string, env: Record<string, boolean>): boolean;
164
+ /**
165
+ * Every Swift source under a root, so an absence claim can be made about the app rather than about
166
+ * whichever files were on the reviewer's mind.
167
+ */
168
+ export declare function swiftSourcesUnder(root: string): Array<[path: string, source: string]>;
169
+ //# sourceMappingURL=source-assertions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-assertions.d.ts","sourceRoot":"","sources":["../../../src/__tests__/helpers/source-assertions.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAAO,GAC9C,IAAI,CAON;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,MAAM,CAWR;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtF;AAED,qFAAqF;AACrF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAKtD;AAuED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,GACZ,MAAM,CA0BR;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAoEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAyBrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAiC/F;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAIrF"}
@@ -1,5 +1,2 @@
1
- /**
2
- * Test preload — currently a no-op since we use bun:sqlite natively.
3
- * Kept for future test setup needs.
4
- */
1
+ export {};
5
2
  //# sourceMappingURL=preload.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../../src/__tests__/preload.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
1
+ {"version":3,"file":"preload.d.ts","sourceRoot":"","sources":["../../src/__tests__/preload.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ export declare const DEFAULT_DESKTOP_SNAPSHOT = "desktop-snapshot.png";
2
+ type CaptureResult = {
3
+ error?: Error;
4
+ status: number | null;
5
+ stderr?: string;
6
+ };
7
+ type DesktopSnapshotDependencies = {
8
+ platform?: NodeJS.Platform;
9
+ cwd?: string;
10
+ capture?: (executable: string, arguments_: string[]) => CaptureResult;
11
+ };
12
+ /**
13
+ * Write a main-display snapshot to a stable caller-visible path.
14
+ *
15
+ * `screencapture` writes into a private sibling directory first so a denied Screen Recording
16
+ * grant or an interrupted capture cannot truncate a useful snapshot from the previous run.
17
+ */
18
+ export declare function exportDesktopSnapshot(output?: string, dependencies?: DesktopSnapshotDependencies): string;
19
+ export {};
20
+ //# sourceMappingURL=desktop-snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-snapshot.d.ts","sourceRoot":"","sources":["../../src/cli/desktop-snapshot.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,wBAAwB,yBAAyB,CAAC;AAE/D,KAAK,aAAa,GAAG;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,KAAK,2BAA2B,GAAG;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC3B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,aAAa,CAAC;CACvE,CAAC;AAcF;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,SAA2B,EACjC,YAAY,GAAE,2BAAgC,GAC7C,MAAM,CAuDR"}