bullet: integrated work for human review #48

Merged
EduarD3V merged 86 commits from bullet-develop into main 2026-10-15 09:05:42 +02:00
Owner

Rolling PR. All bullet work lands on bullet-develop via per-issue PRs (auto-merged after review + gates). Merge this single PR into main when satisfied.

Integrated issues:

  • PR #58 — MCP remote endpoint: deploy mcp/ over Streamable HTTP (Cloudflare Worker or k3s) + link from app

Automated by bullet: implemented → reviewed (independent model) → gates green → auto-merged into bullet-develop. Trunk is human territory; this PR is the one button the human presses.

**Rolling PR.** All bullet work lands on `bullet-develop` via per-issue PRs (auto-merged after review + gates). Merge this single PR into `main` when satisfied. Integrated issues: - PR #58 — MCP remote endpoint: deploy mcp/ over Streamable HTTP (Cloudflare Worker or k3s) + link from app Automated by bullet: implemented → reviewed (independent model) → gates green → auto-merged into bullet-develop. Trunk is human territory; this PR is the one button the human presses.
scripts/smoke-real.sh reads the PIN from .p12pass at runtime, re-wraps the PBES1
(3DES) container as PBES2/AES in a temp dir because WebCrypto has no DES, signs a
placeholder PDF through the shipped PAdES path and verifies the CMS with both the
app verifier and openssl cms -verify. Missing PIN/container skips with exit 0.
Feature-by-feature matrix: signing formats, multi-signature operations,
verification, identity acquisition, batch, app surface. Every row cites a
decompiled path (tag reference/decompile-1.9) or a repo file/issue, with a
status and a needed/browser-moot verdict (moot rows name the web-native
replacement). Closes with a prioritized gap list for the operator.
PKCS#12: PBES1 (DES/RC2/RC4) is implemented in src/lib/legacy-pbe.ts and the
container MAC is verified — the section still said the opposite. XAdES:
browser-side verification is in src/lib/verify/ since #8, and the unsupported
variants are Detached/Enveloping, not verification.
Verification pass over every citation in the audit against the extraction of
tag reference/decompile-1.9 (machine-resolves all 114 decompiled + 44 repo
citations, none past EOF) found:

- `validation/ValidateBinarySignature.java` -> `signvalidation/` (typo, prose)
- `AOCAdESSigner.java:229 applyTimeStamp` -> :40,234 (229 is a warning line)
- AOCAdESASiCSSigner :29 -> :27-30; AOPDFSigner :119 -> :118; AOOOXMLSigner
  :189 -> :188; verifyCades cms.ts :100 -> :101; mcp server tools -> :60,71,100
- FacturaE row now cites the actual policy/whitelist/enveloped-forcing lines
- SignatureFormatDetectorPadesCades :451 (private helper) -> :164 resolvePDFFormat
- Section B gained the missing Status column
- Section E: the multi-file queue is WIRED (FileDrop multiple + createBatchQueue
  + per-file rows + one ZIP, App.test.ts:251); the old row claimed one file at a
  time, contradicting README and the app. Gap #7 was therefore wrong: replaced by
  whole-directory input, folded into the massive-ops entry; gap list 19 -> 18.
