Compare commits
46
Commits
e39724f215
...
c0ce1bd026
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0ce1bd026 | ||
|
|
ce4d994598 | ||
|
|
0e2b3098df | ||
|
|
8f918bc040 | ||
|
|
51a414d386 | ||
|
|
e08c0ab590 | ||
|
|
30ee500054 | ||
|
|
6ac04950bf | ||
|
|
43610f3356 | ||
|
|
0d29bdb3ed | ||
|
|
ff99ee7b62 | ||
|
|
ff5a89647a | ||
|
|
b4a27f2354 | ||
|
|
263d365640 | ||
|
|
ace4f674d5 | ||
|
|
865f1e7a72 | ||
|
|
0bf7504900 | ||
|
|
176149b832 | ||
|
|
a53f0f1a14 | ||
|
|
28320ff35a | ||
|
|
8bfaa06e64 | ||
|
|
0b60f3d84e | ||
|
|
8db4e4ef35 | ||
|
|
ae00f3502e | ||
|
|
3b92736a99 | ||
|
|
4bad1f228e | ||
|
|
ca20031bf9 | ||
|
|
2af4f143d8 | ||
|
|
bac7706480 | ||
|
|
3fd75a818f | ||
|
|
927728daf1 | ||
|
|
0d97f48929 | ||
|
|
a338d3a1dd | ||
|
|
956d9459a6 | ||
|
|
6f0b4029c4 | ||
|
|
a971d3cf4b | ||
|
|
7afc642a9f | ||
|
|
5f4d044444 | ||
|
|
55d9c69a21 | ||
|
|
a02412d7c7 | ||
|
|
30f7f222d2 | ||
|
|
71675b1cc0 | ||
|
|
3784fa7b99 | ||
|
|
0d3dc123b5 | ||
|
|
19474445f0 | ||
|
|
452ec2fc08 |
No files matched your search
@@ -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, а не названием роли.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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 & 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
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
registry=https://registry.npmjs.org/
|
||||
strict-ssl=true
|
||||
engine-strict=true
|
||||
@@ -0,0 +1 @@
|
||||
24.21.0
|
||||
@@ -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
@@ -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');
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
## Граница этапа
|
||||
|
||||
@@ -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
|
||||
|
||||
|
+647
-3
@@ -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",
|
||||
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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
|
||||
|
||||
## Один результат
|
||||
|
||||
@@ -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
|
||||
|
||||
## Один результат
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- Этап: S00
|
||||
- Оператор: storage
|
||||
- Reviewer: qa
|
||||
- Зависимости: S00-002
|
||||
- Зависимости: S00-002, S00-023
|
||||
- Требования: STORE-001, STORE-004
|
||||
|
||||
## Один результат
|
||||
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
@@ -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 при блокере.
|
||||
Executable
+38
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
Reference in new issue
Block a user