Compare commits

...
46 Commits
Author SHA1 Message Date
wt c0ce1bd026 status
Foundation checks / foundation (push) Failing after 2m49s
2026-10-15 18:43:29 +07:00
wt ce4d994598 S00-005: Approve storage design after independent transaction checks
Changes: Accept ADR 0004 and record independent S00-005 review, experiment status and handoff; preserve explicit native browser and durability limitations.
Validation: Fresh-cache npm ci and npm run check passed on Node 24.21.0/npm 11.19.0; exact lock pins, HTTPS/SHA512, installed license texts and runtime refusal checks passed. make check passed 72 tooling tests. Scoped diff and whitespace reviewed; native browser quota and process restart were not tested.

Task: S00-005
Agent: auto-qa-4078580caf39
Role: qa
Action: review
Status: done

Task title: Выбрать IndexedDB wrapper и migration policy
2026-10-15 18:38:02 +07:00
wt 0e2b3098df S00-005: Select idb and verify transactional storage failure policies
Changes: Propose ADR 0004 comparing native IndexedDB, idb and Dexie; select idb 8.0.4 with account, transaction, quota, upgrade and key ownership policy. Repair rollback and add injected quota/blocked upgrade checks, exact wrapper lock, runtime guard, license texts and reproduction notes. Preserve historical recovery artifacts.
Validation: Fresh-cache npm ci and npm run check passed on Node 24.21.0/npm 11.19.0; manifest, HTTPS/SHA512 and license checks passed. make check passed 72 tooling tests; tasks.py validate and git diff --check passed. Native browser quota, disk durability and process restart were not tested; ADR remains proposed for independent QA.

Task: S00-005
Agent: auto-storage-0ce544ac4b40
Role: storage
Action: claim
Status: review

Task title: Выбрать IndexedDB wrapper и migration policy
2026-10-15 18:33:15 +07:00
wt 8f918bc040 S00-003: Approve bounded XMPP selection after independent WSS and SCRAM checks
Changes: Accept ADR 0002 for pinned Stanza with mandatory bounded transport, verified SASL and SHA512 composition; record independent review findings and handoff with external limits retained.
Validation: Fresh-cache pinned npm ci and browser build succeeded; all 112 tests passed. Verified 51 locked entries, 26 installed notices and 340 unchanged vendor files; six offer permutations and six adversarial XML checks passed; make check passed 72 tooling tests. No actual browser, independent full XMPP server/client, OMEMO or release readiness acceptance.

Task: S00-003
Agent: auto-qa-d49749753ac6
Role: qa
Action: review
Status: done

Task title: Проверить XMPP WSS/auth библиотеку
2026-10-15 18:26:01 +07:00
wt 51a414d386 S00-003: Evaluate Stanza with verified SCRAM and bounded WSS ingress
Changes: Propose the pinned XMPP library decision and add an isolated browser-built selection spike combining reviewed public SASL, SHA-512 and parser seams. Exercise TLS/xmpp handshake, strongest compatible non-PLUS authentication, rejected unsafe paths and extension mappings; document duplicate-body export projection and remaining limits. Preserve dependency notices, evidence and QA handoff.
Validation: Clean-cache pinned npm ci succeeded; npm test passed 112 checks; check-dependencies.py verified 51 locked entries, 26 installed packages and 340 unchanged vendor files; make check passed 72 tooling tests. No actual browser runtime, independent full XMPP server/client or production encryption/release readiness claim.

Task: S00-003
Agent: auto-protocol-f474114f0168
Role: protocol
Action: claim
Status: review

Task title: Проверить XMPP WSS/auth библиотеку
2026-10-15 18:20:05 +07:00
wt e08c0ab590 S00-023: Approve the interrupted storage experiment recovery checkpoint
Changes: Accept S00-023 recovery readiness and record independent QA evidence and handoff; preserve unfinished S00-005 storage acceptance.
Validation: Inspect implementation and prior checkpoint diffs/messages; verify inventory, pins and matching hashes; repeat both node syntax checks and tasks.py validate, exit 0. No storage runtime or browser checks performed.

Task: S00-023
Agent: auto-qa-2de51e85c611
Role: qa
Action: review
Status: done

Task title: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 18:09:39 +07:00
wt 30ee500054 S00-023: Document the interrupted storage experiment restart point
Changes: Preserve existing storage artifacts and add a scoped recovery inventory with missing outputs, unfinished rollback logic and next steps for S00-005; record recovery evidence and handoff.
Validation: Assignment/packet, manifest-lock inventory assertions, both node --check commands and tasks.py validate passed. Storage runtime, quota and browser behavior were not tested; original storage task remains unaccepted.

Task: S00-023
Agent: auto-storage-e6946c6f94e3
Role: storage
Action: claim
Status: review

Task title: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 18:06:23 +07:00
wt 6ac04950bf S00-025: Approve launcher recovery after independent argument checks
Changes: Accept bounded S00-025 launcher remediation; record independent QA evidence and handoff without accepting parent storage work.
Validation: Reproduced conflicting arguments with expected exit 2; active argv help and Codex 0.159.0 checks exit 0; tasks.py validate and git diff --check pass. Inspected executor diff/message and accepted dependency evidence. No model, browser, sandbox or storage test rerun.

Task: S00-025
Agent: auto-qa-27d3befe5097
Role: qa
Action: review
Status: done

Task title: Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 18:02:07 +07:00
wt 43610f3356 S00-025: Verify storage recovery launcher after argument repair
Changes: Record reproduction of the S00-023 argument conflict and verification of the active reviewed S00-028 invocation; replace blocked S00-025 handoff with bounded QA instructions. Preserve all storage source artifacts.
Validation: Conflicting invocation returns expected exit 2; current argv help and version checks return 0; tasks.py validate passes. No model, storage or browser correctness test performed.

Task: S00-025
Agent: auto-storage-8b47cee148df
Role: storage
Action: claim
Status: review

Task title: Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 17:59:38 +07:00
wt 0d29bdb3ed S00-028: Approve the compatible Codex launcher after independent sandbox checks
Changes: Accept S00-028 launcher documentation and bounded verification; record independent QA evidence and handoff. Preserve source and local configuration.
Validation: Inspected implementation diff/message and exact active argv; bounded CLI help and approved host wrapper with inner workspace-write probe exited 0. Project write/read/remove passed; outside write denied EROFS30. make check passed 72 tests in 18.544s. No additional model or storage task ran.

Task: S00-028
Agent: auto-qa-2160ddeb5c08
Role: qa
Action: review
Status: done

Task title: Repair conflicting Codex harness sandbox arguments
2026-10-15 17:55:20 +07:00
wt ff99ee7b62 S00-028: Document and verify the compatible Codex runner invocation
Changes: Document the coordinator-selected Codex 0.159.0 argv without conflicting sandbox flags; record parser and filesystem evidence plus QA handoff. Preserve existing local configuration and portable example.
Validation: Exact active argv help and approved host wrapper with inner workspace-write probe exited 0; project write/read/remove passed and outside write denied EROFS30. Final make check passed 72 tests after shortening the documentation to meet prompt budgets. No additional model or storage work ran.

Task: S00-028
Agent: auto-ci-cd-e3a716a94424
Role: ci-cd
Action: claim
Status: review

Task title: Repair conflicting Codex harness sandbox arguments
2026-10-15 17:52:47 +07:00
wt ff5a89647a S00-030: Approve the bounded host-context sandbox diagnostic
Changes: Record independent S00-030 QA acceptance and append the verified result to its handoff.
Validation: Inspected executor diff/message and probe hash; approved host wrapper and inner workspace-write probe exit 0, project write/read/remove passed and outside write denied EROFS 30. make check exit 0 with 72 tests; git diff --check exit 0. No additional model/storage task ran; acceptance is limited to this host/version and one filesystem boundary.

Task: S00-030
Agent: auto-qa-a9442b15e749
Role: qa
Action: review
Status: done

Task title: Provide a valid Codex sandbox app-server socket directory
2026-10-15 17:49:22 +07:00
wt b4a27f2354 S00-030: Confirm the host-context sandbox diagnostic for socket recovery
Changes: Clarify S00-030 host execution guidance in docs/AUTOMATION.md and record the successful bounded diagnostic with QA handoff.
Validation: Approved host wrapper and inner workspace-write probe exit 0; project write/read/remove passed, outside write denied EROFS 30. make check exit 0 with 72 tooling tests; git diff --check exit 0. No additional model or storage task ran; app readiness is outside scope.

Task: S00-030
Agent: auto-ci-cd-8c7bc1551426
Role: ci-cd
Action: claim
Status: review

Task title: Provide a valid Codex sandbox app-server socket directory
2026-10-15 17:46:48 +07:00
wt 263d365640 S00-031: Approve the independently verified Codex host sandbox diagnostic
Changes: Accept S00-031 with independent QA evidence and handoff; confirm the automation guidance matches the bounded host-context diagnostic.
Validation: Approved host stat verified uid1000/mode0700; inner workspace-write probe exited 0 with project write/read/remove and outside write denial EROFS30. make check passed 72 tests; scope, executor message and git diff --check passed. No model or storage task ran; acceptance covers this host/version and one filesystem boundary.

Task: S00-031
Agent: auto-qa-56c26814c30b
Role: qa
Action: review
Status: done

Task title: Supply a verified private Codex app-server socket environment
2026-10-15 17:44:13 +07:00
wt ace4f674d5 S00-031: Document verified host context for the Codex sandbox probe
Changes: Add host-specific socket mask diagnosis and bounded inner workspace-write invocation to automation guidance; record independent evidence and QA handoff.
Validation: Approved host stat confirmed uid1000/mode0700; no-model inner sandbox exited 0 with project write/read/remove and outside write denial EROFS30. Final make check passed 72 tests and git diff --check passed. Initial verbose draft exceeded prompt budgets; compacted guidance passed the rerun. No storage task or model executed.

Task: S00-031
Agent: auto-ci-cd-d45ccd3ee7d6
Role: ci-cd
Action: claim
Status: review

Task title: Supply a verified private Codex app-server socket environment
2026-10-15 17:42:02 +07:00
wt 865f1e7a72 S00-031: Record newly available external input for independent verification
Changes: Release external waiting state for S00-031 through tasks.py. Input: New verified root cause and supported host execution context supplied in state/handoffs/S00-031.md. Actual host /tmp/codex-daemon-1000 uid1000/mode0700 is valid; outer sandbox deliberately masks it read-only mode000. Installed-version primary source confirms this design; TMPDIR/no-daemon cannot fix nested guard. Approved OUTER HOST EXECUTION of bounded no-model inner codex workspace-write probe passes project write/read/remove and denies outside write EROFS30, no outside file created. No socket mutation or broader roots. Fresh executor/QA must read handoff, request approved outer host execution and independently repeat, document and review; no task acceptance claimed.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-031
Agent: coordinator-input-s00-031
Role: orchestrator
Action: release-external
Status: todo

Task title: Supply a verified private Codex app-server socket environment
2026-10-15 17:38:03 +07:00
wt 0bf7504900 S00-030: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-030. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-030 no-model sandbox diagnostic still exits 1 before execution with the original socket directory ownership/mode guard, despite XDG_RUNTIME_DIR being user-owned mode 0700. Actual socket directory repair requires host owner input.
Automatic blocker limit reached; explicit external resolution required.

Task: S00-030
Agent: auto-ci-cd-d37c9faf28f3
Role: ci-cd
Action: claim
Status: blocked

Task title: Provide a valid Codex sandbox app-server socket directory
2026-10-15 17:32:24 +07:00
wt 176149b832 S00-030: Record newly available external input for independent verification
Changes: Release external waiting state for S00-030 through tasks.py. Input: Coordinator confirms valid host-side sandbox execution without modifying socket ownership/mode: codex -c sandbox_mode='"workspace-write"' -c sandbox_workspace_write.network_access=true sandbox -- python3 /tmp/luma-filesystem-sandbox-probe.py, launched via approved host tool execution (outer require_escalated), exit 0. Inside actual Codex workspace-write sandbox synthetic project write/read/remove passed, unique /home/tola outside-project write denied EROFS (errno 30), no outside file created, uid 1000. Public diagnostic state/runs/coordinator-sandbox/output.log; synthetic probe script /tmp/luma-filesystem-sandbox-probe.py. Failure occurred when invoking nested sandbox from restricted context, not in this approved host context. Fresh S00-030 executor/QA should request approved outer host execution of the bounded inner sandbox diagnostic, retaining inner workspace-write and proxy/TLS settings. Do not chmod global sockets or weaken policy. Host input now supplied and verified; task implementation/docs and independent review remain unaccepted.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-030
Agent: coordinator-input-s00-030
Role: orchestrator
Action: release-external
Status: todo

Task title: Provide a valid Codex sandbox app-server socket directory
2026-10-15 17:30:32 +07:00
wt a53f0f1a14 S00-028: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-028. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-028 cannot independently validate project filesystem sandbox: installed Codex sandbox exits before command execution because the app-server socket directory must be user-owned with mode 0700. Host socket repair is outside launcher arguments scope.
Automatic blocker limit reached; explicit external resolution required.

Task: S00-028
Agent: auto-ci-cd-211cf3a2bd08
Role: ci-cd
Action: claim
Status: blocked

Task title: Repair conflicting Codex harness sandbox arguments
2026-10-15 17:26:50 +07:00
wt 28320ff35a S00-022: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-022. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: Pinned libomemo.js@3.0.0 supplies real embedded WASM but is a different unaudited ratchet and excludes consumer SCE; no approved decision reconciles it with S00-022 retained audited engine/persistence requirement.

Task: S00-022
Agent: auto-crypto-bcd7f2a71667
Role: crypto
Action: claim
Status: blocked

Task title: Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
2026-10-15 17:17:19 +07:00
wt 8bfaa06e64 S00-027: Approve registry connectivity after independent HTTPS checks
Changes: Accept the bounded S00-027 connectivity record and add independent security review evidence and handoff.
Validation: Fresh-cache control, candidate and pinned npm queries exited 0; fresh HTTPS artifact SHA512 SRI, size and SHA256 matched. make check exited 0 with 72 tests; executor patch/message, owner confirmation, scope and whitespace checked. No crypto/backend or distribution acceptance.

Task: S00-027
Agent: auto-security-ccce71646176
Role: security
Action: review
Status: done

Task title: Restore approved npm registry connectivity for the OMEMO2 backend investigation
2026-10-15 17:14:47 +07:00
wt 0b60f3d84e S00-027: Document verified HTTPS npm registry connectivity
Changes: Add the bounded S00-027 connectivity record with owner confirmation, fresh registry responses, pinned libomemo.js artifact integrity and independent review handoff.
Validation: Both live metadata queries and pinned metadata query exited 0; HTTPS archive SHA512 matched registry SRI without executing scripts. make check exited 0 with 72 tests; whitespace and scope inspected. No crypto/backend acceptance or application build claimed.

Task: S00-027
Agent: auto-ci-cd-40ae46e8efe7
Role: ci-cd
Action: claim
Status: review

Task title: Restore approved npm registry connectivity for the OMEMO2 backend investigation
2026-10-15 17:12:33 +07:00
wt 8db4e4ef35 S00-020: Approve pinned Linux XMPP toolchain restoration
Changes: Accept S00-020 with independent QA evidence and handoff; refresh scoped runtime/cache result paths after fresh retrieval and installation.
Validation: verify.py passed official HTTPS checksum, Node 24.21.0/npm 11.19.0, isolated strict npm ci and Stanza import. Independent archive/input/eight-version/strict-setting assertions, tasks.py validate and diff/scope checks passed. Browser/WSS and product readiness remain untested.

Task: S00-020
Agent: auto-qa-9e1e289654ba
Role: qa
Action: review
Status: done

Task title: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 17:09:30 +07:00
wt ae00f3502e S00-020: Verify pinned Linux XMPP toolchain restoration
Changes: Refresh the environment-access result and document S00-020 restoration, accepted prerequisites and independent QA handoff; preserve probe code and parent dependency graph.
Validation: verify.py passed official HTTPS vendor checksum, Node 24.21.0/npm 11.19.0, fresh-cache strict npm ci and Stanza import. Separate artifact/input/version/strict-setting assertions and tasks.py validate passed. No browser/WSS or product readiness check performed.

Task: S00-020
Agent: auto-ci-cd-2a0186c54ce9
Role: ci-cd
Action: claim
Status: review

Task title: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 17:07:16 +07:00
wt 3b92736a99 S00-024: Approve restored Linux toolchain continuation checkpoint
Changes: Accept the bounded S00-024 restoration checkpoint, add independent QA evidence and handoff, and refresh successful probe runtime/cache paths.
Validation: Independently reran verify.py with official HTTPS checksum, Node 24.21.0/npm 11.19.0, fresh-cache locked npm ci and Stanza import; archive/input/installed-version/strict-config assertions, tasks.py validate and diff/scope/whitespace checks passed. Browser/WSS, interoperability and product readiness remain unverified.

Task: S00-024
Agent: auto-qa-e19cd93db255
Role: qa
Action: review
Status: done

Task title: Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 17:04:44 +07:00
wt 4bad1f228e S00-024: Verify restored pinned Linux XMPP environment access
Changes: Add the S00-024 continuation checkpoint, refresh verified runtime/cache paths, and replace the blocked handoff with QA and S00-020 restart instructions.
Validation: Existing verify.py passed official HTTPS/checksum retrieval, Node 24.21.0/npm 11.19.0, fresh-cache locked npm ci and Stanza import. Independent archive/input/version/config assertions, tasks.py validate and diff/scope/whitespace checks passed. Browser/WSS and product readiness remain unverified.

Task: S00-024
Agent: auto-ci-cd-49907d48a6d1
Role: ci-cd
Action: claim
Status: review

Task title: Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 17:02:37 +07:00
wt ca20031bf9 S00-026: Approve pinned Linux artifact access after independent verification
Changes: Accept the bounded S00-026 access probe; add independent QA evidence and refresh successful result runtime/cache paths. Preserve parent task incompleteness.
Validation: Repeat verify.py successfully with fresh official HTTPS downloads, matching vendor SHA256, exact Node 24.21.0/npm 11.19.0, eight locked packages and actual Stanza import. Independently check copied input bytes, installed versions, integrity and strict npm settings. tasks.py validate and git diff --check pass. Inspect executor patch/message and coordinator access confirmation. Browser/WSS and detached signature/provenance audits remain outside this review.

Task: S00-026
Agent: auto-qa-7d3de2e3c758
Role: qa
Action: review
Status: done

Task title: Provide approved access to the pinned Linux experiment artifacts
2026-10-15 17:00:17 +07:00
wt 2af4f143d8 S00-026: Verify pinned Linux runtime and isolated locked dependency access
Changes: Add a bounded reproducible HTTPS/checksum/runtime probe under environment-access, vendor checksums, unchanged parent manifest/lock copies, strict npm settings, result and reproduction notes. Record evidence and QA handoff; submit S00-026 without completing parent tasks.
Validation: Run verify.py twice successfully: vendor Linux archive SHA256 matched, Node 24.21.0/npm 11.19.0 executed, fresh-cache npm ci installed eight integrity-locked packages with scripts disabled, parent bytes stayed unchanged and Stanza import passed. tasks.py validate, git diff --check and scoped file whitespace checks passed. No browser/WSS, detached signature or provenance audit performed.

Task: S00-026
Agent: auto-ci-cd-4255c34da227
Role: ci-cd
Action: claim
Status: review

Task title: Provide approved access to the pinned Linux experiment artifacts
2026-10-15 16:57:59 +07:00
wt bac7706480 S00-028: Record newly available external input for independent verification
Changes: Release external waiting state for S00-028 through tasks.py. Input: Coordinator supplies the active compatible argv from runner.local.json: codex -c sandbox_workspace_write.network_access=true exec --approve-for-me --ephemeral -. Explicit --sandbox was removed before the S00-024/S00-022/S00-025 fresh agent launches. --approve-for-me retains workspace-write per installed CLI help; no danger-full-access or bypass option. Actual subsequent run state/runs/auto-storage-ae5930b6fbdf/run.json shows the fresh harness executed tools and reached its own escalate, unlike the original immediate CLI exit 2. Installed CLI bounded help check codex exec --approve-for-me --ephemeral --help exited 0. Outbound network config retains existing proxy and TLS, independently probed via codex -c sandbox_mode='"workspace-write"' -c sandbox_workspace_write.network_access=true sandbox -- python3 /tmp/luma-probe-registry.py, exit 0; state/runs/coordinator-network/output.log. Fresh S00-028 executor/QA must verify supported argv and filesystem scope; no storage task completion or launcher repair acceptance claimed.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-028
Agent: coordinator-input-s00-028
Role: orchestrator
Action: release-external
Status: todo

Task title: Repair conflicting Codex harness sandbox arguments
2026-10-15 16:54:15 +07:00
wt 3fd75a818f S00-025: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-025. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-023 harness exited before task execution: codex exec rejected mutually exclusive --approve-for-me and --sandbox flags. Repairing coordinator argv is outside S00-025 storage-only scope.

Task: S00-025
Agent: auto-storage-ae5930b6fbdf
Role: storage
Action: claim
Status: blocked

Task title: Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 16:47:40 +07:00
wt 927728daf1 S00-027: Record newly available external input for independent verification
Changes: Release external waiting state for S00-027 through tasks.py. Input: Approved agent sandbox outbound network enabled with documented sandbox_workspace_write.network_access=true in runner.local.json, retaining workspace-write filesystem restrictions, configured proxy and TLS. Verified with installed CLI: codex -c sandbox_mode='"workspace-write"' -c sandbox_workspace_write.network_access=true sandbox -- python3 /tmp/luma-probe-registry.py, exit 0. Both npm metadata queries returned actual values (@signalapp/libsignal-client 0.105.0; libomemo.js 3.0.0 GPL-3.0). Official pinned candidate artifact downloaded, SHA512 SRI matched; no package scripts executed. Public verification and commands: state/runs/coordinator-network/verification.json and output.log. Supplied metadata/archive also in /tmp/luma-approved-registry/. Fresh S00-027 executor and independent security reviewer must reproduce checks under their session, no crypto/backend acceptance claimed.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-027
Agent: coordinator-input-s00-027
Role: orchestrator
Action: release-external
Status: todo

Task title: Restore approved npm registry connectivity for the OMEMO2 backend investigation
2026-10-15 16:45:59 +07:00
wt 0d97f48929 S00-022: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-022. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: All ten live npm metadata queries fail with EAI_AGAIN resolving the configured proxy, including the known libsignal-client control. Required release and artifact verification for S00-022 cannot proceed; failures are not E404 evidence.

Task: S00-022
Agent: auto-crypto-316a193d6c3e
Role: crypto
Action: claim
Status: blocked

Task title: Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
2026-10-15 16:41:17 +07:00
wt a338d3a1dd S00-022: Record newly available external input for independent verification
Changes: Release external waiting state for S00-022 through tasks.py. Input: New primary-source candidate supplied: libomemo.js@3.0.0 https://github.com/conversejs/libomemo.js declares legacy OMEMO and OMEMO2 0.9.1 urn:xmpp:omemo:2, WASM and browser worker-client; Converse 14 source uses this dependency and encryptSCE/decryptSCE. Source links and limitations in state/runs/coordinator-research/omemo2-sources.md. Previous npm E404 queries did not include libomemo.js. Upstream explicitly lacks third-party security audit; GPL-3.0 compatibility, actual target tests, preservation of accepted verified engine/persistence and security review remain mandatory. New candidate is sourcing input only, not permission to replace an accepted engine, waive audit/requirements, or claim acceptance. Fresh task executor must evaluate this candidate under the existing card and escalate any unresolved bounded requirement.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-022
Agent: coordinator-input-s00-022
Role: orchestrator
Action: release-external
Status: todo

Task title: Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
2026-10-15 16:38:43 +07:00
wt 956d9459a6 S00-026: Record newly available external input for independent verification
Changes: Release external waiting state for S00-026 through tasks.py. Input: Approved external artifact preparation completed through configured HTTPS proxy with TLS verification, without changing system tools or project sources. Authentic local supply: /tmp/luma-approved-toolchain/SHASUMS256.txt, node-v24.21.0-linux-x64.tar.xz, npm-11.19.0.tgz; provenance and SHA256/SRI in /tmp/luma-approved-toolchain/verification.json. Exact Node v24.21.0/npm 11.19.0 execute. Unchanged copied parent lock installed with npm ci --ignore-scripts --engine-strict and Stanza 12.22.1, exit 0; /tmp/luma-approved-toolchain/dependency-verification.json and npm-ci.log. Cache /tmp/luma-approved-toolchain/npm-cache permits fresh offline npm ci without proxy access. Fresh executor must independently verify supplied archives/checksums and repeat installation inside its environment-access scope; no task acceptance claimed.
Validation: External input availability checked; tasks.py release-external succeeded. Acceptance and dependent tasks remain unverified pending fresh executor and independent review.

Task: S00-026
Agent: coordinator-input-s00-026
Role: orchestrator
Action: release-external
Status: todo

Task title: Provide approved access to the pinned Linux experiment artifacts
2026-10-15 16:38:33 +07:00
wt 6f0b4029c4 S00-024: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-024. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-024 cannot establish readiness for S00-020: official checksum retrieval fails with configured proxy DNS error and host Node/npm differ from accepted pins; no partial environment-access artifacts exist.

Task: S00-024
Agent: auto-ci-cd-988167c541cf
Role: ci-cd
Action: claim
Status: blocked

Task title: Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 16:35:52 +07:00
wt a971d3cf4b S00-023: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-023. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: Agent exited with code 2

Task: S00-023
Agent: auto-storage-87486895c792
Role: storage
Action: claim
Status: blocked

Task title: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
2026-10-15 16:33:42 +07:00
wt 7afc642a9f S00-020: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-020. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: Previous runner session was interrupted before submit/escalate; processes stopped. User now explicitly requests resuming development. Preserve partial artifacts, verify them in a fresh bounded remediation, and return original to a new executor only after independent review.

Task: S00-020
Agent: auto-ci-cd-5a18c8aa1b42
Role: ci-cd
Action: claim
Status: blocked

Task title: Restore access to the pinned Linux XMPP experiment toolchain
2026-10-15 16:33:21 +07:00
wt 5f4d044444 S00-005: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-005. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: Previous runner session was interrupted before submit/escalate; processes stopped. User now explicitly requests resuming development. Preserve partial artifacts, verify them in a fresh bounded remediation, and return original to a new executor only after independent review.

Task: S00-005
Agent: auto-storage-88c3286c6641
Role: storage
Action: claim
Status: blocked

Task title: Выбрать IndexedDB wrapper и migration policy
2026-10-15 16:32:52 +07:00
wt 55d9c69a21 S00-004: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-004. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-004 acceptance requires establishing legacy/OMEMO2 AND worker/wasm AND persistence without a homegrown ratchet. Reproduced (this run) probes and live public-registry queries confirm only the audited session/ratchet engine + persistence are verified (@signalapp/libsignal-client@0.105.0, Node native addon, zero .wasm). The browser/worker/WASM deployment and the OMEMO/OMEMO2 wire layer are not available from any maintained package on the reachable registry (@signalapp/libsignal, @converse/omemo, @xmpp/omemo, omemo2, omemo-js, omemo-client, omemo-top, omemo-wasm all E404). ENC-009 forbids a homegrown algorithm/ratchet and ENC-011 forbids substituting media AES-GCM for the OMEMO2 wire format, so the spike cannot synthesize a browser backend inside S00-004 scope.

Task: S00-004
Agent: auto-crypto-fe1c81c17ded
Role: crypto
Action: claim
Status: blocked