The review fixed eleven wrong/misleading citations and two empty Status cells by hand;
nothing kept them fixed. docs/check-parity-doc.py checks the doc's contract: five filled
cells per matrix row, full/partial/missing status, needed/browser-moot verdict, numbered
and prioritized §G gaps, every citation resolving inside its source tree (decompiled/ or
decompiled-configurador/, per the doc's Method section), and the eight multi-part
corrections of df0e0b2d pinned as file+line+identifier facts.

Fails on 38f3cb13: 2 empty-cell/status defects + 8 missing corrected citations.
Covers the tenth corrected citation (SimpleAfirma.java:607 — the lines the review dropped,
637/705, are CLI/protocol dispatch, not the window). The pre-fix doc already cited 607, so
this one pins the fact rather than catching 38f3cb13.
The real FNMT citizen keystore of #39 is written the way the Windows PKCS#12
exporter writes: no definite length above the leaves, chunked OCTET STRINGs,
and an implicit [0] encryptedContent built from chunks. Rejected as
"not a PKCS#12 container" before this.

- readTlv: indefinite lengths (X.690 8.1.3.6), flattening a constructed string
  into the primitive form every consumer expects.
- openSafeContents: ciphertext of a chunked implicit [0] is the concatenation
  of its chunks.
- verify/x509: DN table gains 2.5.4.42 (GN), so the verify report prints the
  same subject as the certificate picker.
- pkcs12.test.ts: DER -> BER re-encoder, round trip + openssl-checked signature.
scripts/smoke-real.sh drives the app's own modules end to end — load the
container, CAdES-sign a document, verify the .csig — and cross-checks the
result with openssl cms -verify and openssl x509 -subject -dates:

- --cert/--pin-file for the real keystore (gitignored, read at runtime, never
  printed); missing keystore or PIN file skips with a notice and exit 0.
- a generated self-signed container from src/test/fixtures.ts runs by default.
- asserts signature validity, signer identity (subject, issuer, serial,
  validity window) and the validity dates in the report output; the report's
  certChain status is printed as it is (signing embeds only the signer
  certificate, #6).
- pnpm verify:real runs it; README documents the headless run and the matching
  UI flow step by step.
The installed xmlsec1 1.3.12 prints `Verification status: OK` / `FAILED`, which the
oracle parser (built for a bare `OK`/1.3 `status: OK`) rejected as "no verdict" — five
XAdES/App tests failed on its output instead of on the signature. Accept the three
wordings; FAILED still maps to FAIL, so the tampered-document test keeps failing as
intended.
Every run of App.test.ts failed 1-2 tests, a different set each time: the PKCS#12 load
and the signing round trip (PBKDF2 + AES + RSA import over Node's WebCrypto under
jsdom) sometimes take longer than the 1 s default waitFor, so the assertion ran before
the DOM update. Reproduced with this branch's src/lib changes reverted (1 failure), so
it predates #39. Same assertions, a realistic bound (the file already used 15 s for the
same kind of wait).
One definition in src/test/fixtures.ts, used by the App, CertPicker and batch tests: the
asynchronous waits that follow a PKCS#12 load, a 250k-iteration seal, a signing round trip
or a store unlock were racing testing-library's 1 s default, so a full run failed a
different 1-2 tests every time. Assertions untouched.
The app opens in Spanish, so the step list is only screenshot-able with the labels the
user actually sees; keep the English wording in brackets.
Finding 7 (flake class): the per-spot 15s waits left every other findByRole/waitFor on
testing-library's 1s default; full-suite runs still failed deterministically (wrong-PIN,
keystore-repick, emptied-list) at ~1.2-1.6s. A global configure() first looked useless
because @testing-library/dom resolves to a different pnpm copy for src/test-setup.ts than
for the queries, so the allowance never reached them. Import configure through
@testing-library/svelte (its re-export IS the copy screen binds to) and pin the instance
with src/test/setup.test.ts.

Gate: pnpm check 0 errors; pnpm test green over consecutive full-suite runs.
Finding 1 coverage gap: a4ca1063 taught xmlsec1Verify to read the 1.3.x
`Verification status: OK|FAILED` line but shipped no test, so a revert (or a
1.2.x-style format regression) would only surface as five opaque XAdES failures.
src/test/oracle.test.ts mocks spawnSync and pins both tool formats, the FAILED->FAIL
mapping, and the loud `no verdict` throw for a tool that could not run.

Gate: pnpm check 0 errors (1328 files); pnpm test 19 files / 153 tests green.
name/short_name, standalone display, start_url + scope at the root, theme_color #008d80 and
background_color #fbfaf7 (the app.css tokens: @theme --color-brand, --background), and the PNG
set Chrome/Firefox install needs (192 any, 512 any, 512 maskable). The art is the seal already
drawn in App.svelte, kept as SVG sources so the PNGs are regenerable with rsvg-convert — the
maskable variant holds the mark inside the 80% safe circle.

Co-authored-by: omp <[email protected]>
SvelteKit's built-in service-worker support: src/service-worker.ts is compiled to
build/service-worker.js and registered from the prerendered boot script, so no plugin and no
vite.config.ts change is needed. It precaches everything $app/manifest lists (prerendered pages,
static files, hashed build chunks) under a cache named after the build version, answers an offline
navigation with the cached URL or the cached root, and serves frozen build files cache-first.

Nothing is ever written to a cache at runtime: only same-origin GET requests are handled and there
is no cache.put, so a produced signature cannot be cached. No skipWaiting on install -- the waiting
worker is what the update prompt uses. Precache paths go through $app/paths so a subpath deployment
does not precache the site root. tsconfig excludes src/service-worker.ts explicitly: Kit's own
validator only recognises the file name once the entry exists (pnpm check was warning otherwise).

Refs: verified in Chromium -- 33 entries precached, all HTTP 200, offline / and /sidebar-07 render.

