feat(logs): logs données restructurés — identité en préfixe, trace résolution/barrière, inspection outbox
Chemin données bas-niveau du polyfill rendu lisible pour diagnostiquer en session live : - Format identité-en-premier : `[<identity>][polyfill] OP shortNuri (label)` ; console.error épars (store-registry, inbox) unifiés au même préfixe. - Trace (derrière le flag debug) : issue de la barrière ensureRepoOpen (synced|timed-out + durée) et résultat sémantique de chaque étage de résolution (resolvePointer/canonicalDoc/resolveAccount/resolveShimDoc/readScopeIndex). - outbox-log.ts (nouveau) : inspection read-only de l'outbox hors-ligne au démarrage de session ; console.warn si non vide (anomalie, toujours visible), sous flag si vide. Le comptage seul est fiable (payloads BARE opaques côté JS). Pas de changement fonctionnel. bun test 126 pass ; tsc 0 erreur. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GbGgNEHRejVKoREvFuDFg
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* outbox-log — read-only diagnostic inspection of `@ng-org/web`'s offline write
|
||||
* outbox, at session bootstrap (polyfill-era, low-level-data-path trace).
|
||||
*
|
||||
* ── What this surfaces ──────────────────────────────────────────────────────
|
||||
* `@ng-org/web` (the real injected SDK) queues writes made while offline/
|
||||
* disconnected in an "outbox", persisted client-side in `sessionStorage` by the
|
||||
* WASM verifier (see `sdk/rust/src/local_broker.rs` `JsStorageConfig::
|
||||
* get_js_storage_config` in the `nextgraph-rs` core repo — read-only reference,
|
||||
* NOT vendored here). A non-empty outbox at session start is an ANOMALY worth
|
||||
* surfacing unconditionally: it means writes from a previous (disconnected)
|
||||
* session are still queued and haven't reached the broker yet.
|
||||
*
|
||||
* ── sessionStorage key shapes (verified in the core repo, not guessed) ──────
|
||||
* The outbox is keyed per LOCAL PEER id (`peer_id`, the persistent local peer's
|
||||
* pubkey — NOT the ng-eventually shim's `account`/`identity` concept), via two
|
||||
* key families written by `session_write`/read by `session_read`:
|
||||
* - `ng_peer_last_seq@<peerId>` — the peer's last reserved seq number.
|
||||
* - `ng_outboxes@<peerId>@start` — the seq number the outbox starts at.
|
||||
* - `ng_outboxes@<peerId>@<00000-idx>` — one queued (base64url + BARE-encoded)
|
||||
* event per zero-padded index, contiguous from 0 until the first miss (the
|
||||
* exact shape `outbox_read_function` walks — see `local_broker.rs`).
|
||||
* We don't know `peerId` ahead of time (it's internal to the injected SDK), so
|
||||
* we DISCOVER it by scanning `sessionStorage` for `@start` markers instead of
|
||||
* requiring it to be passed in — this also means the probe works unchanged
|
||||
* however many peers/wallets the browser session has touched.
|
||||
*
|
||||
* ── Read-only, defensive, best-effort ────────────────────────────────────────
|
||||
* This NEVER writes or deletes a key (unlike the real `outbox_read_function`,
|
||||
* which drains on read) — it only counts. The queued event bytes are opaque
|
||||
* (BARE-encoded Rust structs, base64url'd); decoding them to report concrete
|
||||
* write TARGETS (topics/docs) would mean duplicating the WASM verifier's wire
|
||||
* format in this polyfill, which is explicitly out of scope (SDK internals live
|
||||
* in the `@ng-eventually/client`-independent core repo, per this repo's
|
||||
* doctrine) — so only the pending COUNT is reported, never fabricated targets.
|
||||
* `sessionStorage` access itself can throw (sandboxed iframe, disabled storage —
|
||||
* see the exact error string handled in the core repo's `main.ts`
|
||||
* `convert_error`), so the whole probe is wrapped in one try/catch: unavailable
|
||||
* → skip silently, never throw into the caller.
|
||||
*
|
||||
* Polyfill-era; removed at the real multi-store migration alongside the rest of
|
||||
* this low-level trace instrumentation.
|
||||
*/
|
||||
|
||||
import { accessLogPrefix, logStage } from "./access-log";
|
||||
|
||||
/** Matches an outbox "start" marker key, capturing the peer id. */
|
||||
const OUTBOX_START_KEY = /^ng_outboxes@(.+)@start$/;
|
||||
|
||||
/** Safety bound on the per-peer index walk, so a corrupted/mocked storage
|
||||
* (e.g. a `@start` marker with no matching index gaps) can't spin forever.
|
||||
* Real outboxes are queued-while-offline writes — nowhere near this size. */
|
||||
const MAX_SCAN_PER_PEER = 10_000;
|
||||
|
||||
/**
|
||||
* Inspect the outbox NOW and log its state — count only, never targets (see
|
||||
* module doc). Non-empty → `console.warn`, ALWAYS printed (anomaly, not gated
|
||||
* by the access-log flag). Empty → a normal {@link logStage} line, gated by the
|
||||
* access-log flag like the rest of the low-level trace. Read-only: never
|
||||
* mutates `sessionStorage`. Never throws.
|
||||
*/
|
||||
export function inspectOutbox(): void {
|
||||
try {
|
||||
const storage = (globalThis as any)?.sessionStorage;
|
||||
if (!storage) return;
|
||||
|
||||
const peers = new Set<string>();
|
||||
const length: number = storage.length ?? 0;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const key = storage.key?.(i);
|
||||
if (!key) continue;
|
||||
const m = OUTBOX_START_KEY.exec(key);
|
||||
const peerId = m?.[1];
|
||||
if (peerId) peers.add(peerId);
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
for (const peer of peers) {
|
||||
let idx = 0;
|
||||
while (idx < MAX_SCAN_PER_PEER) {
|
||||
const idxKey = "ng_outboxes@" + peer + "@" + String(idx).padStart(5, "0");
|
||||
if (storage.getItem(idxKey) === null) break;
|
||||
total++;
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total > 0) {
|
||||
// Anomaly: ALWAYS visible, regardless of the access-log flag.
|
||||
console.warn(accessLogPrefix() + " OUTBOX " + total + " pending write(s)");
|
||||
} else {
|
||||
logStage("OUTBOX empty");
|
||||
}
|
||||
} catch {
|
||||
// sessionStorage unavailable / access denied — skip silently. Diagnostic
|
||||
// only, never a hard dependency of the read/write path.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user