Task title: Проверить browser OMEMO backend
2026-10-15 15:42:43 +07:00
wt a02412d7c7 S00-021: Approve S00-021: independently verified browser OMEMO blocker
Changes:
- Wrote state/evidence/S00-021-review.md recording independent re-checks of the
  executor's remediation for S00-004's interruption ("Agent timed out before
  escalation").
- Reproduced both committed probes unchanged: smoke.mjs exit 0 (SMOKE_PASS,
  631-byte session record byte-for-byte reload) and wasm-unsupported.mjs exit 2
  (wasmFiles 0, native-only prebuilds), matching executor evidence exactly.
- Independently re-queried npm registry: @signalapp/libsignal (browser WASM) E404;
  @signalapp/libsignal-client 0.105.0 (native, AGPL-3.0) exists; confirms the
  browser/worker/WASM + OMEMO2 wire-layer blocker is genuine.
- Audited scope via git: commit 30f7f22 touches only the in-scope ADR
  docs/decisions/0003-crypto-library.md plus workflow metadata; experiments/**
  unchanged; working tree clean. No requirement bypass: S00-004 remains blocked,
  ADR 0003 records the honest blocker and forbids done on persistence+Ratchet alone.

Validation:
- make check: 72 tooling tests OK.
- Both probes reproduced, exit codes and output match evidence.
- Acceptance met: interruption repro row exists for next executor; original not
  declared done via bypass.

Task: S00-021
Reviewer agent: auto-security-5d2ae592b569
Role: security
Action: approve

Task: S00-021
Agent: auto-security-5d2ae592b569
Role: security
Action: review
Status: done

Task title: Устранить прерывание: Проверить browser OMEMO backend
2026-10-15 15:40:32 +07:00
wt 30f7f222d2 S00-021: Record verified browser OMEMO backend blocker for S00-004
Changes:
- Re-ran both committed probes unchanged (smoke exit 0 SMOKE_PASS; wasm-unsupported exit 2 UNSUPPORTED_BLOCKER) and recorded actual outputs in state/evidence/S00-021-auto-crypto-fcef9b6781bf.md.
- Verified on the reachable npm proxy that no maintained browser/WASM-capable OMEMO2 package exists: @signalapp/libsignal-client 0.105.0 is native-only (zero .wasm); @signalapp/libsignal, omemo-client, omemo-top, omemo-wasm, @converse/omemo are all E404.
- Wrote docs/decisions/0003-crypto-library.md (proposed) documenting the verified audited session/ratchet + persistence capability and the honest blocker for the browser/worker/WASM endpoint and OMEMO2 wire layer, with traceable primary sources.
- Wrote state/handoffs/S00-021.md with the reproducible next step for the S00-004 continuation.

Validation:
- smoke.mjs -> exit 0 (SMOKE_PASS, 631-byte record byte-for-byte reload).
- wasm-unsupported.mjs -> exit 2 (wasmFiles 0, native prebuilds only).
- npm view E404s confirmed against the same proxy that returns @signalapp/libsignal-client 0.105.0, so responses are genuine.
- make check: 72 tooling tests passed (OK).
- No source changes outside scope: experiments/** unchanged; only state/tasks.json (workflow metadata) plus the in-scope ADR. S00-004 stays blocked (honest blocker), not bypassed.

Task: S00-021
Agent: auto-crypto-fcef9b6781bf
Role: crypto
Action: claim
Status: review

Task title: Устранить прерывание: Проверить browser OMEMO backend
2026-10-15 15:38:49 +07:00
wt 71675b1cc0 S00-004: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-004. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: Agent timed out; process terminated before escalation

Task: S00-004
Agent: auto-crypto-8bf3dcf711b9
Role: crypto
Action: claim
Status: blocked

Task title: Проверить browser OMEMO backend
2026-10-15 15:32:48 +07:00
wt 3784fa7b99 Claim S00-020 environment toolchain access task by interrupted runner 2026-10-15 15:02:32 +07:00
wt 0d3dc123b5 Record coordinator network release commit message artifact 2026-10-15 14:26:38 +07:00
wt 19474445f0 S00-020: Release environment remediation for verified network access
Changes: Release S00-020 external wait through tasks.py after approved HTTPS probes. The local ignored runner configuration now uses automatic approval review with the workspace-write sandbox retained. S00-003 remains blocked until remediation implementation and independent review.
Validation: Official Node v24.21.0 SHASUMS256.txt and npm Stanza registry HEAD requests returned HTTPS 200 through the configured proxy in approved host execution. tasks.py release-external succeeded; runner --dry-run selects S00-020 with configuration_required=false. Pinned runtime execution, locked installation and integrity verification have not been performed.
2026-10-15 14:26:06 +07:00
wt 452ec2fc08 S00-003: Record blocked task and remediation handoff
Changes: Preserve incomplete work and blocker metadata for S00-003. This checkpoint does not mark implementation accepted.
Validation: Session ended with status blocked.
Blocker: S00-003 cannot reproduce accepted Node 24.21.0/npm 11.19.0 or install locked Stanza: host tools are 24.1.0/11.3.0, parent node_modules is absent, and normal official HTTPS retrieval fails with unresolved configured proxy.

Task: S00-003
Agent: auto-protocol-59f4d359b339
Role: protocol
Action: claim
Status: blocked

Task title: Проверить XMPP WSS/auth библиотеку
2026-10-15 11:14:13 +07:00
181 changed files with 14544 additions and 65 deletions

No files matched your search

+43
View File
@@ -18,6 +18,19 @@ Harness обязан запускать новую conversation/session для
Автоматические commits включены по умолчанию. Перед первым реальным запуском foundation должна быть сохранена начальным commit в Git checkout с активной веткой, настроенной identity и чистым index/working tree. Каждый agent transition заканчивается отдельным осмысленным commit перед следующим процессом. Executor и reviewer заранее пишут сообщения; Git выполняет runner. Правила, примеры и восстановление — `docs/GIT_WORKFLOW.md`.
## Codex 0.159.0: argv (S00-028)
Coordinator выбрал из project root:
```sh
codex -c sandbox_workspace_write.network_access=true exec --approve-for-me --ephemeral -
```
`--approve-for-me` включает workspace-write/auto-review и конфликтует с `--sandbox`.
`-` читает fresh prompt из stdin; resume/fork не использовать. Проверки без модели:
argv + `--help` и sandbox probe ниже, timeout20, exit0; project write passed,
outside denied EROFS30. Полное evidence: `state/evidence/S00-028-auto-ci-cd-e3a716a94424.md`.
## Запуск
```sh
@@ -65,6 +78,36 @@ Release только делает resolver готовым к проверяем
Журналы: `state/runs/<agent-id>/run.json`, `prompt.md`, `output.log`; хранить без secrets. Каждый report содержит task/role, exit/status, timeout/violation и результат, не включает private command environment.
## Вложенный sandbox: проверенный host context (S00-031)
На этом Linux host с Codex 0.159.0 `/tmp/codex-daemon-1000` имеет uid 1000,
mode 0700; внутри workspace sandbox тот же путь виден с mode 000.
Ошибка вложенного запуска `app-server socket directory must be a user-owned directory
with mode 0700` сама по себе не доказывает неверные host permissions. Для S00-030
использовать проверенный ниже host context; исправление mode на host не требуется.
Проверенный способ: одобренный внешний host execution
(`sandbox_permissions: require_escalated`) запускает **внутренний workspace-write sandbox**
из корня проекта, с timeout 20 секунд:
```sh
codex -c 'sandbox_mode="workspace-write"' -c sandbox_workspace_write.network_access=true sandbox -- python3 /tmp/luma-filesystem-sandbox-probe.py
```
До запуска проверить host `stat`: directory, owner=current uid, mode 0700.
Script содержит только synthetic project write/read/remove и exclusive-create вне
проекта с обязательным отказом EROFS/EACCES/EPERM. Его полный текст и воспроизводимая
команда сохранены в `state/evidence/S00-031-auto-ci-cd-d45ccd3ee7d6.md`;
временный `/tmp` script нужно сначала прочитать или восстановить из evidence.
Executor получил exit 0: project probe passed, outside write denied EROFS 30,
outside file не создан. Ни модель, ни storage task не запускались.
Не менять socket permissions/mask, writable roots или environment location.
Если approved host execution недоступен, выполнить blocker protocol.
Результат относится к этому host/version и одной filesystem boundary; QA повторяет
проверку независимо. Это не app readiness. Общие понятия:
[официальная документация OpenAI](https://developers.openai.com/codex/security).
## Гарантии инструмента
Dependency/role guards, retired reporter, recovery journal definitions+state, fresh subprocess IDs, bounded prompts, separate reviewer, automatic unblocking only after done и stop на внешнем blocker. Истинность evidence/качество модели/OS sandbox обеспечиваются реальным harness и review, а не названием роли.
+145
View File
@@ -0,0 +1,145 @@
# ADR 0002: XMPP library and required browser integration seams
- Status: accepted for bounded S00-003 library selection; independent QA: `state/evidence/S00-003-review.md`.
- Date: 2026-10-15. Owner: auto-protocol-f474114f0168 (protocol).
- Reviewer: auto-qa-d49749753ac6 (qa), 2026-10-15.
- Requirements: ACC-004, NET-003, NET-004.
- Prerequisites: accepted scope/stack ADRs and bounded SASL, parser, SHA-512
experiments (S00-017/018/019); independently reviewed Linux environment S00-020.
## Question and decision
Select Stanza 12.22.1 (MIT), with the three reviewed public integration seams
mandatory before connect: bounded transport, fail-closed SASL feature, and the
SHA-512 mechanism composition. Stock Stanza alone is unsuitable for this profile.
Its stock SASL handler does not verify server-final, its browser hyphenated
SHA-512 HMAC is wrong, and its persistent parser has no finite resource budgets.
The accompanying spike proves the composed candidate; it creates no production
adapter, shared contract, crypto implementation or provider deployment.
| Option | Evidence and tradeoff |
| --- | --- |
| Stanza 12.22.1 plus reviewed extensions | Selected: actual browser bundle, public Client/Factory/Registry/Transport APIs, verified fixed SCRAM transcripts, loopback WSS and extension fixtures. JSON models and built-in MAM/MUC/Jingle/upload mappings fit the planned adapter boundary. Exact-version alias and replacement plugins require regression checks on upgrades. |
| Stock Stanza | Rejected: the accepted remediation experiments reproduce security/framing defects. Installing the package alone does not meet this decision. |
| xmpp.js | Viable alternative with browser WebSocket and modular packages; its primary README documents MIT and client/component modules. Not installed or executed here; no superiority/security finding or exact pin is claimed. It would require a separate equivalent integration experiment, rather than assuming its SASL/parser meet the profile. |
## One runnable selection experiment
`experiments/0002-xmpp-library/selection-spike` combines unchanged reviewed
bounded-ingress and SHA-512 alias source with the ordering guard extended to
non-PLUS SHA-512/256/1. Existing library crypto implements all hashes, HMAC,
SCRAM and SASLprep. No vendor file, private field or primitive is changed.
The real Client.connect, XML registry, feature dispatcher, queue and browser
SCRAM backend run against a synthetic loopback HTTPS/WebSocket peer. Its
self-signed certificate is generated temporarily by OpenSSL, trusted explicitly
by the test socket only, and hostname/certificate checks remain enabled. No
system trust store is changed. An uninjected native WebSocket rejects the same
untrusted certificate; a plaintext endpoint is rejected before a socket exists.
The trusted peer observes TLS plus the xmpp subprotocol, framing open, exact
proof and a second open only after a valid server-final. Forged/premature finals
never authenticate or restart. This is a real WSS handshake with a fixture peer,
not interoperability with a deployed independent XMPP server.
Mechanism policy filters PLUS because browser TLS binding is unavailable,
selects SHA-512 then SHA-256 then SHA-1 independent of offer order, and disables
all other fallbacks. NET-003 permits a configured protected fallback; this
selection enables none. PLUS-only/PLAIN-only/empty offers fail. Diagnostics
contain selected mechanism, `channelBinding: unavailable`, and disabled fallback;
no credential, nonce, proof or peer payload. Synthetic tlsUnique is rejected.
GS2 n,, and c=biws are verified. A future platform with real binding must add a
separately reviewed implementation before offering PLUS.
Parser experiment ceilings are preserved: 4096 UTF-8 bytes, depth 8, 32 elements,
16 attributes, 1024 aggregate attribute units, 128 name units and 256 attribute
value units. Standalone complete text XML is preflighted by saxes before JXT
import. Oversized and split XML fail on the actual WSS path. These numbers are
local experiment limits, not a production message/media-size contract.
Registry fixtures import/export MAM forwarded IDs, MUC join, Jingle terminate,
HTTP upload URLs and legacy opaque OMEMO bytes. An explicitly invented opaque
namespace demonstrates Client.use/Registry.define extension and namespace
isolation. It is not an OMEMO2 implementation or encrypted messaging claim.
Imported messages expose both selected `body` and `alternateLanguageBodies`;
exporting both concatenates text in this version. A regression records it;
the MAM fixture exports only the language collection. Future adapters must
construct deliberate outbound shapes rather than echo imported objects.
## Reproduction, versions and licenses
Follow the experiment README. Exact Node 24.21.0/npm 11.19.0 and Vite 8.3.4 remain
from accepted ADR 0001. Stanza 12.22.1, saxes 6.0.0, events 3.3.0 and test peer
ws 8.22.0 have exact direct pins; lock v3 includes all optional hosts. These are
already reviewed package families, not a new stack. MIT/ISC runtime notices,
Stanza bundled ancestry, and build MIT/ISC/BSD/Apache/MPL notices are preserved
in inventory/notices; saxes' omitted package license is preserved from the
reviewed versioned upstream copy. MPL build code is unmodified. Dependency
monitoring, provenance and advisory clearance remain continuing release work.
Browser syntax floors are the accepted Chrome/Edge 111, Firefox 114, Safari 16.4
build targets, not a product support certification. Bundle graph excludes Node
platform/crypto and unresolved builtins; events is explicitly bundled. Native
WebSocket, secure RNG, URL and TextEncoder are required. Server/test tooling
Node crypto is only an independent oracle; it does not replace browser crypto.
## Primary sources inspected on 2026-10-15
- [RFC 7395](https://www.rfc-editor.org/rfc/rfc7395.html), sections 3.1–3.4,
3.7 and 3.9: subprotocol, complete text XML, framing, restart and TLS.
- [RFC 5802](https://www.rfc-editor.org/rfc/rfc5802.html#section-5) and
[RFC 7677](https://www.rfc-editor.org/rfc/rfc7677.html#section-3): fixed SHA-1/256
exchange vectors and server verification. SHA-512 independent Scramp fixtures
and RFC 4231 primitive vectors retain their accepted S00-019 provenance.
- [Stanza upstream](https://github.com/legastero/stanza) and
[xmpp.js upstream](https://github.com/xmppjs/xmpp.js): candidate APIs, browser
positioning and licenses. Exact installed Stanza 12.22.1 Client.d.ts/.js,
lib/sasl, plugins/features, jxt/Registry and protocol modules are authoritative
for shipped extension behavior; all 340 files match the independently installed
S00-020 vendor graph. Upstream branches do not establish version guarantees.
- [XEP-0313](https://xmpp.org/extensions/xep-0313.html),
[XEP-0045](https://xmpp.org/extensions/xep-0045.html),
[XEP-0166](https://xmpp.org/extensions/xep-0166.html),
[XEP-0363](https://xmpp.org/extensions/xep-0363.html): fixture namespaces and
envelope/extension shapes. Shipped protocol comments identify older revisions;
a passing simple fixture does not establish complete current-XEP coverage.
## Consequences, limits and review
The spike is sufficient input for bounded selection review. Production still
requires S01 contracts and its own implementations/tests. The minimal transport
omits complete stream sequencing, error/close negotiation, redirect and keepalive
policies. Queue/rate/network-allocation bounds, worker/CPU performance, timeout,
full SASL extensions/final-challenge compatibility and constant-time verifier
assurance are not established. Library verifier uses string equality. Strict
success-only final layout and conservative send-race rejection reduce availability.
No Chrome/Firefox/Safari runtime execution, independent XMPP server/client,
real account, OMEMO encryption, production feature semantics, full browser matrix
or release readiness was verified. Browser-built code runs in Node with host web
APIs; socket injection adds fixture CA trust, not a browser channel-binding API.
Old accepted experiment source is unchanged; shared interfaces/specs/stack and
network/TLS/CORS policies remain unchanged. Any API incompatibility on upgrade
requires a new bounded decision, not a private patch or silent downgrade.
QA should reproduce clean install/build/tests and dependency checks, inspect the
stock defect controls, explicit extension projection and scope, then record an
independent decision. Evidence: `state/evidence/S00-003-auto-protocol-f474114f0168.md`.
Coordinator owns transition commits; independent acceptance is recorded below.
## Independent acceptance
QA reviewer auto-qa-d49749753ac6 accepts this exact-version library selection
with all three integration seams mandatory. Fresh-cache locked installation,
browser bundle and all 112 tests passed on reviewed Linux Node 24.21.0/npm
11.19.0; 51 locked entries, 26 installed package notices and 340 unchanged vendor
files verified. Lock, guard and bundle hashes match executor evidence. Additional
six offer permutations selected SHA-512; six adversarial XML documents rejected
before registry access and a valid body imported. make check passed 72 tooling
tests. Public shipped APIs, composed guard diff, fixture trust boundary and
extension projection were inspected. Report: `state/evidence/S00-003-review.md`.
Acceptance covers the bounded combined spike and selection only. All native
browser, independent server/client, OMEMO, aggregate resource, lifecycle,
timeout/timing and release limitations above remain. Stock Stanza without the
required seams is not accepted. Coordinator must commit the review transition
before any subsequent fresh task session.
+141
View File
@@ -0,0 +1,141 @@
# ADR 0003: OMEMO session/ratchet engine and browser backend feasibility
- Status: proposed
- Task: S00-004, remediation S00-021.
- Date: 2026-10-15.
- Decision owner: auto-crypto-fcef9b6781bf (crypto).
- Reviewer: security (separate agent for S00-021 review).
- Requirements: ENC-003, ENC-009, ENC-011.
## Context and required capabilities
Input: immutable ENC-003 (legacy OMEMO and OMEMO 2 wire formats with compatible
device lists/bundles and identity/session lifecycle), ENC-009 (encryption runs in
the client endpoint with proven implementations and compatible vectors; no
homegrown algorithm, no server-side decryption, no treating shared storage as
proven interoperability), and ENC-011 (OMEMO 2 wire format by an approved
compatible revision with SCE and payload/key/MAC rules; not replaced by media
AES-GCM). Also the accepted scope (ADR 0000), stack (ADR 0001) and reviewer
decisions S00-001/S00-002, plus the S00-004 blocker report and handoff.
Output is one focused probe record and this ADR (proposed) establishing what is
verifiably feasible today for a Web client: an audited session/ratchet engine
with secure session-record persistence, and the honest boundary for the
browser/worker/WASM endpoint and the OMEMO2 wire layer. Acceptance requires a
real library/backend, verified vectors, no homegrown ratchet, and any unsupported
format recorded as a blocker (not success).
## Candidates and primary sources
Primary public sources and exact reachable-registry records retrieved
2026-10-15 via the configured npm proxy. These sources establish library and
distribution boundaries; none of them is treated as a tested product
integration.
| Candidate | Reachable registry | License | Browser/WASM reality |
| --- | --- | --- | --- |
| `@signalapp/libsignal-client@0.105.0` | exists | AGPL-3.0-only | Node native addon (`dist/Native.js` uses `node-gyp-build`; `prebuilds/*/*.node`). No `.wasm` asset ships; cannot execute in a browser worker. |
| `@signalapp/libsignal` | 404 Not Found | — | Signal's TypeScript/WASM web distribution is not published to the reachable npm registry; `npm view @signalapp/libsignal version` returns E404. |
| `omemo-client` | 404 Not Found | — | not on reachable registry |
| `omemo-top` | 404 Not Found | — | not on reachable registry |
| `omemo-wasm` | 404 Not Found | — | not on reachable registry |
| `@converse/omemo` | 404 Not Found | — | not on reachable registry |
Sources:
- [`signalapp/libsignal` repository `package.json`](https://github.com/signalapp/libsignal/blob/main/node/package.json) defines the Node distribution of the libsignal client (native bridge).
- Signal documentation on libsignal WASM/web: the library is compiled to WebAssembly for Signal's browser clients, but that artifact is produced from source into the Signal client builds; it is not shipped as a maintained standalone `@signalapp/libsignal` npm package on the reachable registry (E404, queried 2026-10-15).
- The exact registry records were queried with `npm view <pkg> version`; results (version or E404) are captured in evidence: `state/evidence/S00-021-auto-crypto-fcef9b6781bf.md`.
## Focused experiment and actual result
The previously committed probes inside
`experiments/0003-crypto-library/session-probe/` were reproduced unchanged from
the S00-004 handoff (this run re-ran both, actual exit codes recorded below):
1. `node experiments/0003-crypto-library/session-probe/smoke.mjs` — exit 0,
`SMOKE_PASS`. A real A/B pair using
`@signalapp/libsignal-client` sealed-sender APIs established a PreKey session,
round-tripped the known plaintext `known vector: OMEMO session smoke`, stored a
631-byte session record and reloaded it byte-for-byte. This verifies an audited
session/ratchet engine and session-record persistence without any homegrown
ratchet; it mirrors Signal canonical in-memory store semantics.
2. `node experiments/0003-crypto-library/session-probe/wasm-unsupported.mjs` —
exit 2. Inspects the installed package: `wasmFiles: 0`; `prebuilds` are
`darwin-arm64, darwin-x64, linux-arm64, linux-x64, win32-arm64, win32-x64`
native `*.node` files; `dist/Native.js` loads via `node-gyp-build`. This
confirms that the shipped artifact cannot run in a browser worker/WASM
endpoint, and the distribution ships no `.wasm` asset.
Result: the audited session/ratchet engine and persistence are verified; the
browser/worker/WASM deployment and the OMEMO/OMEMO2 XMPP wire layer are **not**
established by any maintained package on the reachable registry. Per focused
check "unsupported format recorded blocker, not success", this is recorded as a
blocker, not as an achieved capability.
## Decision
Adopt `@signalapp/libsignal-client` **only** as the audited session/ratchet
engine and session-record persistence primitive for the Node-side spike and
downstream adapters that can run a native/external environment, and keep it as
the verified evidence that an audited library (not a homegrown ratchet) supplies
the whistler/session layer and durable session-records.
Record as a blocker on S00-004 that the browser/worker/WASM endpoint and the
OMEMO2 wire-format layer are not available from any maintained package on the
reachable registry, and that `@signalapp/libsignal` (the WASM distribution) is
not published there. This remediation does **not** replace the missing browser
backend with a homemade ratchet, does not reuse the shared Node session store as
if it proved browser interoperability (ENC-009), and does not pretend the
unsupported OMEMO2 wire layer is complete. The correct next step for S00-004's
continued execution is: (a) obtain a verified browser/WASM-capable OMEMO2
backend (a distribution with a real `.wasm`/web build and an OMEMO2/S
ce-format mapping) or (b) escalate with traceable upstream primary sources
resolving ENC-009/ENC-011 client-endpoint encryption for the browser transport.
## License / security / browser constraints
`@signalapp/libsignal-client` is AGPL-3.0-only. Its AGPL-3.0 obligations apply if
the library (or modified copies) is distributed. Its official notices and license
are bundled in `node_modules/@signalapp/libsignal-client` and its
`acknowledgments`; the reader is referred to the package license text and the
Signal acknowledgments file before any redistribution. AGPL compatibility with
the eventual product distribution decision is a separate review item for the
accepted stack/licensing tasks.
Browser constraints (from the accepted stack ADR 0001 and Web profile): the
absent WASM distribution is a hard capability boundary here — no `.wasm` asset to
instantiate in a worker. No plaintext fallback, no spy/`decrypt` bypass, and no
server-side decryption path is introduced. Any real OMEMO2 wire mapping must be
produced from approved primary sources and preserve ENC-009/ENC-011 client-endpoint
encryption; media/transport AES-GCM alone does not satisfy ENC-011.
## Consequences and downstream contract
- S00-004's acceptance requires legacy/OMEMO2 AND worker/wasm AND persistence.
Two of the three (browser/WASM and the OMEMO2 wire layer) remain unresolved and
are recorded here as a blocker; only persistence + audited ratchet/session are
established. A new executor of S00-004 must not declare it done on this basis
(it would bypass the requirements).
- Any crypto boundary contract (planned `docs/contracts/crypto.md`, S12 tasks)
must key off a verified, maintained library and real vectors; it must not
assume a browser/WASM OMEMO2 primitive that is still absent.
- No shared contract, plan definition, requirement snapshot or spec file is
changed by this ADR.
## Rejected alternatives and migration trigger
1. Hand-write the Double Ratchet or OMEMO2 wire format. Rejected: forbidden by
ENC-009 (no homegrown algorithm/primitive replacement) and the crypto role
constraints.
2. Reuse the Node session store as "interoperability". Rejected: ENC-009 forbids
treating shared session storage as proof of interoperability; it is a
persistence spike only.
3. Declare `@signalapp/libsignal-client` a full browser backend. Rejected:
verified native-only, zero `.wasm`; would be a false success.
Migration/continuation trigger: a verified browser/WASM-capable OMEMO2 package or
a resolved, traceable upstream path that satisfies ENC-009/ENC-011 becomes
available; then S00-004 (or its assigned continuation) may be re-run and the
browser-capable backend validated with known vectors in the web target before
acceptance.
@@ -0,0 +1,62 @@
# S00-027: Approved npm registry connectivity record
- Status: accepted — independent security review S00-027; evidence: `state/evidence/S00-027-review.md`.
- Reviewer: auto-security-ccce71646176 (security), 2026-10-15.
- Executor: auto-ci-cd-40ae46e8efe7 (ci-cd), 2026-10-15.
- Input: S00-027 card, S00-022/S00-027 blockers, accepted ADR 0000/0001 and direct dependency evidence.
- Output: connectivity evidence only for resuming the separate OMEMO2 investigation.
## Environment-owner confirmation
Canonical `state/tasks.json` history revision 74 records coordinator-input-s00-027 releasing the external wait on 2026-10-15T09:45:53+00:00. The confirmation explicitly enables sandbox outbound network while retaining workspace-write restrictions, configured proxy and TLS. It reports successful prior probes and requires fresh executor and reviewer verification. This session reproduced access; it did not alter proxy, DNS, TLS, runner configuration or system tools. No credentials or private proxy values are recorded.
## Actual commands and responses
All commands ran from project root. A fresh directory was created with Python `tempfile.mkdtemp(prefix='luma-s00-027-')`: `/tmp/luma-s00-027-kdeu0fv9`. Its cache was empty before the first query. Existing coordinator metadata, archive and cache were not used.
| Command | Exit | Actual response |
| --- | --- | --- |
| `node --version` | 0 | v24.1.0 |
| `npm --version` | 0 | 11.3.0 |
| `npm config get strict-ssl` | 0 | true |
| `npm view @signalapp/libsignal-client version --json --fetch-retries=0 --fetch-timeout=15000 --cache=/tmp/luma-s00-027-kdeu0fv9/cache` | 0 | `"0.105.0"` |
| `npm view libomemo.js version license dist.integrity dist.tarball --json --fetch-retries=0 --fetch-timeout=15000 --cache=/tmp/luma-s00-027-kdeu0fv9/cache` | 0 | 3.0.0; GPL-3.0; integrity and URL below |
| `npm view libomemo.js@3.0.0 version license dist.integrity dist.tarball --json --fetch-retries=0 --fetch-timeout=15000 --cache=/tmp/luma-s00-027-kdeu0fv9/pinned-cache --registry=https://registry.npmjs.org/ --strict-ssl=true` | 0 | Same fields, fresh second cache |
No EAI_AGAIN or E404 occurred. npm emitted an update notice on the control query; no update was performed. Host runtime versions are explicitly recorded; these metadata/download-only probes do not build the application or replace ADR 0001 tool pins.
Pinned public artifact: [libomemo.js 3.0.0 tarball](https://registry.npmjs.org/libomemo.js/-/libomemo.js-3.0.0.tgz). Primary metadata source: [versioned registry record](https://registry.npmjs.org/libomemo.js/3.0.0).
Registry integrity and independently computed SHA512 SRI both equal:
```text
sha512-B4he9DOS+1LQrut+b2xjOjuSkKMAkZGes7ckB5JgVk/EHVNyxLIHx6NvQ3eJVaT1XeNJaWZBonXw3Nl5YKEbig==
```
## Artifact retrieval and integrity verification
An inline Python wrapper ran the pinned command above with `subprocess.run(..., capture_output=True, text=True)`, required exit 0, parsed its JSON into `meta`, and asserted `meta['version'] == '3.0.0'`. The following retrieval/check body then ran unchanged inside that wrapper (root is the temporary directory above):
```python
url=meta['dist.tarball'];assert url=='https://registry.npmjs.org/libomemo.js/-/libomemo.js-3.0.0.tgz'
with urllib.request.urlopen(url,context=ssl.create_default_context(),timeout=15) as response:
assert response.status==200 and response.url==url
data=response.read()
sri='sha512-'+base64.b64encode(hashlib.sha512(data).digest()).decode()
assert sri==meta['dist.integrity']
(root/'libomemo.js-3.0.0.tgz').write_bytes(data)
```
Imports were `subprocess,json,pathlib,urllib.request,ssl,hashlib,base64`. The wrapper also wrote public verification JSON under the temporary root. Actual wrapper exit: 0. HTTP status: 200; final URL unchanged; archive size: 1,061,939 bytes. SHA256: `be30116eca7014a35b3372dc1270d462cf5392ae6affc92c4f4778d003e7bbf8`. Archive saved only under `/tmp`; it was neither extracted nor installed nor executed. Python used its default validated SSL context and existing configured environment; no certificate/proxy-policy bypass was used.
## Acceptance boundary and review handoff
Both required live metadata queries and the pinned artifact/SRI verification succeeded in the fresh agent session. This proposes acceptance of network restoration only. Registry integrity matches bytes to registry metadata; it does not establish independent provenance, safety or crypto suitability. GPL-3.0 is reported metadata, not a distribution approval.
No crypto/backend decision, browser/WASM/worker execution, SCE mapping, audited interoperability, persistence compatibility, application build or release readiness was tested or accepted. ENC-003/009/011 remain obligations of the original investigation. The existing crypto ADR and experiments, specs, contracts and plan definitions are unchanged.
The assigned independent security reviewer should repeat both queries with a new empty cache, repeat pinned metadata/download/SHA512 comparison under normal TLS and the approved environment, inspect scope and evidence, then record the review through tasks.py. Only reviewed resolver completion may return S00-022 to the queue for a new agent. Coordinator owns Git commits.
## Independent acceptance
Security reviewer auto-security-ccce71646176 reproduced both live metadata queries with a new empty temporary cache and the pinned metadata/download/SHA512 check under normal TLS. Responses, 1,061,939-byte archive and SHA256 matched the executor record. No package scripts were executed. `make check` passed 72 tooling tests. This accepts registry connectivity only; all crypto/backend and distribution limits above remain. Coordinator owns the review commit.
+179
View File
@@ -0,0 +1,179 @@
# ADR 0004: IndexedDB wrapper and migration policy
- Status: accepted; bounded storage design independently reviewed on 2026-10-15.
- Task / requirements: S00-005 / STORE-001, STORE-004.
- Date: 2026-10-15.
- Decision owner: auto-storage-0ce544ac4b40 (storage).
- Reviewer: auto-qa-4078580caf39 (qa); evidence: `state/evidence/S00-005-review.md`.
## Context and required capabilities
Choose browser persistence tooling and policy, using accepted ADR 0001 (TypeScript,
ESM, Node 24.21.0/npm 11.19.0). Account history, crypto state and settings require
isolation, transactional writes and honest failure reporting. This decision does
not define application record schemas or port signatures. The registry reserves
`docs/contracts/persistence.md` for S01; that file does not exist yet. No current
experiment needs a new shared interface. Downstream implementation must wait for
its accepted models/lifecycle/persistence contracts.
## Candidates and primary sources
Documentation and registry metadata inspected on 2026-10-15:
| Candidate | Benefits | Cost / outcome |
| --- | --- | --- |
| Native IndexedDB | Standard API, no package | Event/request handling requires more adapter code; reference behavior, not selected ergonomics |
| idb 8.0.4, ISC | Promise API, typed schemas, explicit transaction completion and upgrade callbacks | Native transaction lifetime still applies; selected and executed |
| Dexie 4.4.6, Apache-2.0 | Query helpers, declarative versions and transaction API | Broader abstraction than this bounded adapter needs; researched, not installed or benchmarked |
Select **idb 8.0.4**. Its small API surface keeps native transaction boundaries
visible while providing promises and TypeScript declarations. Dexie remains a
viable option if measured query requirements justify it. No speed or security
superiority is claimed. `npm view` verified exact candidate versions/licenses;
installed idb metadata has no runtime dependencies. Only the isolated experiment
manifest/lock changes here; production adoption belongs to later scoped tasks.
Sources:
- [idb documentation](https://github.com/jakearchibald/idb): promises, `tx.done`,
typed schemas, transaction lifetime and upgrade/blocked/blocking/terminated APIs.
- [Dexie design](https://dexie.org/docs/Tutorial/Design): queries, transactions,
versioning and asynchronous transaction limits.
- [IndexedDB standard](https://w3c.github.io/IndexedDB/): request errors,
transaction abort/commit, version changes and durability hints.
- [Storage standard](https://storage.spec.whatwg.org/): estimates and persistence
requests; [WebKit storage policy](https://webkit.org/blog/14403/updates-to-storage-policy/)
illustrates browser-controlled quotas/eviction. No fixed quota is promised.
- Registry records: [idb 8.0.4](https://registry.npmjs.org/idb/8.0.4),
[Dexie 4.4.6](https://registry.npmjs.org/dexie/4.4.6),
[fake-indexeddb 6.2.5](https://registry.npmjs.org/fake-indexeddb/6.2.5).
- [fake-indexeddb documentation](https://github.com/dumbmatter/fakeIndexedDB):
in-memory testing implementation, without disk persistence.
## Transaction and account policy
Use one versioned application database per origin with distinct history, crypto,
settings and metadata stores. Every account-owned key and index starts with the
canonical accountId; unscoped scans are forbidden in account APIs. AccountId is
an opaque application identity, not a password or a raw JID database name. Capture
account/generation before asynchronous work and validate it at application entry;
do not resolve an account from whichever context is active after awaiting.
History mutations and their archive checkpoint share one readwrite transaction.
Any coupled crypto mutation must use that same database transaction when required
by the future crypto contract. Separate stores allow this; separate databases
would not. Cache is disposable and must never be the only copy of crypto state.
Network/worker/crypto preparation finishes before starting a transaction. Await
only IDB requests within it; any other asynchronous work risks auto-commit.
Success is published only after `tx.done` resolves. Synchronous exceptions,
request failures and completion failures all produce a failed save; abort an
active transaction and observe completion before returning. Never swallow a
request error and then report success. External XMPP delivery is not part of the
local transaction: reconcile delivery separately rather than blindly resend.
Use strict durability hints for critical state where supported/verified, with
default durability explicitly recorded otherwise; neither is a hardware vault
or proof against device failure, eviction or user clearing.
## Schema upgrade and connection ownership
Use monotonically increasing integer versions, sequential oldVersion steps and
one versionchange transaction. A fresh database runs every needed step. Modify
existing stores via the upgrade transaction; do not recreate them accidentally.
Upgrade code is deterministic and local: no network, worker waits, secrets or
unbounded whole-history transformation. Large resumable transformations require
an independently designed recovery/checkpoint contract before implementation.
Each tab/worker owns its connection. On blocking/versionchange, stop accepting
new writes, let current work finish/abort, close the connection and request reload
to the supported schema. On blocked, show an upgrade-wait state with instructions
to close/reload other tabs; do not delete the database or claim readiness. A wait
deadline changes UI state, not the upgrade result. IndexedDB open cannot simply
be cancelled: a late success for a retired attempt must be closed and ignored.
On terminated, invalidate the handle and reopen under lifecycle control.
An aborted migration leaves the prior version/data available for investigation
and a corrected forward retry. No automatic wipe, schema downgrade or silent
rebuild of non-recoverable state. An older build receiving VersionError asks for
an update; release rollback must retain a compatible reader or ship a corrective
forward migration. Tests must cover fresh creation and each supported old version.
## Quota, eviction and key ownership
Estimates are advisory and race with other origin storage. Request persistent
storage through supported StorageManager APIs and report the actual granted
state; denial does not mean storage is unavailable. Quota/unknown disk failures
must stop the batch without advancing checkpoint. UI reports history not saved
locally and retains bounded pending work in memory under the later queue policy.
Retry is explicit/bounded after freeing disposable media/cache; never remove
crypto, trust state or unsynced history to make space automatically. Reload or
origin clearing may lose pending memory and local data; startup must detect missing
schema/account markers and request a real recovery path rather than imply success.
Browser testing still must verify native exhaustion, eviction and restart behavior.
Storage owns byte persistence and isolation, **not cryptographic key generation,
trust or unlock policy**. Crypto owns serialization/versioning of opaque state
and any wrapping/unlock material under its accepted ADR/contract. This task does
not authorize saving raw private keys. Passwords remain memory-only; no password,
unlock secret or reusable wrapping secret in localStorage/IndexedDB. Persistent
unlock needs its own accepted decision. Same-origin script access is a security
boundary: IndexedDB alone provides neither XSS resistance nor hardware backing.
Account removal must delete its records across all owned stores atomically,
invalidate in-flight context and release in-memory crypto; exact deletion and
suppression semantics remain S01/application work, not an implemented API here.
## Focused experiment and actual result
`experiments/0004-storage-design` uses exact idb 8.0.4 and fake-indexeddb 6.2.5,
locked HTTPS/SHA512 dependencies and the accepted Node/npm pins. Fresh-cache
`npm ci --ignore-scripts --no-audit --no-fund` and `npm run check` passed.
- Real fixture ConstraintError during v1→v2 unique-index migration aborts open;
v1 store/data survive, new store/index disappear. Corrected retry reaches v2,
is queryable and survives connection reopen in the same process.
- Explicit QuotaExceededError injection after successful history/checkpoint
requests returns failed save, aborts both mutations and preserves account B.
A clean retry commits both. This tests policy, not native quota generation.
- Holding v1 produces versionchange then blocked; upgrade has not executed.
Closing v1 allows v2 and preserves checkpoint. Opening v1 again fails VersionError.
These are synthetic API experiments, not production adapters. Browser inventory
was empty. Native disk quota, crash/process restart, browser multi-tab, eviction,
persistence grants and crypto unlock were not tested. No browser readiness or
application durability acceptance is implied. See README and task evidence for
commands and limits; preserved RECOVERY.md describes the historical checkpoint.
## License / browser constraints and consequences
idb's ISC notice is retained in the experiment; fake-indexeddb's Apache-2.0 text
is retained as a test-only dependency notice. Neither package declares runtime
dependencies in this lock graph. Installs disable lifecycle scripts and enforce
normal TLS; audit/provenance assessment was not performed. Future distributions
must retain notices for the actual bundled graph.
idb targets modern Chrome/Firefox/Safari/Edge and excludes IE. TypeScript types
do not polyfill IndexedDB, Proxy or storage capabilities. ADR 0001 syntax floors
are not a browser test matrix; S00-006 owns product support. If IndexedDB opening
fails (private mode, policy, permissions or security errors), expose unavailable
storage and fail saves honestly. No fake-indexeddb production fallback.
S01 must define exact keys/indexes, normalized account identifiers, save errors,
cancellation, crypto ownership and valid/invalid fixtures before adapter work.
Future tests must add account deletion/suppression, cancelled generations,
every supported migration and native browser quota/restart coverage. New query
requirements, browser incompatibility, unacceptable migration cost or changed
crypto atomicity requirements trigger a bounded ADR revision, never an implicit
wrapper/stack replacement.
## Independent acceptance
QA reviewer auto-qa-4078580caf39 independently repeated fresh-cache npm ci and
all rollback, injected quota-policy and blocked-upgrade assertions on Linux,
Node 24.21.0/npm 11.19.0. Exact lock pins, integrity metadata, installed license
texts and runtime refusal were checked; make check passed 72 tooling tests.
Accepted only the wrapper decision and documented storage policies/fixture
experiments. Native browser quota, disk/crash durability, process restart,
multi-tab behavior, eviction, persistence grants and crypto unlock remain
unverified. S01 contracts and production adapters still require their own work.
See the independent review report for commands, source inspection and limits.
Coordinator owns the review commit.
+221
View File
@@ -0,0 +1,221 @@
# ADR 0005: Web browser qualification and reference performance passport
- Status: proposed; awaits independent security review of S00-006.
- Task / requirements: S00-006 / NFR-003, NFR-005; specification §27.
- Date: 2026-10-15.
- Decision owner: auto-orchestrator-710c6ba5d8ea (orchestrator).
- Reviewer: assigned security reviewer, not yet recorded.
## Context and decision
Input: accepted ADR 0001, exact requirements, architecture and contract registry,
S00-002 executor/review evidence. Output: one qualification passport with concrete
hardware, browser policy, measurable budgets and permission/fallback procedures.
This decision adds no shared interface, application implementation or dependency.
Budgets are proposed engineering acceptance targets, not observed performance or
a universal SLA. Approval selects reference devices; actual product qualification
requires later browser runs on these devices and the production app.
Select six targets below. The machine-readable table and budget values are in
`experiments/0005-browser-matrix/matrix.json`; this ADR defines their meaning.
| ID | Reference hardware | OS family | Browser / syntax lower bound | CSS viewport / DPR | Modes |
| --- | --- | --- | --- | --- | --- |
| win-chrome | ThinkPad T14 Gen 2 Intel, i5-1135G7, 16 GiB | Windows 11 24H2 x64 | Chrome stable / 111 | 1366×768 / 1 | tab, installed |
| win-edge | Same ThinkPad | Same Windows | Edge stable / 111 | 1366×768 / 1 | tab, installed |
| linux-firefox | Same ThinkPad | Ubuntu 24.04 LTS x64, GNOME Wayland | Firefox stable / 114 | 1366×768 / 1 | tab |
| mac-safari | MacBook Air M1 2020, 8 GiB, 7-core GPU | macOS 15 | Safari stable / 18 | 1280×800 / 2 | tab, installed web app |
| android-chrome | Pixel 6a 128 GB, Tensor, 6 GiB | Android 15 | Chrome stable / 111 | 393×851 / 2.75 | tab, installed |
| ios-safari | iPhone 13 128 GB, A15, 4 GiB | iOS 18 | Safari stable / 18 | 390×844 / 3 | tab, Home Screen |
All measurements use 60 Hz. Desktop SSD, mobile internal storage; leave ≥20%
free. Mobile portrait, standard display scaling/font size, browser UI expanded
at navigation; record actual inner viewport after browser chrome. Desktop set
window inner viewport explicitly. Also test mobile landscape, 320 CSS px width,
wide 1440 CSS px, keyboard/IME, dark mode, 200% text/zoom and reduced motion as
functional variants; they are not substitutes for physical performance devices.
Support policy: qualify the vendor-supported stable build available for each OS
at release cutoff, then freeze its full build in the run record. Floors above
come from ADR 0001, with Safari 18 selected for the named OS families; they do
not authorize using obsolete vulnerable builds online or promise support for all
versions since the floor. If an OS/browser is no longer vendor supported at the
cutoff, revise this passport through review before qualification. Beta/dev,
embedded webviews, other iOS browsers, private profiles and extensions are not
qualified targets. Private/blocked storage must still fail honestly.
Exact patch versions are intentionally resolved at qualification, rather than
inventing future release pins now. Every run MUST supply all `freeze_fields` in
the JSON: full OS/browser build, acquisition source and installer hash (or Apple
OS build), app/asset/fixture hashes, profile reset recipe, tools, time, actual
viewport/DPR/refresh rate and network settings. Archive synthetic fixtures and
build assets with the record. A record saying only “latest” or “stable” is
invalid. Retest the frozen builds for comparison; qualify a new stable version
with a new record after security updates. No automatic acceptance carries over.
Physical devices may be rented/lab-owned; no claim of their availability here.
Emulation/Playwright WebKit alone cannot qualify Safari or mobile hardware.
## PWA and capability policy
Tab mode is primary. Installation is optional progressive enhancement, not a
native Desktop/Android client. No automatic install prompt is assumed. Firefox
uses tab mode in this passport. iOS ordinary Safari tab has no promised Web Push;
Home Screen mode with granted permission, valid subscription and configured
provider is a separate required push scenario. No installation mode promises
continuous sockets/media after hiding, locking, closing or terminating browser.
Resume requires reconciliation; password re-entry/unlock follows its accepted
policy. No offline send/delivery guarantee is introduced.
The JSON `fallbacks` table is normative for all six targets. Probe capabilities
and actual initialization outcomes, not user agent names. Missing required
crypto/secure transport blocks the protected operation. Missing optional media,
location, push, install or biometric capability disables only the dependent
feature and gives a reason. Synthetic fixtures never replace production crypto,
IndexedDB or transports. Storage failure and eviction follow accepted ADR 0004;
this cache budget does not revise persistence schemas or unlock policy.
## Budgets and measurement protocol
| Metric | Desktop | Mobile | Gate |
| --- | --- | --- | --- |
| Cold startup p95 | 2500 ms | 4000 ms | navigation start → usable local shell |
| Warm startup p95 | 1200 ms | 2000 ms | same end point, cached assets/history |
| Action → painted result p95 | 100 ms | 150 ms | open chat, select, type, scroll |
| rAF gap p95 / maximum | 20 / 100 ms | 25 / 150 ms | foreground scripted scroll, 60 Hz |
| Peak app process RAM | 350 MiB | 250 MiB | 10-minute non-call session |
| Retained RAM growth | 30 MiB | 25 MiB | final settled minute minus first |
| Disposable cache ceiling | 128 MiB | 64 MiB | all accounts combined per origin |
Protocol for a later production qualification (not executed in this task):
1. Build production assets with accepted ADR 0001 pins and lockfile. Serve via
valid HTTPS with ordinary certificate validation; no dev server timings. Use
no extensions, other tabs or competing foreground apps; power connected,
power saver off, thermal nominal, ≥50% battery. Record OS/browser builds and
tool versions before runs. Disable diagnostics during timing except required
trace collection. Keep measurements with tracing separately labeled.
2. Deterministic synthetic dataset: one active account, 100 conversations × 100
messages, text `message-00000` through `message-09999`, each padded with ASCII
`x` to 512 characters; 50 visible messages in selected chat. No real JIDs,
credentials or conversations. Seed through future test fixture adapters, not
production authentication bypass. Hash the exact serialized fixture and seed
script; check local row count before each run. Cache workload: 1 MiB synthetic
byte blobs until the ceiling plus two blobs. No audio/video running in the RAM
workload; call/media owners must later define separate active-call budgets.
3. Controlled network: 10 Mbit/s download, 2 Mbit/s upload, 80 ms round-trip,
zero intentional loss, no CPU throttle. Use a recorded external shaper for
physical devices; record tool version/config and measured throughput/RTT to
fixture host. WSS/MAM/server latency is measured separately and not part of
usable-local-shell time. “Ready” means conversation list and 50 local messages
painted, composer focusable and input accepted. Login/unlock/password human
time is excluded and reported separately. Never mark connected/crypto ready
merely to satisfy this local timing gate.
4. Thirty cold runs: retain seeded IndexedDB, clear HTTP/disposable asset cache
and unregister prior service worker, terminate browser, reopen at target URL.
“Cold” here means application asset cold, not OS disk cold. Thirty warm runs:
first prime assets/worker and fixture, close tab and reopen with same profile.
Installed/Home Screen mode repeats both sequences and reports separately.
Record navigation start and app-ready mark using Performance API; confirm
visible ready UI in trace/screenshot. Report raw runs, failures and p95 via
sorted nearest-rank ceil(0.95*n). Do not discard slow runs or mix modes.
5. For each mode collect 100 actions each (open chat, select message, type one
character, scroll one viewport), paced 500 ms apart; 60 seconds continuous
deterministic scroll. Measure input timestamp → frame containing changed UI
with browser performance trace. A double-rAF marker is only a proxy and must
be labeled. Collect rAF timestamps in foreground; compute consecutive gaps,
p95 and maximum. Record hidden/throttled runs separately, not as foreground
passes. Any action failure fails function regardless of percentile.
6. RAM: record baseline blank profile browser process tree, then sample app
renderer/WebContent plus dedicated workers every second during 10 minutes of
repeated chat switching/scrolling. Chromium task manager/process trace,
Firefox about:processes plus OS RSS, Safari WebContent/worker OS RSS (macOS
Activity Monitor; iOS Instruments on physical device). Use isolated browser
profile so shared workers are attributable. Report sum RSS and baseline
separately, not JS heap as total RAM. Peak must fit ceiling; last-minute
median minus first-minute median must fit growth budget, with no forced GC.
If attribution/tool access is unavailable, RAM gate is unverified, never 0.
7. Cache: sum owned disposable asset/media payload bytes with adapter inventory;
compare browser origin storage estimate as advisory, not exact cache size.
Limit is min(reference ceiling, 10% reported quota) when estimate available;
if unavailable, use reference ceiling and stop on actual quota error. Before
admitting another blob evict disposable LRU entries or reject the write;
never exceed budget deliberately. History, crypto/trust and pending unsynced
work are excluded from disposable cache and must not be evicted by this gate.
Confirm counts after reopen; inject QuotaExceededError and test actual device
low-disk failure separately. No checkpoint advance on save failure. Browser
may evict the entire origin: verify explicit recovery state after user clear.
Pass each target/mode independently; a fast desktop cannot offset mobile failure.
These numbers are initial product targets chosen for responsiveness and bounded
resources, not a benchmark derived from the small S00-002 counter. Later measured
infeasibility requires reviewed budget/device change, not silently relaxing gates.
## Contextual permissions and negative scenarios
For each target AND mode, run the following against a trusted HTTPS test origin
with synthetic identities. Fresh profile has no prompts at startup. Trigger only
via explicit relevant action: Start call/record (mic/camera), Send location
(geolocation), Enable notifications (push), Enable unlock (only S00-007 policy).
Storage persistence request must explain context rather than masquerade as login.
| State | Reproduction | Required observation |
| --- | --- | --- |
| prompt / granted | Reset site and OS permissions; invoke each action and grant | Only requested capability activates; no unrelated permission prompt |
| denied | Deny browser prompt, then deny at OS level separately | Reason/actionable settings path; text still usable; no retry prompt loop |
| hardware missing | Disconnect USB camera/mic on desktop; use OS-disabled device on mobile | Capture fails honestly; camera-only failure may offer working audio |
| revoked during session | Start capture then revoke site/OS permission; unplug device; separately revoke location/notification permission | End/reconcile capture, stop watches; clear usable/push state on observed failure or resume; no stale success |
| unsupported | Disable API via test-only fixture boundary or use naturally absent mode | Each JSON fallback observed, protected operations remain blocked |
| ignored prompt / busy | Leave prompt unanswered then cancel action; separately hold camera in another app | Pending is cancellable; late grant is stopped/ignored; busy error not labeled denial |
| lifecycle / loss | Hide, lock, sleep, offline, close, reopen; remove subscription and clear origin | No continuous session promise; resync/unlock and explain lost local state |
A Permissions API query may be unsupported for a descriptor: catch failure and
rely on real API result and track/error events; recheck on focus/resume. Revocation
may arrive through track ended or subsequent failed operation, not a uniform
permission event. Include denied→settings grant→explicit retry and account switch
while prompt pending. Do not log coordinates, device labels, endpoints or secrets.
Screenshots contain synthetic UI only. Browser/OS policy injection is labeled as
fixture evidence; it does not prove physical device revoke or hardware absence.
## Candidates, sources and constraints
Primary sources inspected 2026-10-15; these describe mechanisms, not results on
our selected devices:
- [Vite build compatibility](https://vite.dev/guide/build.html): syntax floors and
lack of automatic API polyfills. The matrix does not change ADR 0001 targets.
- [Chrome release channels](https://developer.chrome.com/docs/web-platform/chrome-release-channels):
stable qualification is chosen over beta/dev; exact run builds still required.
- [WebKit iOS Web Push](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/):
Home Screen eligibility and direct user interaction for permission requests.
- [Media Capture and Streams](https://www.w3.org/TR/mediacapture-streams/):
permission/device errors and track lifecycle underpin media negative scenarios.
- [Permissions](https://www.w3.org/TR/permissions/): permission state and change
handling; implementation support for individual descriptors remains conditional.
- [Storage Standard](https://storage.spec.whatwg.org/): persistence/estimates are
browser controlled; ceilings are application policy, not guaranteed disk quota.
- [Web Authentication Level 3](https://www.w3.org/TR/webauthn-3/): authenticator
operations do not establish a universal browser hardware-backed storage vault.
Rejected: Chromium-only coverage (misses independent engines/iOS behavior),
“all modern browsers” without reference hardware, emulator-only performance,
unconditional PWA push/background claims and frozen obsolete online binaries.
A broad rolling channel without recorded full builds is also insufficient.
Selected: bounded three-engine/six-target qualification with immutable run records.
No added libraries: dependency license review is not needed for this document and
stdlib checker. Existing stack/library notices remain with their respective ADRs.
No secrets, crypto implementation, network proxy, CORS/TLS exception or application
module is introduced. S01 contracts and downstream performance/browser/provider
implementation tasks retain ownership of exact adapter and telemetry interfaces.
## Focused experiment and actual result
`python3 experiments/0005-browser-matrix/check.py` validates the passport's target
coverage, reproduction fields, budget consistency and fallback coverage; negative
mutations prove incomplete targets, missing fallback and relaxed guards fail.
This is document/fixture validation only. No physical reference device, browser
permission behavior, production startup/RAM/render/cache measurement or provider
integration was executed. Their procedures above are external qualification
checks, not passed results. S00-006 selects a passport; it does not certify release.
Acceptance/qualification failure, OS vendor support ending, new engine/API need,
or changed resource workload requires a separately reviewed passport revision.
@@ -0,0 +1,4 @@
engine-strict=true
strict-ssl=true
ignore-scripts=true
registry=https://registry.npmjs.org/
@@ -0,0 +1,81 @@
# S00-020 Linux environment access probe
Normal configured HTTPS access, authorized by the assigned environment-access
probe and this session's enabled network, succeeded on 2026-10-15. The previous
configured-proxy DNS failure did not reproduce. No proxy/environment/system
configuration was changed and no alternate network route was selected. No
separate environment-owner connectivity statement was supplied; the evidence is
actual access through the existing configured path, not an assertion about an
administrator's network changes.
Run from the project root on Linux x86_64 with Python supporting tarfile's
`data` extraction filter (Python 3.12+ recommended), curl and xz support:
```sh
python3 experiments/0002-xmpp-library/environment-access/verify.py
```
The probe retrieves the official Node 24.21.0 Linux x64 archive and vendor
SHASUMS256.txt through verified HTTPS. It checks SHA256 before safe extraction
into a fresh `/tmp/luma-S00-026-*` directory, executes exact Node 24.21.0 and
npm 11.19.0, and installs byte-identical parent manifest/lock copies here using
`npm ci --ignore-scripts --strict-ssl=true --engine-strict=true` with a fresh
isolated cache. All eight locked dependencies retain HTTPS registry URLs and
SHA512 integrity. It checks every installed version, unchanged manifest/lock
bytes and a real Stanza createClient import. Any command/check failure exits
unsuccessfully; `result.json` records the last successful run only.
`SHASUMS256.txt` preserves retrieved vendor checksum material; `result.json`
records the verified archive hash, input hashes and temporary paths. Reviewer
should repeat the probe with fresh retrieval, rather than trust these reports.
The runtime/archive/cache remain temporary, node_modules is Git-ignored, and no
system installation is performed. These paths are available to later local
sessions until temporary storage is removed; rerun the probe if absent.
No TLS/proxy/engine bypass, dependency additions, stack/lock/spec modification,
credentials, real messages, npm lifecycle scripts or Git mutations. The copied
parent graph was already inspected in prior experiment tasks; this probe adds
no dependency or library-selection decision. Node/npm retain their distribution
license files; dependency notices remain in the existing experiment inventories.
Checksum verification trusts the vendor HTTPS origin; no detached signature,
package provenance/advisory audit, browser/WSS authentication, application build
or interoperability claim is made. The S00-026 access prerequisite and S00-024 continuation checkpoint are now
independently accepted. S00-020 is submitted for its own independent QA review;
S00-003 remains separate unfinished work. Historical checkpoints follow.
## S00-024 continuation checkpoint
On 2026-10-15 executor auto-ci-cd-49907d48a6d1 repeated the existing probe
successfully after independent S00-026 acceptance. The interrupted environment
restoration can now be reviewed: official HTTPS checksum/archive retrieval,
Node v24.21.0/npm 11.19.0 execution, fresh-cache installation of eight unchanged
parent locked packages and actual Stanza 12.22.1 import all passed. result.json
contains this session's temporary paths. No probe implementation change was
needed; S00-026 supplied the missing access and verification artifacts.
After independent S00-024 review, a fresh S00-020 executor should rerun the
command above, verify result.json and use its runtime_bin with an isolated cache
within its own card. Host Node/npm are not the pinned runtime. Temporary paths
can expire. This checkpoint does not install dependencies into the parent
experiment or complete S00-020/S00-003; their own acceptance remains required.
## S00-020 environment restoration result
On 2026-10-15 executor auto-ci-cd-2a0186c54ce9 repeated the reviewed probe in
this fresh assigned session. Official checksum/archive HTTPS retrieval succeeded
through the existing configured route. Vendor SHA256 matched before extraction;
Linux Node v24.21.0/npm 11.19.0 executed, fresh-cache npm ci installed all eight
unchanged parent locked dependencies and Stanza 12.22.1 createClient imported.
Separate assertions rechecked downloaded artifacts, copied input hashes, every
installed version and effective strict TLS/engine/script settings. No probe code,
parent manifest/lock, stack, specs or network/system configuration changed.
See state/evidence/S00-020-auto-ci-cd-2a0186c54ce9.md and the current result.json.
QA must rerun verify.py with fresh retrieval and inspect scope before accepting
S00-020. After its reviewed acceptance the coordinator may assign S00-003 to a
fresh executor. That executor can use result.json runtime_bin for pinned tools;
the verified Stanza installation is confined here, not in the parent experiment.
The parent task must perform its own scoped dependency installation and WSS/
authentication checks. Temporary paths may expire; rerun the probe if needed.
No browser, WSS, application readiness or interoperability result is supplied.
@@ -0,0 +1,34 @@
aec7b2464afb99f078c19cb06d201d543bd3b311cba071282bce1b17c97e58bb node-v24.21.0-aix-ppc64.tar.gz
22ca85110f26015696a3fa9216bc372ae65203d170622eaf7d211e2dd5bb49e3 node-v24.21.0-arm64.msi
bed7eea5325e1108f32ce5228ddd6a5f0f08a499ee42aa7442aea583702f6057 node-v24.21.0-darwin-arm64.tar.gz
6239d4cf92d864487ec8cd3615038f7b67e7f58b77b21cd2f09ea9fbd68065fe node-v24.21.0-darwin-arm64.tar.xz
1462cb3b3046b815cf8ea436d3da450ec1a9f11dac7e5a46b0ada5305d7e8097 node-v24.21.0-darwin-x64.tar.gz
0ae5a24c24bb7d015cd816c5036b3f90f2945aa872fcf54e58da054753b3a299 node-v24.21.0-darwin-x64.tar.xz
57c6bee2e30bbbee5bd51d6cc343eb992e174b56a2a1d0eab7a7510771c20ea2 node-v24.21.0-headers.tar.gz
7497abc2fac1d332fae580046b6b39d0de283dfaf267207a32b59753739c1517 node-v24.21.0-headers.tar.xz
724282c3b43aec998aa9527380465b45d229e021b58035f5f4f63095eabfe5d5 node-v24.21.0-linux-arm64.tar.gz
6ad1325edbdb5649c379b75a237147a666c95d4f9ae8d340fef2d1575d289ad2 node-v24.21.0-linux-arm64.tar.xz
51c5d53066ee92920b61783b03b06ecb99f3661160f7b15ca77e8474115a67bc node-v24.21.0-linux-ppc64le.tar.gz
1936fd64623a2f98d1fb31b456686d10c30e92639899cfefdefa8835d22adccd node-v24.21.0-linux-ppc64le.tar.xz
5f2fa37422e0c75de35c1686fe51d78324f2c2a8fe2cc239a41d9c000a29d938 node-v24.21.0-linux-s390x.tar.gz
2ef7e2ecbf7a6c2f3d08d106f6b2f279419c6dac89fe91b8cad193af7890082c node-v24.21.0-linux-s390x.tar.xz
3d63405fc65a0d2d2976c1f0bc2fd27bb0bd07212469e705aac3f03ae5ab4c9c node-v24.21.0-linux-x64-musl.tar.gz
34ab095af8efe9018f21489d3dc19871fb5edd4f130a71469b69c05fd4e32a5e node-v24.21.0-linux-x64-musl.tar.xz
6e1db87ef58b8819e5d5402eff1536491b18edd8eb7bee5ef7897876e88dc5ff node-v24.21.0-linux-x64.tar.gz
fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6 node-v24.21.0-linux-x64.tar.xz
848cc8d138b712b5ae0f38de08fde62feb404226a63dde07d0ea5ccddbbd9dd7 node-v24.21.0-win-arm64.7z
8779b1bde1d39f8d420e3b57aa657b39891af434d3de44a919044cec06785921 node-v24.21.0-win-arm64.zip
4e5f86609712acf841f3a5e2d9d4854b4c149f6ead024fc38381b6d7b272c636 node-v24.21.0-win-x64.7z
158f7685b44de51f6c0df1d153526cbcd3e1bc739a8dfc607721cef75de9e541 node-v24.21.0-win-x64.zip
bb0eaee134f9357f22aea915ee793343e627aefc1e66488164bac6915bce2cac node-v24.21.0-x64.msi
9831a74b04c270a429bd5a240e37712c4fe229b02b032e18ff2e0702c17c20fd node-v24.21.0.pkg
622424efb5dc0c26c93fbb619ff10737ee289c605b837c88778e186925d82777 node-v24.21.0.tar.gz
a6f54defb6fd7c84f41dba13d61e78e9b4e0961712cf61f29715c05f5ced94fc node-v24.21.0.tar.xz
dff59da18b6ffe1bf1ca99e1d2af4906080c481740619f5b5098c0fca28bd9b7 win-arm64/node.exe
2c0c3215d59c09d7c136da4949696dae121a299e9bdfc64cd9130a58610af63d win-arm64/node.lib
32ce76dd79deddcf6e08f612da714fc3a28fa39c0e4fe12283e019a90eb76483 win-arm64/node_pdb.7z
d565f0a7a8eda1662886475471e454dbe2c2a53c9d1a8562fc986bd11c24fd72 win-arm64/node_pdb.zip
ba4e6d110e8c1592a1ecd390f6b05f3da124b13871a5be62b341a07a853c6c32 win-x64/node.exe
a0a84aa03917b578d286010b7521837e9ff1136ffb2e4a406c14316c33fd06f7 win-x64/node.lib
0df783d02aee58bbe9dcfc157ccc5cc37987d24c4f35889587b39500b12030a9 win-x64/node_pdb.7z
122f2af58bfb25816f12e49179217c3ff5bf96c1e083f3ccb0969a5b4794ff85 win-x64/node_pdb.zip
@@ -0,0 +1,99 @@
{
"name": "luma-xmpp-library-spike",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "luma-xmpp-library-spike",
"version": "0.0.0",
"dependencies": {
"stanza": "12.22.1"
},
"engines": {
"node": "24.21.0",
"npm": "11.19.0"
}
},
"node_modules/@types/node": {
"version": "22.20.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
"integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/punycode": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.4.tgz",
"integrity": "sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/sdp": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
"integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
"license": "MIT"
},
"node_modules/stanza": {
"version": "12.22.1",
"resolved": "https://registry.npmjs.org/stanza/-/stanza-12.22.1.tgz",
"integrity": "sha512-WwoSl3u5cCEAbed5ao+yt8ByH6AsBJcPDY0gax3JGQkBX/7wEZ2oxu9nMJ7O1tm8slDyH4FBYIpeluHrT+g/LA==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.0.0",
"@types/punycode": "^2.1.0",
"@types/ws": "^8.18.1",
"punycode": "^2.1.1",
"sdp": "^3.0.2",
"ws": "^8.18.2"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/ws": {
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
"integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
@@ -0,0 +1,10 @@
{
"name": "luma-xmpp-library-spike",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {"node": "24.21.0", "npm": "11.19.0"},
"packageManager": "npm@11.19.0",
"scripts": {"test": "node --test test/*.test.mjs"},
"dependencies": {"stanza": "12.22.1"}
}
@@ -0,0 +1,20 @@
{
"platform": "Linux x86_64",
"origin": "https://nodejs.org/dist/v24.21.0/",
"archive": "node-v24.21.0-linux-x64.tar.xz",
"archive_sha256": "fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6",
"versions": {
"node": "v24.21.0",
"npm": "11.19.0"
},
"runtime_bin": "/tmp/luma-S00-026-j1pmys7w/node-v24.21.0-linux-x64/bin",
"cache": "/tmp/luma-S00-026-j1pmys7w/npm-cache",
"parent_sha256": {
"package.json": "e0ffb0c94e198e26f8e44e9b22ff4e121760d9f1197ed7bd5a90e1b4bb112766",
"package-lock.json": "9c85ad62721eb2deacb298f29fa2bae211a29c964707d52c2dfbf8614b7dbb05"
},
"locked_packages": 8,
"stanza": "12.22.1",
"import_check": "Stanza createClient import passed",
"checksum_method": "Vendor HTTPS SHASUMS256.txt; no detached signature check"
}
@@ -0,0 +1,82 @@
#!/usr/bin/env python3
"""Repeat the bounded Linux x64 environment probe; never install system tools."""
import hashlib
import json
import os
from pathlib import Path
import platform
import shutil
import subprocess
import tarfile
import tempfile
HERE = Path(__file__).resolve().parent
PARENT = HERE.parent
ARCHIVE = 'node-v24.21.0-linux-x64.tar.xz'
ORIGIN = 'https://nodejs.org/dist/v24.21.0/'
def run(argv, **kwargs):
return subprocess.check_output(argv, text=True, **kwargs).strip()
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def main():
if (platform.system(), platform.machine()) != ('Linux', 'x86_64'):
raise SystemExit('This probe requires Linux x86_64; no alternate runtime is selected.')
work = Path(tempfile.mkdtemp(prefix='luma-S00-026-'))
for name in ('SHASUMS256.txt', ARCHIVE):
subprocess.run(['curl', '--fail', '--silent', '--show-error',
'--proto', '=https', '--connect-timeout', '10',
'--max-time', '60', ORIGIN + name, '-o', str(work / name)], check=True)
shutil.copyfile(work / 'SHASUMS256.txt', HERE / 'SHASUMS256.txt')
entries = dict(line.split()[::-1] for line in (work / 'SHASUMS256.txt').read_text().splitlines())
archive_hash = digest(work / ARCHIVE)
if entries.get(ARCHIVE) != archive_hash:
raise SystemExit('Vendor archive checksum mismatch; extraction refused.')
# Python data filter prevents traversal and unsafe special-file extraction.
with tarfile.open(work / ARCHIVE) as archive:
archive.extractall(work, filter='data')
runtime = work / 'node-v24.21.0-linux-x64' / 'bin'
env = dict(os.environ, PATH=str(runtime) + os.pathsep + os.environ.get('PATH', ''))
versions = {tool: run([str(runtime / tool), '--version'], env=env) for tool in ('node', 'npm')}
if versions != {'node': 'v24.21.0', 'npm': '11.19.0'}:
raise SystemExit('Pinned runtime versions do not match.')
for name in ('package.json', 'package-lock.json'):
shutil.copyfile(PARENT / name, HERE / name)
lock = json.loads((HERE / 'package-lock.json').read_text())
for name, package in lock['packages'].items():
if name and (not package['resolved'].startswith('https://registry.npmjs.org/')
or not package['integrity'].startswith('sha512-')):
raise SystemExit('Locked dependency lacks required HTTPS/SHA512 integrity.')
subprocess.run([str(runtime / 'npm'), 'ci', '--ignore-scripts', '--no-audit', '--no-fund',
'--strict-ssl=true', '--engine-strict=true',
'--registry=https://registry.npmjs.org/', '--fetch-retries=0',
'--fetch-timeout=15000', '--cache', str(work / 'npm-cache')],
cwd=HERE, env=env, check=True)
hashes = {}
for name in ('package.json', 'package-lock.json'):
if (PARENT / name).read_bytes() != (HERE / name).read_bytes():
raise SystemExit('Copied parent manifest/lock changed.')
hashes[name] = digest(HERE / name)
for name, package in lock['packages'].items():
if name:
installed = json.loads((HERE / name / 'package.json').read_text())
if installed['version'] != package['version']:
raise SystemExit('Installed version differs from parent lock: ' + name)
stanza = run([str(runtime / 'node'), '--input-type=module', '-e',
"import {createClient} from 'stanza'; if(typeof createClient !== 'function') process.exit(1); console.log('Stanza createClient import passed')"], cwd=HERE, env=env)
report = {'platform': 'Linux x86_64', 'origin': ORIGIN, 'archive': ARCHIVE,
'archive_sha256': archive_hash, 'versions': versions, 'runtime_bin': str(runtime),
'cache': str(work / 'npm-cache'), 'parent_sha256': hashes,
'locked_packages': len(lock['packages']) - 1, 'stanza': '12.22.1',
'import_check': stanza, 'checksum_method': 'Vendor HTTPS SHASUMS256.txt; no detached signature check'}
(HERE / 'result.json').write_text(json.dumps(report, indent=2) + '\n')
print(json.dumps(report, indent=2))
if __name__ == '__main__':
main()
@@ -0,0 +1,4 @@
engine-strict=true
strict-ssl=true
ignore-scripts=true
registry=https://registry.npmjs.org/
@@ -0,0 +1,57 @@
# Locked dependency license inventory
Includes optional host entries. No new package families beyond reviewed parser/SHA512 experiments.
| Package | Version | License |
| --- | --- | --- |
| @oxc-project/types | 0.154.0 | MIT |
| @rolldown/binding-android-arm-eabi | 1.2.14 | MIT |
| @rolldown/binding-android-arm64 | 1.2.14 | MIT |
| @rolldown/binding-darwin-arm64 | 1.2.14 | MIT |
| @rolldown/binding-darwin-x64 | 1.2.14 | MIT |
| @rolldown/binding-freebsd-x64 | 1.2.14 | MIT |
| @rolldown/binding-linux-arm-gnueabihf | 1.2.14 | MIT |
| @rolldown/binding-linux-arm64-gnu | 1.2.14 | MIT |
| @rolldown/binding-linux-arm64-musl | 1.2.14 | MIT |
| @rolldown/binding-linux-ppc64-gnu | 1.2.14 | MIT |
| @rolldown/binding-linux-s390x-gnu | 1.2.14 | MIT |
| @rolldown/binding-linux-x64-gnu | 1.2.14 | MIT |
| @rolldown/binding-linux-x64-musl | 1.2.14 | MIT |
| @rolldown/binding-openharmony-arm64 | 1.2.14 | MIT |
| @rolldown/binding-win32-arm64-msvc | 1.2.14 | MIT |
| @rolldown/binding-win32-x64-msvc | 1.2.14 | MIT |
| @rolldown/pluginutils | 1.0.1 | MIT |
| @types/node | 22.20.5 | MIT |
| @types/punycode | 2.1.4 | MIT |
| @types/ws | 8.18.2 | MIT |
| detect-libc | 2.1.2 | Apache-2.0 |
| events | 3.3.0 | MIT |
| fdir | 6.5.0 | MIT |
| fsevents | 2.3.3 | MIT |
| lightningcss | 1.33.0 | MPL-2.0 |
| lightningcss-android-arm64 | 1.33.0 | MPL-2.0 |
| lightningcss-darwin-arm64 | 1.33.0 | MPL-2.0 |
| lightningcss-darwin-x64 | 1.33.0 | MPL-2.0 |
| lightningcss-freebsd-x64 | 1.33.0 | MPL-2.0 |
| lightningcss-linux-arm-gnueabihf | 1.33.0 | MPL-2.0 |
| lightningcss-linux-arm64-gnu | 1.33.0 | MPL-2.0 |
| lightningcss-linux-arm64-musl | 1.33.0 | MPL-2.0 |
| lightningcss-linux-x64-gnu | 1.33.0 | MPL-2.0 |
| lightningcss-linux-x64-musl | 1.33.0 | MPL-2.0 |
| lightningcss-win32-arm64-msvc | 1.33.0 | MPL-2.0 |
| lightningcss-win32-x64-msvc | 1.33.0 | MPL-2.0 |
| nanoid | 3.3.20 | MIT |
| picocolors | 1.1.1 | ISC |
| picomatch | 4.0.7 | MIT |
| postcss | 8.5.30 | MIT |
| punycode | 2.3.1 | MIT |
| rolldown | 1.2.14 | MIT |
| saxes | 6.0.0 | ISC |
| sdp | 3.2.2 | MIT |
| source-map-js | 1.2.2 | BSD-3-Clause |
| stanza | 12.22.1 | MIT |
| tinyglobby | 0.2.17 | MIT |
| undici-types | 6.21.0 | MIT |
| vite | 8.3.4 | MIT |
| ws | 8.22.0 | MIT |
| xmlchars | 2.2.0 | MIT |
@@ -0,0 +1,51 @@
# S00-003 combined XMPP selection spike
One isolated experiment; no application adapter or production provider. See
`docs/decisions/0002-xmpp-library.md` for selection rationale and limits.
Use the authentic pinned Node 24.21.0 / npm 11.19.0 from S00-020. On this host its
reviewed temporary bin path was `/tmp/luma-S00-026-j1pmys7w/node-v24.21.0-linux-x64/bin`;
paths may expire. Recreate via the reviewed environment-access probe if needed.
Keep its verified runtime directory first in PATH. OpenSSL must be available.
No system install, trust-store change or secret is needed.
From project root, with the pinned runtime in PATH:
```sh
node --version
npm --version
npm ci --prefix experiments/0002-xmpp-library/selection-spike --ignore-scripts --no-audit --no-fund --engine-strict=true --strict-ssl=true --fetch-retries=0 --fetch-timeout=15000 --cache /tmp/luma-selection-independent-clean
npm test --prefix experiments/0002-xmpp-library/selection-spike
python3 experiments/0002-xmpp-library/selection-spike/check-dependencies.py
make check
```
Use a new empty cache for independent clean-install evidence. The checker compares
vendor bytes against the reviewed `environment-access/node_modules/stanza` graph;
that graph must exist (its verify.py recreates it without altering parent inputs).
No application code or accepted prerequisite source is changed by these commands.
`dist` and node_modules are ignored build artifacts. npm test explicitly invokes
Vite even though install lifecycle scripts are disabled. Lock updates need review.
Tests run the Vite browser artifact in Node. Reused S00-019 regression tests cover
known HMAC vectors, public alias, real feature dispatcher, forged/out-of-order
finals, reset, pending sends, synthetic binding rejection and no derived cache.
WSS tests use a fresh ephemeral localhost certificate/key under /tmp, explicitly
trusted only by the test socket; rejectUnauthorized stays true. A native socket
without that trust must reject it. No certificate/key is committed. The peer
checks fixed public RFC or accepted independent Scramp transcripts; it is a
scripted fixture, not an independent full XMPP implementation. Synthetic fixture
credentials are public/invented data only, never read from the environment.
Bounded ingress and SHA-512 composition are byte-identical reviewed copies.
Only the copied ordering guard extends allowed non-PLUS mechanisms to 512/256/1
and emits safe policy diagnostics. No fallback is enabled. Browser TLS data is
unavailable and never synthesized. Registry tests cover basic MAM/MUC/Jingle/
upload/legacy opaque OMEMO plus an invented extension namespace, not encrypted
interoperability or OMEMO2 wire compliance. They explicitly select the language
collection on export because echoing both library body projections duplicates text;
a separate stock-behavior regression preserves that finding.
No real browser/server/client interoperability, load bound, constant-time audit,
full XEP coverage or product readiness result is implied. All inherited limits
remain in the ADR. Changes are subject to independent QA review.
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,146 @@
import {SaxesParser} from 'saxes';
import jxt from 'stanza/jxt/index.js';
// Experiment policy only. The byte ceiling bounds SAX token/namespace storage
// even before callbacks can inspect a completed name or attribute value.
export const LIMITS = Object.freeze({bytes: 4096, depth: 8, elements: 32,
attributes: 16, attributeChars: 1024, nameChars: 128, valueChars: 256});
class IngressError extends Error {
constructor(code) { super(code); this.code = code; }
}
const fail = code => { throw new IngressError(code); };
export function validateMessage(data) {
if (typeof data !== 'string') fail('text-required');
// Avoid encoding an attacker-sized string. UTF-8 bytes >= UTF-16 units for
// valid XML; isolated surrogates are subsequently rejected by saxes.
if (data.length > LIMITS.bytes) fail('bytes');
if (new TextEncoder().encode(data).length > LIMITS.bytes) fail('bytes');
if (!data.startsWith('<')) fail('framing');
let depth = 0, elements = 0, attributes = 0, attributeChars = 0, ended = false;
const parser = new SaxesParser({xmlns: true, fragment: false,
defaultXMLVersion: '1.0', forceXMLVersion: true});
parser.on('error', () => fail('invalid-xml'));
for (const event of ['doctype', 'comment', 'processinginstruction', 'xmldecl']) {
parser.on(event, () => fail('restricted-xml'));
}
parser.on('opentagstart', tag => {
if (++depth > LIMITS.depth) fail('depth');
if (++elements > LIMITS.elements) fail('elements');
if (tag.name.length > LIMITS.nameChars) fail('name');
});
parser.on('attribute', attr => {
if (++attributes > LIMITS.attributes) fail('attributes');
if (attr.name.length > LIMITS.nameChars) fail('name');
if (attr.value.length > LIMITS.valueChars) fail('attribute-value');
attributeChars += attr.name.length + attr.value.length;
if (attributeChars > LIMITS.attributeChars) fail('attribute-chars');
});
parser.on('closetag', () => { depth--; });
parser.on('end', () => { ended = true; });
// close() detects unfinished XML. Throwing from callbacks aborts immediately;
// there is no recovery listener, DTD fetch, custom entity or tree construction.
parser.write(data).close();
if (!ended || depth !== 0 || elements === 0) fail('framing');
}
export function importMessage(data, registry, config) {
validateMessage(data);
// Only bounded, complete documents reach the actual candidate parser/import.
const xml = jxt.parse(data, {allowComments: false});
if (!xml) fail('invalid-xml');
const kind = registry.getImportKey(xml);
const stanza = registry.import(xml, {lang: config.lang,
acceptLanguages: config.acceptLanguages});
if (!kind || !stanza) fail('unknown-root');
return {kind, stanza};
}
// Public Transport interface and Client.transports constructor registry.
// Socket injection is solely for finite fixtures; default is browser WebSocket.
export function installBoundedTransport(client, Socket = globalThis.WebSocket) {
class BoundedTransport {
#client; #registry; #sm; #socket; #config; #closed = true;
hasStream = false;
stream;
constructor(agent, sm, registry) {
this.#client = agent; this.#sm = sm; this.#registry = registry;
}
connect(config) {
if (this.#socket || !this.#closed) throw new Error('Fresh transport required');
if (new URL(config.url).protocol !== 'wss:') throw new Error('WSS required');
this.#config = config;
this.#closed = false;
const socket = this.#socket = new Socket(config.url, 'xmpp');
socket.onopen = () => {
if (this.#closed) return;
if (socket.protocol !== 'xmpp') return this.#reject('subprotocol');
this.#sm.started = false;
this.#client.emit('connected');
this.restart();
};
socket.onmessage = event => {
if (this.#closed) return;
let parsed;
try { parsed = importMessage(event.data, this.#registry, this.#config); }
catch (error) { return this.#reject(error instanceof IngressError ? error.code : 'invalid-xml'); }
// No raw payload event/log or async parser continuation escapes ingress.
if (parsed.kind === 'stream') {
if (parsed.stanza.action === 'open') {
this.hasStream = true;
this.stream = parsed.stanza;
this.#client.emit('stream:start', parsed.stanza);
} else {
this.#client.emit('stream:end');
this.disconnect(false);
}
} else {
this.#client.emit('stream:data', parsed.stanza, parsed.kind);
}
};
socket.onerror = () => this.#reject('socket-error');
socket.onclose = () => this.disconnect(false);
}
#reject(code) {
if (this.#closed) return;
// Close/clear before notifying consumers; a reentrant callback cannot
// revive the attempt. Never expose parser error text containing input.
this.disconnect(false);
this.#client.emit('ingress:rejected', code);
}
disconnect(clean = true) {
if (this.#closed) return;
this.#closed = true;
const socket = this.#socket;
this.#socket = undefined;
if (socket) {
socket.onopen = socket.onmessage = socket.onerror = socket.onclose = null;
if (clean && this.hasStream && socket.readyState === 1) {
socket.send(this.#registry.export('stream', {action: 'close'}).toString());
}
socket.close();
}
this.hasStream = false;
this.stream = undefined;
this.#config = undefined;
if (this.#client.transport === this) this.#client.emit('--transport-disconnected');
}
restart() {
if (this.#closed) return;
this.hasStream = false;
this.stream = undefined;
this.#write(this.#registry.export('stream', {action: 'open',
lang: this.#config.lang, to: this.#config.server, version: '1.0'}).toString());
}
#write(data) {
if (this.#closed || this.#socket?.readyState !== 1) throw new Error('Socket closed');
this.#socket.send(data);
}
async send(name, data) {
const xml = this.#registry.export(name, data);
if (!xml) throw new Error('Unknown outgoing stanza');
this.#write(xml.toString());
}
}
client.transports['bounded-websocket'] = BoundedTransport;
}
@@ -0,0 +1,12 @@
import client from 'stanza/Client.js';
import jxt from 'stanza/jxt/index.js';
import platform from 'stanza/platform';
import sasl from 'stanza/lib/sasl/index.js';
export {default as validatedSasl} from './validated-sasl.mjs';
export {Sha512Mechanism} from './sha512-mechanism.mjs';
export const Client = client.default;
export const parse = jxt.parse;
export const {Buffer, createHash, createHmac, name: backend, randomBytes} = platform;
export const StockScram = sasl.SCRAM;
export {installBoundedTransport, validateMessage, importMessage, LIMITS} from './bounded-ingress.mjs';
export const {attribute, text} = jxt;
@@ -0,0 +1,55 @@
"""Check installed locked graph, notices, browser backend and reviewed source reuse."""
import hashlib
import json
from pathlib import Path
root = Path(__file__).resolve().parent
lock = json.loads((root / 'package-lock.json').read_text())['packages']
manifest = json.loads((root / 'package.json').read_text())
inventory = (root / 'LICENSE_INVENTORY.md').read_text()
notices = (root / 'THIRD_PARTY_NOTICES.md').read_text()
for section in ('dependencies', 'devDependencies'):
for name, version in manifest[section].items():
assert lock['node_modules/' + name]['version'] == version
installed = 0
for path, entry in lock.items():
if not path:
continue
name = path.split('node_modules/')[-1]
assert entry['resolved'].startswith('https://registry.npmjs.org/')
assert entry['integrity'].startswith('sha512-')
assert f"| {name} | {entry['version']} | {entry['license']} |" in inventory
package = root / path
if not package.exists():
assert entry.get('optional'), name
continue
meta = json.loads((package / 'package.json').read_text())
assert meta['version'] == entry['version']
assert meta['license'] == entry['license']
licenses = [f for f in package.iterdir() if f.is_file() and
('license' in f.name.lower() or f.name.lower().startswith(('notice', 'copying')))]
if name == 'saxes':
licenses = [root / 'licenses/saxes-6.0.0-LICENSE']
if not licenses and name.startswith('@rolldown/binding-'):
licenses = [root / 'node_modules/rolldown/LICENSE', root / 'node_modules/rolldown/THIRD-PARTY-LICENSE']
assert licenses, name
for file in licenses:
assert file.read_text().rstrip() in notices, file
installed += 1
# Independently installed, reviewed locked vendor graph, read only.
original = root.parent / 'environment-access/node_modules/stanza'
current = root / 'node_modules/stanza'
files = {f.relative_to(current) for f in current.rglob('*') if f.is_file()}
assert files == {f.relative_to(original) for f in original.rglob('*') if f.is_file()}
for file in files:
assert (current / file).read_bytes() == (original / file).read_bytes(), file
for local, reviewed in [('bounded-ingress.mjs', 'parser-validation/bounded-ingress.mjs'),
('sha512-mechanism.mjs', 'sha512-validation/sha512-mechanism.mjs'),
('licenses/saxes-6.0.0-LICENSE', 'parser-validation/licenses/saxes-6.0.0-LICENSE')]:
assert (root / local).read_bytes() == (root.parent / reviewed).read_bytes(), local
modules = json.loads((root / 'dist/modules.json').read_text())
assert any(x.endswith('/stanza/platform/browser/crypto/Hmac.js') for x in modules)
assert not any('/platform/node/' in x or x.startswith('node:') or 'browser-external' in x for x in modules)
print(f'{len(lock)-1} locked / {installed} installed packages; notices verified; {len(files)} unchanged Stanza files; reviewed parser/alias reused; browser backend')
for file in ['package-lock.json', 'validated-sasl.mjs', 'dist/browser.mjs']:
print(file, hashlib.sha256((root / file).read_bytes()).hexdigest())
@@ -0,0 +1,64 @@
The ISC License
Copyright (c) Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
====
The following license is the one that governed sax, from which saxes
was forked. Isaac Schlueter is not *directly* involved with saxes so
don't go bugging him for saxes issues.
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
====
`String.fromCodePoint` by Mathias Bynens is no longer used, but it can
still be found in old commits. It was once used according to terms of
MIT License, as follows:
Copyright Mathias Bynens <https://mathiasbynens.be/>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,945 @@
{
"name": "luma-xmpp-selection-spike",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "luma-xmpp-selection-spike",
"version": "0.0.0",
"dependencies": {
"events": "3.3.0",
"saxes": "6.0.0",
"stanza": "12.22.1",
"ws": "8.22.0"
},
"devDependencies": {
"vite": "8.3.4"
},
"engines": {
"node": "24.21.0",
"npm": "11.19.0"
}
},
"node_modules/@oxc-project/types": {
"version": "0.154.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.154.0.tgz",
"integrity": "sha512-KYOUGQFlLWR+s4QMV2Q9ko38TZm7EdO87Qm7rvZLqk/webse/cDjtyxKhg/47l0byCbs/k46LT8qc4PKjAikzA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/oxc-project"
}
},
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.14.tgz",
"integrity": "sha512-aWR+XKH9+rW7m5fWq/5360J3mHycrx/o6xZM2DQFdJnTMdyP7/WzxPjhtLbVWdKo2y612/4U1bWqb3HQhiF7vg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.14.tgz",
"integrity": "sha512-QiVxHofl6A8lQU7WAtOUY4QMVZXohxwz7WJJoeh8NBEUyKfIkMRKhYyq8az/1RypNiIXzNTtiS6KwHz0erxYRQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.14.tgz",
"integrity": "sha512-lTQOzHuBIooh3jIr5XINQkjPGEuGy+Dsy0meXDr6Sjty0b99kYgGh4uBC/8mJX1T0kC8d6S0aPP1Y+bHkh6aBw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.14.tgz",
"integrity": "sha512-hyMKEGatjmBYCs/Q4sbIcL/GqpFEqsX3wc+HYVQ/85A4rxc1o7rPm4DUI4GV4k/h0ITddwFSGpqQunI14GXjTA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.14.tgz",
"integrity": "sha512-u371GbXoVXrEgP20PwzoGNOdAOqAMhzo19JzzY+V4YmmnC+DTOoqdbMu+O+54emM59Mik/UdrtWgcuEDKWRPKA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.14.tgz",
"integrity": "sha512-BqwKsdZQf6EqVWr8fv2MrDYzAvOZ7+NYFSJESRGs87fkseEhs0TU9TXgRJTkZoRRkxTizyEg1LKUXfU3/78uqg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.14.tgz",
"integrity": "sha512-SD9kY8M009x/7s57Z1dgUVlP5+uYBgKQK0Tkr1WIWngrtUJmRCW1RJFMY7QDla2khXhmg2i5Io7MUjcmZNZ/KQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.14.tgz",
"integrity": "sha512-BJ2CL19M6uuWesMgnqJzgxoI+N+eV7b0A9O9vLV42y+gitC41fNa8KElau1tHVzuLWX5GzEuxMPhAO0tQXQMkg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.14.tgz",
"integrity": "sha512-4+Kl8daPV8NDHaD7mfCcc1pRM+Qg7ab8DyMm5FG5WoZgv4h2i3KFN1CybYBNbQqmuYm7ftJLvmMI1eHYJ22S/Q==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.14.tgz",
"integrity": "sha512-V/h8ePq1fKDrmtFMz6xcCN3DKvuvMRrwwD1q0PkBeeo4DpK64biaKvqWGVxqCeYKqeRk7gE7/iKPxLCn08O97A==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.14.tgz",
"integrity": "sha512-WxalXzgrLVpKC93xfpaJ3gCXUWzeCr+FRCbDVD2mUAy4L6Vo3lNVz0epUEUpxPWJkLNYUwM4OSIikv36OJ9mLg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.14.tgz",
"integrity": "sha512-3G3tnAmTpdk9jO3GQtyHXqeFD2SlecfTws/Y3M2mmj9nMBwwuLT4kur8sZKeu7ENUHSpVO3cIsm5A52LEWWwlg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.14.tgz",
"integrity": "sha512-qyr5XiNGxm2ytx17vvtcJ9nXn6v1gENETi503bJx3OGwZ1tSonj7BosxbMa/xeAfSl5iS9X6KjEWYXLu6v1L7Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.14.tgz",
"integrity": "sha512-oA4o5o0S2yB199YyYXDzKUv1Z56Rc+mgOjwm7Z/vJ9Z8igiDPYvoOuDyo6g7AK/ZaF09nPhDKVnGBcYHO11GXA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.14.tgz",
"integrity": "sha512-9mE94bf2GZUaR/fUSXCMpBoWkctu421Mi0ORpm4zqfbkjKep7RxzPFa5etcGs5h+2KApoWBOA/hAJa/7saGgEQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.20.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz",
"integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/punycode": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@types/punycode/-/punycode-2.1.4.tgz",
"integrity": "sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.2",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.2.tgz",
"integrity": "sha512-67MQl+fpWKVTT1NYdnmo3U4sc/xPo/zQBncVnI74qmQa0z/b+1g6iYqNmGCPbxO+zz2aklb08a0oHfegiVd0/w==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.33.0",
"lightningcss-darwin-arm64": "1.33.0",
"lightningcss-darwin-x64": "1.33.0",
"lightningcss-freebsd-x64": "1.33.0",
"lightningcss-linux-arm-gnueabihf": "1.33.0",
"lightningcss-linux-arm64-gnu": "1.33.0",
"lightningcss-linux-arm64-musl": "1.33.0",
"lightningcss-linux-x64-gnu": "1.33.0",
"lightningcss-linux-x64-musl": "1.33.0",
"lightningcss-win32-arm64-msvc": "1.33.0",
"lightningcss-win32-x64-msvc": "1.33.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz",
"integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz",
"integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz",
"integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz",
"integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz",
"integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz",
"integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz",
"integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz",
"integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz",
"integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz",
"integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz",
"integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/nanoid": {
"version": "3.3.20",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
"integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.30",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.30.tgz",
"integrity": "sha512-zkd+e9S1AQHhmaBKye9zlFHWjOwVgqXl1q3ECYy8561K7qxR2cREf/STLskulBFd78k6AOFeA+ff1TtEzk9bAA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.20",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/rolldown": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.14.tgz",
"integrity": "sha512-eTBTTqMR6uHKLgcUqpJP8ntzDI6V4LYA8negAMBEI7gkgVvOdrx6VsvS2Ym+83KhOPdLCHWJu0w7UQFJZhHKNw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.154.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm-eabi": "1.2.14",
"@rolldown/binding-android-arm64": "1.2.14",
"@rolldown/binding-darwin-arm64": "1.2.14",
"@rolldown/binding-darwin-x64": "1.2.14",
"@rolldown/binding-freebsd-x64": "1.2.14",
"@rolldown/binding-linux-arm-gnueabihf": "1.2.14",
"@rolldown/binding-linux-arm64-gnu": "1.2.14",
"@rolldown/binding-linux-arm64-musl": "1.2.14",
"@rolldown/binding-linux-ppc64-gnu": "1.2.14",
"@rolldown/binding-linux-s390x-gnu": "1.2.14",
"@rolldown/binding-linux-x64-gnu": "1.2.14",
"@rolldown/binding-linux-x64-musl": "1.2.14",
"@rolldown/binding-openharmony-arm64": "1.2.14",
"@rolldown/binding-win32-arm64-msvc": "1.2.14",
"@rolldown/binding-win32-x64-msvc": "1.2.14"
}
},
"node_modules/saxes": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
"integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
"license": "ISC",
"dependencies": {
"xmlchars": "^2.2.0"
},
"engines": {
"node": ">=v12.22.7"
}
},
"node_modules/sdp": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.2.tgz",
"integrity": "sha512-xZocWwfyp4hkbN4hLWxMjmv2Q8aNa9MhmOZ7L9aCZPT+dZsgRr6wZRrSYE3HTdyk/2pZKPSgqI7ns7Een1xMSA==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
"integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/stanza": {
"version": "12.22.1",
"resolved": "https://registry.npmjs.org/stanza/-/stanza-12.22.1.tgz",
"integrity": "sha512-WwoSl3u5cCEAbed5ao+yt8ByH6AsBJcPDY0gax3JGQkBX/7wEZ2oxu9nMJ7O1tm8slDyH4FBYIpeluHrT+g/LA==",
"license": "MIT",
"dependencies": {
"@types/node": "^22.0.0",
"@types/punycode": "^2.1.0",
"@types/ws": "^8.18.1",
"punycode": "^2.1.1",
"sdp": "^3.0.2",
"ws": "^8.18.2"
}
},
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT"
},
"node_modules/vite": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.3.4.tgz",
"integrity": "sha512-IftLE8phJhdNRIGNEV4JCCvfNScoZQVfcXaN8TQropWGJtOHcu+iAB9pHcuGvmEb/kddw0LBe/jS8ztdC6xdiQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
"picomatch": "^4.0.7",
"postcss": "^8.5.29",
"rolldown": "~1.2.12",
"tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"funding": {
"url": "https://github.com/vitejs/vite?sponsor=1"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.7.1",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
"sugarss": "^5.0.0",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"@vitejs/devtools": {
"optional": true
},
"esbuild": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/ws": {
"version": "8.22.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",
"integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"license": "MIT"
}
}
}
@@ -0,0 +1,24 @@
{
"name": "luma-xmpp-selection-spike",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "24.21.0",
"npm": "11.19.0"
},
"packageManager": "npm@11.19.0",
"scripts": {
"build": "vite build",
"test": "npm run build && node --test test/*.test.mjs"
},
"dependencies": {
"stanza": "12.22.1",
"events": "3.3.0",
"saxes": "6.0.0",
"ws": "8.22.0"
},
"devDependencies": {
"vite": "8.3.4"
}
}
@@ -0,0 +1,28 @@
import sasl from 'stanza/lib/sasl/index.js';
// Public constructor selects the existing browser backend's supported hash alias.
// The delegate name never goes on the wire. No private field is read or changed.
export class Sha512Mechanism {
name = 'SCRAM-SHA-512';
providesMutualAuthentication = true;
#delegate;
constructor(name) {
if (name !== this.name) throw new Error('Unsupported mechanism');
this.#delegate = new sasl.SCRAM('SCRAM-SHA512');
}
getExpectedCredentials() { return this.#delegate.getExpectedCredentials(); }
// Keep derived credentials attempt-local; no cache leaves this boundary.
getCacheableCredentials() { return null; }
createResponse(credentials) {
if (credentials.tlsUnique) throw new Error('Channel binding unavailable');
return this.#delegate.createResponse(credentials);
}
processChallenge(value) { this.#delegate.processChallenge(value); }
processSuccess(value) { this.#delegate.processSuccess(value); }
finalize() { return this.#delegate.finalize(); }
}
export function registerSha512(client) {
client.sasl.disable('SCRAM-SHA-512');
client.sasl.register('SCRAM-SHA-512', Sha512Mechanism, 1000);
}
@@ -0,0 +1,83 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {readFileSync} from 'node:fs';
import {createHash as oracleHash, createHmac as oracleHmac} from 'node:crypto';
import {Buffer as BrowserBuffer, createHash, createHmac, backend, StockScram, Sha512Mechanism, randomBytes} from '../dist/browser.mjs';
const hmacVectors = JSON.parse(readFileSync(new URL('./hmac-vectors.json', import.meta.url)));
const scramVectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
test('bundle resolves browser backend and contains no external runtime imports', () => {
assert.equal(backend, 'browser');
const modules = JSON.parse(readFileSync(new URL('../dist/modules.json', import.meta.url)));
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/Hmac.js')));
assert.ok(modules.some(id => id.endsWith('/platform/browser/crypto/SHA-512.js')));
assert.ok(!modules.some(id => /platform\/node\/|^node:|browser-external/.test(id)));
const bundle = readFileSync(new URL('../dist/browser.mjs', import.meta.url), 'utf8');
assert.ok(!/^import\s|\brequire\(/m.test(bundle));
});
for (const vector of hmacVectors) {
for (const algorithm of ['sha512', 'SHA512']) {
test(`RFC 4231 case ${vector.case}, browser ${algorithm}`, () => {
const key = BrowserBuffer.from(vector.key, 'hex');
const data = BrowserBuffer.from(vector.data, 'hex');
const actual = createHmac(algorithm, key).update(data).digest('hex');
assert.equal(actual.slice(0, vector.digest.length), vector.digest);
});
}
}
test('reproduce browser hyphenated HMAC mismatch, keep Node independent control', () => {
const vector = hmacVectors[0];
const key = BrowserBuffer.from(vector.key, 'hex');
const data = BrowserBuffer.from(vector.data, 'hex');
const broken = createHmac('sha-512', key).update(data).digest('hex');
assert.notEqual(broken, vector.digest);
assert.equal(broken, '9656975ee5de55e75f2976ecce9a04501060b9dc22a6eda2eaef638966280182477fe09f080b2bf564649cad42af8607a2bd8d02979df3a980f15e2326a0a22a');
assert.equal(oracleHmac('sha512', Buffer.from(vector.key, 'hex')).update(Buffer.from(vector.data, 'hex')).digest('hex'), vector.digest);
});
const abc = 'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192' +
'992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f';
for (const algorithm of ['sha512', 'sha-512', 'SHA512', 'SHA-512']) {
test(`fixed SHA512 abc digest, browser ${algorithm}`, () => {
assert.equal(createHash(algorithm).update('abc').digest('hex'), abc);
});
}
for (const length of [0, 1, 63, 64, 65, 127, 128, 129, 131, 256]) {
test(`browser HMAC alias key length ${length} agrees with Node crypto`, () => {
const key = Buffer.alloc(length, 0xab);
const data = Buffer.alloc(257, 0xcd);
assert.equal(createHmac('sha512', BrowserBuffer.from(key)).update(BrowserBuffer.from(data)).digest('hex'),
oracleHmac('sha512', key).update(data).digest('hex'));
assert.equal(createHash('sha512').update(BrowserBuffer.from(data)).digest('hex'),
oracleHash('sha512').update(data).digest('hex'));
});
}
test('stock browser SHA512 proof mismatch; public alias delegate matches independent vector', () => {
const vector = scramVectors[0];
const credentials = {username: vector.username, password: vector.password, clientNonce: vector.nonce};
const stock = new StockScram('SCRAM-SHA-512');
stock.createResponse(credentials);
stock.processChallenge(BrowserBuffer.from(vector.first));
assert.notEqual(stock.createResponse(credentials).toString(), vector.proof);
const selected = new Sha512Mechanism('SCRAM-SHA-512');
assert.equal(selected.name, vector.mechanism);
assert.equal(selected.createResponse(credentials).toString(), vector.initial);
selected.processChallenge(BrowserBuffer.from(vector.first));
assert.equal(selected.createResponse(credentials).toString(), vector.proof);
selected.processSuccess(BrowserBuffer.from(vector.final));
assert.deepEqual(selected.finalize(), {authenticated: true, mutuallyAuthenticated: true});
assert.equal(selected.getCacheableCredentials(), null);
});
test('native browser RNG path works without fixed nonce, no invented channel binding', () => {
assert.equal(randomBytes(32).length, 32);
const selected = new Sha512Mechanism('SCRAM-SHA-512');
const initial = selected.createResponse({username: 'user', password: 'pencil'}).toString();
assert.match(initial, /^n,,n=user,r=[0-9a-f]{64}$/);
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512-PLUS'));
assert.throws(() => new Sha512Mechanism('SCRAM-SHA-512').createResponse({tlsUnique: BrowserBuffer.alloc(32)}));
});
@@ -0,0 +1,42 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {Client, importMessage, attribute, text} from '../dist/browser.mjs';
const client=new Client();
const roundtrip=(kind,xml)=>{
const imported=importMessage(xml,client.stanzas,{});
assert.equal(imported.kind,kind);
// The API exposes both a selected body and the full language collection.
// For this fixture choose the language collection as the sole export input;
// passing both representations concatenates text in Stanza 12.22.1.
const output = {...imported.stanza};
if (output.archive?.item?.message) {
output.archive = {...output.archive, item: {...output.archive.item, message: {...output.archive.item.message}}};
delete output.archive.item.message.body;
}
const emitted=client.stanzas.export(kind,output).toString();
const again=importMessage(emitted,client.stanzas,{}).stanza;
return {stanza:imported.stanza,again,emitted};
};
for(const [name,kind,xml,check] of [
['MAM result/forwarded stable IDs','message','<message xmlns="jabber:client"><result xmlns="urn:xmpp:mam:2" id="archive-1" queryid="query-1"><forwarded xmlns="urn:xmpp:forward:0"><message xmlns="jabber:client" id="message-1"><body>synthetic</body><origin-id xmlns="urn:xmpp:sid:0" id="origin-1"/></message></forwarded></result></message>',s=>{assert.equal(s.archive.id,'archive-1');assert.equal(s.archive.item.message.originId,'origin-1');assert.equal(s.archive.item.message.body,'synthetic');}],
['MUC join','presence','<presence xmlns="jabber:client" to="room@conference.example.invalid/user"><x xmlns="http://jabber.org/protocol/muc"/></presence>',s=>assert.equal(s.muc.type,'join')],
['Jingle signaling','iq','<iq xmlns="jabber:client" type="set" id="call-1"><jingle xmlns="urn:xmpp:jingle:1" action="session-terminate" sid="session-1"><reason><success/></reason></jingle></iq>',s=>{assert.equal(s.jingle.action,'session-terminate');assert.equal(s.jingle.sid,'session-1');}],
['HTTP upload slot','iq','<iq xmlns="jabber:client" type="result" id="upload-1"><slot xmlns="urn:xmpp:http:upload:0"><put url="https://upload.example.invalid/put"/><get url="https://upload.example.invalid/get"/></slot></iq>',s=>{assert.equal(s.httpUpload.download,'https://upload.example.invalid/get');}],
['legacy opaque OMEMO encoding','message','<message xmlns="jabber:client"><encrypted xmlns="eu.siacs.conversations.axolotl"><header sid="1"><key rid="2">AA==</key><iv>AA==</iv></header><payload>AA==</payload></encrypted></message>',s=>{assert.equal(s.omemo.header.sid,1);assert.equal(s.omemo.header.keys[0].rid,2);assert.equal(s.omemo.payload.toString('hex'),'00');}]
]) test(`${name}: real registry import/export`,()=>{const r=roundtrip(kind,xml);check(r.stanza);check(r.again);});
test('public plugin registry supports future opaque extension without vendor patch',()=>{
// Deliberately NOT a claimed OMEMO2 wire implementation or encryption result.
client.use(c=>c.stanzas.define({namespace:'urn:luma:selection-spike:opaque',element:'opaque',
path:'message.spikeOpaque',fields:{id:attribute('id'),value:text()}}));
const r=roundtrip('message','<message xmlns="jabber:client"><opaque xmlns="urn:luma:selection-spike:opaque" id="fixture-1">synthetic-data</opaque></message>');
assert.deepEqual(r.stanza.spikeOpaque,{id:'fixture-1',value:'synthetic-data'});
assert.deepEqual(r.again.spikeOpaque,r.stanza.spikeOpaque);
const wrong=importMessage('<message xmlns="jabber:client"><opaque xmlns="urn:luma:wrong" id="fixture-1">ignored</opaque></message>',client.stanzas,{});
assert.equal(wrong.stanza.spikeOpaque,undefined);
});
test('record raw dual-body export behavior requiring explicit projection',()=>{
const r=importMessage('<message xmlns="jabber:client"><body>synthetic</body></message>',client.stanzas,{});
const raw=client.stanzas.export('message',r.stanza).toString();
assert.equal(importMessage(raw,client.stanzas,{}).stanza.body,'syntheticsynthetic');
});
@@ -0,0 +1,19 @@
// Public RFC examples only. Not accounts, secrets, or network endpoints.
export const vectors = [
{
source: 'https://www.rfc-editor.org/rfc/rfc5802.html#section-5',
mechanism: 'SCRAM-SHA-1', username: 'user', password: 'pencil',
nonce: 'fyko+d2lbbFgONRv9qkxdawL',
first: 'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096',
proof: 'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=',
final: 'v=rmF9pqV8S7suAoZWja4dJRkFsKQ='
},
{
source: 'https://www.rfc-editor.org/rfc/rfc7677.html#section-3',
mechanism: 'SCRAM-SHA-256', username: 'user', password: 'pencil',
nonce: 'rOprNGfwEbeRWgbNEkqO',
first: 'r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096',
proof: 'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=',
final: 'v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4='
}
];
@@ -0,0 +1,44 @@
[
{
"case": 1,
"key": "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
"data": "4869205468657265",
"digest": "87aa7cdea5ef619d4ff0b4241a1d6cb02379f4e2ce4ec2787ad0b30545e17cdedaa833b7d6b8a702038b274eaea3f4e4be9d914eeb61f1702e696c203a126854"
},
{
"case": 2,
"key": "4a656665",
"data": "7768617420646f2079612077616e7420666f72206e6f7468696e673f",
"digest": "164b7a7bfcf819e2e395fbe73b56e0a387bd64222e831fd610270cd7ea2505549758bf75c05a994a6d034f65f8f0e6fdcaeab1a34d4a6b4b636e070a38bce737"
},
{
"case": 3,
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
"digest": "fa73b0089d56a284efb0f0756c890be9b1b5dbdd8ee81a3655f83e33b2279d39bf3e848279a722c806b485a47e67c807b946a337bee8942674278859e13292fb"
},
{
"case": 4,
"key": "0102030405060708090a0b0c0d0e0f10111213141516171819",
"data": "cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
"digest": "b0ba465637458c6990e5a8c5f61d4af7e576d97ff94b872de76f8050361ee3dba91ca5c11aa25eb4d679275cc5788063a5f19741120c4f2de2adebeb10a298dd"
},
{
"case": 5,
"key": "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c",
"data": "546573742057697468205472756e636174696f6e",
"digest": "415fad6271580a531d4179bc891d87a6"
},
{
"case": 6,
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": "54657374205573696e67204c6172676572205468616e20426c6f636b2d53697a65204b6579202d2048617368204b6579204669727374",
"digest": "80b24263c7c1a3ebb71493c1dd7be8b49b46d1f41b4aeec1121b013783f8f3526b56d037e05f2598bd0fd2215d6a1e5295e64f73f63f0aec8b915a985d786598"
},
{
"case": 7,
"key": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"data": "5468697320697320612074657374207573696e672061206c6172676572207468616e20626c6f636b2d73697a65206b657920616e642061206c6172676572207468616e20626c6f636b2d73697a6520646174612e20546865206b6579206e6565647320746f20626520686173686564206265666f7265206265696e6720757365642062792074686520484d414320616c676f726974686d2e",
"digest": "e37b6a775dc87dbaa4dfa9f96e5e3ffddebd71f8867289865df5a32d20cdc944b6022cac3c4982b10d5eeb55c3e4de15134676fb6de0446065c97440fa8c6a58"
}
]
@@ -0,0 +1,302 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {readFileSync} from 'node:fs';
import {Client, parse, validatedSasl, StockScram} from '../dist/browser.mjs';
const vectors = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
const tick = () => new Promise(resolve => setImmediate(resolve));
const deferred = () => {
let resolve;
const promise = new Promise(r => {resolve = r;});
return {promise, resolve};
};
function harness(vector = vectors[1], fixed = true, mechanisms = [vector.mechanism]) {
const client = new Client({jid: 'user@example.invalid', autoReconnect: false,
credentials: {username: vector.username, password: vector.password, clientNonce: vector.nonce}});
const result = {sent: [], success: 0, failed: 0, restart: 0, disconnect: 0, verification: []};
// Capture fixture effects; use the real Client, XML registry, feature dispatcher,
// mechanism factory and SCRAM crypto. Never connect to a real provider.
client.send = async (kind, stanza) => {result.sent.push({kind, stanza});};
client.transport = {authenticated: false, restart: () => {result.restart++;}};
client.disconnect = async () => {result.disconnect++;};
client.on('auth:success', () => {result.success++;});
client.on('auth:failed', () => {result.failed++;});
if (!fixed) client.sasl.register('SCRAM-SHA-512', StockScram, 1000);
if (fixed) {
client.use(validatedSasl);
const original = client.sasl.createMechanism.bind(client.sasl);
client.sasl.createMechanism = names => {
const mech = original(names);
if (mech) for (const method of ['processSuccess', 'finalize']) {
const fn = mech[method].bind(mech);
mech[method] = (...args) => {result.verification.push(method); return fn(...args);};
}
return mech;
};
}
const receive = async (type, value) => {
const body = value === undefined ? '' : Buffer.from(value).toString('base64');
const xml = parse(`<${type} xmlns="urn:ietf:params:xml:ns:xmpp-sasl">${body}</${type}>`);
const stanza = client.stanzas.import(xml);
assert.equal(stanza.type, type);
client.emit('sasl', stanza);
await tick();
};
const start = async () => {
client.emit('features', {sasl: {mechanisms}});
await tick();
};
return {client, result, receive, start};
}
function rejected(h) {
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
assert.equal(h.result.failed, 1);
assert.equal(h.result.disconnect, 1);
assert.equal(h.client.features.negotiated.sasl, false);
assert.equal(h.client.transport.authenticated, false);
assert.equal(h.client.listenerCount('sasl'), 0);
}
for (const vector of vectors) {
test(`${vector.mechanism}: independent Scramp proof and verified final through feature dispatcher`, async () => {
const h = harness(vector);
await h.start();
assert.equal(h.result.sent[0].stanza.mechanism, vector.mechanism);
assert.equal(h.result.sent[0].stanza.value.toString(), `n,,n=user,r=${vector.nonce}`);
await h.receive('challenge', vector.first);
assert.equal(h.result.sent[1].stanza.value.toString(), vector.proof);
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
assert.equal(h.client.transport.authenticated, false);
await h.receive('success', vector.final);
assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
assert.equal(h.result.success, 1);
assert.equal(h.result.restart, 1);
assert.equal(h.result.disconnect, 0);
assert.equal(h.client.features.negotiated.sasl, true);
assert.equal(h.client.transport.authenticated, true);
await h.receive('success', vector.final);
assert.equal(h.result.success, 1);
assert.equal(h.result.restart, 1);
});
for (const [label, final] of [
['missing', undefined],
['forged', 'v=' + (vector.final[2] === 'A' ? 'B' : 'A') + vector.final.slice(3)],
['error', 'e=invalid-proof'],
['conflicting error', `${vector.final},e=invalid-proof`],
['duplicate verifier', `${vector.final},${vector.final}`],
['bad base64', 'v=***'],
['oversized', 'v=' + 'A'.repeat(4096)]
]) {
test(`${vector.mechanism}: reject ${label} server-final after proof`, async () => {
const h = harness(vector);
await h.start();
await h.receive('challenge', vector.first);
await h.receive('success', final);
rejected(h);
if (label === 'forged') assert.deepEqual(h.result.verification, ['processSuccess', 'finalize']);
await h.receive('success', vector.final);
rejected(h);
});
}
test(`${vector.mechanism}: reject valid-looking final before proof`, async () => {
const h = harness(vector);
await h.start();
await h.receive('success', vector.final);
rejected(h);
assert.deepEqual(h.result.verification, []);
});
}
for (const [label, final] of [['missing', undefined], ['forged', 'v=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=']]) {
test(`stock 12.22.1 reproduces ${label} premature acceptance`, async () => {
const h = harness(vectors[1], false);
await h.start();
await h.receive('success', final);
assert.equal(h.result.success, 1);
assert.equal(h.result.restart, 1);
assert.equal(h.client.transport.authenticated, true);
});
test(`replacement rejects original ${label} premature defect`, async () => {
const h = harness();
await h.start();
await h.receive('success', final);
rejected(h);
});
}
for (const [label, first] of [
['wrong nonce', vectors[1].first.replace('r=rOpr', 'r=wrong')],
['unextended nonce', `r=${vectors[1].nonce},s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096`],
['iteration overflow', vectors[1].first.replace('i=4096', 'i=100001')],
['invalid iterations', vectors[1].first.replace('i=4096', 'i=NaN')],
['duplicate attribute', vectors[1].first + ',i=4096'],
['mandatory extension', 'm=required,' + vectors[1].first]
]) {
test(`reject ${label} before library proof work`, async () => {
const h = harness();
await h.start();
await h.receive('challenge', first);
rejected(h);
assert.equal(h.result.sent.length, 1);
});
}
test('reject success while proof send is pending; no late revival', async () => {
const h = harness();
await h.start();
const pending = deferred();
h.client.send = () => pending.promise;
await h.receive('challenge', vectors[1].first);
await h.receive('success', vectors[1].final);
rejected(h);
pending.resolve();
await tick();
await h.receive('success', vectors[1].final);
rejected(h);
});
test('duplicate challenge while sending proof fails closed', async () => {
const h = harness();
await h.start();
const pending = deferred();
h.client.send = () => pending.promise;
await h.receive('challenge', vectors[1].first);
await h.receive('challenge', vectors[1].first);
rejected(h);
pending.resolve();
await tick();
rejected(h);
});
test('reset while awaiting credentials prevents late auth and listener leak', async () => {
const h = harness();
const pending = deferred();
h.client.getCredentials = () => pending.promise;
await h.start();
h.client.emit('--reset-stream-features');
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
await tick();
await h.receive('success', vectors[1].final);
assert.equal(h.result.sent.length, 0);
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
assert.equal(h.client.listenerCount('sasl'), 0);
});
test('reset after proof, new negotiation succeeds without old handlers', async () => {
const h = harness();
await h.start();
await h.receive('challenge', vectors[1].first);
h.client.emit('--reset-stream-features');
await h.receive('success', vectors[1].final);
assert.equal(h.result.success, 0);
assert.equal(h.client.listenerCount('sasl'), 0);
await h.start();
assert.equal(h.client.listenerCount('sasl'), 1);
await h.receive('challenge', vectors[1].first);
await h.receive('success', vectors[1].final);
assert.equal(h.result.success, 1);
assert.equal(h.result.restart, 1);
});
test('fixture policy filters PLUS and plaintext; strongest available non-PLUS selected', async () => {
const h = harness(vectors[1], true, ['PLAIN', 'SCRAM-SHA-1', 'SCRAM-SHA-512-PLUS', 'SCRAM-SHA-256', 'SCRAM-SHA-512']);
await h.start();
assert.equal(h.result.sent[0].stanza.mechanism, 'SCRAM-SHA-512');
});
for (const names of [['PLAIN'], ['SCRAM-SHA-512-PLUS'], []]) {
test(`no allowed mechanism (${names.join(',')}) disconnects`, async () => {
const h = harness(vectors[1], true, names);
await h.start();
rejected(h);
assert.equal(h.result.sent.length, 0);
});
}
for (const type of ['failure', 'abort']) {
test(`${type} is terminal; valid final cannot revive it`, async () => {
const h = harness();
await h.start();
await h.receive(type);
await h.receive('success', vectors[1].final);
rejected(h);
});
}
test('success during pending initial credentials fails closed', async () => {
const h = harness();
const pending = deferred();
h.client.getCredentials = () => pending.promise;
await h.start();
await h.receive('success', vectors[1].final);
rejected(h);
pending.resolve({username: 'user', password: 'pencil', clientNonce: vectors[1].nonce});
await tick();
assert.equal(h.result.sent.length, 0);
rejected(h);
});
test('reset during proof send cancels continuation', async () => {
const h = harness();
await h.start();
const pending = deferred();
h.client.send = () => pending.promise;
await h.receive('challenge', vectors[1].first);
h.client.emit('--reset-stream-features');
pending.resolve();
await tick();
await h.receive('success', vectors[1].final);
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
assert.equal(h.client.listenerCount('sasl'), 0);
});
for (const stage of ['credentials', 'initial send', 'proof send']) {
test(`${stage} rejection disconnects without success`, async () => {
const h = harness();
const reject = async () => {throw new Error('synthetic fixture failure');};
if (stage === 'credentials') h.client.getCredentials = reject;
if (stage === 'initial send') h.client.send = reject;
await h.start();
if (stage === 'proof send') {
h.client.send = reject;
await h.receive('challenge', vectors[1].first);
}
rejected(h);
});
}
test('repeat challenge after completed proof is rejected', async () => {
const h = harness();
await h.start();
await h.receive('challenge', vectors[1].first);
await h.receive('challenge', vectors[1].first);
rejected(h);
});
test('synthetic binding input fails without auth or plaintext fallback', async () => {
const h = harness();
h.client.config.credentials.tlsUnique = Buffer.alloc(32);
await h.start();
rejected(h);
assert.equal(h.result.sent.length, 0);
});
test('server silence after proof never authenticates; reset discards attempt', async () => {
const h = harness();
let cacheEvents = 0;
h.client.on('credentials:update', () => {cacheEvents++;});
await h.start();
await h.receive('challenge', vectors[0].first);
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
assert.equal(h.client.transport.authenticated, false);
assert.equal(cacheEvents, 0);
assert.equal(h.client.config.credentials.saltedPassword, undefined);
h.client.emit('--reset-stream-features');
await h.receive('success', vectors[0].final);
assert.equal(h.result.success, 0);
assert.equal(h.result.restart, 0);
});
@@ -0,0 +1,32 @@
[
{
"mechanism": "SCRAM-SHA-512",
"username": "user",
"password": "pencil",
"nonce": "rOprNGfwEbeRWgbNEkqO",
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=gMGXRcevScNtxZ6/8lQYpGtnsNAc3mGcmNomv+xnoOMw+3R2xNJdMNnzMlTN8PPC6wdp6dybEmDYXYTxwnYPJQ==",
"final": "v=ZQnYEgWQMFmmsM8aQMF0nDDCy/AgCzkwk8CmMZYcMg0vSVlKDanekLtifDSeVGT4+5ZxXnJq199RVG2rR7N7Zw=="
},
{
"mechanism": "SCRAM-SHA-512",
"username": "user",
"password": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"nonce": "rOprNGfwEbeRWgbNEkqO",
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=FQ+Ds6nMR5kU+jnkwyhJGBK8lA2hTFKTqEBVSUCvSgmexvd4GGT4uAqD3iACaCkLYP0Bc6JHwnHMWkpGNOX2rg==",
"final": "v=dXX1mz64UYbnX15NloHSoWfqoMVVvFg23i8KXwHigIVa+U6w+eJBBwhNg47qeIqkRLhhVnRnvcVvQMXpqav+4A=="
},
{
"mechanism": "SCRAM-SHA-512",
"username": "user",
"password": "p\u00e9ncil",
"nonce": "rOprNGfwEbeRWgbNEkqO",
"initial": "n,,n=user,r=rOprNGfwEbeRWgbNEkqO",
"first": "r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096",
"proof": "c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=Jwk58tl/01ikMIUvnr0MXiTPWZJOiqtTyIAKx2A7IL85TXsL9S/fvBEIYvd/a0QIAQrNp47BSvResR5QB63TSg==",
"final": "v=QwFhP0JOInTkJ/uTsZ2DRt00V0dQtkZKotpq815cU8X2SpF/5qbOd8sd6TCj8NjX/GPke8Xj4yg6whLHu7KNlw=="
}
]
@@ -0,0 +1,122 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {createServer} from 'node:https';
import {mkdtempSync, readFileSync, rmSync} from 'node:fs';
import {tmpdir} from 'node:os';
import {join} from 'node:path';
import {execFileSync} from 'node:child_process';
import {once} from 'node:events';
import WS, {WebSocketServer} from 'ws';
import {Client, installBoundedTransport, validatedSasl, importMessage, validateMessage, backend} from '../dist/browser.mjs';
import {vectors as rfc} from './fixtures.mjs';
const sha512 = JSON.parse(readFileSync(new URL('./scramp-vectors.json', import.meta.url)));
const vectors = [...rfc, sha512[0]];
const enc = s => Buffer.from(s).toString('base64');
const sasl = (type, data='') => `<${type} xmlns="urn:ietf:params:xml:ns:xmpp-sasl">${enc(data)}</${type}>`;
const waitFor = async fn => {
for (let i=0;i<300;i++) { if (fn()) return; await new Promise(r=>setTimeout(r,10)); }
throw new Error('Fixture timeout');
};
const dir = mkdtempSync(join(tmpdir(), 'luma-wss-'));
// Ephemeral synthetic localhost CA/server certificate; never committed.
execFileSync('openssl', ['req','-x509','-newkey','rsa:2048','-nodes','-keyout',join(dir,'key.pem'),
'-out',join(dir,'cert.pem'),'-days','1','-subj','/CN=localhost',
'-addext','subjectAltName=DNS:localhost,IP:127.0.0.1'], {stdio:'ignore'});
const cert = readFileSync(join(dir,'cert.pem'));
const key = readFileSync(join(dir,'key.pem'));
test.after(()=>rmSync(dir,{recursive:true,force:true}));
async function scenario(t, vector, offers, mode='valid') {
const server=createServer({key,cert});
const wss=new WebSocketServer({server,handleProtocols: protocols=>protocols.has('xmpp')?'xmpp':false});
const seen={opens:0,success:0,failed:0,rejected:[],policies:[],peerErrors:[]};
const client=new Client({jid:'user@example.invalid',server:'example.invalid',autoReconnect:false,
credentials:{username:vector.username,password:vector.password,clientNonce:vector.nonce},
transports:{'bounded-websocket':'wss://127.0.0.1'},transportPreferenceOrder:['bounded-websocket']});
// Explicit CA trust for this fixture only; hostname/TLS validation stays enabled.
class TrustedSocket extends WS {constructor(url,protocol){super(url,protocol,{ca:cert,rejectUnauthorized:true});}}
installBoundedTransport(client,TrustedSocket);
client.use(validatedSasl);
client.on('auth:success',()=>seen.success++);
client.on('auth:failed',()=>seen.failed++);
client.on('ingress:rejected',code=>seen.rejected.push(code));
client.on('sasl:policy',policy=>seen.policies.push(policy));
wss.on('connection',(socket,req)=>{
seen.protocol=req.headers['sec-websocket-protocol'];
seen.tls=socket._socket.encrypted;
socket.on('message', (bytes,isBinary)=>{
try {
assert.equal(isBinary,false);
const xml=bytes.toString();
if (xml.startsWith('<open')) {
seen.opens++;
socket.send('<open xmlns="urn:ietf:params:xml:ns:xmpp-framing" from="example.invalid" version="1.0" id="fixture"/>');
if (seen.opens===1) {
if (mode==='oversize') return socket.send('<message xmlns="jabber:client"><body>'+ 'x'.repeat(4096)+'</body></message>');
if (mode==='split') return socket.send('<message xmlns="jabber:client">');
socket.send('<stream:features xmlns:stream="http://etherx.jabber.org/streams"><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">'+offers.map(x=>`<mechanism>${x}</mechanism>`).join('')+'</mechanisms></stream:features>');
}
} else if (xml.startsWith('<auth')) {
seen.mechanism=/mechanism="([^"]+)"/.exec(xml)?.[1];
const initial=Buffer.from(/>([^<]+)</.exec(xml)[1],'base64').toString();
assert.equal(initial,`n,,n=user,r=${vector.nonce}`);
socket.send(mode==='premature'?sasl('success',vector.final):sasl('challenge',vector.first));
} else if (xml.startsWith('<response')) {
seen.proof=Buffer.from(/>([^<]+)</.exec(xml)[1],'base64').toString();
assert.equal(seen.proof,vector.proof);
socket.send(sasl('success',mode==='forged'?'v='+ 'A'.repeat(vector.final.length-2):vector.final));
}
} catch(error) {seen.peerErrors.push(error.message); socket.close();}
});
});
server.listen(0,'127.0.0.1'); await once(server,'listening');
client.updateConfig({transports:{'bounded-websocket':`wss://127.0.0.1:${server.address().port}`}});
t.after(async()=>{await client.disconnect(); for(const socket of wss.clients)socket.terminate(); await new Promise(r=>wss.close(r)); await new Promise(r=>server.close(r));});
await client.connect();
await waitFor(()=> mode==='valid'? seen.opens===2 : seen.failed||seen.rejected.length||seen.peerErrors.length);
assert.deepEqual(seen.peerErrors,[]);
assert.equal(seen.protocol,'xmpp'); assert.equal(seen.tls,true);
if(mode==='valid') {
assert.equal(seen.success,1); assert.equal(seen.failed,0); assert.equal(seen.mechanism,vector.mechanism);
assert.equal(seen.proof,vector.proof); assert.equal(seen.opens,2);
assert.deepEqual(seen.policies,[{selected:vector.mechanism,channelBinding:'unavailable',fallback:'disabled'}]);
} else {assert.equal(seen.success,0);assert.equal(seen.opens,1);}
return seen;
}
test('browser bundle uses browser backend with TLS validation enabled',()=>{assert.equal(backend,'browser');assert.notEqual(process.env.NODE_TLS_REJECT_UNAUTHORIZED,'0');});
test('public transport rejects plaintext endpoint before socket creation',async()=>{
const client=new Client({transports:{'bounded-websocket':'ws://127.0.0.1'},transportPreferenceOrder:['bounded-websocket']});
let sockets=0; installBoundedTransport(client,class{constructor(){sockets++;}});
await assert.rejects(client.connect(),/WSS required/);assert.equal(sockets,0);
});
for(const vector of vectors) {
const offers=['PLAIN','SCRAM-SHA-512-PLUS','SCRAM-SHA-256-PLUS','SCRAM-SHA-1',
...(vector.mechanism!=='SCRAM-SHA-1'?['SCRAM-SHA-256']:[]),
...(vector.mechanism==='SCRAM-SHA-512'?['SCRAM-SHA-512']:[])];
test(`trusted WSS strongest compatible ${vector.mechanism}, verified restart`,t=>scenario(t,vector,offers));
for(const mode of ['forged','premature'])test(`WSS ${vector.mechanism} rejects ${mode}`,t=>scenario(t,vector,offers,mode));
}
for(const mode of ['oversize','split']) test(`WSS bounded ingress rejects ${mode}`,async t=>{
const seen=await scenario(t,vectors[2],['SCRAM-SHA-512'],mode);assert.equal(seen.rejected.length,1);
});
for(const offers of [['SCRAM-SHA-512-PLUS'],['PLAIN'],[]]) test(`WSS refuses unsupported offer ${offers}`,async t=>{
const seen=await scenario(t,vectors[2],offers,'unsupported'); assert.equal(seen.failed,1); assert.equal(seen.mechanism,undefined);
});
test('default native WebSocket rejects untrusted WSS certificate',async t=>{
const server=createServer({key,cert}); const wss=new WebSocketServer({server});
server.listen(0,'127.0.0.1');await once(server,'listening');
t.after(async()=>{wss.close();await new Promise(r=>server.close(r));});
const socket=new globalThis.WebSocket(`wss://127.0.0.1:${server.address().port}`,'xmpp');
let opened=false;socket.onopen=()=>{opened=true;socket.close();};
await new Promise((resolve,reject)=>{const timer=setTimeout(()=>{socket.close();reject(new Error('TLS timeout'));},3000);socket.onerror=()=>{clearTimeout(timer);resolve();};});
assert.equal(opened,false);
});
test('XML resource and standalone boundaries compose before registry import',()=>{
const client=new Client();let imports=0; const registry={getImportKey:()=>{imports++;return 'message';},import:()=>{imports++;return {};}};
for(const data of ['<message>', '<message/><message/>','<!DOCTYPE x><x/>','<x>'+ '<x>'.repeat(8)+'</x>'.repeat(8)+'</x>',new Uint8Array(1)]) {
assert.throws(()=>importMessage(data,registry,{}));assert.equal(imports,0);
}
assert.doesNotThrow(()=>validateMessage('<message xmlns="jabber:client"><body>safe &amp; synthetic</body></message>'));
assert.equal(importMessage('<message xmlns="jabber:client"><body>synthetic</body></message>',client.stanzas,{}).stanza.body,'synthetic');
});
@@ -0,0 +1,113 @@
// Selection experiment: reviewed guard with explicit strongest-first non-PLUS policy.
import {registerSha512} from './sha512-mechanism.mjs';
// No production transport, credential persistence, or channel binding is provided.
const SUPPORTED = new Set(['SCRAM-SHA-512', 'SCRAM-SHA-256', 'SCRAM-SHA-1']);
const BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
function text(value) {
// SASL's parsed additional-data is a Buffer, not raw XML/base64 text.
if (!value || typeof value.toString !== 'function' || value.length > 4096) {
throw new Error('Invalid SASL data');
}
const result = value.toString('utf8');
if (!/^[\x21-\x7e]*$/.test(result)) throw new Error('Invalid SASL data');
return result;
}
function firstMessage(value, nonce) {
// Deliberately narrow, no extensions. Bound work before calling library crypto.
const match = /^r=([\x21-\x2b\x2d-\x7e]+),s=([A-Za-z0-9+/=]+),i=([1-9][0-9]*)$/.exec(text(value));
if (!match || !BASE64.test(match[2]) || !match[1].startsWith(nonce) ||
match[1].length <= nonce.length || Number(match[3]) > 100000) {
throw new Error('Invalid SCRAM server-first');
}
}
function finalMessage(value, name) {
const final = text(value);
const length = {'SCRAM-SHA-512': 88, 'SCRAM-SHA-256': 44, 'SCRAM-SHA-1': 28}[name];
if (!length) throw new Error('Unsupported mechanism');
if (!final.startsWith('v=') || final.length !== length + 2 || !BASE64.test(final.slice(2))) {
throw new Error('Invalid SCRAM server-final');
}
}
export default function validatedSasl(client) {
registerSha512(client);
let active;
// Public plugin + named feature registration replaces the stock handler before use.
client.registerFeature('sasl', 100, async (features, done) => {
if (active) active();
const offered = features.sasl.mechanisms;
const names = ['SCRAM-SHA-512', 'SCRAM-SHA-256', 'SCRAM-SHA-1'].filter(name => offered.includes(name));
client.emit('sasl:policy', {selected: names[0] ?? null, channelBinding: 'unavailable', fallback: 'disabled'});
const mech = client.sasl.createMechanism(names);
let phase = 'starting';
let credentials;
let nonce;
const cleanup = () => {
client.off('sasl', handler);
client.off('--reset-stream-features', reset);
if (active === reset) active = undefined;
};
const reset = () => {
phase = 'closed';
cleanup();
client.features.negotiated.sasl = false;
};
const fail = () => {
if (phase === 'closed') return;
phase = 'closed';
cleanup();
client.features.negotiated.sasl = false;
if (client.transport) client.transport.authenticated = false;
client.emit('auth:failed');
done('disconnect', 'SCRAM validation failed'); // Never log peer data/credentials.
};
const handler = async sasl => {
if (phase === 'closed') return;
try {
if (sasl.type === 'challenge' && phase === 'server-first') {
phase = 'sending-proof'; // Reject duplicate challenges and racing success.
firstMessage(sasl.value, nonce);
mech.processChallenge(sasl.value);
const response = mech.createResponse(credentials);
await client.send('sasl', {type: 'response', value: response});
if (phase !== 'closed') phase = 'server-final';
return;
}
if (sasl.type !== 'success' || phase !== 'server-final') return fail();
finalMessage(sasl.value, mech.name);
mech.processSuccess(sasl.value);
const result = mech.finalize();
if (!result.authenticated || !result.mutuallyAuthenticated) return fail();
phase = 'closed';
cleanup();
client.features.negotiated.sasl = true;
if (client.transport) client.transport.authenticated = true;
client.emit('auth:success', client.config.credentials);
done('restart');
} catch {
fail();
}
};
active = reset;
client.features.negotiated.sasl = false;
client.on('sasl', handler);
client.once('--reset-stream-features', reset);
try {
if (!mech || !SUPPORTED.has(mech.name)) return fail();
credentials = await client.getCredentials();
if (phase === 'closed') return;
// Restrict this experiment to non-PLUS; never invent TLS binding bytes.
if (credentials.tlsUnique) return fail();
const response = mech.createResponse(credentials);
nonce = /,r=([^,]+)$/.exec(response.toString('utf8'))?.[1];
if (!nonce) return fail();
await client.send('sasl', {type: 'auth', mechanism: mech.name, value: response});
if (phase !== 'closed') phase = 'server-first';
} catch {
fail();
}
});
}
@@ -0,0 +1,23 @@
import {defineConfig} from 'vite';
import {fileURLToPath} from 'node:url';
export default defineConfig({
resolve: {alias: {events: fileURLToPath(new URL('./node_modules/events/events.js', import.meta.url))}},
build: {
target: ['chrome111', 'edge111', 'firefox114', 'safari16.4'],
minify: false,
lib: {entry: 'browser-entry.mjs', formats: ['es'], fileName: () => 'browser.mjs'},
rollupOptions: {plugins: [{
name: 'backend-evidence',
generateBundle() {
const modules = [...this.getModuleIds()].map(id => id.replace(process.cwd(), '.')).sort();
if (!modules.some(id => id.endsWith('/stanza/platform/browser/crypto/Hmac.js'))) {
throw new Error('Browser HMAC missing');
}
if (modules.some(id => /stanza\/platform\/node\/|^node:|browser-external/.test(id))) {
throw new Error('Unexpected Node runtime backend');
}
this.emitFile({type: 'asset', fileName: 'modules.json', source: JSON.stringify(modules, null, 2) + '\n'});
}
}]}
}
});
+49
View File
@@ -0,0 +1,49 @@
{
"name": "0003-crypto-library",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "0003-crypto-library",
"dependencies": {
"@signalapp/libsignal-client": "0.105.0"
},
"engines": {
"node": ">=24.20.0"
}
},
"node_modules/@signalapp/libsignal-client": {
"version": "0.105.0",
"resolved": "https://registry.npmjs.org/@signalapp/libsignal-client/-/libsignal-client-0.105.0.tgz",
"integrity": "sha512-6H7aQ6/grE6A/wG3nAm+c7aMWhj472/wtbTnNzb9z2KEPZ933PwYGi93zy6Pm63Mxd7SiOSfoa4gSTr++F44UQ==",
"license": "AGPL-3.0-only",
"dependencies": {
"node-gyp-build": "^4.8.0",
"type-fest": "^4.26.0"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/type-fest": {
"version": "4.41.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}
@@ -0,0 +1,11 @@
{
"name": "0003-crypto-library",
"private": true,
"type": "module",
"engines": {
"node": ">=24.20.0"
},
"dependencies": {
"@signalapp/libsignal-client": "0.105.0"
}
}
@@ -0,0 +1,236 @@
// Focused known-answer smoke for an audited OMEMO session/ratchet engine.
// Uses Signal libsignal-client (@signalapp/libsignal-client), NOT a hand-written
// ratchet. Store semantics mirror Signal's InMemorySignalProtocolStore test
// stores. Verifies real end-to-end session encryption and session-record
// serialization (persistence vector) without authoring any cryptographic
// primitive. Runs on the Node distribution (prebuilt native addon).
import { createRequire } from 'node:module';
import assert from 'node:assert';
const require = createRequire(import.meta.url);
const lib = require('@signalapp/libsignal-client');
const {
Aci,
ProtocolAddress,
PrivateKey,
PreKeyRecord,
SignedPreKeyRecord,
KyberPreKeyRecord,
KEMKeyPair,
PreKeyBundle,
SessionRecord,
IdentityChange,
CiphertextMessageType,
processPreKeyBundle,
signalEncrypt,
signalDecrypt,
signalDecryptPreKey,
CiphertextMessage,
IdentityKeyPair,
ServerCertificate,
SenderCertificate,
ContentHint,
SignalMessage: SignalLibSignalMessage,
PreKeySignalMessage: SignalLibPreKey,
sealedSenderEncryptMessage,
sealedSenderDecryptMessage,
initLogger,
LogLevel,
} = lib;
const now = new Date();
const aliceAci = Aci.fromUuid('9d0652a3-dcc3-4d11-975f-74d61598733f');
const bobAci = Aci.fromUuid('796abedb-ca4e-4f18-8803-1fde5b921f9f');
const aliceAddr = ProtocolAddress.new(aliceAci, 1);
const bobAddr = ProtocolAddress.new(bobAci, 3);
function inMemorySessionStore() {
const state = new Map();
return {
async saveSession(name, record) {
state.set(`${name.name()}::${name.deviceId()}`, record.serialize());
},
async getSession(name) {
const s = state.get(`${name.name()}::${name.deviceId()}`);
return s ? SessionRecord.deserialize(s) : null;
},
async getExistingSessions(addresses) {
return addresses.map((a) => {
const s = state.get(`${a.name()}::${a.deviceId()}`);
if (!s) throw new Error('no session for ' + a.toString());
return SessionRecord.deserialize(s);
});
},
async _storedSessions() {
return Array.from(state.values());
},
};
}
function inMemoryIdentityKeyStore() {
const idKeys = new Map();
const identityKey = PrivateKey.generate();
return {
async getIdentityKey() { return identityKey; },
async getIdentityKeyPair() {
return new IdentityKeyPair(identityKey.getPublicKey(), identityKey);
},
async getLocalRegistrationId() { return 5; },
async isTrustedIdentity(name, key) {
const current = idKeys.get(`${name.name()}::${name.deviceId()}`);
return current ? current.equals(key) : true;
},
async saveIdentity(name, key) {
const current = idKeys.get(`${name.name()}::${name.deviceId()}`);
idKeys.set(`${name.name()}::${name.deviceId()}`, key);
const changed = !(current && current.equals(key));
return changed ? IdentityChange.ReplacedExisting : IdentityChange.NewOrUnchanged;
},
async getIdentity(name) {
return idKeys.get(`${name.name()}::${name.deviceId()}`) ?? null;
},
};
}
function inMemoryPreKeyStore() {
const state = new Map();
return {
async savePreKey(id, record) { state.set(id, record.serialize()); },
async getPreKey(id) {
const r = state.get(id);
if (!r) throw new Error('pre-key ' + id + ' not found');
return PreKeyRecord.deserialize(r);
},
async removePreKey(id) { state.delete(id); },
};
}
function inMemorySignedPreKeyStore() {
const state = new Map();
return {
async saveSignedPreKey(id, record) { state.set(id, record.serialize()); },
async getSignedPreKey(id) {
const r = state.get(id);
if (!r) throw new Error('signed pre-key ' + id + ' not found');
return SignedPreKeyRecord.deserialize(r);
},
};
}
function inMemoryKyberPreKeyStore() {
const state = new Map();
const used = new Set();
return {
async saveKyberPreKey(id, record) { state.set(id, record.serialize()); },
async getKyberPreKey(id) {
const r = state.get(id);
if (!r) throw new Error('kyber pre-key ' + id + ' not found');
return KyberPreKeyRecord.deserialize(r);
},
async markKyberPreKeyUsed(id) { used.add(id); },
async hasKyberPreKeyBeenUsed(id) { return used.has(id); },
};
}
async function rawDecryptPreKey(preKeyMessage, addr, localAddr, sessionStore, identityStore, preK, sPreK, kyber) {
return signalDecryptPreKey(preKeyMessage, addr, localAddr, sessionStore, identityStore, preK, sPreK, kyber);
}
async function main() {
initLogger(LogLevel.Trace, (level, target, file, line, message) => {
if (level >= LogLevel.Warn || /decrypt|pre.?key|session|cipher/i.test(message)) {
console.log(`[log:${level}]`, message);
}
});
const aliceStore = inMemorySessionStore();
const bobStore = inMemorySessionStore();
const aliceKeys = inMemoryIdentityKeyStore();
const bobKeys = inMemoryIdentityKeyStore();
const bPreK = inMemoryPreKeyStore();
const bSPreK = inMemorySignedPreKeyStore();
const bKyberStore = inMemoryKyberPreKeyStore();
const bPreKey = PrivateKey.generate();
const bSPreKey = PrivateKey.generate();
const bIdentityKey = await bobKeys.getIdentityKey();
const bPreKeyId = 31337;
const bSignedPreKeyId = 22;
const bSignedPreKeySig = bIdentityKey.sign(bSPreKey.getPublicKey().serialize());
const bKyberPrekeyId = 777;
const bKyberKeyPair = await KEMKeyPair.generate();
const bKyberPrekeySignature = bIdentityKey.sign(bKyberKeyPair.getPublicKey().serialize());
const bPreKeyBundle = PreKeyBundle.new(
5, bAddrDevice(), bPreKeyId, bPreKey.getPublicKey(),
bSignedPreKeyId, bSPreKey.getPublicKey(), bSignedPreKeySig,
bIdentityKey.getPublicKey(),
bKyberPrekeyId, bKyberKeyPair.getPublicKey(), bKyberPrekeySignature,
);
await bPreK.savePreKey(bPreKeyId, PreKeyRecord.new(bPreKeyId, bPreKey.getPublicKey(), bPreKey));
await bSPreK.saveSignedPreKey(bSignedPreKeyId, SignedPreKeyRecord.new(bSignedPreKeyId, 42, bSPreKey.getPublicKey(), bSPreKey, bSignedPreKeySig));
await bKyberStore.saveKyberPreKey(bKyberPrekeyId, KyberPreKeyRecord.new(bKyberPrekeyId, 42, bKyberKeyPair, bKyberPrekeySignature));
// Alice (local) processes Bob's bundle to prepare a session to Bob.
await processPreKeyBundle(bPreKeyBundle, bobAddr, aliceAddr, aliceStore, aliceKeys, now);
// --- Primary canonical known-answer: sealed-sender establishes the session. ---
const aIdentityKey = await aliceKeys.getIdentityKey();
const trustRoot = PrivateKey.generate();
const serverKey = PrivateKey.generate();
const serverCert = ServerCertificate.new(1, serverKey.getPublicKey(), trustRoot);
const senderCert = SenderCertificate.new(
aliceAci.toString(), null, aliceAddr.deviceId(),
aIdentityKey.getPublicKey(), now.getTime() + 100000, serverCert, serverKey,
);
const aPlaintext = Buffer.from('known vector: OMEMO session smoke', 'utf8');
const aSealed = await sealedSenderEncryptMessage(aPlaintext, bobAddr, senderCert, aliceStore, aliceKeys);
const bResult = await sealedSenderDecryptMessage(
aSealed, trustRoot.getPublicKey(), now.getTime(),
null, bobAci.toString(), bobAddr.deviceId(),
bobStore, bobKeys, bPreK, bSPreK, bKyberStore,
);
assert.deepStrictEqual(
new Uint8Array(bResult.message()),
new Uint8Array(aPlaintext),
'sealed-sender session known plaintext round-trips (session established)',
);
// A must now hold a session to Bob.
const aliceSession = await aliceStore.getSession(bobAddr);
assert.ok(aliceSession instanceof SessionRecord, 'alice holds a session record to bob after encryption');
const aliceSessionBytes = aliceSession.serialize();
assert.ok(aliceSessionBytes.length > 0, 'alice session record serializes');
// Note (documented, not a failure): in this libsignal release a separate raw
// `signalEncrypt` call toward Bob re-initiates a PreKey flow (native log:
// "Building PreKeyWhisperMessage ... with preKeyId") rather than continuing a
// Whisper chain. That is a session-management integration detail for the
// OMEMO adapter layer, not a home-written ratchet. We rely on the verified
// sealed-sender session path for the known-answer result above.
// Persistence vector: Bob established and stores a session record; it
// serializes into non-empty bytes and reloads byte-for-byte.
const stored = await bobStore._storedSessions();
assert.ok(stored.length === 1, 'bob keeps exactly one established session record');
const storedBytes = new Uint8Array(stored[0]);
assert.ok(storedBytes.length > 0, 'session record serializes to non-empty bytes');
const reloaded = SessionRecord.deserialize(storedBytes);
assert.ok(reloaded instanceof SessionRecord, 'session record deserializes');
assert.strictEqual(
Buffer.from(reloaded.serialize()).equals(Buffer.from(storedBytes)),
true,
'session round-trips byte-for-byte',
);
console.log('KAS_OK known plaintext:', Buffer.from(bResult.message()).toString('utf8'));
console.log('KAS_OK established via sealed-sender PreKey session');
console.log('KAS_OK bob stored session bytes:', storedBytes.length);
console.log('SMOKE_PASS');
}
function bAddrDevice() { return 3; }
main().catch((e) => { console.error('SMOKE_FAIL', (e && e.stack) || e); process.exit(1); });
@@ -0,0 +1,41 @@
// Focused check 2: unsupported format recorded as blocker, not success.
// Verifies the honest boundary of what @signalapp/libsignal-client provides for
// a Web (browser/worker) deployment and for the OMEMO/OMEMO2 wire format.
// This script deliberately does NOT claim success for anything it cannot show.
import { existsSync, readdirSync, statSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const here = dirname(fileURLToPath(import.meta.url));
const pkgDir = join(here, '..', 'node_modules', '@signalapp', 'libsignal-client');
function walkWasm(dir, acc = []) {
for (const e of readdirSync(dir)) {
const p = join(dir, e);
if (statSync(p).isDirectory()) walkWasm(p, acc);
else if (e.endsWith('.wasm')) acc.push(p);
}
return acc;
}
const wasmFiles = existsSync(pkgDir) ? walkWasm(pkgDir) : null;
const prebuildDir = join(pkgDir, 'prebuilds');
const prebuilds = existsSync(prebuildDir) ? readdirSync(prebuildDir) : [];
console.log('pkgPresent:', existsSync(pkgDir));
console.log('wasmFiles:', wasmFiles ? wasmFiles.length : 'n/a');
console.log('prebuilds:', prebuilds.join(', '));
if (!wasmFiles || wasmFiles.length === 0) {
// This is an unsupported backend for a browser worker, not a success.
const msg =
'UNSUPPORTED_BLOCKER: no .wasm asset ships in @signalapp/libsignal-client; ' +
'the distribution is a Node native addon (prebuilds/*/*.node). A browser ' +
'worker/WASM deployment is NOT established by this npm package and must be ' +
'treated as a blocker until a verified browser/WASM distribution is found.';
console.log(msg);
process.exit(2);
} else {
console.log('wasm present (unexpected here)');
process.exit(0);
}
+3
View File
@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
strict-ssl=true
engine-strict=true
+1
View File
@@ -0,0 +1 @@
24.21.0
+41
View File
@@ -0,0 +1,41 @@
# Storage design experiment (S00-005)
Synthetic fixtures only. No app adapter, shared contract, secrets or real messages.
ADR: `docs/decisions/0004-storage-design.md` (accepted bounded design; see independent QA evidence).
`RECOVERY.md` preserves the earlier interruption inventory; its missing-output
notes are historical, superseded by this task's code and evidence.
From this directory, using Node 24.21.0 and bundled npm 11.19.0:
```sh
npm ci --ignore-scripts --no-audit --no-fund --cache /tmp/luma-storage-review-cache --fetch-retries=0 --fetch-timeout=15000
npm run check
```
`check` refuses other Node/npm versions. Lockfile pins idb 8.0.4 (ISC) and
fake-indexeddb 6.2.5 (Apache-2.0), with HTTPS URLs/SHA512 integrity and no transitives.
`.npmrc` retains verified TLS. Runtime dependencies are isolated to this experiment.
Full license texts are in `licenses/`; production distributions must preserve
idb's notice. fake-indexeddb is a test fixture and must not enter production paths.
- `src/rollback.mjs`: actual unique-index ConstraintError, failed upgrade rollback,
complete schema/data assertions, corrected retry and same-process connection reopen.
- `src/quota-blocked.mjs`: explicitly injected quota error after successful requests,
multi-store abort/checkpoint invariance, account B preservation, retry/reopen;
held old connection, versionchange/blocked and release; stale-version refusal.
- `src/_probe.mjs`: preserved exploratory native-API fixture, outside check suite.
Each fixture uses its own synthetic database name and deletes it before/after.
The blocked scenario has a 3-second failure deadline. CI should impose an overall
process timeout as well. Exceptions/assertion failures cause nonzero exit;
PASS text is printed only after the corresponding assertions.
fake-indexeddb is in memory. Quota fault injection checks the policy response and
real fixture transaction abort, not a native request/commit QuotaExceededError.
Reopen is not process restart. No real browser was available (empty inventory).
Browser multi-tab, physical quota/disk failure, crash/restart, eviction and
StorageManager permission/persistence grants remain external integration checks.
Future browser testing should use a disposable isolated origin/profile, repeat
the schema and blocked scenarios on native IDB, then validate request/commit
quota failures and restart persistence without filling a user's normal origin.
No TLS, CORS or encryption workaround is required or authorized.
@@ -0,0 +1,47 @@
# Interrupted S00-005 checkpoint
Recovery task: S00-023; executor: auto-storage-e6946c6f94e3; 2026-10-15.
This directory contains unfinished research, not an accepted storage design.
S00-023 inventories preserved work and supplies a bounded restart point;
S00-005 must still implement and independently review its original acceptance.
## Preserved inputs
- package.json / package-lock.json agree on fake-indexeddb 6.2.5.
Lock metadata lists Apache-2.0, HTTPS registry URL and SHA512 integrity.
This inventory does not replace S00-005 license/API/browser research.
- .nvmrc and manifest select Node 24.21.0; manifest selects npm 11.19.0,
consistent with accepted ADR 0001. Keep these pins for execution.
- .npmrc retains registry HTTPS and strict-ssl. No TLS bypass is needed.
- src/_probe.mjs is an exploratory in-memory open/delete probe.
- src/rollback.mjs is an unfinished migration experiment. Both files pass
node --check, but neither has been executed or accepted by this recovery.
- docs/decisions/0004-storage-design.md has not been created.
- check:quota points to missing src/quota-blocked.mjs; npm run check is
not a completed test suite and must not be reported as passing.
## Known unfinished rollback code
Static inspection shows failedUpgradeRollsBack and retryAfterRollbackSucceeds
attempt createObjectStore("accounts") although seedV1 already created it.
The intended unique-index data failure therefore has not been demonstrated.
The promise in failedUpgradeRollsBack also overwrites req.onsuccess/onerror;
its aborted flag cannot reliably distinguish success from failure. Assertions
must verify the actual failure, preserved schema/data/indexes, and successful
corrected retry. Do not rely on the existing PASS text as evidence.
## Next assigned S00-005 session
Read the original card, STORE-001/STORE-004, ADR 0001 and this checkpoint.
Complete candidate research and ADR, repair the incomplete rollback experiment,
and implement the required quota/blocked-upgrade scenario within the original
scope. Execute with the pinned runtime and locked dependencies, retaining normal
HTTPS verification. Record actual outcomes and explicit fixture/browser limits.
In-memory fake-indexeddb is not browser durability, quota or restart evidence.
No shared persistence contract or application adapter has been established here.
The historical S00-005 interruption is recorded in
state/runs/auto-storage-88c3286c6641/run.json (recovered_interruption=true).
S00-025 independently resolved the subsequent launcher interruption. This fresh
S00-023 session received its inputs and produced a reviewable recovery checkpoint.
After independent QA and coordinator commit, a new executor may resume S00-005.
@@ -0,0 +1,209 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control with
that entity. For the purposes of this definition, "control" means (i) the
power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (ii) ownership of
fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source,
and configuration files.
"Object" form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled
object code, generated documentation, and conversions to
other media types.
"Work" shall mean the work of authorship, whether in Source or Object
form, made available under the License, as indicated by a copyright notice
that is included in or attached to the work (an example is provided in the
Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent,
as a whole, an original work of authorship. For the purposes of this
License, Derivative Works shall not include works that remain separable
from, or merely link (or bind by name) to the interfaces of, the Work and
Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor for
inclusion in the Work by the copyright owner or by an individual or
Legal Entity authorized to submit on behalf of the copyright owner.
For the purposes of this definition, "submitted" means any form of
electronic, verbal, or written communication sent to the Licensor or its
representatives, including but not limited to communication on electronic
mailing lists, source code control systems, and issue tracking systems
that are managed by, or on behalf of, the Licensor for the purpose of
discussing and improving the Work, but excluding communication that is
conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on
behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable copyright license to reproduce, prepare
Derivative Works of, publicly display, publicly perform, sublicense,
and distribute the Work and such Derivative Works in
Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
royalty-free, irrevocable (except as stated in this section) patent
license to make, have made, use, offer to sell, sell, import, and
otherwise transfer the Work, where such license applies only to those
patent claims licensable by such Contributor that are necessarily
infringed by their Contribution(s) alone or by combination of their
Contribution(s) with the Work to which such Contribution(s) was submitted.
If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or
contributory patent infringement, then any patent licenses granted to
You under this License for that Work shall terminate as of the date such
litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works
thereof in any medium, with or without modifications, and in Source or
Object form, provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works a
copy of this License; and
2. You must cause any modified files to carry prominent notices stating
that You changed the files; and
3. You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain
to any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution,
then any Derivative Works that You distribute must include a readable copy
of the attribution notices contained within such NOTICE file, excluding
those notices that do not pertain to any part of the Derivative Works,
in at least one of the following places: within a NOTICE text file
distributed as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or, within a
display generated by the Derivative Works, if and wherever such
third-party notices normally appear. The contents of the NOTICE file are
for informational purposes only and do not modify the License.
You may add Your own attribution notices within Derivative Works that You
distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and may
provide additional or different license terms and conditions for use,
reproduction, or distribution of Your modifications, or for any such
Derivative Works as a whole, provided Your use, reproduction, and
distribution of the Work otherwise complies with the conditions
stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally
submitted for inclusion in the Work by You to the Licensor shall be under
the terms and conditions of this License, without any additional
terms or conditions. Notwithstanding the above, nothing herein shall
supersede or modify the terms of any separate license agreement you may
have executed with Licensor regarding such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor
provides the Work (and each Contributor provides its Contributions)
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
either express or implied, including, without limitation, any warranties
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS
FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any risks
associated with Your exercise of permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort
(including negligence), contract, or otherwise, unless required by
applicable law (such as deliberate and grossly negligent acts) or agreed
to in writing, shall any Contributor be liable to You for damages,
including any direct, indirect, special, incidental, or consequential
damages of any character arising as a result of this License or out of
the use or inability to use the Work (including but not limited to damages
for loss of goodwill, work stoppage, computer failure or malfunction,
or any and all other commercial damages or losses), even if such
Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose
to offer, and charge a fee for, acceptance of support, warranty,
indemnity, or other liability obligations and/or rights consistent with
this License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf of any
other Contributor, and only if You agree to indemnify, defend, and hold
each Contributor harmless for any liability incurred by, or claims
asserted against, such Contributor by reason of your accepting any such
warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "[]" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included
on the same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2017 Jeremy Scheff
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the License for the specific language governing
permissions and limitations under the License.
@@ -0,0 +1,6 @@
ISC License (ISC)
Copyright (c) 2016, Jake Archibald <jaffathecake@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+35
View File
@@ -0,0 +1,35 @@
{
"name": "0004-storage-design",
"version": "0.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "0004-storage-design",
"version": "0.0.0",
"dependencies": {
"fake-indexeddb": "6.2.5",
"idb": "8.0.4"
},
"engines": {
"node": "24.21.0",
"npm": "11.19.0"
}
},
"node_modules/fake-indexeddb": {
"version": "6.2.5",
"resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz",
"integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==",
"license": "Apache-2.0",
"engines": {
"node": ">=18"
}
},
"node_modules/idb": {
"version": "8.0.4",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.4.tgz",
"integrity": "sha512-jd0ITCrN+Zah4J7R/Z2jnR3W7A4BXDAjmo62MwWuSjxCiueUAf7X5rbodEdidWzeknwXHmLpdZkoiiCppNi30Q==",
"license": "ISC"
}
}
}
@@ -0,0 +1,20 @@
{
"name": "0004-storage-design",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "24.21.0",
"npm": "11.19.0"
},
"packageManager": "npm@11.19.0",
"scripts": {
"check:rollback": "node src/rollback.mjs",
"check:quota": "node src/quota-blocked.mjs",
"check": "node src/check-runtime.mjs && npm run check:rollback && npm run check:quota"
},
"dependencies": {
"fake-indexeddb": "6.2.5",
"idb": "8.0.4"
}
}
@@ -0,0 +1,18 @@
import "fake-indexeddb/auto";
function open(name, version, upgrade) {
return new Promise((resolve, reject) => {
const req = indexedDB.open(name, version);
const ev = { upgraded:false, blocked:false };
req.onupgradeneeded = () => { ev.upgraded = true; if (upgrade) upgrade(req.result); };
req.onsuccess = () => resolve({db:req.result, ev});
req.onerror = () => reject(new Error(`open ${name} v${version}: ${req.error?.name}`));
req.onblocked = () => { ev.blocked=true; console.log("BLOCKED", name); };
});
}
// fresh DB clears to v2 cleanly
const o = await open("fresh", 2, (db)=>{ const a=db.createObjectStore("accounts",{keyPath:"accountId"}); a.add({accountId:"A1",displayName:"Alice"}); });
console.log("fresh v2 ok", o.db.version, [...o.db.objectStoreNames]);
o.db.close();
// not deleting cleanly - check delete
await new Promise((res,rej)=>{ const r=indexedDB.deleteDatabase("fresh"); r.onsuccess=res; r.onerror=()=>rej(r.error); });
console.log("after delete", await indexedDB.databases());
@@ -0,0 +1,3 @@
import assert from 'node:assert/strict';
assert.equal(process.version, 'v24.21.0');
assert.match(process.env.npm_config_user_agent ?? '', /^npm\/11\.19\.0 /);
@@ -0,0 +1,69 @@
import 'fake-indexeddb/auto';
import assert from 'node:assert/strict';
import { openDB, deleteDB } from 'idb';
const name = 'luma-design-quota-blocked';
await deleteDB(name);
let db = await openDB(name, 1, { upgrade(db) {
for (const store of ['history', 'checkpoints', 'settings', 'crypto']) db.createObjectStore(store);
} });
// Illustrative composite keys, not an application contract.
await db.put('history', { text: 'Synthetic other account' }, ['B', 'm1']);
await db.put('checkpoints', 'before', 'A');
await db.put('settings', 'B-setting', ['B', 'theme']);
await db.put('crypto', 'B-opaque-fixture-not-key-material', ['B', 'state']);
// Explicit quota fault injection: fake-indexeddb has no real disk quota.
async function saveBatch(injectFailure) {
const tx = db.transaction(['history', 'checkpoints'], 'readwrite');
const completion = tx.done;
completion.catch(() => {});
try {
await tx.objectStore('history').put({ text: 'Synthetic pending' }, ['A', 'm1']);
await tx.objectStore('checkpoints').put('after', 'A');
if (injectFailure) throw new DOMException('Synthetic quota injection', 'QuotaExceededError');
await completion;
return { saved: true };
} catch (error) {
try { tx.abort(); } catch { /* Engine may have already aborted. */ }
await completion.catch(() => {});
return { saved: false, reason: error.name };
}
}
assert.deepEqual(await saveBatch(true), { saved: false, reason: 'QuotaExceededError' });
assert.equal(await db.get('history', ['A', 'm1']), undefined);
assert.equal(await db.get('checkpoints', 'A'), 'before');
assert.deepEqual(await db.get('history', ['B', 'm1']), { text: 'Synthetic other account' });
assert.equal(await db.get('settings', ['B', 'theme']), 'B-setting');
assert.equal(await db.get('crypto', ['B', 'state']), 'B-opaque-fixture-not-key-material');
assert.deepEqual(await saveBatch(false), { saved: true });
db.close();
db = await openDB(name, 1);
assert.equal(await db.get('checkpoints', 'A'), 'after');
assert.deepEqual(await db.get('history', ['A', 'm1']), { text: 'Synthetic pending' });
let versionChange = false;
let upgraded = false;
db.addEventListener('versionchange', () => { versionChange = true; });
let signalBlocked;
const blocked = new Promise(resolve => { signalBlocked = resolve; });
const pending = openDB(name, 2, {
blocked() { signalBlocked(); },
upgrade(db) { upgraded = true; db.createObjectStore('v2Marker'); },
});
const timer = setTimeout(() => { throw new Error('blocked event timeout'); }, 3000);
await blocked;
assert.equal(versionChange, true);
assert.equal(upgraded, false);
assert.equal(db.version, 1);
db.close();
const next = await pending;
clearTimeout(timer);
assert.equal(upgraded, true);
assert.equal(next.version, 2);
assert.equal(await next.get('checkpoints', 'A'), 'after');
next.close();
await assert.rejects(openDB(name, 1), { name: 'VersionError' });
await deleteDB(name);
console.log('PASS quota policy (injected): no saved success or checkpoint advance on abort; account isolation; retry/reopen');
console.log('PASS blocked upgrade: versionchange then blocked; close releases upgrade; stale version refused');
@@ -0,0 +1,46 @@
import 'fake-indexeddb/auto';
import assert from 'node:assert/strict';
import { openDB, deleteDB } from 'idb';
// Synthetic API semantics; reopening is not a disk/process restart.
const name = 'luma-design-rollback';
await deleteDB(name);
let db = await openDB(name, 1, { upgrade(db) {
db.createObjectStore('accounts', { keyPath: 'accountId' });
} });
await db.put('accounts', { accountId: 'A1', displayName: 'Fixture One' });
db.close();
let constraint;
await assert.rejects(openDB(name, 2, { upgrade(db, old, next, tx) {
assert.equal(old, 1);
db.createObjectStore('migrationMarker');
const accounts = tx.objectStore('accounts');
accounts.createIndex('displayName', 'displayName', { unique: true });
accounts.put({ accountId: 'A1', displayName: 'Duplicate' }).catch(() => {});
accounts.add({ accountId: 'A2', displayName: 'Duplicate' }).catch(error => { constraint = error.name; });
// Observe promise rejections without suppressing native request error/abort.
tx.done.catch(() => {});
} }), { name: 'AbortError' });
assert.equal(constraint, 'ConstraintError');
db = await openDB(name, 1);
assert.equal(db.version, 1);
assert.deepEqual([...db.objectStoreNames], ['accounts']);
assert.deepEqual([...db.transaction('accounts').store.indexNames], []);
assert.deepEqual(await db.getAll('accounts'), [{ accountId: 'A1', displayName: 'Fixture One' }]);
db.close();
db = await openDB(name, 2, { upgrade(db, old, next, tx) {
db.createObjectStore('migrationMarker');
const accounts = tx.objectStore('accounts');
accounts.createIndex('displayName', 'displayName', { unique: true });
accounts.add({ accountId: 'A2', displayName: 'Fixture Two' }).catch(() => {});
tx.done.catch(() => {});
} });
assert.equal(db.version, 2);
assert.equal((await db.getAll('accounts')).length, 2);
assert.equal((await db.getFromIndex('accounts', 'displayName', 'Fixture Two')).accountId, 'A2');
db.close();
db = await openDB(name, 2);
assert.equal((await db.getAll('accounts')).length, 2);
db.close();
await deleteDB(name);
console.log('PASS rollback: ConstraintError; v1 schema/data restored; corrected v2 retry and reopen');
+21
View File
@@ -0,0 +1,21 @@
# Browser passport validation
From project root, Python 3.10+ stdlib only:
```sh
python3 experiments/0005-browser-matrix/check.py
```
`matrix.json` contains target recipes, resource ceilings, required run-record
fields and capability fallback rules. Normative measurement and permission
procedures: `docs/decisions/0005-browser-matrix.md`. Target OS families/stable
channels must be resolved to supported full builds before a real qualification;
archive full build identifiers, installer hash or Apple OS build, app and fixture
hashes, actual viewport and tool configuration. No browser binaries are supplied.
The checker rejects missing targets/reproduction fields/fallbacks, compiler floor
regressions, impossible RAM ceilings and missing iOS Home Screen mode. Negative
fixtures exercise those refusals; percentile fixtures verify nearest-rank p95.
It does not execute a browser, simulate a successful permission request, measure
resources, qualify hardware or prove a real fallback implementation. A passing
passport check must not be reported as passing production browser acceptance.
+88
View File
@@ -0,0 +1,88 @@
"""Validate a qualification passport, not browser capability or app performance."""
import copy
import json
import math
from pathlib import Path
HERE = Path(__file__).resolve().parent
REQUIRED_FALLBACKS = {
"secure-context", "crypto-worker-wasm", "indexeddb", "persistence", "push",
"install", "media", "recording-codec", "geolocation", "webauthn",
"permissions-query", "lifecycle",
}
REPRODUCTION = {
"target_id", "run_utc", "device_model", "cpu", "ram_gib", "os_full_build",
"browser_full_build", "browser_channel", "installer_source",
"installer_sha256_or_os_build", "viewport_css", "actual_inner_size_css",
"dpr", "refresh_hz", "display_mode", "app_commit", "asset_sha256",
"fixture_sha256", "profile_reset_recipe", "network_profile",
"measurement_tool_version",
}
def validate(data):
targets = data["targets"]
ids = [t["id"] for t in targets]
assert len(ids) == len(set(ids)), "Duplicate target"
assert set(ids) == {"win-chrome", "win-edge", "linux-firefox", "mac-safari",
"android-chrome", "ios-safari"}, "Missing engine/device target"
assert set(data["freeze_fields"]) >= REPRODUCTION, "Incomplete reproduction record"
for t in targets:
for key in ("device", "cpu", "os", "browser", "channel", "budget"):
assert isinstance(t[key], str) and t[key].strip(), f"Missing {key}"
assert t["channel"] == "stable", "Unqualified channel"
assert t["ram_gib"] > 0 and t["dpr"] > 0 and t["hz"] == 60
assert len(t["viewport_css"]) == 2 and min(t["viewport_css"]) >= 320
assert "tab" in t["modes"] and len(t["modes"]) == len(set(t["modes"]))
floor = {"Chrome":111, "Edge":111, "Firefox":114, "Safari":18}[t["browser"]]
assert t["minimum_major"] >= floor, "Target below accepted syntax/OS floor"
b = data["budgets"][t["budget"]]
assert all(isinstance(v, (int, float)) and v > 0 for v in b.values())
assert b["warm_start_p95_ms"] <= b["cold_start_p95_ms"]
assert b["raf_gap_p95_ms"] <= b["raf_gap_max_ms"]
assert b["ram_growth_mib"] < b["ram_peak_mib"] < t["ram_gib"] * 1024
assert set(data["fallbacks"]) >= REQUIRED_FALLBACKS, "Missing capability fallback"
for rule in data["fallbacks"].values():
assert len(rule["detection"].strip()) >= 20, "No actionable detection"
assert len(rule["fallback"].strip()) >= 50, "No actionable fallback"
ios = next(t for t in targets if t["id"] == "ios-safari")
assert "home-screen" in ios["modes"], "iOS push mode missing"
def p95(values):
assert values and all(math.isfinite(v) and v >= 0 for v in values)
return sorted(values)[math.ceil(0.95 * len(values)) - 1]
def main():
data = json.loads((HERE / "matrix.json").read_text())
validate(data)
# Reject plausible omissions and unsafe qualification drift.
mutants = []
m = copy.deepcopy(data); m["targets"].pop(); mutants.append(m)
m = copy.deepcopy(data); m["targets"][0]["cpu"] = ""; mutants.append(m)
m = copy.deepcopy(data); m["freeze_fields"].remove("browser_full_build"); mutants.append(m)
m = copy.deepcopy(data); del m["fallbacks"]["crypto-worker-wasm"]; mutants.append(m)
m = copy.deepcopy(data); m["fallbacks"]["media"]["fallback"] = ""; mutants.append(m)
m = copy.deepcopy(data); m["targets"][0]["minimum_major"] = 80; mutants.append(m)
m = copy.deepcopy(data); m["targets"][-1]["modes"] = ["tab"]; mutants.append(m)
m = copy.deepcopy(data); m["budgets"]["mobile"]["ram_peak_mib"] = 10000; mutants.append(m)
for i, m in enumerate(mutants, 1):
try:
validate(m)
except AssertionError:
continue
raise AssertionError(f"Invalid passport accepted: mutation {i}")
# Thirty-run nearest-rank semantics: two slow runs fail a 2500 ms p95 gate;
# a single tail still remains in raw evidence, not silently discarded.
assert p95([1000] * 28 + [3000, 4000]) == 3000
assert p95([2500] * 30) == 2500
assert p95([1000] * 29 + [4000]) == 1000
print(f"PASS: {len(data['targets'])} target recipes, {len(REPRODUCTION)} freeze fields, "
f"{len(data['fallbacks'])} capability fallbacks; {len(mutants)} negative mutations rejected; "
"nearest-rank percentile fixtures passed.")
print("LIMIT: document validation only; no browser/device execution or app budget pass.")
if __name__ == "__main__":
main()
@@ -0,0 +1,30 @@
{
"schema_version": 1,
"targets": [
{"id":"win-chrome","device":"Lenovo ThinkPad T14 Gen 2 Intel","cpu":"Intel Core i5-1135G7","ram_gib":16,"os":"Windows 11 24H2 x64","browser":"Chrome","minimum_major":111,"channel":"stable","viewport_css":[1366,768],"dpr":1,"hz":60,"modes":["tab","installed"],"budget":"desktop"},
{"id":"win-edge","device":"Lenovo ThinkPad T14 Gen 2 Intel","cpu":"Intel Core i5-1135G7","ram_gib":16,"os":"Windows 11 24H2 x64","browser":"Edge","minimum_major":111,"channel":"stable","viewport_css":[1366,768],"dpr":1,"hz":60,"modes":["tab","installed"],"budget":"desktop"},
{"id":"linux-firefox","device":"Lenovo ThinkPad T14 Gen 2 Intel","cpu":"Intel Core i5-1135G7","ram_gib":16,"os":"Ubuntu 24.04 LTS x64 GNOME Wayland","browser":"Firefox","minimum_major":114,"channel":"stable","viewport_css":[1366,768],"dpr":1,"hz":60,"modes":["tab"],"budget":"desktop"},
{"id":"mac-safari","device":"MacBook Air 13-inch M1 2020","cpu":"Apple M1 8-core CPU / 7-core GPU","ram_gib":8,"os":"macOS 15","browser":"Safari","minimum_major":18,"channel":"stable","viewport_css":[1280,800],"dpr":2,"hz":60,"modes":["tab","installed"],"budget":"desktop"},
{"id":"android-chrome","device":"Google Pixel 6a 128GB","cpu":"Google Tensor","ram_gib":6,"os":"Android 15","browser":"Chrome","minimum_major":111,"channel":"stable","viewport_css":[393,851],"dpr":2.75,"hz":60,"modes":["tab","installed"],"budget":"mobile"},
{"id":"ios-safari","device":"Apple iPhone 13 128GB","cpu":"Apple A15 Bionic","ram_gib":4,"os":"iOS 18","browser":"Safari","minimum_major":18,"channel":"stable","viewport_css":[390,844],"dpr":3,"hz":60,"modes":["tab","home-screen"],"budget":"mobile"}
],
"freeze_fields":["target_id","run_utc","device_model","cpu","ram_gib","os_full_build","browser_full_build","browser_channel","installer_source","installer_sha256_or_os_build","viewport_css","actual_inner_size_css","dpr","refresh_hz","display_mode","app_commit","asset_sha256","fixture_sha256","profile_reset_recipe","network_profile","measurement_tool_version"],
"budgets": {
"desktop":{"cold_start_p95_ms":2500,"warm_start_p95_ms":1200,"interaction_p95_ms":100,"raf_gap_p95_ms":20,"raf_gap_max_ms":100,"ram_peak_mib":350,"ram_growth_mib":30,"cache_mib":128},
"mobile":{"cold_start_p95_ms":4000,"warm_start_p95_ms":2000,"interaction_p95_ms":150,"raf_gap_p95_ms":25,"raf_gap_max_ms":150,"ram_peak_mib":250,"ram_growth_mib":25,"cache_mib":64}
},
"fallbacks": {
"secure-context":{"detection":"isSecureContext false / endpoint TLS failure","fallback":"Block login and protected operations; explain HTTPS/WSS requirement; never bypass certificates."},
"crypto-worker-wasm":{"detection":"Required WebCrypto, Worker, WASM initialization or self-test fails","fallback":"Block encrypted send/unlock; show unsupported capability; never plaintext or fake crypto."},
"indexeddb":{"detection":"Open/transaction fails including private-mode policy or quota","fallback":"Report local save failure; no checkpoint advance; preserve bounded pending memory; explicit recovery per ADR 0004."},
"persistence":{"detection":"persist absent/false; estimate absent or quota smaller than cache budget","fallback":"Explain best-effort storage and loss risk; lower disposable cache limit; never delete crypto or unsynced history."},
"push":{"detection":"Service worker/PushManager/Notifications missing, denied, revoked, subscription invalid, provider absent or iOS ordinary tab","fallback":"Disable background notification claim; show active-tab unread UI; offer Home Screen setup on iOS only by user action."},
"install":{"detection":"No install prompt or standalone mode","fallback":"Continue supported tab UI; show manual browser install instructions only where applicable."},
"media":{"detection":"getUserMedia/WebRTC absent, NotAllowedError, NotFoundError, NotReadableError or track ended","fallback":"Stop affected capture/call state, release tracks, explain reason; retain text; offer audio only if microphone actually works."},
"recording-codec":{"detection":"MediaRecorder absent or isTypeSupported false; runtime encoder/decoder fails","fallback":"Disable affected recording/playback with explanation; allow supported file selection/download; do not claim a sent recording."},
"geolocation":{"detection":"API absent, denied, timeout, unavailable or revoked","fallback":"Stop watch, show failure, permit explicit manual location entry; never invent coordinates."},
"webauthn":{"detection":"PublicKeyCredential missing, authenticator unavailable, cancel or failure","fallback":"Offer only accepted S00-007 unlock alternative; otherwise stay locked; no claim of hardware vault."},
"permissions-query":{"detection":"navigator.permissions absent or query descriptor rejects","fallback":"Use contextual API request after user gesture and observe its real result; no granted-state inference."},
"lifecycle":{"detection":"hidden, frozen, discarded, offline or closed tab","fallback":"No socket/media continuity promise; reconcile actual state on resume; closed-tab push remains conditional per ADR 0000."}
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
Зафиксировать Web scope, browser targets и проверенные кандидаты, не устанавливая непроверенный framework по догадке.
Задач: 19; entry: нет; exit: S00-010.
Задач: 31; entry: нет; exit: S00-010.
## [S01: Предметные контракты](stages/S01.md)
+16 -4
View File
@@ -1,14 +1,14 @@
# Матрица задач
Generated; state revision **51**. Canonical status: state/tasks.json.
Generated; state revision **118**. Canonical status: state/tasks.json.
| ID | Этап | Оператор | Reviewer | Статус | Owner | Зависимости | Задача |
| --- | --- | --- | --- | --- | --- | --- | --- |
| [S00-001](tasks/S00-001.md) | S00 | orchestrator | security | done | auto-orchestrator-48aab142ecde | | Утвердить Web-профиль |
| [S00-002](tasks/S00-002.md) | S00 | orchestrator | security | done | auto-orchestrator-68a05c587e33 | S00-001, S00-011, S00-012 | Выбрать framework и build runtime |
| [S00-003](tasks/S00-003.md) | S00 | protocol | qa | todo | | S00-001, S00-002, S00-017, S00-018, S00-019 | Проверить XMPP WSS/auth библиотеку |
| [S00-004](tasks/S00-004.md) | S00 | crypto | security | todo | | S00-001, S00-002 | Проверить browser OMEMO backend |
| [S00-005](tasks/S00-005.md) | S00 | storage | qa | todo | | S00-002 | Выбрать IndexedDB wrapper и migration policy |
| [S00-003](tasks/S00-003.md) | S00 | protocol | qa | done | auto-protocol-f474114f0168 | S00-001, S00-002, S00-017, S00-018, S00-019, S00-020 | Проверить XMPP WSS/auth библиотеку |
| [S00-004](tasks/S00-004.md) | S00 | crypto | security | blocked | auto-crypto-fe1c81c17ded | S00-001, S00-002, S00-021, S00-022 | Проверить browser OMEMO backend |
| [S00-005](tasks/S00-005.md) | S00 | storage | qa | done | auto-storage-0ce544ac4b40 | S00-002, S00-023 | Выбрать IndexedDB wrapper и migration policy |
| [S00-006](tasks/S00-006.md) | S00 | orchestrator | security | todo | | S00-002 | Зафиксировать browser/reference matrix |
| [S00-007](tasks/S00-007.md) | S00 | security | orchestrator | todo | | S00-004, S00-005 | Утвердить password и crypto unlock модель |
| [S00-008](tasks/S00-008.md) | S00 | ci-cd | qa | todo | | S00-003, S00-004 | Описать WSS/upload/TURN/push topology |
@@ -22,6 +22,18 @@ Generated; state revision **51**. Canonical status: state/tasks.json.
| [S00-017](tasks/S00-017.md) | S00 | protocol | security | done | auto-protocol-599343cd9e3f | S00-001, S00-002 | Establish fail-closed SCRAM server-final validation |
| [S00-018](tasks/S00-018.md) | S00 | protocol | security | done | auto-protocol-89fe6563092c | S00-001, S00-002, S00-017 | Validate bounded standalone XML ingress for the XMPP candidate |
| [S00-019](tasks/S00-019.md) | S00 | crypto | security | done | auto-crypto-c68801170fa4 | S00-001, S00-002, S00-017, S00-018 | Validate browser-compatible SCRAM-SHA-512 through a public mechanism API |
| [S00-020](tasks/S00-020.md) | S00 | ci-cd | qa | done | auto-ci-cd-2a0186c54ce9 | S00-001, S00-002, S00-017, S00-018, S00-019, S00-024 | Restore access to the pinned Linux XMPP experiment toolchain |
| [S00-021](tasks/S00-021.md) | S00 | crypto | security | done | auto-crypto-fcef9b6781bf | S00-001, S00-002 | Устранить прерывание: Проверить browser OMEMO backend |
| [S00-022](tasks/S00-022.md) | S00 | crypto | security | blocked | auto-crypto-bcd7f2a71667 | S00-001, S00-002, S00-021, S00-027, S00-029 | Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint |
| [S00-023](tasks/S00-023.md) | S00 | storage | qa | done | auto-storage-e6946c6f94e3 | S00-002, S00-025 | Устранить прерывание: Выбрать IndexedDB wrapper и migration policy |
| [S00-024](tasks/S00-024.md) | S00 | ci-cd | qa | done | auto-ci-cd-49907d48a6d1 | S00-001, S00-002, S00-017, S00-018, S00-019, S00-026 | Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain |
| [S00-025](tasks/S00-025.md) | S00 | storage | qa | done | auto-storage-8b47cee148df | S00-002, S00-028 | Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy |
| [S00-026](tasks/S00-026.md) | S00 | ci-cd | qa | done | auto-ci-cd-4255c34da227 | S00-001, S00-002, S00-017, S00-018, S00-019 | Provide approved access to the pinned Linux experiment artifacts |
| [S00-027](tasks/S00-027.md) | S00 | ci-cd | security | done | auto-ci-cd-40ae46e8efe7 | S00-001, S00-002, S00-021 | Restore approved npm registry connectivity for the OMEMO2 backend investigation |
| [S00-028](tasks/S00-028.md) | S00 | ci-cd | qa | done | auto-ci-cd-e3a716a94424 | S00-002, S00-030 | Repair conflicting Codex harness sandbox arguments |
| [S00-029](tasks/S00-029.md) | S00 | orchestrator | security | blocked | | S00-001, S00-002, S00-021, S00-027 | Record an approved browser OMEMO backend sourcing strategy |
| [S00-030](tasks/S00-030.md) | S00 | ci-cd | qa | done | auto-ci-cd-8c7bc1551426 | S00-002, S00-031 | Provide a valid Codex sandbox app-server socket directory |
| [S00-031](tasks/S00-031.md) | S00 | ci-cd | qa | done | auto-ci-cd-d45ccd3ee7d6 | S00-002 | Supply a verified private Codex app-server socket environment |
| [S00-010](tasks/S00-010.md) | S00 | qa | orchestrator | todo | | S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 | Gate: Профиль и проверка стека |
| [S01-001](tasks/S01-001.md) | S01 | orchestrator | security | todo | | S00-010 | Описать identity/model contracts |
| [S01-002](tasks/S01-002.md) | S01 | orchestrator | security | todo | | S00-010, S01-001 | Описать async account context |
+6 -6
View File
@@ -14,7 +14,7 @@ Allocated означает наличие плана/приёмки, а не р
| ACC-001 | web | Web requirement; cross-platform clauses apply only in Web context. | S06-003, S21-001 |
| ACC-002 | web | Web requirement; cross-platform clauses apply only in Web context. | S06-001, S21-001 |
| ACC-003 | out_of_scope | Native host/port TCP configuration; Web uses ACC-004 WSS. | |
| ACC-004 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S05-001, S06-003, S21-001 |
| ACC-004 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S00-020, S05-001, S06-003, S21-001 |
| ACC-005 | web | Web requirement; cross-platform clauses apply only in Web context. | S05-001, S06-011, S21-001 |
| ACC-006 | web | Web requirement; cross-platform clauses apply only in Web context. | S06-003, S06-004, S21-001 |
| ACC-007 | web | Web requirement; cross-platform clauses apply only in Web context. | S06-002, S06-009, S20-008, S21-001 |
@@ -27,8 +27,8 @@ Allocated означает наличие плана/приёмки, а не р
| ACC-014 | web | Web requirement; cross-platform clauses apply only in Web context. | S01-002, S02-010, S06-006, S06-010, S21-001 |
| NET-001 | web | Web requirement; cross-platform clauses apply only in Web context. | S02-008, S05-001, S05-002, S05-009, S21-001 |
| NET-002 | out_of_scope | Native transport discovery; Web WSS addressed by ACC-004/NET-001. | |
| NET-003 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S05-003, S21-001 |
| NET-004 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S05-003, S05-009, S21-001 |
| NET-003 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S00-020, S05-003, S21-001 |
| NET-004 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-003, S00-017, S00-018, S00-019, S00-020, S05-003, S05-009, S21-001 |
| NET-005 | web | Web requirement; cross-platform clauses apply only in Web context. | S05-004, S21-001 |
| NET-006 | web | Web requirement; cross-platform clauses apply only in Web context. | S05-002, S05-010, S06-004, S20-002, S21-001 |
| NET-007 | web | Web requirement; cross-platform clauses apply only in Web context. | S05-007, S21-001 |
@@ -97,15 +97,15 @@ Allocated означает наличие плана/приёмки, а не р
| MUC-008 | web | Web requirement; cross-platform clauses apply only in Web context. | S13-009, S21-003 |
| ENC-001 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-018, S20-008, S21-003 |
| ENC-002 | web | Web requirement; cross-platform clauses apply only in Web context. | S01-005, S08-004, S12-007, S12-008, S12-018, S12-020, S21-003 |
| ENC-003 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S12-005, S12-006, S12-007, S12-008, S12-009, S12-010, S12-011, S12-012, S12-014, S12-021, S21-003 |
| ENC-003 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S00-022, S00-027, S00-029, S12-005, S12-006, S12-007, S12-008, S12-009, S12-010, S12-011, S12-012, S12-014, S12-021, S21-003 |
| ENC-004 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-015, S12-021, S13-006, S21-003 |
| ENC-005 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-016, S12-021, S21-003 |
| ENC-006 | web | Web requirement; cross-platform clauses apply only in Web context. | S01-005, S12-002, S12-003, S12-020, S21-003 |
| ENC-007 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-004, S12-019, S21-003 |
| ENC-008 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-019, S21-003 |
| ENC-009 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S12-001, S12-013, S12-017, S12-020, S21-003 |
| ENC-009 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S00-022, S00-027, S00-029, S12-001, S12-013, S12-017, S12-020, S21-003 |
| ENC-010 | web | Web requirement; cross-platform clauses apply only in Web context. | S12-020, S20-004, S21-003 |
| ENC-011 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S12-010, S12-011, S12-012, S12-013, S12-014, S21-003 |
| ENC-011 | web | Web requirement; cross-platform clauses apply only in Web context. | S00-004, S00-022, S00-027, S00-029, S12-010, S12-011, S12-012, S12-013, S12-014, S21-003 |
| FILE-001 | web | Web requirement; cross-platform clauses apply only in Web context. | S14-002, S21-004 |
| FILE-002 | web | Web requirement; cross-platform clauses apply only in Web context. | S14-001, S14-011, S21-004 |
| FILE-003 | web | Web requirement; cross-platform clauses apply only in Web context. | S01-006, S14-001, S21-004 |
+15 -3
View File
@@ -8,9 +8,9 @@
- [S00-001](../tasks/S00-001.md): Утвердить Web-профиль — orchestrator; done.
- [S00-002](../tasks/S00-002.md): Выбрать framework и build runtime — orchestrator; done.
- [S00-003](../tasks/S00-003.md): Проверить XMPP WSS/auth библиотеку — protocol; todo.
- [S00-004](../tasks/S00-004.md): Проверить browser OMEMO backend — crypto; todo.
- [S00-005](../tasks/S00-005.md): Выбрать IndexedDB wrapper и migration policy — storage; todo.
- [S00-003](../tasks/S00-003.md): Проверить XMPP WSS/auth библиотеку — protocol; done.
- [S00-004](../tasks/S00-004.md): Проверить browser OMEMO backend — crypto; blocked.
- [S00-005](../tasks/S00-005.md): Выбрать IndexedDB wrapper и migration policy — storage; done.
- [S00-006](../tasks/S00-006.md): Зафиксировать browser/reference matrix — orchestrator; todo.
- [S00-007](../tasks/S00-007.md): Утвердить password и crypto unlock модель — security; todo.
- [S00-008](../tasks/S00-008.md): Описать WSS/upload/TURN/push topology — ci-cd; todo.
@@ -24,6 +24,18 @@
- [S00-017](../tasks/S00-017.md): Establish fail-closed SCRAM server-final validation — protocol; done.
- [S00-018](../tasks/S00-018.md): Validate bounded standalone XML ingress for the XMPP candidate — protocol; done.
- [S00-019](../tasks/S00-019.md): Validate browser-compatible SCRAM-SHA-512 through a public mechanism API — crypto; done.
- [S00-020](../tasks/S00-020.md): Restore access to the pinned Linux XMPP experiment toolchain — ci-cd; done.
- [S00-021](../tasks/S00-021.md): Устранить прерывание: Проверить browser OMEMO backend — crypto; done.
- [S00-022](../tasks/S00-022.md): Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint — crypto; blocked.
- [S00-023](../tasks/S00-023.md): Устранить прерывание: Выбрать IndexedDB wrapper и migration policy — storage; done.
- [S00-024](../tasks/S00-024.md): Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain — ci-cd; done.
- [S00-025](../tasks/S00-025.md): Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy — storage; done.
- [S00-026](../tasks/S00-026.md): Provide approved access to the pinned Linux experiment artifacts — ci-cd; done.
- [S00-027](../tasks/S00-027.md): Restore approved npm registry connectivity for the OMEMO2 backend investigation — ci-cd; done.
- [S00-028](../tasks/S00-028.md): Repair conflicting Codex harness sandbox arguments — ci-cd; done.
- [S00-029](../tasks/S00-029.md): Record an approved browser OMEMO backend sourcing strategy — orchestrator; blocked.
- [S00-030](../tasks/S00-030.md): Provide a valid Codex sandbox app-server socket directory — ci-cd; done.
- [S00-031](../tasks/S00-031.md): Supply a verified private Codex app-server socket environment — ci-cd; done.
- [S00-010](../tasks/S00-010.md): Gate: Профиль и проверка стека — qa; todo.
## Граница этапа
+15 -3
View File
@@ -1,9 +1,9 @@
id,stage,operator,reviewer,status,owner,depends_on,title
S00-001,S00,orchestrator,security,done,auto-orchestrator-48aab142ecde,,Утвердить Web-профиль
S00-002,S00,orchestrator,security,done,auto-orchestrator-68a05c587e33,"S00-001, S00-011, S00-012",Выбрать framework и build runtime
S00-003,S00,protocol,qa,todo,,"S00-001, S00-002, S00-017, S00-018, S00-019",Проверить XMPP WSS/auth библиотеку
S00-004,S00,crypto,security,todo,,"S00-001, S00-002",Проверить browser OMEMO backend
S00-005,S00,storage,qa,todo,,S00-002,Выбрать IndexedDB wrapper и migration policy
S00-003,S00,protocol,qa,done,auto-protocol-f474114f0168,"S00-001, S00-002, S00-017, S00-018, S00-019, S00-020",Проверить XMPP WSS/auth библиотеку
S00-004,S00,crypto,security,blocked,auto-crypto-fe1c81c17ded,"S00-001, S00-002, S00-021, S00-022",Проверить browser OMEMO backend
S00-005,S00,storage,qa,done,auto-storage-0ce544ac4b40,"S00-002, S00-023",Выбрать IndexedDB wrapper и migration policy
S00-006,S00,orchestrator,security,todo,,S00-002,Зафиксировать browser/reference matrix
S00-007,S00,security,orchestrator,todo,,"S00-004, S00-005",Утвердить password и crypto unlock модель
S00-008,S00,ci-cd,qa,todo,,"S00-003, S00-004",Описать WSS/upload/TURN/push topology
@@ -17,6 +17,18 @@ S00-016,S00,ci-cd,qa,done,auto-ci-cd-351a0cbbabef,"S00-001, S00-011, S00-013, S0
S00-017,S00,protocol,security,done,auto-protocol-599343cd9e3f,"S00-001, S00-002",Establish fail-closed SCRAM server-final validation
S00-018,S00,protocol,security,done,auto-protocol-89fe6563092c,"S00-001, S00-002, S00-017",Validate bounded standalone XML ingress for the XMPP candidate
S00-019,S00,crypto,security,done,auto-crypto-c68801170fa4,"S00-001, S00-002, S00-017, S00-018",Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
S00-020,S00,ci-cd,qa,done,auto-ci-cd-2a0186c54ce9,"S00-001, S00-002, S00-017, S00-018, S00-019, S00-024",Restore access to the pinned Linux XMPP experiment toolchain
S00-021,S00,crypto,security,done,auto-crypto-fcef9b6781bf,"S00-001, S00-002",Устранить прерывание: Проверить browser OMEMO backend
S00-022,S00,crypto,security,blocked,auto-crypto-bcd7f2a71667,"S00-001, S00-002, S00-021, S00-027, S00-029",Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
S00-023,S00,storage,qa,done,auto-storage-e6946c6f94e3,"S00-002, S00-025",Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
S00-024,S00,ci-cd,qa,done,auto-ci-cd-49907d48a6d1,"S00-001, S00-002, S00-017, S00-018, S00-019, S00-026",Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
S00-025,S00,storage,qa,done,auto-storage-8b47cee148df,"S00-002, S00-028",Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
S00-026,S00,ci-cd,qa,done,auto-ci-cd-4255c34da227,"S00-001, S00-002, S00-017, S00-018, S00-019",Provide approved access to the pinned Linux experiment artifacts
S00-027,S00,ci-cd,security,done,auto-ci-cd-40ae46e8efe7,"S00-001, S00-002, S00-021",Restore approved npm registry connectivity for the OMEMO2 backend investigation
S00-028,S00,ci-cd,qa,done,auto-ci-cd-e3a716a94424,"S00-002, S00-030",Repair conflicting Codex harness sandbox arguments
S00-029,S00,orchestrator,security,blocked,,"S00-001, S00-002, S00-021, S00-027",Record an approved browser OMEMO backend sourcing strategy
S00-030,S00,ci-cd,qa,done,auto-ci-cd-8c7bc1551426,"S00-002, S00-031",Provide a valid Codex sandbox app-server socket directory
S00-031,S00,ci-cd,qa,done,auto-ci-cd-d45ccd3ee7d6,S00-002,Supply a verified private Codex app-server socket environment
S00-010,S00,qa,orchestrator,todo,,"S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009",Gate: Профиль и проверка стека
S01-001,S01,orchestrator,security,todo,,S00-010,Описать identity/model contracts
S01-002,S01,orchestrator,security,todo,,"S00-010, S01-001",Описать async account context
1 id stage operator reviewer status owner depends_on title
2 S00-001 S00 orchestrator security done auto-orchestrator-48aab142ecde Утвердить Web-профиль
3 S00-002 S00 orchestrator security done auto-orchestrator-68a05c587e33 S00-001, S00-011, S00-012 Выбрать framework и build runtime
4 S00-003 S00 protocol qa todo done auto-protocol-f474114f0168 S00-001, S00-002, S00-017, S00-018, S00-019 S00-001, S00-002, S00-017, S00-018, S00-019, S00-020 Проверить XMPP WSS/auth библиотеку
5 S00-004 S00 crypto security todo blocked auto-crypto-fe1c81c17ded S00-001, S00-002 S00-001, S00-002, S00-021, S00-022 Проверить browser OMEMO backend
6 S00-005 S00 storage qa todo done auto-storage-0ce544ac4b40 S00-002 S00-002, S00-023 Выбрать IndexedDB wrapper и migration policy
7 S00-006 S00 orchestrator security todo S00-002 Зафиксировать browser/reference matrix
8 S00-007 S00 security orchestrator todo S00-004, S00-005 Утвердить password и crypto unlock модель
9 S00-008 S00 ci-cd qa todo S00-003, S00-004 Описать WSS/upload/TURN/push topology
17 S00-017 S00 protocol security done auto-protocol-599343cd9e3f S00-001, S00-002 Establish fail-closed SCRAM server-final validation
18 S00-018 S00 protocol security done auto-protocol-89fe6563092c S00-001, S00-002, S00-017 Validate bounded standalone XML ingress for the XMPP candidate
19 S00-019 S00 crypto security done auto-crypto-c68801170fa4 S00-001, S00-002, S00-017, S00-018 Validate browser-compatible SCRAM-SHA-512 through a public mechanism API
20 S00-020 S00 ci-cd qa done auto-ci-cd-2a0186c54ce9 S00-001, S00-002, S00-017, S00-018, S00-019, S00-024 Restore access to the pinned Linux XMPP experiment toolchain
21 S00-021 S00 crypto security done auto-crypto-fcef9b6781bf S00-001, S00-002 Устранить прерывание: Проверить browser OMEMO backend
22 S00-022 S00 crypto security blocked auto-crypto-bcd7f2a71667 S00-001, S00-002, S00-021, S00-027, S00-029 Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
23 S00-023 S00 storage qa done auto-storage-e6946c6f94e3 S00-002, S00-025 Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
24 S00-024 S00 ci-cd qa done auto-ci-cd-49907d48a6d1 S00-001, S00-002, S00-017, S00-018, S00-019, S00-026 Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
25 S00-025 S00 storage qa done auto-storage-8b47cee148df S00-002, S00-028 Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
26 S00-026 S00 ci-cd qa done auto-ci-cd-4255c34da227 S00-001, S00-002, S00-017, S00-018, S00-019 Provide approved access to the pinned Linux experiment artifacts
27 S00-027 S00 ci-cd security done auto-ci-cd-40ae46e8efe7 S00-001, S00-002, S00-021 Restore approved npm registry connectivity for the OMEMO2 backend investigation
28 S00-028 S00 ci-cd qa done auto-ci-cd-e3a716a94424 S00-002, S00-030 Repair conflicting Codex harness sandbox arguments
29 S00-029 S00 orchestrator security blocked S00-001, S00-002, S00-021, S00-027 Record an approved browser OMEMO backend sourcing strategy
30 S00-030 S00 ci-cd qa done auto-ci-cd-8c7bc1551426 S00-002, S00-031 Provide a valid Codex sandbox app-server socket directory
31 S00-031 S00 ci-cd qa done auto-ci-cd-d45ccd3ee7d6 S00-002 Supply a verified private Codex app-server socket environment
32 S00-010 S00 qa orchestrator todo S00-001, S00-002, S00-003, S00-004, S00-005, S00-006, S00-007, S00-008, S00-009 Gate: Профиль и проверка стека
33 S01-001 S01 orchestrator security todo S00-010 Описать identity/model contracts
34 S01-002 S01 orchestrator security todo S00-010, S01-001 Описать async account context
+647 -3
View File
@@ -270,7 +270,8 @@
"S00-002",
"S00-017",
"S00-018",
"S00-019"
"S00-019",
"S00-020"
],
"requirements": [
"ACC-004",
@@ -322,7 +323,9 @@
"outcome": "Spike и ADR устанавливают реальную возможность legacy/OMEMO2, worker/wasm и persistence без самодельного ratchet.",
"depends_on": [
"S00-001",
"S00-002"
"S00-002",
"S00-021",
"S00-022"
],
"requirements": [
"ENC-003",
@@ -373,7 +376,8 @@
"title": "Выбрать IndexedDB wrapper и migration policy",
"outcome": "ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.",
"depends_on": [
"S00-002"
"S00-002",
"S00-023"
],
"requirements": [
"STORE-001",
@@ -1115,6 +1119,646 @@
],
"context_budget_chars": 48000
},
{
"id": "S00-020",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-003",
"blocker_depth": 1,
"unblocks": [
"S00-003"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Restore access to the pinned Linux XMPP experiment toolchain",
"outcome": "One independently reviewed environment-access probe demonstrates authentic pinned Linux Node/npm and integrity-verified installation of the parent locked dependency graph without changing accepted stack or network security policy.",
"depends_on": [
"S00-001",
"S00-002",
"S00-017",
"S00-018",
"S00-019",
"S00-024"
],
"requirements": [
"ACC-004",
"NET-003",
"NET-004"
],
"scope": [
"experiments/0002-xmpp-library/environment-access/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/protocol.md",
"docs/decisions/README.md",
"state/blockers/S00-020.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Read S00-003 blocker evidence and accepted stack pins.",
"Obtain external approved HTTPS connectivity or authentic locally supplied pinned Linux runtime and locked dependency artifacts.",
"Verify vendor checksum for runtime, exact node/npm versions, and lock integrity with a script confined to the remediation directory; install a copy of the parent manifest/lock only inside that directory.",
"Document reproducible approved artifact paths or connectivity and actual results; submit for independent review."
],
"acceptance": [
"Node 24.21.0 and npm 11.19.0 execute on this Linux host with authenticated vendor checksum verification recorded.",
"Copied unchanged parent package-lock installs successfully with package integrity enforced and Stanza 12.22.1 available.",
"No stack/lock/spec changes, engine or TLS bypass, proxy policy bypass or secrets in evidence."
],
"checks": [
"Exact runtime version and official checksum comparison.",
"Isolated npm ci of unchanged parent manifest/lock with lifecycle scripts disabled and TLS verification retained.",
"Independent review repeats access/runtime/lock checks."
],
"context_budget_chars": 48000
},
{
"id": "S00-021",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-004",
"blocker_depth": 1,
"unblocks": [
"S00-004"
],
"role": "crypto",
"reviewer_role": "security",
"title": "Устранить прерывание: Проверить browser OMEMO backend",
"outcome": "Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.",
"depends_on": [
"S00-001",
"S00-002"
],
"requirements": [],
"scope": [
"docs/decisions/0003-crypto-library.md",
"experiments/0003-crypto-library/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/crypto.md",
"docs/decisions/README.md",
"state/blockers/S00-021.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Прочитать blocker report и handoff.",
"Воспроизвести конкретную причину.",
"Исправить только локальную причину в scope или уточнить внешний input.",
"Выполнить focused проверку и submit."
],
"acceptance": [
"Причина воспроизводимо устранена.",
"Исходная задача не завершена обходом требований."
],
"checks": [
"Повторить сценарий прерывания.",
"Проверить отсутствие изменений вне scope."
],
"context_budget_chars": 48000
},
{
"id": "S00-022",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-004",
"blocker_depth": 1,
"unblocks": [
"S00-004"
],
"role": "crypto",
"reviewer_role": "security",
"title": "Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint",
"outcome": "One independently reviewed, traceable primary-source outcome that establishes a verified browser/worker/WASM-capable OMEMO2 backend (a maintained library with a real web/WASM build and an OMEMO2/S ce-format mapping, or an approved external library/provider decision with license + support + browser constraints), preserving the verified audited session/ratchet engine and persistence from the existing spike, without a homegrown ratchet or ENC-009/ENC-011 bypass.",
"depends_on": [
"S00-001",
"S00-002",
"S00-021",
"S00-027",
"S00-029"
],
"requirements": [
"ENC-003",
"ENC-009",
"ENC-011"
],
"scope": [
"docs/decisions/0003-crypto-library.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/crypto.md",
"docs/decisions/README.md",
"state/blockers/S00-022.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Read S00-004 blocker evidence, ADR 0003, and accepted ENC-003/ENC-009/ENC-011 with ADRs 0000-0002.",
"Confirm the residual browser/worker/WASM OMEMO2 backend is unavailable from any maintained package on the reachable registry (reproduce wasm-unsupported probe and npm view queries).",
"Source or confirm a maintained browser/worker/WASM-capable OMEMO2 encryption layer: verify its web/WASM build, license, primary-source docs, support, and an OMEMO2 wire mapping against ENC-011; or record the concrete external product/administrative decision (library, provider, or license acceptance) required to bring such a backend in.",
"Run a focused known-answer OMEMO2 roundtrip in the web-compatible target and record actual results (or record an unsupported format blocker, not success) and update docs/decisions/0003-crypto-library.md; submit evidence and handoff for independent security review."
],
"acceptance": [
"A browser/worker/WASM-capable OMEMO2 backend is established with a real, verified web build and an OMEMO2 wire mapping from traceable primary sources, or an explicit external decision (with specific requested input) is recorded that unblocks sourcing it.",
"The OMEMO2 wire-format mapping does not bypass ENC-009/ENC-011 client-endpoint encryption.",
"No homegrown ratchet, no spec/plan/shared-contract change without plan, no secrets in evidence, and no out-of-scope edits; unsupported format recorded as blocker, not success."
],
"checks": [
"Known-answer OMEMO2 roundtrip in the web/WASM target, or a traceable upstream/external decision covering ENC-009/ENC-011.",
"Web/WASM asset presence verified (non-zero real footprint), license and support checked.",
"Independent security review confirms no bypass and no unsupported format counted as success."
],
"context_budget_chars": 48000
},
{
"id": "S00-023",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-005",
"blocker_depth": 1,
"unblocks": [
"S00-005"
],
"role": "storage",
"reviewer_role": "qa",
"title": "Устранить прерывание: Выбрать IndexedDB wrapper и migration policy",
"outcome": "Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.",
"depends_on": [
"S00-002",
"S00-025"
],
"requirements": [],
"scope": [
"docs/decisions/0004-storage-design.md",
"experiments/0004-storage-design/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/storage.md",
"docs/decisions/README.md",
"state/blockers/S00-023.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Прочитать blocker report и handoff.",
"Воспроизвести конкретную причину.",
"Исправить только локальную причину в scope или уточнить внешний input.",
"Выполнить focused проверку и submit."
],
"acceptance": [
"Причина воспроизводимо устранена.",
"Исходная задача не завершена обходом требований."
],
"checks": [
"Повторить сценарий прерывания.",
"Проверить отсутствие изменений вне scope."
],
"context_budget_chars": 48000
},
{
"id": "S00-024",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-003",
"blocker_depth": 2,
"unblocks": [
"S00-020"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain",
"outcome": "Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.",
"depends_on": [
"S00-001",
"S00-002",
"S00-017",
"S00-018",
"S00-019",
"S00-026"
],
"requirements": [],
"scope": [
"experiments/0002-xmpp-library/environment-access/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/protocol.md",
"docs/decisions/README.md",
"state/blockers/S00-020.md",
"state/blockers/S00-024.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Прочитать blocker report и handoff.",
"Воспроизвести конкретную причину.",
"Исправить только локальную причину в scope или уточнить внешний input.",
"Выполнить focused проверку и submit."
],
"acceptance": [
"Причина воспроизводимо устранена.",
"Исходная задача не завершена обходом требований."
],
"checks": [
"Повторить сценарий прерывания.",
"Проверить отсутствие изменений вне scope."
],
"context_budget_chars": 48000
},
{
"id": "S00-025",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-005",
"blocker_depth": 2,
"unblocks": [
"S00-023"
],
"role": "storage",
"reviewer_role": "qa",
"title": "Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy",
"outcome": "Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.",
"depends_on": [
"S00-002",
"S00-028"
],
"requirements": [],
"scope": [
"docs/decisions/0004-storage-design.md",
"experiments/0004-storage-design/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/storage.md",
"docs/decisions/README.md",
"state/blockers/S00-023.md",
"state/blockers/S00-025.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Прочитать blocker report и handoff.",
"Воспроизвести конкретную причину.",
"Исправить только локальную причину в scope или уточнить внешний input.",
"Выполнить focused проверку и submit."
],
"acceptance": [
"Причина воспроизводимо устранена.",
"Исходная задача не завершена обходом требований."
],
"checks": [
"Повторить сценарий прерывания.",
"Проверить отсутствие изменений вне scope."
],
"context_budget_chars": 48000
},
{
"id": "S00-026",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-003",
"blocker_depth": 3,
"unblocks": [
"S00-024"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Provide approved access to the pinned Linux experiment artifacts",
"outcome": "One independently verified approved access path supplies authentic pinned Linux Node/npm and integrity-verifiable parent locked dependencies for S00-020.",
"depends_on": [
"S00-001",
"S00-002",
"S00-017",
"S00-018",
"S00-019"
],
"requirements": [],
"scope": [
"experiments/0002-xmpp-library/environment-access/**"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/protocol.md",
"docs/decisions/README.md",
"state/blockers/S00-020.md",
"state/blockers/S00-024.md",
"state/blockers/S00-026.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Obtain environment-owner confirmation of restored approved HTTPS access or local authentic runtime/checksum/dependency artifact paths, without credentials in reports.",
"Verify Linux Node 24.21.0 vendor checksum material and executing Node 24.21.0/npm 11.19.0.",
"Install unchanged copies of parent manifest and lock only inside environment-access using npm ci with scripts disabled and TLS/integrity verification retained.",
"Record reproducible paths, checks and limits for independent review; leave original tasks unfinished."
],
"acceptance": [
"Approved access or authentic local artifacts are available and documented without policy bypass.",
"Exact pinned Linux runtime executes and vendor checksum verification succeeds.",
"Unchanged copied parent lock installs with integrity enforced and Stanza 12.22.1 available; no stack/spec/lock change."
],
"checks": [
"Repeat original official HTTPS retrieval or independently verify approved supplied artifact provenance/checksum.",
"Check exact node/npm versions and isolated locked npm ci with scripts disabled.",
"Verify source changes stay within environment-access and no TLS/proxy/engine bypass occurs."
],
"context_budget_chars": 48000
},
{
"id": "S00-027",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-004",
"blocker_depth": 2,
"unblocks": [
"S00-022"
],
"role": "ci-cd",
"reviewer_role": "security",
"title": "Restore approved npm registry connectivity for the OMEMO2 backend investigation",
"outcome": "One independently reviewed connectivity record proving that the fresh project agent can retrieve real libomemo.js and libsignal-client metadata and a pinned candidate artifact through approved HTTPS registry access.",
"depends_on": [
"S00-001",
"S00-002",
"S00-021"
],
"requirements": [
"ENC-003",
"ENC-009",
"ENC-011"
],
"scope": [
"docs/decisions/0003-registry-connectivity.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/crypto.md",
"docs/decisions/README.md",
"state/blockers/S00-022.md",
"state/blockers/S00-027.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Obtain environment-owner confirmation that approved proxy/DNS/registry access is restored for the agent sandbox; never record credentials.",
"Reproduce the two npm metadata queries using a temporary cache and normal certificate validation.",
"Verify a pinned candidate artifact can be downloaded and integrity checked in a temporary directory without executing package scripts.",
"Record exact commands, exit codes, versions and integrity result in the bounded connectivity document; submit for independent review."
],
"acceptance": [
"Both known-control and candidate npm metadata queries return actual registry responses without EAI_AGAIN.",
"A pinned candidate artifact download and integrity check succeed over normal HTTPS.",
"No TLS/proxy-policy bypass, secrets, crypto implementation or backend acceptance; remediation only restores network access."
],
"checks": [
"npm view @signalapp/libsignal-client version --json --fetch-retries=0 --fetch-timeout=15000 with a temporary cache",
"npm view libomemo.js version license dist.integrity dist.tarball --json --fetch-retries=0 --fetch-timeout=15000 with a temporary cache",
"Pinned artifact retrieval and registry integrity verification without install scripts"
],
"context_budget_chars": 48000
},
{
"id": "S00-028",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-005",
"blocker_depth": 3,
"unblocks": [
"S00-025"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Repair conflicting Codex harness sandbox arguments",
"outcome": "A reviewed compatible runner argv starts a fresh harness with project write sandbox without the mutually exclusive argument error.",
"depends_on": [
"S00-002",
"S00-030"
],
"requirements": [],
"scope": [
"runner.local.json",
"runner.example.json",
"docs/AUTOMATION.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/storage.md",
"docs/decisions/README.md",
"state/blockers/S00-023.md",
"state/blockers/S00-025.md",
"state/blockers/S00-028.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Inspect the recorded CLI usage error and configured argv without logging secrets",
"Obtain coordinator input for the active runner configuration and preserve project-only sandbox",
"Correct only conflicting launcher arguments and document the supported invocation",
"Validate argument compatibility with a bounded non-product smoke check and submit for independent review"
],
"acceptance": [
"The conflicting approval/sandbox combination is removed from the active launcher",
"Project write sandbox remains enforced",
"No original storage task is marked complete or executed during launcher repair"
],
"checks": [
"Inspect codex exec help and the selected argument combination",
"Run a bounded launcher smoke check without resuming a conversation or selecting product work"
],
"context_budget_chars": 48000
},
{
"id": "S00-029",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-004",
"blocker_depth": 2,
"unblocks": [
"S00-022"
],
"role": "orchestrator",
"reviewer_role": "security",
"title": "Record an approved browser OMEMO backend sourcing strategy",
"outcome": "One explicit architecture/security/product-owner sourcing decision naming the retained engine browser port or authorizing bounded evaluation of an alternative, with license/support/security-assessment and persistence compatibility requirements, without waiving ENC-003/009/011.",
"depends_on": [
"S00-001",
"S00-002",
"S00-021",
"S00-027"
],
"requirements": [
"ENC-003",
"ENC-009",
"ENC-011"
],
"scope": [
"docs/decisions/0003-browser-backend-decision.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/crypto.md",
"docs/decisions/README.md",
"state/blockers/S00-022.md",
"state/blockers/S00-029.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Obtain explicit owner input naming a backend strategy; do not infer approval from candidate availability.",
"Record traceable primary sources, exact target, license/support constraints and required independent security assessment and SCE path.",
"If alternative engine is selected, require separate reviewed plan change for preservation wording before S00-022 resumes implementation.",
"Submit only the bounded decision for independent security review; do not implement crypto."
],
"acceptance": [
"Explicit owner decision is recorded; the candidate is not declared audited or interoperable without evidence.",
"Decision preserves ENC-003/009/011 and identifies remaining browser/SCE/vector/persistence checks.",
"Any engine replacement is contingent on reviewed plan change; no silent replacement or implementation occurs."
],
"checks": [
"Verify owner input and primary-source traceability for named target/license/support/security and SCE boundaries.",
"Independent security review confirms decision addresses engine/persistence preservation and contains no requirement bypass."
],
"context_budget_chars": 48000
},
{
"id": "S00-030",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-005",
"blocker_depth": 4,
"unblocks": [
"S00-028"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Provide a valid Codex sandbox app-server socket directory",
"outcome": "Coordinator provides a valid private app-server socket environment and a bounded no-model Codex sandbox probe executes with project write restrictions intact.",
"depends_on": [
"S00-002",
"S00-031"
],
"requirements": [],
"scope": [
"docs/AUTOMATION.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/storage.md",
"docs/decisions/README.md",
"state/blockers/S00-023.md",
"state/blockers/S00-025.md",
"state/blockers/S00-028.md",
"state/blockers/S00-030.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Read S00-028 evidence and coordinate host socket environment ownership/mode correction without changing product files",
"Document the supplied supported host setup in docs/AUTOMATION.md",
"Run a bounded no-model project write/read/remove and filesystem restriction diagnostic",
"Record evidence and submit for independent QA"
],
"acceptance": [
"Codex sandbox no longer fails with the socket directory ownership/mode error",
"Project write sandbox remains enforced without bypass or broader writable roots",
"No storage task or additional model is executed"
],
"checks": [
"Bounded codex sandbox diagnostic executes a synthetic project write/read/remove probe",
"Verify effective filesystem restrictions and record exact exit codes"
],
"context_budget_chars": 48000
},
{
"id": "S00-031",
"stage": "S00",
"kind": "blocker",
"root_task": "S00-005",
"blocker_depth": 5,
"unblocks": [
"S00-030"
],
"role": "ci-cd",
"reviewer_role": "qa",
"title": "Supply a verified private Codex app-server socket environment",
"outcome": "Host owner supplies a verified valid actual app-server socket directory and a fresh no-model Codex sandbox probe passes under unchanged project write restrictions.",
"depends_on": [
"S00-002"
],
"requirements": [],
"scope": [
"docs/AUTOMATION.md"
],
"inputs": [
"docs/WEB_PROFILE.md",
"docs/ARCHITECTURE.md",
"docs/CONTRACTS.md",
"docs/TESTING.md",
"roles/storage.md",
"docs/decisions/README.md",
"state/blockers/S00-023.md",
"state/blockers/S00-025.md",
"state/blockers/S00-028.md",
"state/blockers/S00-030.md",
"state/blockers/S00-031.md"
],
"spec_sections": [],
"ui_sections": [],
"steps": [
"Obtain host owner confirmation of the actual Codex app-server socket directory and supported ownership/mode correction",
"Run one bounded no-model synthetic project write/read/remove and outside-project denial probe",
"Document the verified supplied host setup in docs/AUTOMATION.md",
"Record exact results and submit for independent QA"
],
"acceptance": [
"Actual app-server socket directory is valid and the original guard no longer fails",
"Synthetic project write/read/remove succeeds and outside-project write is denied without broader writable roots",
"No storage task or additional model executes"
],
"checks": [
"Bounded codex sandbox command exits 0 with positive project probe and negative outside-project probe",
"Record directory ownership/mode confirmation without secrets and exact diagnostic exit codes"
],
"context_budget_chars": 48000
},
{
"id": "S00-010",
"stage": "S00",
+57
View File
@@ -0,0 +1,57 @@
# S00-001: Утвердить Web-профиль
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: orchestrator
- Reviewer: security
- Зависимости: нет
- Требования: GEN-005, GEN-006, WEB-002
## Один результат
Паспорт scope разделяет Web и native-only требования и перечисляет внешние providers без secrets.
## Разрешённые пути
- `docs/decisions/0000-scope.md`
- `experiments/0000-scope/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/orchestrator.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. Паспорт scope разделяет Web и native-only требования и перечисляет внешние providers без secrets.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. Паспорт scope разделяет Web и native-only требования и перечисляет внешние providers без secrets.
2. Каждый excluded ID имеет причину
3. Web Push не называется живым closed-tab socket
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Каждый excluded ID имеет причину
2. Web Push не называется живым closed-tab socket
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-002: Выбрать framework и build runtime
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: orchestrator
- Reviewer: security
- Зависимости: S00-001, S00-011, S00-012
- Требования: NFR-007
## Один результат
ADR сравнивает два допустимых стека, фиксирует конкретные проверенные versions, lockfile и один минимальный rendering spike.
## Разрешённые пути
- `docs/decisions/0001-stack.md`
- `experiments/0001-stack/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/orchestrator.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. ADR сравнивает два допустимых стека, фиксирует конкретные проверенные versions, lockfile и один минимальный rendering spike.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. ADR сравнивает два допустимых стека, фиксирует конкретные проверенные versions, lockfile и один минимальный rendering spike.
2. Clean install/build spike
3. лицензии и runtime/browser ограничения записаны
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Clean install/build spike
2. лицензии и runtime/browser ограничения записаны
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-005: Выбрать IndexedDB wrapper и migration policy
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: storage
- Reviewer: qa
- Зависимости: S00-002, S00-023
- Требования: STORE-001, STORE-004
## Один результат
ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
## Разрешённые пути
- `docs/decisions/0004-storage-design.md`
- `experiments/0004-storage-design/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
2. Rollback experiment
3. quota/blocked upgrade сценарий
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Rollback experiment
2. quota/blocked upgrade сценарий
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-005: Выбрать IndexedDB wrapper и migration policy
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: storage
- Reviewer: qa
- Зависимости: S00-002, S00-023
- Требования: STORE-001, STORE-004
## Один результат
ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
## Разрешённые пути
- `docs/decisions/0004-storage-design.md`
- `experiments/0004-storage-design/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. ADR определяет transactions/quota/schema upgrade и key ownership для browser storage.
2. Rollback experiment
3. quota/blocked upgrade сценарий
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Rollback experiment
2. quota/blocked upgrade сценарий
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-006: Зафиксировать browser/reference matrix
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: orchestrator
- Reviewer: security
- Зависимости: S00-002
- Требования: NFR-003, NFR-005
## Один результат
Паспорт содержит concrete browsers/devices и измеряемые startup/render/cache budgets с методикой проверки.
## Разрешённые пути
- `docs/decisions/0005-browser-matrix.md`
- `experiments/0005-browser-matrix/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/orchestrator.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. Паспорт содержит concrete browsers/devices и измеряемые startup/render/cache budgets с методикой проверки.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. Паспорт содержит concrete browsers/devices и измеряемые startup/render/cache budgets с методикой проверки.
2. Каждый target воспроизводим
3. неподдержанный feature имеет fallback
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Каждый target воспроизводим
2. неподдержанный feature имеет fallback
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-007: Утвердить password и crypto unlock модель
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: security
- Reviewer: orchestrator
- Зависимости: S00-004, S00-005
- Требования: STORE-003, LOCK-004, SEC-001
## Один результат
ADR отделяет session password в памяти от persisted crypto key wrapping и app lock verifier.
## Разрешённые пути
- `docs/decisions/0006-secret-model.md`
- `experiments/0006-secret-model/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/security.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. ADR отделяет session password в памяти от persisted crypto key wrapping и app lock verifier.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. ADR отделяет session password в памяти от persisted crypto key wrapping и app lock verifier.
2. Reload требует login при отсутствии persistent solution
3. plaintext localStorage запрещён
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Reload требует login при отсутствии persistent solution
2. plaintext localStorage запрещён
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-009: Согласовать результаты spikes
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: orchestrator
- Reviewer: security
- Зависимости: S00-003, S00-004, S00-005, S00-006, S00-007, S00-008
- Требования: GEN-007
## Один результат
Принятое решение фиксирует совместимые stack/adapters и blockers до следующих этапов.
## Разрешённые пути
- `docs/decisions/0008-readiness.md`
- `experiments/0008-readiness/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/orchestrator.md
- docs/decisions/README.md
## Шаги
1. Прочитать приведённые requirements, контракт через реестр и evidence прямых dependencies.
2. До выбора стека: читать реальные первичные документы кандидатов, выполнить только разрешённый focused experiment, записать решение/ограничения. Не считать framework/backend уже принятым.
3. Работать только над одним указанным outcome. Если для него нужен новый shared contract — создать отдельную remediation через escalate, заблокировать original и завершить запуск; не расширять текущий scope.
4. Принятое решение фиксирует совместимые stack/adapters и blockers до следующих этапов.
5. Добавить focused fixtures/checks из списка ниже, выполнить их и записать фактический результат.
## Приёмка
1. Принятое решение фиксирует совместимые stack/adapters и blockers до следующих этапов.
2. Crypto/WSS APIs подтверждены экспериментом
3. несовместимость останавливает gate
4. Нет изменений вне scope; реальный результат/evidence и handoff позволяют другому агенту продолжить.
## Focused checks
1. Crypto/WSS APIs подтверждены экспериментом
2. несовместимость останавливает gate
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+1 -1
View File
@@ -5,7 +5,7 @@
- Этап: S00
- Оператор: protocol
- Reviewer: qa
- Зависимости: S00-001, S00-002, S00-017, S00-018, S00-019
- Зависимости: S00-001, S00-002, S00-017, S00-018, S00-019, S00-020
- Требования: ACC-004, NET-003, NET-004
## Один результат
+1 -1
View File
@@ -5,7 +5,7 @@
- Этап: S00
- Оператор: crypto
- Reviewer: security
- Зависимости: S00-001, S00-002
- Зависимости: S00-001, S00-002, S00-021, S00-022
- Требования: ENC-003, ENC-009, ENC-011
## Один результат
+1 -1
View File
@@ -5,7 +5,7 @@
- Этап: S00
- Оператор: storage
- Reviewer: qa
- Зависимости: S00-002
- Зависимости: S00-002, S00-023
- Требования: STORE-001, STORE-004
## Один результат
+58
View File
@@ -0,0 +1,58 @@
# S00-020: Restore access to the pinned Linux XMPP experiment toolchain
- Remediation для: S00-003; root: S00-003; depth: 1
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-001, S00-002, S00-017, S00-018, S00-019, S00-024
- Требования: ACC-004, NET-003, NET-004
## Один результат
One independently reviewed environment-access probe demonstrates authentic pinned Linux Node/npm and integrity-verified installation of the parent locked dependency graph without changing accepted stack or network security policy.
## Разрешённые пути
- `experiments/0002-xmpp-library/environment-access/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/protocol.md
- docs/decisions/README.md
- state/blockers/S00-020.md
## Шаги
1. Read S00-003 blocker evidence and accepted stack pins.
2. Obtain external approved HTTPS connectivity or authentic locally supplied pinned Linux runtime and locked dependency artifacts.
3. Verify vendor checksum for runtime, exact node/npm versions, and lock integrity with a script confined to the remediation directory; install a copy of the parent manifest/lock only inside that directory.
4. Document reproducible approved artifact paths or connectivity and actual results; submit for independent review.
## Приёмка
1. Node 24.21.0 and npm 11.19.0 execute on this Linux host with authenticated vendor checksum verification recorded.
2. Copied unchanged parent package-lock installs successfully with package integrity enforced and Stanza 12.22.1 available.
3. No stack/lock/spec changes, engine or TLS bypass, proxy policy bypass or secrets in evidence.
## Focused checks
1. Exact runtime version and official checksum comparison.
2. Isolated npm ci of unchanged parent manifest/lock with lifecycle scripts disabled and TLS verification retained.
3. Independent review repeats access/runtime/lock checks.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-021: Устранить прерывание: Проверить browser OMEMO backend
- Remediation для: S00-004; root: S00-004; depth: 1
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: crypto
- Reviewer: security
- Зависимости: S00-001, S00-002
- Требования: инженерная подготовка
## Один результат
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Разрешённые пути
- `docs/decisions/0003-crypto-library.md`
- `experiments/0003-crypto-library/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/crypto.md
- docs/decisions/README.md
- state/blockers/S00-021.md
## Шаги
1. Прочитать blocker report и handoff.
2. Воспроизвести конкретную причину.
3. Исправить только локальную причину в scope или уточнить внешний input.
4. Выполнить focused проверку и submit.
## Приёмка
1. Причина воспроизводимо устранена.
2. Исходная задача не завершена обходом требований.
## Focused checks
1. Повторить сценарий прерывания.
2. Проверить отсутствие изменений вне scope.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+58
View File
@@ -0,0 +1,58 @@
# S00-022: Confirm or source a verified browser/WASM-capable OMEMO2 backend for the Web client endpoint
- Remediation для: S00-004; root: S00-004; depth: 1
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: crypto
- Reviewer: security
- Зависимости: S00-001, S00-002, S00-021, S00-027, S00-029
- Требования: ENC-003, ENC-009, ENC-011
## Один результат
One independently reviewed, traceable primary-source outcome that establishes a verified browser/worker/WASM-capable OMEMO2 backend (a maintained library with a real web/WASM build and an OMEMO2/S ce-format mapping, or an approved external library/provider decision with license + support + browser constraints), preserving the verified audited session/ratchet engine and persistence from the existing spike, without a homegrown ratchet or ENC-009/ENC-011 bypass.
## Разрешённые пути
- `docs/decisions/0003-crypto-library.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/crypto.md
- docs/decisions/README.md
- state/blockers/S00-022.md
## Шаги
1. Read S00-004 blocker evidence, ADR 0003, and accepted ENC-003/ENC-009/ENC-011 with ADRs 0000-0002.
2. Confirm the residual browser/worker/WASM OMEMO2 backend is unavailable from any maintained package on the reachable registry (reproduce wasm-unsupported probe and npm view queries).
3. Source or confirm a maintained browser/worker/WASM-capable OMEMO2 encryption layer: verify its web/WASM build, license, primary-source docs, support, and an OMEMO2 wire mapping against ENC-011; or record the concrete external product/administrative decision (library, provider, or license acceptance) required to bring such a backend in.
4. Run a focused known-answer OMEMO2 roundtrip in the web-compatible target and record actual results (or record an unsupported format blocker, not success) and update docs/decisions/0003-crypto-library.md; submit evidence and handoff for independent security review.
## Приёмка
1. A browser/worker/WASM-capable OMEMO2 backend is established with a real, verified web build and an OMEMO2 wire mapping from traceable primary sources, or an explicit external decision (with specific requested input) is recorded that unblocks sourcing it.
2. The OMEMO2 wire-format mapping does not bypass ENC-009/ENC-011 client-endpoint encryption.
3. No homegrown ratchet, no spec/plan/shared-contract change without plan, no secrets in evidence, and no out-of-scope edits; unsupported format recorded as blocker, not success.
## Focused checks
1. Known-answer OMEMO2 roundtrip in the web/WASM target, or a traceable upstream/external decision covering ENC-009/ENC-011.
2. Web/WASM asset presence verified (non-zero real footprint), license and support checked.
3. Independent security review confirms no bypass and no unsupported format counted as success.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-023: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
- Remediation для: S00-005; root: S00-005; depth: 1
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: storage
- Reviewer: qa
- Зависимости: S00-002, S00-025
- Требования: инженерная подготовка
## Один результат
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Разрешённые пути
- `docs/decisions/0004-storage-design.md`
- `experiments/0004-storage-design/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
- state/blockers/S00-023.md
## Шаги
1. Прочитать blocker report и handoff.
2. Воспроизвести конкретную причину.
3. Исправить только локальную причину в scope или уточнить внешний input.
4. Выполнить focused проверку и submit.
## Приёмка
1. Причина воспроизводимо устранена.
2. Исходная задача не завершена обходом требований.
## Focused checks
1. Повторить сценарий прерывания.
2. Проверить отсутствие изменений вне scope.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+57
View File
@@ -0,0 +1,57 @@
# S00-024: Устранить прерывание: Restore access to the pinned Linux XMPP experiment toolchain
- Remediation для: S00-020; root: S00-003; depth: 2
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-001, S00-002, S00-017, S00-018, S00-019, S00-026
- Требования: инженерная подготовка
## Один результат
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Разрешённые пути
- `experiments/0002-xmpp-library/environment-access/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/protocol.md
- docs/decisions/README.md
- state/blockers/S00-020.md
- state/blockers/S00-024.md
## Шаги
1. Прочитать blocker report и handoff.
2. Воспроизвести конкретную причину.
3. Исправить только локальную причину в scope или уточнить внешний input.
4. Выполнить focused проверку и submit.
## Приёмка
1. Причина воспроизводимо устранена.
2. Исходная задача не завершена обходом требований.
## Focused checks
1. Повторить сценарий прерывания.
2. Проверить отсутствие изменений вне scope.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+58
View File
@@ -0,0 +1,58 @@
# S00-025: Устранить прерывание: Устранить прерывание: Выбрать IndexedDB wrapper и migration policy
- Remediation для: S00-023; root: S00-005; depth: 2
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: storage
- Reviewer: qa
- Зависимости: S00-002, S00-028
- Требования: инженерная подготовка
## Один результат
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Разрешённые пути
- `docs/decisions/0004-storage-design.md`
- `experiments/0004-storage-design/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
- state/blockers/S00-023.md
- state/blockers/S00-025.md
## Шаги
1. Прочитать blocker report и handoff.
2. Воспроизвести конкретную причину.
3. Исправить только локальную причину в scope или уточнить внешний input.
4. Выполнить focused проверку и submit.
## Приёмка
1. Причина воспроизводимо устранена.
2. Исходная задача не завершена обходом требований.
## Focused checks
1. Повторить сценарий прерывания.
2. Проверить отсутствие изменений вне scope.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+60
View File
@@ -0,0 +1,60 @@
# S00-026: Provide approved access to the pinned Linux experiment artifacts
- Remediation для: S00-024; root: S00-003; depth: 3
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-001, S00-002, S00-017, S00-018, S00-019
- Требования: инженерная подготовка
## Один результат
One independently verified approved access path supplies authentic pinned Linux Node/npm and integrity-verifiable parent locked dependencies for S00-020.
## Разрешённые пути
- `experiments/0002-xmpp-library/environment-access/**`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/protocol.md
- docs/decisions/README.md
- state/blockers/S00-020.md
- state/blockers/S00-024.md
- state/blockers/S00-026.md
## Шаги
1. Obtain environment-owner confirmation of restored approved HTTPS access or local authentic runtime/checksum/dependency artifact paths, without credentials in reports.
2. Verify Linux Node 24.21.0 vendor checksum material and executing Node 24.21.0/npm 11.19.0.
3. Install unchanged copies of parent manifest and lock only inside environment-access using npm ci with scripts disabled and TLS/integrity verification retained.
4. Record reproducible paths, checks and limits for independent review; leave original tasks unfinished.
## Приёмка
1. Approved access or authentic local artifacts are available and documented without policy bypass.
2. Exact pinned Linux runtime executes and vendor checksum verification succeeds.
3. Unchanged copied parent lock installs with integrity enforced and Stanza 12.22.1 available; no stack/spec/lock change.
## Focused checks
1. Repeat original official HTTPS retrieval or independently verify approved supplied artifact provenance/checksum.
2. Check exact node/npm versions and isolated locked npm ci with scripts disabled.
3. Verify source changes stay within environment-access and no TLS/proxy/engine bypass occurs.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+59
View File
@@ -0,0 +1,59 @@
# S00-027: Restore approved npm registry connectivity for the OMEMO2 backend investigation
- Remediation для: S00-022; root: S00-004; depth: 2
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: security
- Зависимости: S00-001, S00-002, S00-021
- Требования: ENC-003, ENC-009, ENC-011
## Один результат
One independently reviewed connectivity record proving that the fresh project agent can retrieve real libomemo.js and libsignal-client metadata and a pinned candidate artifact through approved HTTPS registry access.
## Разрешённые пути
- `docs/decisions/0003-registry-connectivity.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/crypto.md
- docs/decisions/README.md
- state/blockers/S00-022.md
- state/blockers/S00-027.md
## Шаги
1. Obtain environment-owner confirmation that approved proxy/DNS/registry access is restored for the agent sandbox; never record credentials.
2. Reproduce the two npm metadata queries using a temporary cache and normal certificate validation.
3. Verify a pinned candidate artifact can be downloaded and integrity checked in a temporary directory without executing package scripts.
4. Record exact commands, exit codes, versions and integrity result in the bounded connectivity document; submit for independent review.
## Приёмка
1. Both known-control and candidate npm metadata queries return actual registry responses without EAI_AGAIN.
2. A pinned candidate artifact download and integrity check succeed over normal HTTPS.
3. No TLS/proxy-policy bypass, secrets, crypto implementation or backend acceptance; remediation only restores network access.
## Focused checks
1. npm view @signalapp/libsignal-client version --json --fetch-retries=0 --fetch-timeout=15000 with a temporary cache
2. npm view libomemo.js version license dist.integrity dist.tarball --json --fetch-retries=0 --fetch-timeout=15000 with a temporary cache
3. Pinned artifact retrieval and registry integrity verification without install scripts
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+61
View File
@@ -0,0 +1,61 @@
# S00-028: Repair conflicting Codex harness sandbox arguments
- Remediation для: S00-025; root: S00-005; depth: 3
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-002, S00-030
- Требования: инженерная подготовка
## Один результат
A reviewed compatible runner argv starts a fresh harness with project write sandbox without the mutually exclusive argument error.
## Разрешённые пути
- `runner.local.json`
- `runner.example.json`
- `docs/AUTOMATION.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
- state/blockers/S00-023.md
- state/blockers/S00-025.md
- state/blockers/S00-028.md
## Шаги
1. Inspect the recorded CLI usage error and configured argv without logging secrets
2. Obtain coordinator input for the active runner configuration and preserve project-only sandbox
3. Correct only conflicting launcher arguments and document the supported invocation
4. Validate argument compatibility with a bounded non-product smoke check and submit for independent review
## Приёмка
1. The conflicting approval/sandbox combination is removed from the active launcher
2. Project write sandbox remains enforced
3. No original storage task is marked complete or executed during launcher repair
## Focused checks
1. Inspect codex exec help and the selected argument combination
2. Run a bounded launcher smoke check without resuming a conversation or selecting product work
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+58
View File
@@ -0,0 +1,58 @@
# S00-029: Record an approved browser OMEMO backend sourcing strategy
- Remediation для: S00-022; root: S00-004; depth: 2
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: orchestrator
- Reviewer: security
- Зависимости: S00-001, S00-002, S00-021, S00-027
- Требования: ENC-003, ENC-009, ENC-011
## Один результат
One explicit architecture/security/product-owner sourcing decision naming the retained engine browser port or authorizing bounded evaluation of an alternative, with license/support/security-assessment and persistence compatibility requirements, without waiving ENC-003/009/011.
## Разрешённые пути
- `docs/decisions/0003-browser-backend-decision.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/crypto.md
- docs/decisions/README.md
- state/blockers/S00-022.md
- state/blockers/S00-029.md
## Шаги
1. Obtain explicit owner input naming a backend strategy; do not infer approval from candidate availability.
2. Record traceable primary sources, exact target, license/support constraints and required independent security assessment and SCE path.
3. If alternative engine is selected, require separate reviewed plan change for preservation wording before S00-022 resumes implementation.
4. Submit only the bounded decision for independent security review; do not implement crypto.
## Приёмка
1. Explicit owner decision is recorded; the candidate is not declared audited or interoperable without evidence.
2. Decision preserves ENC-003/009/011 and identifies remaining browser/SCE/vector/persistence checks.
3. Any engine replacement is contingent on reviewed plan change; no silent replacement or implementation occurs.
## Focused checks
1. Verify owner input and primary-source traceability for named target/license/support/security and SCE boundaries.
2. Independent security review confirms decision addresses engine/persistence preservation and contains no requirement bypass.
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+60
View File
@@ -0,0 +1,60 @@
# S00-030: Provide a valid Codex sandbox app-server socket directory
- Remediation для: S00-028; root: S00-005; depth: 4
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-002, S00-031
- Требования: инженерная подготовка
## Один результат
Coordinator provides a valid private app-server socket environment and a bounded no-model Codex sandbox probe executes with project write restrictions intact.
## Разрешённые пути
- `docs/AUTOMATION.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
- state/blockers/S00-023.md
- state/blockers/S00-025.md
- state/blockers/S00-028.md
- state/blockers/S00-030.md
## Шаги
1. Read S00-028 evidence and coordinate host socket environment ownership/mode correction without changing product files
2. Document the supplied supported host setup in docs/AUTOMATION.md
3. Run a bounded no-model project write/read/remove and filesystem restriction diagnostic
4. Record evidence and submit for independent QA
## Приёмка
1. Codex sandbox no longer fails with the socket directory ownership/mode error
2. Project write sandbox remains enforced without bypass or broader writable roots
3. No storage task or additional model is executed
## Focused checks
1. Bounded codex sandbox diagnostic executes a synthetic project write/read/remove probe
2. Verify effective filesystem restrictions and record exact exit codes
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+61
View File
@@ -0,0 +1,61 @@
# S00-031: Supply a verified private Codex app-server socket environment
- Remediation для: S00-030; root: S00-005; depth: 5
> Generated from planning/tasks.json; status is in state/tasks.json.
- Этап: S00
- Оператор: ci-cd
- Reviewer: qa
- Зависимости: S00-002
- Требования: инженерная подготовка
## Один результат
Host owner supplies a verified valid actual app-server socket directory and a fresh no-model Codex sandbox probe passes under unchanged project write restrictions.
## Разрешённые пути
- `docs/AUTOMATION.md`
Не изменять другие modules, shared contracts или spec snapshots без изменения плана. Stem без расширения разрешает принятые stack-specific extensions и focused test рядом.
## Прочитать
- docs/WEB_PROFILE.md
- docs/ARCHITECTURE.md
- docs/CONTRACTS.md
- docs/TESTING.md
- roles/storage.md
- docs/decisions/README.md
- state/blockers/S00-023.md
- state/blockers/S00-025.md
- state/blockers/S00-028.md
- state/blockers/S00-030.md
- state/blockers/S00-031.md
## Шаги
1. Obtain host owner confirmation of the actual Codex app-server socket directory and supported ownership/mode correction
2. Run one bounded no-model synthetic project write/read/remove and outside-project denial probe
3. Document the verified supplied host setup in docs/AUTOMATION.md
4. Record exact results and submit for independent QA
## Приёмка
1. Actual app-server socket directory is valid and the original guard no longer fails
2. Synthetic project write/read/remove succeeds and outside-project write is denied without broader writable roots
3. No storage task or additional model executes
## Focused checks
1. Bounded codex sandbox command exits 0 with positive project probe and negative outside-project probe
2. Record directory ownership/mode confirmation without secrets and exact diagnostic exit codes
## Блокер и завершение запуска
Для всех ролей scope — только эта задача. При блокере создать issue JSON по state/ISSUE_TEMPLATE.json, вызвать escalate с текущим ID/role/agent, проверить status=blocked и resolution_task, затем немедленно завершить запуск. Не решать blocker и не выбирать следующую задачу самому. Новый агент назначается runner. Правки metadata через CLI — единственное исключение scope.
## Передача
Сохранить evidence/handoff; submit → review и завершить запуск. Reviewer также выполняет только эту задачу, включая обязательное escalate при блокере.
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
#
# Kilo Code CLI adapter for Luma Web runner.
#
# The runner launches the configured argv with the task prompt piped to this
# process's standard input (and stdout/stderr redirected to state/runs/<agent>/output.log).
# Kilo's `run` subcommand does not consume a prompt from stdin, so we read it
# here and pass it to `kilo run` as the message argument.
#
# Scope:
# - only reads the prompt from stdin;
# - forwards the runner-provided project root as the working directory;
# - never writes project sources on its own (Kilo does the actual work).
set -euo pipefail
# Read the full prompt from stdin (the runner pipes the prompt file here).
prompt="$(cat)"
root="$LUMA_PROJECT_ROOT"
if [ -z "$root" ]; then
echo "kilo-agent: LUMA_PROJECT_ROOT is not set" >&2
exit 1
fi
if [ -z "$prompt" ]; then
echo "kilo-agent: empty prompt received on stdin" >&2
exit 1
fi
# Our plan file is committed to the repo; the runner also commits afterwards.
KILO_ARGS=(run --auto --dir "$root")
# Keep a session title informative for later `kilo session` inspection.
KILO_ARGS+=(--title "luma-wave-$LUMA_ROLE")
# --auto auto-approves all tool permissions so the headless runner does not
# stall waiting for interactive approval of bash/read/edit/task tools.
exec kilo "${KILO_ARGS[@]}" "$prompt"
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-020
- Original task: S00-003
- Root task: S00-003
- Reporter: auto-protocol-59f4d359b339
- Role: protocol
- Original owner: auto-protocol-59f4d359b339
- Waits for user/external condition: True
## Cause
S00-003 cannot reproduce accepted Node 24.21.0/npm 11.19.0 or install locked Stanza: host tools are 24.1.0/11.3.0, parent node_modules is absent, and normal official HTTPS retrieval fails with unresolved configured proxy.
## One remediation outcome
One independently reviewed environment-access probe demonstrates authentic pinned Linux Node/npm and integrity-verified installation of the parent locked dependency graph without changing accepted stack or network security policy.
## Evidence / reproduction
node --version => v24.1.0; npm --version => 11.3.0; parent node_modules/stanza absent; curl official v24.21.0 SHASUMS256.txt => exit 5 Could not resolve proxy. Details: state/evidence/S00-003-auto-protocol-59f4d359b339.md
The reporting agent must stop now. A new agent implements only S00-020.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-021
- Original task: S00-004
- Root task: S00-004
- Reporter: auto-crypto-8bf3dcf711b9
- Role: crypto
- Original owner: auto-crypto-8bf3dcf711b9
- Waits for user/external condition: False
## Cause
Agent timed out; process terminated before escalation
## One remediation outcome
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Evidence / reproduction
See original handoff and task evidence.
The reporting agent must stop now. A new agent implements only S00-021.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-022
- Original task: S00-004
- Root task: S00-004
- Reporter: auto-crypto-fe1c81c17ded
- Role: crypto
- Original owner: auto-crypto-fe1c81c17ded
- Waits for user/external condition: True
## Cause
S00-004 acceptance requires establishing legacy/OMEMO2 AND worker/wasm AND persistence without a homegrown ratchet. Reproduced (this run) probes and live public-registry queries confirm only the audited session/ratchet engine + persistence are verified (@signalapp/libsignal-client@0.105.0, Node native addon, zero .wasm). The browser/worker/WASM deployment and the OMEMO/OMEMO2 wire layer are not available from any maintained package on the reachable registry (@signalapp/libsignal, @converse/omemo, @xmpp/omemo, omemo2, omemo-js, omemo-client, omemo-top, omemo-wasm all E404). ENC-009 forbids a homegrown algorithm/ratchet and ENC-011 forbids substituting media AES-GCM for the OMEMO2 wire format, so the spike cannot synthesize a browser backend inside S00-004 scope.
## One remediation outcome
One independently reviewed, traceable primary-source outcome that establishes a verified browser/worker/WASM-capable OMEMO2 backend (a maintained library with a real web/WASM build and an OMEMO2/S ce-format mapping, or an approved external library/provider decision with license + support + browser constraints), preserving the verified audited session/ratchet engine and persistence from the existing spike, without a homegrown ratchet or ENC-009/ENC-011 bypass.
## Evidence / reproduction
cd experiments/0003-crypto-library && node session-probe/smoke.mjs (exit 0, SMOKE_PASS, 631-byte byte-for-byte session record) shows session/ratchet+persistence verified; node session-probe/wasm-unsupported.mjs (exit 2, wasmFiles:0, prebuilds native *.node only) shows browser/worker/WASM NOT established. npm view on live public registry: @signalapp/libsignal-client=0.105.0; @signalapp/libsignal=@converse/omemo=@xmpp/omemo=omemo2=omemo-js=omemo-client=omemo-top=omemo-wasm=E404. Details: see this run handoff and ADR 0003.
The reporting agent must stop now. A new agent implements only S00-022.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-023
- Original task: S00-005
- Root task: S00-005
- Reporter: auto-storage-88c3286c6641
- Role: storage
- Original owner: auto-storage-88c3286c6641
- Waits for user/external condition: False
## Cause
Previous runner session was interrupted before submit/escalate; processes stopped. User now explicitly requests resuming development. Preserve partial artifacts, verify them in a fresh bounded remediation, and return original to a new executor only after independent review.
## One remediation outcome
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Evidence / reproduction
See original handoff and task evidence.
The reporting agent must stop now. A new agent implements only S00-023.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-024
- Original task: S00-020
- Root task: S00-003
- Reporter: auto-ci-cd-5a18c8aa1b42
- Role: ci-cd
- Original owner: auto-ci-cd-5a18c8aa1b42
- Waits for user/external condition: False
## Cause
Previous runner session was interrupted before submit/escalate; processes stopped. User now explicitly requests resuming development. Preserve partial artifacts, verify them in a fresh bounded remediation, and return original to a new executor only after independent review.
## One remediation outcome
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Evidence / reproduction
See original handoff and task evidence.
The reporting agent must stop now. A new agent implements only S00-024.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-025
- Original task: S00-023
- Root task: S00-005
- Reporter: auto-storage-87486895c792
- Role: storage
- Original owner: auto-storage-87486895c792
- Waits for user/external condition: False
## Cause
Agent exited with code 2
## One remediation outcome
Устранить одну указанную причину прерывания и доказать готовность исходной задачи к новому запуску; не выполнять соседние задачи.
## Evidence / reproduction
See original handoff and task evidence.
The reporting agent must stop now. A new agent implements only S00-025.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-026
- Original task: S00-024
- Root task: S00-003
- Reporter: auto-ci-cd-988167c541cf
- Role: ci-cd
- Original owner: auto-ci-cd-988167c541cf
- Waits for user/external condition: True
## Cause
S00-024 cannot establish readiness for S00-020: official checksum retrieval fails with configured proxy DNS error and host Node/npm differ from accepted pins; no partial environment-access artifacts exist.
## One remediation outcome
One independently verified approved access path supplies authentic pinned Linux Node/npm and integrity-verifiable parent locked dependencies for S00-020.
## Evidence / reproduction
node --version: v24.1.0; npm --version: 11.3.0. Official HTTPS SHASUMS256.txt curl exits 5, Could not resolve proxy. See state/evidence/S00-024-auto-ci-cd-988167c541cf.md.
The reporting agent must stop now. A new agent implements only S00-026.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-027
- Original task: S00-022
- Root task: S00-004
- Reporter: auto-crypto-316a193d6c3e
- Role: crypto
- Original owner: auto-crypto-316a193d6c3e
- Waits for user/external condition: True
## Cause
All ten live npm metadata queries fail with EAI_AGAIN resolving the configured proxy, including the known libsignal-client control. Required release and artifact verification for S00-022 cannot proceed; failures are not E404 evidence.
## One remediation outcome
One independently reviewed connectivity record proving that the fresh project agent can retrieve real libomemo.js and libsignal-client metadata and a pinned candidate artifact through approved HTTPS registry access.
## Evidence / reproduction
npm view libomemo.js version license dist-tags --json --fetch-retries=0 --fetch-timeout=15000 --cache=/tmp/luma-s00-022-npm-cache -> exit 1 EAI_AGAIN; same failure for @signalapp/libsignal-client. See state/evidence/S00-022-auto-crypto-316a193d6c3e.md.
The reporting agent must stop now. A new agent implements only S00-027.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-028
- Original task: S00-025
- Root task: S00-005
- Reporter: auto-storage-ae5930b6fbdf
- Role: storage
- Original owner: auto-storage-ae5930b6fbdf
- Waits for user/external condition: True
## Cause
S00-023 harness exited before task execution: codex exec rejected mutually exclusive --approve-for-me and --sandbox flags. Repairing coordinator argv is outside S00-025 storage-only scope.
## One remediation outcome
A reviewed compatible runner argv starts a fresh harness with project write sandbox without the mutually exclusive argument error.
## Evidence / reproduction
state/runs/auto-storage-87486895c792/run.json records returncode=2 and identical start/finish timestamp. output.log reports: error: the argument '--approve-for-me' cannot be used with '--sandbox <SANDBOX_MODE>'. No nested model launched to reproduce.
The reporting agent must stop now. A new agent implements only S00-028.
+22
View File
@@ -0,0 +1,22 @@
# Blocker S00-029
- Original task: S00-022
- Root task: S00-004
- Reporter: auto-crypto-bcd7f2a71667
- Role: crypto
- Original owner: auto-crypto-bcd7f2a71667
- Waits for user/external condition: True
## Cause
Pinned libomemo.js@3.0.0 supplies real embedded WASM but is a different unaudited ratchet and excludes consumer SCE; no approved decision reconciles it with S00-022 retained audited engine/persistence requirement.
## One remediation outcome
One explicit architecture/security/product-owner sourcing decision naming the retained engine browser port or authorizing bounded evaluation of an alternative, with license/support/security-assessment and persistence compatibility requirements, without waiving ENC-003/009/011.
## Evidence / reproduction
npm view libomemo.js@3.0.0 succeeds; pinned tarball README and bundle inspection establish candidate scope, embedded WASM and unaudited/SCE boundaries. Existing wasm-unsupported probe exits 2. See state/evidence/S00-022-auto-crypto-bcd7f2a71667.md.
The reporting agent must stop now. A new agent implements only S00-029.
+23
View File
@@ -0,0 +1,23 @@
# Blocker S00-030
- Original task: S00-028
- Root task: S00-005
- Reporter: auto-ci-cd-211cf3a2bd08
- Role: ci-cd
- Original owner: auto-ci-cd-211cf3a2bd08
- Waits for user/external condition: True
## Cause
S00-028 cannot independently validate project filesystem sandbox: installed Codex sandbox exits before command execution because the app-server socket directory must be user-owned with mode 0700. Host socket repair is outside launcher arguments scope.
Automatic blocker limit reached; explicit external resolution required.
## One remediation outcome
Coordinator provides a valid private app-server socket environment and a bounded no-model Codex sandbox probe executes with project write restrictions intact.
## Evidence / reproduction
codex -c 'sandbox_mode="workspace-write"' -c sandbox_workspace_write.network_access=true sandbox -- python3 -c '<synthetic project write/read/remove probe>' exits 1: error building bubblewrap command: app-server socket directory must be a user-owned directory with mode 0700. See state/evidence/S00-028-auto-ci-cd-211cf3a2bd08.md.
The reporting agent must stop now. A new agent implements only S00-030.
+23
View File
@@ -0,0 +1,23 @@
# Blocker S00-031
- Original task: S00-030
- Root task: S00-005
- Reporter: auto-ci-cd-d37c9faf28f3
- Role: ci-cd
- Original owner: auto-ci-cd-d37c9faf28f3
- Waits for user/external condition: True
## Cause
S00-030 no-model sandbox diagnostic still exits 1 before execution with the original socket directory ownership/mode guard, despite XDG_RUNTIME_DIR being user-owned mode 0700. Actual socket directory repair requires host owner input.
Automatic blocker limit reached; explicit external resolution required.
## One remediation outcome
Host owner supplies a verified valid actual app-server socket directory and a fresh no-model Codex sandbox probe passes under unchanged project write restrictions.
## Evidence / reproduction
Run codex -c sandbox_mode="workspace-write" -c sandbox_workspace_write.network_access=true sandbox -- python3 -c <synthetic probe>; exit 1: app-server socket directory must be a user-owned directory with mode 0700. See state/evidence/S00-030-auto-ci-cd-d37c9faf28f3.md.
The reporting agent must stop now. A new agent implements only S00-031.
@@ -0,0 +1,4 @@
Verify pinned Linux XMPP toolchain restoration
Changes: Refresh the environment-access result and document S00-020 restoration, accepted prerequisites and independent QA handoff; preserve probe code and parent dependency graph.
Validation: verify.py passed official HTTPS vendor checksum, Node 24.21.0/npm 11.19.0, fresh-cache strict npm ci and Stanza import. Separate artifact/input/version/strict-setting assertions and tasks.py validate passed. No browser/WSS or product readiness check performed.
@@ -0,0 +1,4 @@
Document verified HTTPS npm registry connectivity
Changes: Add the bounded S00-027 connectivity record with owner confirmation, fresh registry responses, pinned libomemo.js artifact integrity and independent review handoff.
Validation: Both live metadata queries and pinned metadata query exited 0; HTTPS archive SHA512 matched registry SRI without executing scripts. make check exited 0 with 72 tests; whitespace and scope inspected. No crypto/backend acceptance or application build claimed.
@@ -0,0 +1,4 @@
Verify pinned Linux runtime and isolated locked dependency access
Changes: Add a bounded reproducible HTTPS/checksum/runtime probe under environment-access, vendor checksums, unchanged parent manifest/lock copies, strict npm settings, result and reproduction notes. Record evidence and QA handoff; submit S00-026 without completing parent tasks.
Validation: Run verify.py twice successfully: vendor Linux archive SHA256 matched, Node 24.21.0/npm 11.19.0 executed, fresh-cache npm ci installed eight integrity-locked packages with scripts disabled, parent bytes stayed unchanged and Stanza import passed. tasks.py validate, git diff --check and scoped file whitespace checks passed. No browser/WSS, detached signature or provenance audit performed.
@@ -0,0 +1,4 @@
Verify restored pinned Linux XMPP environment access
Changes: Add the S00-024 continuation checkpoint, refresh verified runtime/cache paths, and replace the blocked handoff with QA and S00-020 restart instructions.
Validation: Existing verify.py passed official HTTPS/checksum retrieval, Node 24.21.0/npm 11.19.0, fresh-cache locked npm ci and Stanza import. Independent archive/input/version/config assertions, tasks.py validate and diff/scope/whitespace checks passed. Browser/WSS and product readiness remain unverified.
@@ -0,0 +1,4 @@
Confirm the host-context sandbox diagnostic for socket recovery
Changes: Clarify S00-030 host execution guidance in docs/AUTOMATION.md and record the successful bounded diagnostic with QA handoff.
Validation: Approved host wrapper and inner workspace-write probe exit 0; project write/read/remove passed, outside write denied EROFS 30. make check exit 0 with 72 tooling tests; git diff --check exit 0. No additional model or storage task ran; app readiness is outside scope.
@@ -0,0 +1,4 @@
Document verified host context for the Codex sandbox probe
Changes: Add host-specific socket mask diagnosis and bounded inner workspace-write invocation to automation guidance; record independent evidence and QA handoff.
Validation: Approved host stat confirmed uid1000/mode0700; no-model inner sandbox exited 0 with project write/read/remove and outside write denial EROFS30. Final make check passed 72 tests and git diff --check passed. Initial verbose draft exceeded prompt budgets; compacted guidance passed the rerun. No storage task or model executed.
@@ -0,0 +1,4 @@
Document and verify the compatible Codex runner invocation
Changes: Document the coordinator-selected Codex 0.159.0 argv without conflicting sandbox flags; record parser and filesystem evidence plus QA handoff. Preserve existing local configuration and portable example.
Validation: Exact active argv help and approved host wrapper with inner workspace-write probe exited 0; project write/read/remove passed and outside write denied EROFS30. Final make check passed 72 tests after shortening the documentation to meet prompt budgets. No additional model or storage work ran.
Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff. Show more