Co-authored-by: omp <[email protected]>
Install app appears only when Chromium fires beforeinstallprompt; the event is cancelled on
arrival so the click can replay prompt(), and the button retires with it because a deferred
prompt is single-use. Update version appears when the registration has a worker waiting, either
already there at mount or installed in the background, and only when an older worker still
controls the page -- on a first install there is no version to be behind. The click posts
SKIP_WAITING; the page reloads once on controllerchange.

app.html gains the manifest link and the teal theme-color meta, i18n gains installApp/updateApp
in both languages, and the buttons sit first in the header action row so an installed app shows
them where the old browser mini-infobar was.

Co-authored-by: omp <[email protected]>
README gets an Installable app section: what the worker caches and why a signature can never be
cached, the install/update flow, and the hosting facts a self-hoster needs -- service workers
require HTTPS except on localhost (a Cloudflare Tunnel already gives it), .webmanifest needs
application/manifest+json, subpath deployments, and the rsvg-convert command that regenerates the
icon set from the committed SVGs.

Caddyfile sets that content type: Go's mime table (verified with mime.TypeByExtension) has no
.webmanifest entry, so Caddy would serve the manifest as octet-stream.

Co-authored-by: omp <[email protected]>
clients.claim() makes the initial install fire controllerchange on an
uncontrolled page too; reloading there wiped whatever a new visitor
typed seconds after arrival. Only a takeover under an existing
controller reloads, once.
pnpm test flaked red on a loaded box: 7-9 failures, every one a 5000ms timeout in a
250k-iteration PBKDF2 test (pkcs12, certstore) or in a jsdom wait on one (App,
CertPicker), all of which pass in isolation. Raise the suite testTimeout to 20s and
give the three testing-library waits the same 15s allowance the neighbouring cases
already use. No assertion changed.
Keep both timeout allowances: testTimeout 20s (both sides) and the integration branch's
hookTimeout 120s. The pushed merge tip d57f9925 committed the raw marker block, which
crashes vitest config loading.
The #26 restructure put the app at src/, leaving mcp/ importing ../../web/src/lib —
every mcp/ test and the build failed on the merge base.
- src/http.ts: Web-standard fetch handler, POST /mcp, bearer token re-checked per
  request, stateless transport (one per request), body cap from AUTOFIRME_MCP_MAX_BYTES
- src/surface.ts: the three tool registrations, shared by the stdio and HTTP entries
- src/server-http.ts: Node adapter; refuses to start without AUTOFIRME_MCP_TOKEN
- test/http.test.ts: credential matrix + real MCP client over a socket, signature
  verified with openssl cms -verify
- mcp/Containerfile, mcp/deploy/k3s.yaml, README + docs/MCP-DESIGN.md remote section
- fix: engine imports pointed at a web/ directory that the #26 restructure removed
Help-menu entry opens a dialog with the deployed server's /mcp address (one field,
one copy button) and its bearer token, persisted in this browser like the theme.
The app never sends either value anywhere; the hint says so in ES and EN.
Reviewer finding #1: 8d710279 fixed the inverted mapping (non-OK verdicts
printed OK under pre-1.3 wording) but shipped no test. Pin the full
verdict matrix — 1.3.x 'Verification status:', 'status:' prefix, bare
OK/FAIL line, no-verdict error — with spawnSync stubbed, so the oracle
stays correct regardless of the xmlsec1 build installed.

Verified red against the pre-fix mapping, green after.
Reviewer finding #2: findByRole('alert') in the wrong-PIN test (and a
CertPicker waitFor observed on rerun) raced testing-library's 1 s async
default — PKCS#12 unlock/sealing runs PBKDF2 with 250k iterations and
exceeds it when vitest parallelizes files. Set asyncUtilTimeout 15 s
once in src/test-setup.ts instead of per-call. Three full suite runs
now pass 148/148; previously failed 2 of 3.
Conflicts resolved:
- src/lib/App.svelte: kept ours' Switch import (#45 visible-signature toggle);
  theirs' MCP settings dialog merged in additively.
- src/test/fixtures.ts: kept ours' consolidated xmlsec1 verdict regex (superset
  of the branch's two-step match; the new src/test/fixtures.test.ts pins it).
- src/test-setup.ts: dropped the branch's duplicated configure() block (HEAD's
  asyncUtilTimeout allowance already covers it).
- mcp/src/tools.ts: added the VerificationReport.status field required by #40
  to the xades-unsupported stub (typecheck fix).
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
EduarD3V/AutoFirme!48
No description provided.