control-plane: the judgement of an organization (one evidence pack, 25 kinds in eight domains, each judged again by its published verifier with the relying party's own trust, revocations and clock; 74/74, 75/75 with --online on testnet 28001, view 48/48; negative controls 45/45 and 20/20); the compliance report is made again from its own findings

This commit is contained in:
Aere Network 2026-10-21 01:12:43 +03:00
parent 6ba33c477f
commit 82c5b261ac
10 changed files with 1902 additions and 17 deletions

View File

@ -15,7 +15,7 @@ notarization command of the verification layer and for the agents' x402 wallet (
| [`verify-layer/`](verify-layer/) | an audit-log sidecar for any deployment: entries are AIP-23 envelopes in a hash chain, the runtime adapter records every running Docker or Kubernetes container without any secret value, and from 2026-10-18 the same for what the operator's own cloud CLI exports (AWS ECS, EC2, Lambda; Azure VMs, Container Instances, Container Apps, Web Apps; GCP Compute Engine, Cloud Run), read from the export file and never from an account, and the head of the chain can be signed by the operator (ML-DSA-65) and notarized on Aere Network under a post-quantum validator certificate; it says plainly what that proves (the history before a published head) and what it does not (that the host told the truth); the same log as a Merkle tree (RFC 9162): signed tree heads, and inclusion and consistency proofs of a few hashes that anyone checks without the log |
| [`proof-kinds/`](proof-kinds/) | the AIP-23 envelope builder the verification layer uses: sixteen proof kinds (`secret-access` since 2026-10-18, `agent-witness-checkpoint` since 2026-10-19), one envelope format, digests instead of raw content |
| [`readiness/`](readiness/) | the post-quantum readiness scanner of a public hostname: real TLS handshakes (hybrid only, hybrid preferred, TLS 1.2), HSTS, the certificate; no connection to an address not proven public; a rate limit per client that X-Forwarded-For cannot bypass |
| [`control-plane/`](control-plane/) | from findings to a finished migration: a prioritized plan from the inventory and the scanner, its execution through the gateway, KMS and PKI with consent per action and a measured proof afterwards, recipes for the servers the products do not touch and a rescan that judges them, a compliance report against NIST IR 8547, the EU roadmap and optionally CNSA 2.0, and a console that checks it all again |
| [`control-plane/`](control-plane/) | from findings to a finished migration: a prioritized plan from the inventory and the scanner, its execution through the gateway, KMS and PKI with consent per action and a measured proof afterwards, recipes for the servers the products do not touch and a rescan that judges them, a compliance report against NIST IR 8547, the EU roadmap and optionally CNSA 2.0, a console that checks it all again, and the judgement of an organization: one evidence pack of 25 kinds of artifact in eight domains (Shield, identity, compliance, AI, payments, devices, audit, proofs), each judged again by the published verifier of its kind with the trust, revocations and clock of the party that relies on it, into one verdict per domain and a summary never stronger than the weakest domain asked for |
| [`agents/`](agents/) | limits an AI agent cannot break unseen: a post-quantum identity (ML-DSA-65), a policy (spending per time window, allowed tools and recipients, which actions need human approval), a signed ledger of every action judged against the policy, approvals and revocation signed by people, and a verifier that re-runs the policy over the whole ledger without trusting the agent; two branches of one ledger are a proof of equivocation anyone can check; and a wallet that pays over x402 only what the agent's ledger records and its policy allows, either holding the payment key for the owner or co-signing from a 2-of-2 contract wallet that neither the agent nor the owner can spend alone; both run on the public testnet with their evidence; and the chain as the witness of a ledger: a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below (a backdated entry is caught); and a hosted witness (sandbox on testnet 28001; mainnet at activation): the agent sends its ledger head signed with its ML-DSA-65 key, never the ledger, the witness checks it continues the last head it holds (a conflicting head is a proof of equivocation, kept and returned), logs it in an RFC 9162 tree whose checkpoints it signs and notarizes, and returns a receipt with an inclusion proof that anyone checks without the witness and that the ledger verifier takes as an anchor |
| [`proof-of-ai/`](proof-of-ai/) | the AIP-23 envelope of an AI agent's action: model, version, weights digest, prompt, input, output, tools, agent, requester, as digests; verified by the same AIP-23 verifier as every other proof |
| [`payments/`](payments/) | the risk step and the receipt of a payment for a person or a company: a declared policy (per-payment and sliding-window limits in USD per payer, assets allowed, counterparties refused with their source) gives a verdict anyone reproduces from the same inputs, with the signals it does not have named as unmeasured (no sanctions list, no identity tier); the receipt is an instruction (payer, payee, the amount in the asset and in USD at the rate declared as an input, the transaction, the verdict with its policy under hash) committed to by an AIP-23 settlement envelope, notarized and read back under a post-quantum certified anchor; a reader checks it against the policy, the inputs and the chain with plain JSON-RPC |
@ -46,7 +46,7 @@ test, counts as a failure of the control. Results measured on 2026-09-29 (Node.j
| verify-layer | 45/45 with the AIP-23 reference verifier, measured from a fresh clone on 2026-10-18 (`AERE_VERIFY_PROOF=<verify-proof.mjs> node proba-sidecar.mjs`, where verify-proof.mjs is the one served at aere.network/tools with verify-anchor.mjs, mpt.mjs, falcon512.mjs, registry-v3.mjs and package.json beside it and `npm install` done there; verify-proof.mjs alone gives 41/45, because the four finality checks need the anchor verifier); without it 40 run, 5 are reported as skipped and the exit code is 2; the cloud adapters 60/60 with the same verifier on 2026-10-18 (`node proba-adaptoare-cloud.mjs`: nine fixtures derived from the vendors' documented CLI and REST examples by `fixturi-cloud/deriva.mjs`; without the verifier 59 run and one is skipped; a real account is unmeasured and the README says so); the Merkle tree 20/20 (`node proba-arbore.mjs`: the reference roots of Certificate Transparency, every proof up to 64 leaves, each attack), measured 2026-09-30 | 37/37 from a fresh clone of this repository on 2026-10-18 (`node control-negativ-sidecar.mjs`: the ten original plantings plus the cloud adapter rules; the case that compares with the development history is skipped here, and is the 38th in the development tree); the tree 14/14 (`node control-negativ-arbore.mjs`) |
| proof-kinds | 32/32 on 2026-10-19 with the same verifier (`AERE_VERIFY_PROOF=... node proba-proof-kinds.mjs`: sixteen kinds, the allowed values of the `secret-access` fields and the head of the agents witness checkpoint included) | nine negative controls inside the test |
| readiness | 6/6 (`node proba-adrese-private.mjs`: the private-address rules, and a local listener no scan may touch) | the rate limit and the queue bound are tested where the service runs, not here (its README says so) |
| control-plane | planner 44/44 (2026-10-18: a key agreement or a client-side TLS configured by the application itself is not routed to the TLS gateway; a self-signed trust anchor is blocked, not "automatic through PKI"; test code is LOW with its own scope), command line 10/10, execution 30/30 on real products started locally, remediation 33/33 on real TLS servers (all five server profiles measured end to end on 2026-10-18: nginx, Apache, HAProxy, Node, Go), compliance report 30/30 (with the AIP-23 verifier; test-code findings are counted apart and are not "exposed today"), console 8/8; the console viewer in a real Chromium, phone and desktop, 26/26 (`node proba-consola-web.mjs`, measured 2026-09-30; needs `playwright-core` and a Chromium, otherwise it exits 2) | remediation 7/7, compliance report 3/3 in this repository; viewer 8/8 (`node control-negativ-consola-web.mjs`) |
| control-plane | planner 44/44 (2026-10-18: a key agreement or a client-side TLS configured by the application itself is not routed to the TLS gateway; a self-signed trust anchor is blocked, not "automatic through PKI"; test code is LOW with its own scope), command line 10/10, execution 30/30 on real products started locally, remediation 33/33 on real TLS servers (all five server profiles measured end to end on 2026-10-18: nginx, Apache, HAProxy, Node, Go), compliance report 30/30 (with the AIP-23 verifier; test-code findings are counted apart and are not "exposed today"), console 8/8; organization judgement 74/74 on 2026-10-20 (`node proba-organizatie.mjs`: artifacts made by the real producers of each kind, the context of the relying party set against that of the pack, fourteen forged packs that used to pass now refused; 75/75 with `--online` on testnet 28001, said as a testnet) and its view in a real Chromium 48/48 (`node proba-organizatie-web.mjs`); the console viewer in a real Chromium, phone and desktop, 26/26 (`node proba-consola-web.mjs`, measured 2026-09-30; needs `playwright-core` and a Chromium, otherwise it exits 2) | remediation 7/7, compliance report 3/3 in this repository; viewer 8/8 (`node control-negativ-consola-web.mjs`); organization judgement 45/45 guards removed, each turning its named row red (`node control-negativ-organizatie.mjs`), and 20/20 defects planted in its view (`node control-negativ-organizatie-web.mjs`) |
| identity | 52/52 (`node proba-identity.mjs`, a presentation optionally signed for one exact request (`request`) included, with the AIP-23 verifier for its envelope test; without it that test is reported as skipped and the exit code is 2), compliance 18/18 (`node proba-conformitate.mjs`) and Travel Rule 20/20 (`node proba-travel-rule.mjs`) with the same verifier (without it each reports one test unmeasured and exits 2; 2026-10-18: a missing originator id or nonce is a refusal, found when the verifier was enrolled in the conformance suite), SD-JWT 31/31 (`node proba-sdjwt.mjs`, the RFC 9901 vectors and fixtures derived from @sd-jwt/core 0.23.0 included; since 2026-10-19 the keys of an AERE identity are judged on their ML-DSA-65 half by default, the Ed25519 half only on request, and every verdict says `postQuantum`), SD-JWT VC 11/11 (`node proba-sdjwt-vc.mjs`); interoperability with @sd-jwt/core and jose in both directions 26/26 and 28/28 (`interop/`, with those libraries installed in a folder you name; otherwise reported unmeasured), measured 2026-10-18, before the 2026-10-19 change; the scripts now ask for the Ed25519 half explicitly and have not been re-run since | 57/57 (`node control-negativ-identity.mjs`); compliance 17/17 (`node control-negativ-conformitate.mjs`); Travel Rule 20/20 (`node control-negativ-travel-rule.mjs`); SD-JWT 39/39 (`node control-negativ-sdjwt.mjs`); SD-JWT VC 20/20 (`node control-negativ-sdjwt-vc.mjs`) |
| agents | policy 27/27 with the AIP-23 verifier (without it 25 run, 2 are reported as skipped and the exit code is 2), ledger 59/59 on 2026-10-19 (since then an equivocation proof must be made with the agent's ML-DSA-65 key), approval and revocation 39/39, the chain as witness 26/26 without a network and 7/7 on testnet 28001 on 2026-09-30 (`proba-agent-ancora-testnet.mjs`, needs a funded testnet key and the AIP-23 verifier), command line 23/23 through files and processes only (on Linux and macOS one more test checks the key file mode; not measured here); the hosted witness 154/154 without a network, measured from a fresh clone of this repository on 2026-10-19 (four more rows, for the Cloud gateway door, run only where the gateway lives), with a local notary mock (`AERE_VERIFY_PROOF=<verify-proof.mjs> node proba-martor-agenti.mjs`; the AIP-23 rows need the verifier, without it they are skipped by name and the exit code is 2), and 75/75 on testnet 28001 earlier on 2026-10-19 with the form before that day's corrections; the current form has not been run on testnet yet (`proba-martor-agenti.mjs --testnet`, needs a funded testnet key or the faucet: a checkpoint notarized, the receipt VALID with post-quantum finality, a backdated ledger caught with it; evidence in `agents/dovezi-martor-agenti-28001/`); x402 wallet 25/25, 2-of-2 co-signing 16/16 and payment verifier 14/14 without a network; on the public testnet 28001, 9/9 with the wallet key and 13/13 with the 2-of-2 contract wallet (`agents/x402/proba-x402-testnet.mjs`, `agents/x402/proba-cosign-testnet.mjs`, each needs a funded testnet key); the contract's artifact recompiles byte for byte with solc 0.8.23 (`node x402/recompileaza-contract.mjs --solc <solc>`) | 28/28 (`node control-negativ-aprobare.mjs`); the chain as witness 11/11 (`node control-negativ-ancora.mjs`); the hosted witness 131/131, plus three changes of order that must leave the probe green (`node control-negativ-martor-agenti.mjs`); x402 30/30 (`node x402/control-negativ-wallet.mjs`); the contract's own tests (7) and their negative control (4/4) run in the Aere Network contracts project, not in this repository |
| proof-of-ai | 6/6 with the AIP-23 verifier (`AERE_VERIFY_PROOF=... node proba-proof-of-ai.mjs`); without it the probe reports NEMASURAT and exits 2 | one negative control inside the test (an envelope touched after its hash: INVALID) |
@ -54,9 +54,9 @@ test, counts as a failure of the control. Results measured on 2026-09-29 (Node.j
| market-settlement | 20/20 on testnet 28001 on 2026-10-02 (`AERE_TESTNET_KEY_FILE=... AERE_ETHERS_DIR=... node proba-piata-28001.mjs`: five scenarios on AereComputeMarketV3 deployed from the committed artifact, every refusal by its named custom error, the provider's notarized envelope read back under a post-quantum certified anchor); the judge on the recorded timelines 26/26 without a chain (`node proba-judecata.mjs`: seven honest readings VALID, eighteen forged or malformed ones INVALID for their named reason, the settlement receipt) | 10/10 (`node proba-judecata.mjs --control-negativ`: form, completeness of the timeline, posting, deadline, window, arbiter, payout, result bytes, replay, proof, each planted out on a copy and each forged fixture then accepted) |
| verifiable-data | 23/23 with the AIP-23 verifier (`AERE_VERIFY_PROOF=... node proba-verifiable-data.mjs`: honest answers VALID, ten forged or malformed ones INVALID for their named reason, availability sampling on a local server; the envelope row is NEMASURAT without the verifier, exit 2); the two chain rows of `--viu` measured on testnet 28001 on 2026-10-02 (a version notarized with a test key and read back under a post-quantum certified anchor; evidence in `dovezi/`) | 5/5 (`node proba-verifiable-data.mjs --control-negativ`: field commitment, index order, completeness, consistency, sampling, each planted out on a copy and each forged fixture then accepted) |
| ai-trust | 16/16 with the AIP-23 verifier, measured 2026-10-05 (`AERE_VERIFY_PROOF=... node proba-lant-de-incredere.mjs`: the whole chain on a committed fixture, then thirteen bundles broken on purpose, each refused for its named reason, among them an agent with a classical key and an audit head signed with one); without the verifier it exits 2 | 11/11 (`node proba-lant-de-incredere.mjs --control-negativ`) |
| conformance | 12/12 on 2026-10-20 in the development repository, with the AIP-23 reference verifier of that repository (byte for byte the one served at aere.network/tools), Python 3 and testnet 28001 read (`AERE_VERIFY_PROOF=<verify-proof.mjs> node proba-conformance.mjs --online`: the rules and the vectors are what the code derives, every rule has a vector, every invalid vector is one operation from its base or one declared change at its producer, the published verifiers pass 791/791, an independent Python implementation passes the 171 it claims and the other 620 are reported not covered, 225,345 malformed inputs and 183 malformed envelope files each get a verdict); from a fresh clone of this repository the last measurement is of 2026-10-18, before the operation log was enrolled (11/11; the twelfth row, the comparison with the published AIP-23 kind vectors, needs their generator, which is not part of this copy, and is reported as information); without `--online` the nine chain vectors are reported unmeasured and the exit code is 2 | 342/342 on 2026-10-20: 260/260 rules (`node proba-conformance.mjs --control-negativ --online`: each rule planted out of its verifier in a copy; the vectors that expect it, and only they, must fail), then 76/76 conditions of the device verifier's compound checks, each planted out alone (exactly the vectors written for it must fail), then six controls of the instruments |
| conformance | 12/12 on 2026-10-20 in the development repository, with the AIP-23 reference verifier of that repository (byte for byte the one served at aere.network/tools), Python 3 and testnet 28001 read (`AERE_VERIFY_PROOF=<verify-proof.mjs> node proba-conformance.mjs --online`: the rules and the vectors are what the code derives, every rule has a vector, every invalid vector is one operation from its base or one declared change at its producer, the published verifiers pass 791/791, an independent Python implementation passes the 171 it claims and the other 620 are reported not covered, 225,345 malformed inputs and 183 malformed envelope files each get a verdict); 11/11 from a fresh clone of this repository on 2026-10-20, alone in an empty folder, nothing written beside it and its tree left clean (the same command; the twelfth row, the comparison with the published AIP-23 kind vectors, needs their generator, which is not part of this copy, and is reported as information); without `--online` the nine chain vectors are reported unmeasured and the exit code is 2 | 342/342 on 2026-10-20: 260/260 rules (`node proba-conformance.mjs --control-negativ --online`: each rule planted out of its verifier in a copy; the vectors that expect it, and only they, must fail), then 76/76 conditions of the device verifier's compound checks, each planted out alone (exactly the vectors written for it must fail), then six controls of the instruments |
| device-identity | 372/372 with the AIP-23 verifier, measured 2026-10-14 (`AERE_VERIFY_PROOF=... node proba-device.mjs`: fifty-four bundles that must be VALID; seventy-seven with something not brought, not read or not readable, each PARTIAL for its named causes; two hundred and four attack rows, each refused for exactly its named rules, among them a clone with another key, a rolled-back or retracted firmware (also when the release is left out of the bundle), a previous owner that still signs, an owner's retirement written under no ownership head, a replayed attestation, an attestation dated ahead of the verifier's clock, a manufacturer the verifier does not trust, a retirement, a classical key under the ML-DSA-65 name, a signature in another base64 text, a day that does not exist; 804 damaged bundles each given a verdict; the command line through files only; the evidence recorded on testnet 28001 judged again offline); the chain as witness 10/10 on testnet 28001 on 2026-10-05, before the repairs of that day (`proba-device-ancora-testnet.mjs`, needs a funded testnet key and the AIP-23 verifier; not run again since: the bundles and readings it recorded give the same verdicts under the verifier of now) | 220/220 (`node control-negativ-device.mjs`: the untouched copy green; each of the 47 rules taken out of a copy, the rows that expect it, and only they, go red; 172 more plantings, most of them one condition of a compound check taken out alone, each with its rows) |
| device-operation | 107/107 on 2026-10-20 (`node proba-operation.mjs`: 18 logs that must be VALID, 15 PARTIAL each for its named causes, 48 attack rows each refused for exactly its named rules, 459 damaged logs each given a verdict, none counting a record the device did not sign; an adversarial review of 2026-10-20 found seven defects, among them a forged equivocation and a retired key that erased a witnessed history, each repaired with its row); the reputation's two implementations pass its 103 vectors 103/103 each through `../conformance/conformance.mjs` | 62/62 on 2026-10-20 (`node control-negativ-operation.mjs`: each of the 17 rules taken out of a copy, then 44 more plantings, each turning exactly its rows red; each defect of the review is put back by one planting) |
| device-operation | 107/107 on 2026-10-20 (`node proba-operation.mjs`: 18 logs that must be VALID, 15 PARTIAL each for its named causes, 48 attack rows each refused for exactly its named rules, 459 damaged logs each given a verdict, none counting a record the device did not sign; an adversarial review of 2026-10-20 found seven defects, among them a forged equivocation and a retired key that erased a witnessed history, each repaired with its row); the reputation's two implementations pass its 103 vectors 103/103 each through `../conformance/conformance.mjs`; the same figures from a fresh clone of this repository on 2026-10-20 | 62/62 on 2026-10-20 (`node control-negativ-operation.mjs`: each of the 17 rules taken out of a copy, then 44 more plantings, each turning exactly its rows red; each defect of the review is put back by one planting; 62/62 also from the fresh clone) |
| exposure-report | 164 passed and 1 `NEMASURAT` of 165 rows in the AERE development tree on 2026-10-14 (`node proba-expunere.mjs`: codec checks with real public vectors, mode 1 and mode 2 through four mock targets that serve recorded responses, the network guard's proof that in modes 1 and 2 nothing leaves the machine, the language gate, the AIP-23 `compliance` envelopes judged by the reference verifier; the `NEMASURAT` row is mode 3 against a real provider, which would send addresses to a third party); in this copy, after `npm install --ignore-scripts` in its directory: 153 passed, 0 red, 0 broken, 12 `NEMASURAT` with the reason (the rows compared with the AIP-23 verifier, the anchor verifier's RLP encoder and MPT walker and the shield's header pins, none of which is in this repository: with `AERE_PROOF_PROTOCOL_VERIFY`, `AERE_VERIFY_PROOF` and `AERE_EXPUNERE_PINI` naming them the rows run, 162 passed and 3 `NEMASURAT`; the scan of the directory for names from the AERE host inventory; the git-ignore row outside a git tree); the exit code is 0 unless a row is red or broken | inside the test, 2026-10-14: each rule has its reverse planting in a copy of the tool (a foreign provider planted on each network path is caught, a label planted in the language gate is refused, each classification rule taken out turns its row red, a `@noble/curves` 2.x planted first is passed over and alone is named as missing), and the recorded fixtures are judged against their declared date |
Code comments, most function and variable names (also many exported between the files of a component), test names and control
@ -66,4 +66,4 @@ interface, command line and data (`definePolicy`, `verifyLedger`, `approve`, ...
## Licence
MIT, see [LICENSE](LICENSE). Files: 1247 (pq-gateway 9, pq-kms 10, pq-secrets 6, pq-pki 6, crypto-inventory 73, verify-layer 56, proof-kinds 3, control-plane 27, agents 44, identity 24, proof-of-ai 3, ai-trust 6, verifiable-data 7, market-settlement 10, payments 8, conformance 803, device-identity 11, device-operation 114, exposure-report 18, readiness 4).
MIT, see [LICENSE](LICENSE). Files: 1253 (pq-gateway 9, pq-kms 10, pq-secrets 6, pq-pki 6, crypto-inventory 73, verify-layer 56, proof-kinds 3, control-plane 33, agents 44, identity 24, proof-of-ai 3, ai-trust 6, verifiable-data 7, market-settlement 10, payments 8, conformance 803, device-identity 11, device-operation 114, exposure-report 18, readiness 4).

View File

@ -100,6 +100,89 @@ envelope recomputed, the plan folded in, and the execution chain checked again.
itself (`chainOk: true` over a modified log is reported as a suspect self-report). `consola-web/index.html` renders that output; it
computes nothing in the browser, and a bundle pasted there directly is shown as `UNCHECKED`.
## 6. Organization judgement: `organizatie.mjs`
node organizatie.mjs --pack pack.json [--context context.json] [--require shield,identity,...] [--at <RFC 3339>] [--online] [--json]
Exit code 0 = VALID, 1 = INVALID, 2 = NOT_MEASURED (or the command could not run), 3 = PARTIAL.
An organization brings an **evidence pack**; every artifact in it is judged again by the published verifier of its kind, and the
result is one verdict per domain and a summary that is never stronger than the weakest domain that was asked for. Nothing the pack
says about itself is believed.
**The pack** (`aere-organization-evidence-pack`):
{ "kind": "aere-organization-evidence-pack", "organization": "...", "createdAt": "...",
"artifacts": [ { "id": "...", "artifact": "<kind>", "input": { ... } }, ... ] }
`id` is letters, digits and `. _ : / -`, at most 128 characters, unique in the pack (a repeated id is judged once, its copies are
`NOT_MEASURED`). `artifact` is one of 25 kinds in eight domains:
| domain | kinds |
|---|---|
| `shield` | `shield-console` (a sidecar bundle, the plan and the execution chain, judged by `consola.mjs`; an execution given with a plan must be the executor's run of that plan) |
| `identity` | `identity-presentation`, `sdjwt-presentation`, `sdjwt-vc-presentation` |
| `compliance` | `compliance-presentation`, `travel-rule-binding`, `travel-rule-message`, `travel-rule-receipt`, `pq-compliance-report` (a report of section 4 and its envelope) |
| `ai` | `ai-trust-bundle`, `agent-ledger` |
| `payments` | `payment-risk`, `payment-receipt`, `market-timeline` |
| `devices` | `device-bundle`, `device-operation-log`, `device-reputation` |
| `audit` | `audit-log`, `audit-inclusion`, `audit-consistency` |
| `proofs` | `envelope` (AIP-23), `dataset-record`, `dataset-answer`, `dataset-consistency`, `dataset-sample` |
Cloud is not a domain of its own: what a Cloud product returns is judged in the domain of the artifact. The 23 kinds of the
conformance suite are judged through the suite's adapter (`../conformance/implementare-aere.mjs` beside a public copy of the control
plane), called as a program, so a verifier that does not start is seen as such; the two kinds of the control plane itself are judged
here (`shield-console` by the console's own evaluation, plus the binding of the execution to the plan given with it;
`pq-compliance-report` by `verifyReport`, by making the report again from its own findings with the same generator, which must give
exactly the report and the envelope's statement, and by the AIP-23 verifier for its envelope). The compliance envelope is not
signed, so a report bound to it shows only that the report is self-consistent; whether its findings are the real plan's is not
judged. A kind no verifier here judges is listed as unjudged.
**The context** (`aere-organization-context`) belongs to the party that relies on the pack, not to the organization:
{ "kind": "aere-organization-context", "now": "<RFC 3339>", "require": ["identity", ...],
"byKind": { "<kind>": { ... } }, "byArtifact": { "<id>": { ... } } }
Every input of every verifier is classified (the test derives the keys from the adapter's source, from the source of the module the
adapter hands a whole input to, and from the 791 vectors of the suite, and fails on a key without a class): the **artifact** comes
only from the pack; a **stapled** input (a status list, a revocation, a firmware policy, signed by a third party) is taken from the
context and from the pack, joined, the context's lists first (a verifier reads only the first entries of a long list, so lists brought
by the pack cannot push the relying party's own revocation out of what is read); a **verifier** input (the trusted issuers or
manufacturers, the audience, the nonce, the expected values, the relying party's own readings: a market's arbiter and arbitration
timeout, the facts a reputation is computed from) comes from the context, and if only the pack brings it, it is used but the artifact
cannot be VALID (`PARTIAL`, because the organization chose its own trust); a **verifier-only** input (the chain to read, a
beneficiary's private KEM keys, the time limit) is never taken from the pack; the **clock** comes from the context for that artifact
or kind, then `--at`, then the context's global `now`, then this machine, never from the pack. For a market timeline and a reputation,
whose module reads the whole input, a key the module does not read by name stays with the artifact (so a member too many is still
refused).
**The verdicts.** Each artifact gets the verifier's verdict (`VALID`, `INVALID`, `PARTIAL`), or `NOT_MEASURED` when the verifier
did not run or gave no verdict of the suite's form: a verdict whose own levels do not give it (by the suite's rule in `verdict.mjs`),
or a verdict for another kind than the one asked in its place (the order of the adapter's answers cannot then be trusted, so none of
them is). When a batch does not complete (too few answers, or the time limit), each artifact is judged alone, so one artifact cannot
take the verdicts of the others with it. A domain is as weak as its weakest artifact; a domain with no artifact is
`NOT_PRESENTED`, and a required domain that is not presented makes the summary `NOT_MEASURED`, never `VALID`. The required domains
are `--require`, then `context.require`, then all eight. Unjudged artifacts cap a `VALID` summary at `PARTIAL` (the pack claims more
than was measured). A verdict written in the pack (`verdict`, `summary.verdict`, an artifact's `verdict` or `expected.verdict`) is
ignored, and named as suspect when it differs from the measurement.
**The network.** Anchoring and finality on a chain are read only with `--online`, and only for the kinds whose verifier reads the
chain (an AIP-23 envelope, a payment receipt, a market timeline, an AI trust bundle, the report's envelope). Without it, each of those
that claims a notarization is listed in `network.needed` and its levels say `NOT_MEASURED`. A device bundle or an operation log
judges its notarization on the relying party's own readings (`anchors`, `readings` in the context), never on the chain read here,
with `--online` or not: when they are missing it is listed in `network.readings`. `network.unmeasured` lists what `--online` could
not read, and `network.finality` names the chain each finality was read on, a testnet said as such (the summary says it too). The
console's Shield evaluation does not read a chain even with `--online`: a notarization claimed inside a sidecar bundle has to be
presented as its own `envelope` artifact.
**What it does not judge.** The truth of what an artifact asserts beyond what its verifier checks (a market timeline, a payment
receipt without the relying party's transaction readings, the findings of a compliance report), the honesty of the host that produced
a sidecar bundle (section 5), and any kind outside the table. `consola-web/index.html` shows the output (`--json`) domain by domain,
each artifact with its verifier and the evidence it rests on; a pack pasted there directly is shown as `UNCHECKED`. The view judges
nothing, but it never shows a verdict stronger than the output's own rows give (a domain no stronger than its weakest artifact, the
summary no stronger than its weakest required domain): an edited output that contradicts itself is shown at the weaker reading and
named. Everything that comes from the pack is shown as text.
## Tests
bash probele-b1.sh # all seven below, each VERDE / ROSU / STRICAT (in the development repository only)
@ -108,11 +191,17 @@ computes nothing in the browser, and a bundle pasted there directly is shown as
node proba-executa-migrare.mjs # 30: on real products started locally (KMS, PQ CA, gateway), with the attacks of the review
node proba-remediere.mjs # 33: real TLS servers on 127.0.0.1 scanned by the readiness scanner, before and after the recipe
node control-negativ-remediere.mjs # 7 of 7 guards broken in a copy, each turns the test red
node proba-raport-conformitate.mjs # 27: on the real outputs of the inventory and the scanner; the envelope through the AIP-23 verifier
node proba-raport-conformitate.mjs # 30: on the real outputs of the inventory and the scanner; the envelope through the AIP-23 verifier
node control-negativ-raport-conformitate.mjs # 3 plantings, each turns the named check red
node proba-consola.mjs # 8: a good bundle and four negative controls
node proba-consola-web.mjs # 26: the viewer in a real Chromium, phone (390 px) and desktop (1440 px); needs playwright-core and a Chromium
node control-negativ-consola-web.mjs # 8 defects planted in a copy of the page, each turns its named check red
node proba-organizatie.mjs # 74 (75 with --online): artifacts made by the real producers (the suite's vectors, checked against INDEX.json; a Shield state,
# a plan and a report made now), a good pack and one broken artifact per kind, and the attacks of the
# adversarial review of 2026-10-21; --online adds a row on testnet 28001
node control-negativ-organizatie.mjs # 45 of 45 guards removed in a copy, each turns its named row red
node proba-organizatie-web.mjs # 48: the organization view in a real Chromium, 390 px and 1440 px, edited outputs and text from the pack included
node control-negativ-organizatie-web.mjs # 20 defects planted in a copy of the page, each turns its named check red
The report test runs the envelope through the AIP-23 reference verifier (`AERE_VERIFY_PROOF=<verify-proof.mjs from aere.network/tools>`).
No third party has reviewed any of this.

View File

@ -39,8 +39,8 @@ header{position:sticky;top:env(safe-area-inset-top,0px);z-index:5;background:col
.mark{display:flex;align-items:center;gap:10px;font-weight:700;letter-spacing:.02em}
.glyph{width:26px;height:26px;flex:none}
.mark .sub{font-weight:500;color:var(--mut);font-size:12px;letter-spacing:.06em;text-transform:uppercase}
.host{margin-left:auto;display:flex;align-items:center;gap:8px;color:var(--mut);font-size:12.5px}
.host .mono{color:var(--ink)}
.host{margin-left:auto;display:flex;align-items:center;gap:8px;color:var(--mut);font-size:12.5px;min-width:0;flex-wrap:wrap}
.host .mono{color:var(--ink);overflow-wrap:anywhere;min-width:0}
.verdict{display:inline-flex;align-items:center;gap:8px;padding:7px 14px;border-radius:999px;font-weight:700;font-size:13px;letter-spacing:.03em;border:1px solid}
.verdict.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}
.verdict.bad{color:var(--crit);border-color:color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 12%,transparent)}
@ -121,7 +121,37 @@ button.ghost{border:1px solid var(--line2);background:transparent;color:var(--mu
.err{color:var(--crit);font-size:12.5px;font-family:var(--mono)}
.foot{margin-top:34px;padding-top:16px;border-top:1px solid var(--line);color:var(--dim);font-size:11.5px;line-height:1.7}
.foot code{font-family:var(--mono);color:var(--mut)}
.warnbox{margin-top:12px;border:1px solid color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 10%,transparent);color:var(--crit);border-radius:10px;padding:11px 13px;font-size:12.5px}
.warnbox{margin-top:12px;border:1px solid color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 10%,transparent);color:var(--crit);border-radius:10px;padding:11px 13px;font-size:12.5px;overflow-wrap:anywhere}
/* organization judgement (organizatie.mjs): one card per domain, each artifact with the verifier and the evidence it rests on */
.verdict.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)}
.notebox{margin-top:12px;border:1px solid var(--line2);background:var(--panel2);color:var(--mut);border-radius:10px;padding:11px 13px;font-size:12.5px;overflow-wrap:anywhere}
.hmeta{font-family:var(--mono);font-size:11px;color:var(--dim);text-transform:none;letter-spacing:0}
.domains{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
@media(max-width:760px){.domains{grid-template-columns:minmax(0,1fr)}}
.dcard{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:14px 15px;box-shadow:var(--shadow);min-width:0;display:flex;flex-direction:column;gap:8px}
.dcard.np{background:var(--panel2);box-shadow:none;border-style:dashed}
.dhead{display:flex;align-items:center;gap:8px 10px;flex-wrap:wrap;min-width:0}
.dtitle{font-weight:600;font-size:14px;overflow-wrap:anywhere}
.did{font-family:var(--mono);font-size:11px;color:var(--dim)}
.pill{margin-left:auto;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:.04em;border:1px solid;border-radius:999px;padding:2px 9px;white-space:nowrap}
.pill.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 10%,transparent)}
.pill.warn{color:var(--warn);border-color:color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 10%,transparent)}
.pill.bad{color:var(--crit);border-color:color-mix(in srgb,var(--crit) 45%,transparent);background:color-mix(in srgb,var(--crit) 10%,transparent)}
.pill.dim{color:var(--mut);border-color:var(--line2);background:transparent}
.pill.sm{margin-left:0;font-size:10px;padding:1px 7px}
.reqtag{font-size:10.5px;color:var(--dim);border:1px solid var(--line2);border-radius:5px;padding:0 6px}
.dreason{font-size:12px;color:var(--mut);overflow-wrap:anywhere}
.arow{border-top:1px solid var(--line);padding-top:8px;display:flex;flex-direction:column;gap:5px;min-width:0}
.aline{display:flex;gap:6px 8px;align-items:center;flex-wrap:wrap;min-width:0}
.aid{font-family:var(--mono);font-size:12px;font-weight:600;overflow-wrap:anywhere;min-width:0}
.akind{font-family:var(--mono);font-size:10.5px;color:var(--mut);border:1px solid var(--line2);border-radius:5px;padding:1px 6px;overflow-wrap:anywhere}
.ameta{font-size:11.5px;color:var(--mut);overflow-wrap:anywhere}
.arules{font-family:var(--mono);font-size:11.5px;color:var(--crit);overflow-wrap:anywhere}
.anote{font-size:11.5px;color:var(--warn);overflow-wrap:anywhere}
.alevels{display:flex;flex-wrap:wrap;gap:3px 10px;font-family:var(--mono);font-size:10.5px}
.alevels .p{color:var(--ok)} .alevels .f{color:var(--crit)} .alevels .n{color:var(--dim)}
.adig{display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:6px;align-items:center;min-width:0}
</style>
</head>
<body>
@ -136,30 +166,37 @@ button.ghost{border:1px solid var(--line2);background:transparent;color:var(--mu
<div>AERE Verification Console<div class="sub">quantum security control plane</div></div>
</div>
<div class="host">
<span>host</span><span class="mono" id="host">—</span>
<span id="hostlab">host</span><span class="mono" id="host">—</span>
<span id="verdict" class="verdict ok"><span class="dot"></span><span id="vtext">OK</span></span>
</div>
</div>
</header>
<div class="wrap">
<p class="lead">The state of a deployment, <b>checked again independently</b> by the control plane: the audit chain is recomputed from its entries, and the integrity of every AIP-23 envelope is recomputed. The console does not trust what the sidecar says about itself.</p>
<p class="lead" id="lead-console">The state of a deployment, <b>checked again independently</b> by the control plane: the audit chain is recomputed from its entries, and the integrity of every AIP-23 envelope is recomputed. The console does not trust what the sidecar says about itself.</p>
<div class="tiles" id="tiles"></div>
<p class="lead" id="lead-org" hidden>The evidence of an organization, <b>judged again domain by domain</b> by the published verifier of each artifact. A domain with no artifact is not presented, never green; a verifier that does not start is not measured; the summary is never stronger than the weakest required domain. Nothing is judged in this browser.</p>
<div id="alert"></div>
<section>
<section id="orgsec" hidden>
<h2>Domains <span class="rule"></span> <span id="orgmeta" class="hmeta"></span></h2>
<div class="domains" id="domains"></div>
</section>
<section id="sec-chain">
<h2>Audit chain <span class="rule"></span> <span id="chainmeta" class="mono" style="font-size:11px;color:var(--dim);text-transform:none;letter-spacing:0"></span></h2>
<div class="chain" id="chain"></div>
</section>
<section>
<section id="sec-ladder">
<h2>Post-quantum certificate <span class="rule"></span></h2>
<div class="ladder" id="ladder"></div>
</section>
<section>
<section id="sec-mig">
<h2>Post-quantum migration <span class="rule"></span></h2>
<div class="mig">
<div class="card">
@ -178,7 +215,7 @@ button.ghost{border:1px solid var(--line2);background:transparent;color:var(--mu
<section class="paste">
<h2>Render your own output <span class="rule"></span></h2>
<p class="lead" style="margin-top:0">Paste the output of <code class="mono" style="color:var(--ink)">node consola.mjs --bundle b.json --json</code> or a bundle from <code class="mono" style="color:var(--ink)">node sidecar.mjs bundle</code>.</p>
<p class="lead" style="margin-top:0">Paste the output of <code class="mono" style="color:var(--ink)">node consola.mjs --bundle b.json --json</code>, the output of <code class="mono" style="color:var(--ink)">node organizatie.mjs --pack pack.json --json</code> (every domain of an organization), or a bundle from <code class="mono" style="color:var(--ink)">node sidecar.mjs bundle</code>.</p>
<textarea id="in" spellcheck="false" placeholder='{ "kind":"aere-control-plane-console", ... }'></textarea>
<div class="row">
<button class="act" id="render">Render</button>
@ -244,9 +281,133 @@ function copyBtn(text){
return b;
}
// judecata organizatiei (organizatie.mjs, 2026-10-20): o VEDERE, nu a doua judecata. O iesire lipita se arata asa cum a dat-o unealta (domeniile,
// verdictele, verificatorul si amprenta dovezii fiecarui artefact); un pachet brut lipit e UNCHECKED: browserul nu judeca nimic. Un domeniu
// neprezentat nu e niciodata verde. Datele intra numai prin textContent.
const PILL = { VALID: "ok", PARTIAL: "warn", NOT_MEASURED: "warn", NOT_PRESENTED: "dim", INVALID: "bad", UNCHECKED: "bad" };
// (revizia B2, 2026-10-21) o iesire lipita poate fi editata: vederea nu judeca nimic, dar nu arata un verdict mai bun decat cel pe care il dau
// chiar randurile iesirii (domeniul nu e mai tare decat cel mai slab artefact al lui, rezumatul nu e mai tare decat cel mai slab domeniu cerut);
// cand iesirea se contrazice, se arata citirea mai slaba si se spune de ce. Un verdict necunoscut e mai slab decat toate.
const RANGV = { INVALID: 0, NOT_MEASURED: 1, NOT_PRESENTED: 1, PARTIAL: 2, VALID: 3 };
const rangV = (v) => Object.hasOwn(RANGV, String(v)) ? RANGV[v] : -1;
const slabV = (vs) => vs.reduce((a, b) => (rangV(b) < rangV(a) ? b : a));
const pill = (v) => Object.hasOwn(PILL, String(v)) ? PILL[v] : "bad";
const STRIPE = { ok: "s-ok", warn: "s-warn", bad: "s-crit", dim: "s-acc" };
const eOrg = (o) => !!o && typeof o === "object" && (o.kind === "aere-organization-judgement" || o.kind === "aere-organization-evidence-pack");
const textVerdict = (v) => v === "NOT_PRESENTED" ? "NOT PRESENTED" : v === "NOT_MEASURED" ? "NOT MEASURED" : String(v || "?");
function consoleMode(on){
for (const id of ["sec-chain","sec-ladder","sec-mig","lead-console"]) document.getElementById(id).hidden = !on;
for (const id of ["orgsec","lead-org"]) document.getElementById(id).hidden = on;
document.getElementById("hostlab").textContent = on ? "host" : "organization";
}
function tile(t){ const c=el("div","tile"); const k=el("div","k"); k.append(el("i","pin "+t.pin),document.createTextNode(t.k)); c.append(k,el("div","v",t.v),el("div","n",t.n),el("div","stripe "+t.s)); return c; }
function randArtefact(r){
const w = el("div","arow"); w.dataset.id = String(r.id); w.dataset.verdict = String(r.verdict);
const l1 = el("div","aline"); l1.append(el("span","aid",String(r.id)), el("span","akind",String(r.artifact)), el("span","pill sm "+pill(r.verdict),textVerdict(r.verdict)));
w.append(l1);
const vf = r.verifier || {};
w.append(el("div","ameta","judged by "+(vf.module||"?")+(vf.through?" through "+vf.through:"")+(r.verifierVerdict && r.verifierVerdict!==r.verdict?" · the verifier said "+r.verifierVerdict:"")));
if (Array.isArray(r.failed) && r.failed.length) w.append(el("div","arules","refused by "+r.failed.join(", ")));
if (r.error) w.append(el("div","anote","not measured: "+r.error));
if (r.capped) w.append(el("div","anote",r.capped));
if (Array.isArray(r.levels)) {
const lv = el("div","alevels");
for (const x of r.levels) { const k = x.state==="PASSED"?"p":x.state==="FAILED"?"f":"n"; const sp = el("span",k,x.level+" "+(x.state==="NOT_MEASURED"?"not measured"+(x.cause?" ("+x.cause+")":""):String(x.state).toLowerCase())); sp.title = String(x.reason||""); lv.append(sp); }
w.append(lv);
}
const dc = r.details && r.details.console;
if (dc) w.append(el("div","ameta","console: audit chain "+(dc.auditChain&&dc.auditChain.ok?"intact":"BROKEN")+", "+(dc.auditChain?dc.auditChain.count:0)+" entries; envelopes "+(dc.envelopes?dc.envelopes.intact+"/"+dc.envelopes.total:"?")+(dc.migration&&dc.migration.total!=null?"; plan "+dc.migration.total+" action(s), "+dc.migration.critical+" critical":"")+(dc.execution?"; execution "+dc.execution.actions+" action(s)":"")));
const dr = r.details && r.details.report;
if (dr && dr.finding) w.append(el("div","ameta","the report says (re-derived from its own findings; the findings are not judged here): "+dr.finding));
const n = r.inputs || {}; const parti = [];
if ((n.selfSupplied||[]).length) parti.push("verifier inputs from the pack: "+n.selfSupplied.join(", "));
if ((n.dropped||[]).length) parti.push("dropped from the pack: "+n.dropped.join(", "));
if (parti.length) w.append(el("div","ameta",parti.join(" · ")));
const dg = el("div","adig"); dg.append(el("span","hlab","evidence"), el("span","hval",r.inputSha256||"—"), copyBtn(r.inputSha256||"")); w.append(dg);
return w;
}
function renderOrg(o){
consoleMode(false);
const pachet = o.kind === "aere-organization-evidence-pack";
const s = (!pachet && o.summary && typeof o.summary === "object") ? o.summary : {};
const doms = (Array.isArray(o.domains) ? o.domains : []).filter((d) => d && typeof d === "object");
const arts = (Array.isArray(o.artifacts) ? o.artifacts : []).filter((r) => r && typeof r === "object");
const req = o.required && Array.isArray(o.required.domains) ? o.required.domains : [];
// ce se arata: niciodata mai bine decat randurile iesirii; fiecare contradictie e numita
const contrazic = [];
const aleDom = (d) => arts.filter((x) => x.domain === d.domain);
const aratatDom = new Map(doms.map((d) => {
const ale = aleDom(d);
const v = ale.length ? slabV([String(d.verdict), ...ale.map((x) => String(x.verdict))]) : "NOT_PRESENTED";
if (v !== d.verdict) contrazic.push("domain "+d.domain+" says "+d.verdict+(ale.length ? ", its artifacts give "+v : ", and has no artifact"));
return [d, v];
}));
let verdict = pachet ? "UNCHECKED" : String(s.verdict || "?");
if (!pachet) {
const din = doms.filter((d) => req.includes(d.domain)).map((d) => aratatDom.get(d) === "NOT_PRESENTED" ? "NOT_MEASURED" : aratatDom.get(d));
let v = slabV([verdict, ...din]);
if (v === "VALID" && (o.unjudged || []).length) v = "PARTIAL";
if (v !== verdict) { contrazic.push("the summary says "+verdict+", its required domains give "+v); verdict = v; }
}
const cls = pill(verdict);
document.getElementById("host").textContent = o.organization || "?";
document.getElementById("verdict").className = "verdict " + cls;
document.getElementById("vtext").textContent = textVerdict(verdict);
const net = o.network || {}; const ceas = o.clock || {};
const neprez = doms.filter((d) => aratatDom.get(d) === "NOT_PRESENTED");
// reteaua: "read" in culoarea buna numai cand chiar s-a citit tot ce se cerea, si o finalitate de testnet e spusa ca atare
const lst = (k) => Array.isArray(net[k]) ? net[k] : [];
const fin = lst("finality").filter((f) => f && typeof f === "object"), pePlasa = fin.filter((f) => f.testnet === true), neNumit = fin.filter((f) => f.chain == null);
const retea = [];
if (lst("needed").length) retea.push(lst("needed").length+" artifact(s) claim a notarization not read: run with --online");
if (lst("readings").length) retea.push(lst("readings").length+" artifact(s) need the relying party's own readings of a notarization (context)");
if (lst("unmeasured").length) retea.push("the chain could not be read for "+lst("unmeasured").length+" artifact(s)");
if (pePlasa.length) retea.push("final only on a testnet (chain "+[...new Set(pePlasa.map((f) => f.chain))].join(", ")+") for "+pePlasa.length+" artifact(s)");
if (neNumit.length) retea.push("final on a chain not named for "+neNumit.length+" artifact(s)");
const reteaBuna = !!net.online && !retea.length;
const reteaN = retea.length ? retea.join(" · ") : net.online ? (fin.length ? "final on chain "+[...new Set(fin.map((f) => f.chain))].join(", ") : "the chain was read for the claims") : "no artifact waits on the chain";
const tiles = pachet ? [
{k:"Pack", pin:"crit", v:"not judged", n:arts.length+" artifact(s) declared, none judged here", s:"s-crit"},
{k:"Domains presented", pin:"warn", v:"—", n:"run organizatie.mjs on it", s:"s-warn"},
{k:"Network", pin:"warn", v:"—", n:"nothing was read", s:"s-warn"},
{k:"Clock", pin:"acc", v:"—", n:"no verifier ran", s:"s-acc"},
] : [
{k:"Summary", pin:cls==="ok"?"ok":cls==="warn"?"warn":"crit", v:textVerdict(verdict), n:"over "+req.length+" required domain(s)"+(Array.isArray(s.weakest)&&s.weakest.length?" · weakest: "+s.weakest.join(", "):""), s:STRIPE[cls]||"s-crit"},
{k:"Domains presented", pin:neprez.length?"warn":"ok", v:(doms.length-neprez.length)+"/"+doms.length, n:neprez.length?"not presented: "+neprez.map((d)=>d.domain).join(", "):"every domain presented", s:neprez.length?"s-warn":"s-ok"},
{k:"Network", pin:reteaBuna?"ok":"warn", v:!net.online?"not read":lst("unmeasured").length?"partly read":"read", n:reteaN, s:reteaBuna?"s-ok":"s-warn"},
{k:"Clock", pin:"acc", v:String(ceas.now||"?").slice(0,10), n:"the verifier's clock, "+String(ceas.now||"").slice(11,19)+" UTC, from "+(ceas.source||"?"), s:"s-acc"},
];
const T = document.getElementById("tiles"); T.innerHTML = "";
for (const t of tiles) T.append(tile(t));
const A = document.getElementById("alert"); A.innerHTML = "";
if (pachet) A.append(el("div","warnbox","An evidence pack pasted here is not judged: the browser checks nothing. Run node organizatie.mjs --pack pack.json --context context.json --json on it and paste that output."));
else {
if (contrazic.length) A.append(el("div","warnbox","This output contradicts itself, so the weaker reading is shown: "+contrazic.join("; ")+". organizatie.mjs never writes such an output: it was edited, or made by another tool."));
if (s.reason) A.append(el("div","notebox",s.reason));
for (const x of (o.selfReport && o.selfReport.suspect) || []) A.append(el("div","warnbox","Suspect self-report, ignored: the pack says "+x.where+" = "+x.says+"; measured "+x.measured+"."));
for (const u of o.unjudged || []) A.append(el("div","warnbox","Not judged: "+u.id+" ("+(u.artifact||"?")+"): "+u.reason));
}
document.getElementById("orgmeta").textContent = pachet ? "" : ((o.pack && o.pack.sha256) ? "pack "+short(o.pack.sha256)+" · " : "")+arts.length+" artifact(s)";
const D = document.getElementById("domains"); D.innerHTML = "";
if (pachet || !doms.length) { D.append(el("div","card", pachet ? "Not judged." : "No domain in this output.")); return; }
for (const d of doms) {
const dv = aratatDom.get(d), np = dv === "NOT_PRESENTED";
const c = el("div","dcard"+(np?" np":"")); c.dataset.domain = String(d.domain); c.dataset.verdict = String(dv);
const h = el("div","dhead"); h.append(el("span","dtitle",String(d.title||d.domain)), el("span","did",String(d.domain)));
if (!req.includes(d.domain)) h.append(el("span","reqtag","not required"));
h.append(el("span","pill "+pill(dv),textVerdict(dv)));
c.append(h, el("div","dreason", np ? "No artifact of this domain in the pack: not presented, not green." : (d.reason||"")));
if (Array.isArray(d.failed) && d.failed.length) c.append(el("div","arules","refused by "+d.failed.join(", ")));
for (const r of arts.filter((x)=>x.domain===d.domain)) c.append(randArtefact(r));
D.append(c);
}
}
// `exemplu`: numai exemplul are o treapta de finalitate cunoscuta (capul lui nu e notarizat); pentru o iesire lipita consola nu masoara
// finalitatea, deci nu se marcheaza nicio treapta si caseta spune ca nu e verificata aici (2026-09-30, B-24)
function render(d, exemplu=false){
if(eOrg(d)){ renderOrg(d); return; }
consoleMode(true);
d=normalize(d);
const ok = d.verdict==="OK";
document.getElementById("host").textContent=d.host||"?";
@ -339,8 +500,12 @@ function render(d, exemplu=false){
document.getElementById("render").addEventListener("click",()=>{
const e=document.getElementById("perr");e.hidden=true;
try{ const o=JSON.parse(document.getElementById("in").value.trim()); render(o); }
catch(err){ e.hidden=false; e.textContent="invalid JSON: "+err.message; }
// (2026-10-20) un JSON stricat si o iesire care nu se poate reda sunt doua lucruri: pana azi o eroare de script la redare iesea "invalid JSON"
let o;
try{ o=JSON.parse(document.getElementById("in").value.trim()); }
catch(err){ e.hidden=false; e.textContent="invalid JSON: "+err.message; return; }
try{ render(o); }
catch(err){ e.hidden=false; e.textContent="cannot render this output: "+err.message; }
});
document.getElementById("reset").addEventListener("click",()=>{
document.getElementById("in").value=JSON.stringify(SAMPLE,null,1);

View File

@ -0,0 +1,72 @@
// Controlul negativ al probei vederii organizatiei in browser (proba-organizatie-web.mjs): fiecare defect se planteaza intr-o COPIE a paginii,
// proba ruleaza pe copie (primeste calea ei) si randul NUMIT trebuie sa iasa rosu, cu proba chiar rulata pana la rezumat; pe copia neatinsa,
// verde. Trei stari: un tipar care nu apare exact o data sau o proba care nu ajunge la rezumat e STRICAT si se numara esec.
// node control-negativ-organizatie-web.mjs iesire 0 = martorul verde si toate plantarile rosii pe randul lor
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';
import { spawn } from 'node:child_process'; import { fileURLToPath } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const PAGINA = path.join(AICI, 'consola-web', 'index.html');
const PLANTARI = [
// [nume, tipar, inlocuire, randul probei (o parte din numele lui)]
['un domeniu neprezentat in culoarea buna', 'NOT_PRESENTED: "dim"', 'NOT_PRESENTED: "ok"', 'domeniul neprezentat aratat NOT PRESENTED'],
['un domeniu neprezentat numarat la "Domains presented"', 'const neprez = doms.filter((d) => aratatDom.get(d) === "NOT_PRESENTED");', 'const neprez = doms.filter((d) => false);', 'domeniul neprezentat aratat NOT PRESENTED'],
['INVALID in culoarea buna', 'INVALID: "bad", UNCHECKED: "bad" };', 'INVALID: "ok", UNCHECKED: "bad" };', 'rezumatul INVALID in culoarea rea'],
['regula refuzata nenumita pe artefact', 'if (Array.isArray(r.failed) && r.failed.length) w.append(el("div","arules","refused by "+r.failed.join(", ")));',
'if (Array.isArray(r.failed) && r.failed.length && false) w.append(el("div","arules","refused by "+r.failed.join(", ")));', 'rezumatul INVALID in culoarea rea'],
['amprenta dovezii prescurtata', 'el("span","hval",r.inputSha256||"—")', 'el("span","hval",short(r.inputSha256))', 'amprenta intreaga a dovezii'],
// (revizia B2) antetul ia VALID in loc de rezumat: corectia il coboara la domenii, dar numeste o iesire reala contradictorie
['verdictul din antet luat VALID, nu din rezumat', 'let verdict = pachet ? "UNCHECKED" : String(s.verdict || "?");',
'let verdict = pachet ? "UNCHECKED" : "VALID";', 'o iesire reala nu e numita contradictorie'],
['culoarea casetei de domeniu din cel mai bun artefact', 'h.append(el("span","pill "+pill(dv),textVerdict(dv)));',
'h.append(el("span","pill "+(arts.some((x)=>x.domain===d.domain&&x.verdict==="VALID")?"ok":pill(dv)),textVerdict(dv)));', 'cate o caseta pe domeniu'],
// revizia B2 (2026-10-21): paznicii noi ai vederii
['antetul necoborat la cel mai slab domeniu cerut (o iesire editata crezuta)', ' let v = slabV([verdict, ...din]);', ' let v = verdict;', 'rezumatul VALID peste un domeniu cerut INVALID'],
['caseta de domeniu necoborata la cel mai slab artefact', 'const v = ale.length ? slabV([String(d.verdict), ...ale.map((x) => String(x.verdict))]) : "NOT_PRESENTED";', 'const v = ale.length ? String(d.verdict) : "NOT_PRESENTED";', 'domeniul identity VALID peste un artefact INVALID'],
['caseta retelei verde peste un lant necitit', 'const reteaBuna = !!net.online && !retea.length;', 'const reteaBuna = !!net.online;', 'caseta retelei'],
['finalitatea pe testnet nespusa in caseta retelei', ' if (pePlasa.length) retea.push(', ' if (pePlasa.length && false) retea.push(', 'caseta retelei'],
['textul din pachet pus ca HTML', 'if(x!=null)n.textContent=x;return n;}', 'if(x!=null)n.innerHTML=x;return n;}', 'un marcaj adus de pachet'],
['alarmele fara rupere de rand', 'color:var(--crit);border-radius:10px;padding:11px 13px;font-size:12.5px;overflow-wrap:anywhere}', 'color:var(--crit);border-radius:10px;padding:11px 13px;font-size:12.5px}', 'text lung fara spatii'],
['numele organizatiei fara rupere de rand', '.host .mono{color:var(--ink);overflow-wrap:anywhere;min-width:0}', '.host .mono{color:var(--ink)}', 'text lung fara spatii'],
['un pachet brut aratat ca judecata', 'const pachet = o.kind === "aere-organization-evidence-pack";', 'const pachet = false && o.kind === "aere-organization-evidence-pack";', 'un pachet brut lipit e UNCHECKED'],
['verdictul scris de pachet ascuns', 'for (const x of (o.selfReport && o.selfReport.suspect) || []) A.append(', 'for (const x of []) A.append(', 'verdictul scris de pachet e aratat ca suspect'],
['sectiunile consolei lasate vizibile peste domenii', 'for (const id of ["sec-chain","sec-ladder","sec-mig","lead-console"]) document.getElementById(id).hidden = !on;',
'for (const id of ["sec-chain","sec-ladder","sec-mig","lead-console"]) document.getElementById(id).hidden = false;', 'sectiunile consolei ascunse'],
['domeniile ramase dupa intoarcerea la consola', 'for (const id of ["orgsec","lead-org"]) document.getElementById(id).hidden = on;', 'for (const id of ["orgsec","lead-org"]) document.getElementById(id).hidden = false;', 'o stare a consolei dupa o judecata'],
['un id de artefact mai lat decat telefonul', '.aid{font-family:var(--mono);font-size:12px;font-weight:600;overflow-wrap:anywhere;min-width:0}', '.aid{font-family:var(--mono);font-size:12px;font-weight:600;white-space:nowrap;min-width:600px}', '390px: judecata buna: pagina cat ecranul'],
// o eroare la redare e prinsa de butonul Render si scrisa in campul de eroare (nu e o eroare de pagina): o prinde randul campului de eroare
['o eroare de script in vederea organizatiei', ' consoleMode(false);\n const pachet', ' consoleMode(false); nuExistaFunctia();\n const pachet', 'nicio iesire reala nu a dat un mesaj de eroare la redare'],
];
function ruleaza(pagina) {
return new Promise((resolve) => {
const c = spawn(process.execPath, [path.join(AICI, 'proba-organizatie-web.mjs'), pagina]); let out = '';
const ceas = setTimeout(() => c.kill(), 300000);
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; });
c.on('close', (cod) => { clearTimeout(ceas); resolve({ cod, rulat: /organizatie-web: \d+\/\d+/.test(out), rosii: out.split('\n').filter((l) => l.includes('[RAU ]')), nemasurat: /^NEMASURAT/m.test(out) }); });
});
}
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'org-web-ctl-'));
let rele = 0;
try {
// capetele de rand la LF inainte de orice (o copie de pe Windows poate avea CRLF), iar martorul e scris din ACEEASI forma ca plantarile
const src = fs.readFileSync(PAGINA, 'utf8').replace(/\r\n/g, '\n');
const m0 = path.join(T, 'martor.html'); fs.writeFileSync(m0, src);
const m = await ruleaza(m0);
if (m.nemasurat) { console.log('NEMASURAT: proba nu poate rula aici (fara browser)'); process.exit(2); }
if (m.rulat && m.cod === 0 && !m.rosii.length) console.log(' OK martorul: copia neatinsa verde');
else { rele++; console.log(` STRICAT martorul nu e verde (cod ${m.cod}, ${m.rosii.length} rosii)`); }
const planteaza = async ([nume, din, inl, tinta], i) => {
if (src.split(din).length !== 2) return [false, ` STRICAT ${nume}: tiparul apare de ${src.split(din).length - 1} ori`];
const f = path.join(T, `p${i}.html`); fs.writeFileSync(f, src.replace(din, () => inl));
const r = await ruleaza(f);
if (!r.rulat) return [false, ` STRICAT ${nume}: proba nu a ajuns la rezumat (cod ${r.cod})`];
if (r.rosii.some((l) => l.includes(tinta))) return [true, ` ROSU cum trebuia ${nume} (proba '${tinta}' pica)`];
return [false, ` CONTROL CAZUT ${nume}: proba '${tinta}' a ramas verde (${r.rosii.length} rosii altundeva)`];
};
// cate doua browsere deodata (fiecare cateva sute de MB, plus judecatile reale pe care le face proba)
const rez = new Array(PLANTARI.length); let k = 0;
await Promise.all(Array.from({ length: 2 }, async () => { while (k < PLANTARI.length) { const i = k++; rez[i] = await planteaza(PLANTARI[i], i); } }));
for (const [bun, linie] of rez) { console.log(linie); if (!bun) rele++; }
} finally { fs.rmSync(T, { recursive: true, force: true }); }
console.log(rele ? `CONTROL NEGATIV: ${rele} probleme` : `DOVEDIT: martorul verde, ${PLANTARI.length} din ${PLANTARI.length} defecte plantate -> proba lor rosie`);
process.exitCode = rele ? 1 : 0;

View File

@ -0,0 +1,112 @@
// Controlul negativ al probei judecatii organizatiei (proba-organizatie.mjs). Fiecare paznic al lui organizatie.mjs e scos, pe rand, intr-o COPIE
// pusa langa original (`_plantat-org-<pid>-<n>.mjs`, ca importurile relative sa ramana aceleasi), prin o conditie falsa LA RULARE sau o
// valoare schimbata (copia trebuie sa se poata incarca, altfel nu masoara nimic); proba ruleaza pe copie (AERE_ORG_MODUL) randurile care pazesc
// acel lucru (AERE_ORG_DOAR), iar randul NUMIT trebuie sa iasa rosu, cu proba chiar rulata pana la rezumat. Pe copia neatinsa, toata proba verde.
// Trei stari: ROSU cum trebuia (bine); CONTROL CAZUT (randul a ramas verde); STRICAT (tiparul nu apare exact o data, sau proba nu a ajuns la
// rezumat: o plantare care nu se poate pune e un ESEC al controlului, nu o linie de informare). La capat: nicio copie plantata ramasa.
// Reteaua: toate rularile primesc un capat de lant INCHIS (127.0.0.1:9), deci nicio plantare (nici "online mereu") nu atinge internetul.
// node control-negativ-organizatie.mjs iesire 0 = martorul verde si fiecare plantare rosie pe randul ei
import fs from 'node:fs';
import path from 'node:path';
import { spawn } from 'node:child_process';
import { fileURLToPath } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const SURSA = path.join(AICI, 'organizatie.mjs');
const PROBA = path.join(AICI, 'proba-organizatie.mjs');
const INCHIS = 'http://127.0.0.1:9';
// [ce se scoate, tiparul (exact o data in organizatie.mjs), inlocuirea, randul probei care trebuie sa iasa rosu]
const PLANTARI = [
['domeniul ia cel mai BUN artefact al lui', ' const verdict = celMaiSlab(ale.map((r) => r.verdict));', ' const verdict = ale.map((r) => r.verdict).reduce((a, b) => (RANG[b] > RANG[a] ? b : a));', 'stricat envelope:'],
['rezumatul ia cel mai BUN domeniu cerut', ' let verdict = celMaiSlab(vs);', ' let verdict = vs.reduce((a, b) => (RANG[b] > RANG[a] ? b : a));', 'pachetul bun: rezumatul'],
['un domeniu cerut si neprezentat numarat VALID in rezumat', "(d.verdict === 'NOT_PRESENTED' ? 'NOT_MEASURED' : d.verdict)", "(d.verdict === 'NOT_PRESENTED' ? 'VALID' : d.verdict)", 'domeniu neprezentat: NOT_PRESENTED'],
['adaptorul lipsa da VALID', "items.map(() => ({ error: `the conformance adapter is not at ${adaptor}: no verifier was run` }))",
"items.map(() => ({ v: 1, verdict: 'VALID', failed: [], levels: NIVELURI.map((l, i) => ({ level: l, state: i ? 'NOT_MEASURED' : 'PASSED', rules: [], cause: i ? 'not-judged' : undefined, reason: 'planted' })) }))", 'adaptorul suitei lipsa'],
['adaptorul lipsa da INVALID', "items.map(() => ({ error: `the conformance adapter is not at ${adaptor}: no verifier was run` }))",
"items.map(() => ({ v: 1, verdict: 'INVALID', failed: ['planted'], levels: NIVELURI.map((l, i) => ({ level: l, state: i ? 'NOT_MEASURED' : 'FAILED', rules: i ? [] : ['planted'], cause: i ? 'not-judged' : undefined, reason: 'planted' })) }))", 'adaptorul suitei lipsa'],
['eroarea unui verificator ignorata (verificatorul de referinta lipsa)', '} else { const a = verdicte[x.lot]; if (a.error) eroare = a.error; else v = a; }', "} else { const a = verdicte[x.lot]; if (a.error && false) eroare = a.error; else v = a.error ? { verdict: 'VALID', failed: [], levels: [] } : a; }", 'verificatorul de referinta AIP-23 lipsa'],
['un lot cu mai putine verdicte decat artefacte crezut', ' const motiv = linii.length === items.length ? null :', ' const motiv = true ? null :', 'mai putine verdicte'],
['un verdict incoerent al adaptorului crezut', ' if (formaVerdictului(j, cauzePartiale)) return j;', ' if (formaVerdictului(j, cauzePartiale) || (j && j.verdict)) return j;', 'verdict incoerent'],
// revizia B2 (2026-10-21): paznicii noi, fiecare cu randul probei care il arata
['verdictul adaptorului nerefacut din nivelurile lui', ' return j.verdict === v && JSON.stringify', ' return (j.verdict === v || true) && JSON.stringify', 'VALID cu integritatea nemasurata'],
['ordinea raspunsurilor lotului necitita', ' if (k >= 0) return items.map(', ' if (k >= 0 && false) return items.map(', 'raspunde in alta ordine'],
['un lot neterminat ia cu el toate verdictele (fara judecata pe rand)', ' if (items.length === 1) return [{ error: lot.motiv }];', ' if (items.length >= 1) return items.map(() => ({ error: lot.motiv }));', 'tine adaptorul pe loc'],
['listele pachetului inaintea celor ale contextului', "{ input[k] = [...v, ...input[k]]; note.stapled[k] = 'context+pack'; }", "{ input[k] = [...input[k], ...v]; note.stapled[k] = 'context+pack'; }", '64 de liste de rechemare'],
['constantele pietei (arbitrul) ale organizatiei', 'market: A, jobId: A, constants: V,', 'market: A, jobId: A, constants: A,', 'arbitrul pietei'],
['faptele reputatiei ale organizatiei', "'device-reputation': { facts: V,", "'device-reputation': { facts: A,", 'faptele reputatiei'],
['o cheie a modulului dat intreg scoasa din clasificare (constants)', 'market: A, jobId: A, constants: V,', 'market: A, jobId: A,', 'acoperire: fiecare cheie pe care adaptorul'],
['o cheie in plus a reputatiei aruncata in loc sa ajunga la modul', "facts: V, params: A, claimed: A, '*': A }", 'facts: V, params: A, claimed: A }', 'acoperire: fiecare cheie pe care adaptorul'],
['un fel care citeste lantul scos din lista', "export const CITESC_LANTUL = ['envelope', 'payment-receipt', 'market-timeline',", "export const CITESC_LANTUL = ['envelope', 'payment-receipt',", 'acoperire: felurile judecate citind lantul'],
['derivarea raportului PQ sarita', " if (dv.ok === false) V.pica(acc, 'integrity', 'pqreport.derivation', dv.reason);", " if (dv.ok === false && false) V.pica(acc, 'integrity', 'pqreport.derivation', dv.reason);", 'rezumatul rescris'],
['raportul PQ lasat in afara retelei', " if (!r.levels || r.artifact === 'shield-console') continue;", ' if (!r.levels || FELURI_PROPRII.includes(r.artifact)) continue;', 'un raport PQ al carui plic'],
['o masina numarata ca cerand --online', '(CITESC_LANTUL.includes(r.artifact) ? iesire.network.needed', '(CITESC_LANTUL.includes(r.artifact) || true ? iesire.network.needed', 'o masina a carei notarizare'],
['lantul ilizibil netrecut in rezumat', ' if (N.unmeasured.length) motive.push(', ' if (N.unmeasured.length && false) motive.push(', 'capat inchis'],
['finalitatea pe testnet nenumita', " iesire.network.finality.push({ id: r.id, chain: m ? Number(m[1]) : null, testnet: m ? !!m[2] : null });", " iesire.network.finality.push({ id: r.id, chain: m ? Number(m[1]) : null, testnet: false });", 'finalitatea citita isi numeste lantul'],
['Shield: executia nelegata de plan', ' if (st.execution && st.execution.chainOk && st.migration && st.migration.total !== null) {', ' if (st.execution && st.execution.chainOk && st.migration && st.migration.total !== null && false) {', 'Shield: o executie din care lipseste'],
['Shield: randul de sfarsit al executiei necitit', ' if (!(obiect(s) && s.total === facute.length', ' if (false && !(obiect(s) && s.total === facute.length', 'Shield: o executie al carei rand de sfarsit'],
['increderea adusa de organizatie lasata sa dea VALID', " if (v.verdict === 'VALID' && proprii.length) {", " if (v.verdict === 'VALID' && proprii.length && false) {", 'pachet fara context'],
['ceasul din pachet folosit', ' else note.dropped.push(k); // verifier-only si clock: o valoare din pachet nu se foloseste niciodata',
' else if (c === C) { input[k] = v; note.fromContext.push(k); } else note.dropped.push(k);', 'ceasul pachetului nu se foloseste'],
['increderea din pachet castiga peste cea a celui care verifica', ' const castigaContextul = (k) => Object.hasOwn(ctx, k);', ' const castigaContextul = (k) => false && Object.hasOwn(ctx, k);', 'atac: emitentii de incredere'],
['listele de stare ale contextului aruncate', '{ input[k] = [...v, ...input[k]];', '{ input[k] = [...input[k]];', 'o lista de stare adusa de cel care verifica'],
['contextul poate scrie artefactul', ' } else note.contextIgnored.push(k); // un context nu poate scrie artefactul organizatiei', ' } else { input[k] = v; }', 'un context nu poate scrie artefactul'],
['cheile KEM private citite din pachet', 'maxAgeS: V, statusLists: S, kem: O };', 'maxAgeS: V, statusLists: S, kem: V };', 'cheile KEM private'],
['verdictul scris de pachet folosit', " spune('pack.verdict', pack.verdict, verdict);", " if (typeof pack.verdict === 'string') iesire.summary.verdict = pack.verdict; spune('pack.verdict', pack.verdict, verdict);", 'verdictele scrise de pachet'],
['reteaua citita mereu (si fara --online)', 'return { file: f, online: !!online };', 'return { file: f, online: true };', 'fara --online'],
['artefactele care cer reteaua nenumite', " if (lantul.some((l) => l.state === 'NOT_MEASURED' && l.cause === 'claim-not-checked')) (CITESC_LANTUL",
" if (lantul.some((l) => l.state === 'NOT_MEASURED' && l.cause === 'claim-not-checked') && false) (CITESC_LANTUL", 'fara --online'],
['un fel necunoscut nu mai coboara rezumatul', " if (iesire.unjudged.length && verdict === 'VALID') {", " if (iesire.unjudged.length && verdict === 'VALID' && false) {", 'un fel pe care nu il judeca nimeni'],
['un id repetat judecat a doua oara', ' if (vazute.has(id)) { nm(', ' if (vazute.has(id) && false) { nm(', 'un id repetat'],
['Shield: lantul de audit rupt ignorat', ' if (!st.auditChain.ok) V.pica(', ' if (!st.auditChain.ok && false) V.pica(', 'Shield: o intrare a jurnalului schimbata'],
['Shield: plicul mincinos ignorat', ' if (st.envelopes.bad.length) V.pica(', ' if (st.envelopes.bad.length && false) V.pica(', 'Shield: lant refacut coerent'],
['Shield: chainOk mincinos ignorat', ' if (st.selfReportSuspect) V.pica(', ' if (st.selfReportSuspect && false) V.pica(', 'Shield: burafu declara chainOk:false'],
['Shield: lantul executiei rupt ignorat', ' if (st.execution && !st.execution.chainOk) V.pica(', ' if (st.execution && !st.execution.chainOk && false) V.pica(', 'stricat shield-console'],
['Shield: planul fara lista ignorat', ' if (st.migration && st.migration.total === null) V.pica(', ' if (st.migration && st.migration.total === null && false) V.pica(', 'Shield: un plan fara lista'],
['Shield: burafu gol acceptat', '&& Array.isArray(b.entries) && b.entries.length > 0)) {', '&& Array.isArray(b.entries) && b.entries.length >= 0)) {', 'Shield: un buraf fara intrari'],
['Shield: semnatura nejudecata trecuta sub tacere', " if (semnate.length) V.nemasurat(acc, 'authenticity', 'claim-not-checked'", " if (semnate.length && false) V.nemasurat(acc, 'authenticity', 'claim-not-checked'", 'Shield: o intrare care poarta o semnatura'],
['raportul PQ dezlegat de plic acceptat', " if (!leg.ok) V.pica(acc, 'integrity', 'pqreport.binding', leg.reason);", " if (!leg.ok && false) V.pica(acc, 'integrity', 'pqreport.binding', leg.reason);", 'stricat pq-compliance-report'],
['o cheie citita de adaptor scoasa din clasificare (trustedIssuers)', 'audience: V, nonce: V, trustedIssuers: V, maxAgeS: V,', 'audience: V, nonce: V, maxAgeS: V,', 'acoperire: fiecare cheie pe care adaptorul'],
['un fel al suitei fara domeniu (device-reputation)', "'device-operation-log': 'devices', 'device-reputation': 'devices',", "'device-operation-log': 'devices',", 'acoperire: fiecare fel al suitei'],
];
function ruleaza(modul, doar) {
return new Promise((gata) => {
const env = { ...process.env, AERE_CONFORMANCE_RPC_28001: INCHIS, AERE_CONFORMANCE_RPC_2800: INCHIS };
if (modul) env.AERE_ORG_MODUL = modul; else delete env.AERE_ORG_MODUL;
if (doar) env.AERE_ORG_DOAR = doar; else delete env.AERE_ORG_DOAR;
const c = spawn(process.execPath, [PROBA], { env, stdio: ['ignore', 'pipe', 'pipe'] }); let out = '';
const ceas = setTimeout(() => c.kill(), 600000);
c.stdout.on('data', (x) => { out += x; }); c.stderr.on('data', (x) => { out += x; });
c.on('close', (cod) => { clearTimeout(ceas); gata({ cod, rulat: /organizatie: \d+\/\d+ cum trebuia/.test(out), rosii: out.split('\n').filter((l) => l.includes('[RAU ]')), nemasurat: /^NEMASURAT/m.test(out), out }); });
});
}
const ramase = () => fs.readdirSync(AICI).filter((f) => /^_plantat-org-/.test(f));
let rele = 0;
const src = fs.readFileSync(SURSA, 'utf8');
if (ramase().length) { console.log(`STRICAT: copii plantate ramase dinainte: ${ramase().join(', ')}`); process.exit(2); }
const m = await ruleaza(null, null);
if (m.nemasurat) { console.log('NEMASURAT: proba nu poate rula aici'); process.exit(2); }
if (m.rulat && m.cod === 0 && !m.rosii.length) console.log(` OK martorul: proba intreaga verde pe organizatie.mjs neatins (${(m.out.match(/organizatie: (\d+\/\d+)/) || [])[1]})`);
else { rele++; console.log(` STRICAT martorul nu e verde (cod ${m.cod}, ${m.rosii.length} rosii): ${m.rosii.slice(0, 2).join(' | ')}`); }
const planteaza = async ([nume, din, inl, tinta], i) => {
const n = src.split(din).length - 1;
if (n !== 1) return [false, ` STRICAT ${nume}: tiparul apare de ${n} ori`];
const f = path.join(AICI, `_plantat-org-${process.pid}-${i}.mjs`);
fs.writeFileSync(f, src.replace(din, () => inl));
try {
const r = await ruleaza(f, tinta);
if (!r.rulat) return [false, ` STRICAT ${nume}: proba nu a ajuns la rezumat (cod ${r.cod}): ${r.out.trim().split('\n').slice(-1)[0].slice(0, 160)}`];
if (r.rosii.some((l) => l.includes(tinta))) return [true, ` ROSU cum trebuia ${nume} (randul '${tinta}' pica)`];
return [false, ` CONTROL CAZUT ${nume}: randul '${tinta}' a ramas verde`];
} finally { fs.rmSync(f, { force: true }); }
};
const rez = new Array(PLANTARI.length); let k = 0;
// cate doua probe deodata: fiecare porneste adaptorul suitei si verificatorul de referinta; laptopul e impartit
await Promise.all(Array.from({ length: 2 }, async () => { while (k < PLANTARI.length) { const i = k++; rez[i] = await planteaza(PLANTARI[i], i); } }));
for (const [bun, linie] of rez) { console.log(linie); if (!bun) rele++; }
if (ramase().length) { rele++; console.log(` STRICAT copii plantate ramase: ${ramase().join(', ')}`); }
console.log(rele ? `CONTROL NEGATIV: ${rele} probleme` : `DOVEDIT: martorul verde, ${PLANTARI.length} din ${PLANTARI.length} paznici scosi -> randul lor rosu`);
process.exitCode = rele ? 1 : 0;

View File

@ -0,0 +1,149 @@
// fixturi-organizatie.mjs: artefactele pe care se masoara judecata organizatiei (organizatie.mjs), DERIVATE din producatori, niciunul scris de
// mana in forma pe care o presupune consumatorul:
// - pentru cele 23 de feluri ale suitei de conformitate: vectorii ei (tools/aere-conformance/vectori, publicati in aere-quantum/conformance),
// pe care generatorul lor ii deriva din producatori si ii verifica octet cu octet; fiecare vector folosit aici se cere egal cu amprenta lui din
// INDEX.json. Un vector `{ artifact, input }` se IMPARTE dupa clasele din organizatie.mjs: artefactul si ce e semnat de un tert merg in pachet,
// intrarile verificatorului (ceasul, increderea, provocarea, citirile lui) in contextul celui care verifica;
// - pentru felurile planului de control: starea Shield facuta acum de sidecar-ul real (doua inregistrari, un buraf), planificatorul real si
// executorul real (uscat), iar raportul de conformitate PQ facut de raport-conformitate.mjs pe planul acela.
// Fiecare fel are un artefact BUN si un frate STRICAT, refuzat de verificatorul lui pentru o regula numita.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { execFileSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { INTRARI, PACK_KIND, CONTEXT_KIND, FELURI_PROPRII } from './organizatie.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const RAD = path.resolve(AICI, '..', '..');
const sha = (b) => '0x' + crypto.createHash('sha256').update(b).digest('hex');
export function dosarulSuitei() {
const c = [path.join(AICI, '..', 'conformance'), path.join(RAD, 'tools', 'aere-conformance')];
return c.find((d) => fs.existsSync(path.join(d, 'vectori', 'INDEX.json'))) || null;
}
let INDEX = null;
export function indexul() {
if (INDEX) return INDEX;
const d = dosarulSuitei(); if (!d) throw new Error('the conformance vectors are not beside the control plane (../conformance/vectori) nor in tools/aere-conformance');
INDEX = { dir: path.join(d, 'vectori'), j: JSON.parse(fs.readFileSync(path.join(d, 'vectori', 'INDEX.json'), 'utf8')) };
return INDEX;
}
/** Un vector, cerut egal octet cu octet cu amprenta lui din INDEX.json (deci chiar cel scris de generator). */
export function vectorul(id) {
const { dir, j } = indexul();
const e = j.vectors.find((v) => v.id === id); if (!e) throw new Error('no vector ' + id + ' in INDEX.json');
const b = fs.readFileSync(path.join(dir, ...id.split('/')) + '.json');
if (sha(b) !== e.sha256) throw new Error(`vector ${id}: sha256 ${sha(b).slice(0, 14)} is not the one in INDEX.json (${e.sha256.slice(0, 14)})`);
return JSON.parse(b.toString('utf8'));
}
/** inputul unui vector -> partea pachetului (artefactul, ce e semnat de un tert) si partea contextului (verificatorul, ceasul) */
export function imparte(fel, input) {
const cls = INTRARI[fel];
const pack = {}, context = {};
for (const [k, v] of Object.entries(input)) {
// '*': o cheie pe care modulul nu o citeste pe nume, la un fel citit intreg (un membru in plus): a artefactului, ca in organizatie.mjs
const c = Object.hasOwn(cls, k) ? cls[k] : cls['*'];
if (c === undefined) throw new Error(`${fel}: the vector has an input ${k} that organizatie.mjs does not classify`);
if (c === 'artifact' || c === 'stapled') pack[k] = v; else context[k] = v;
}
return { pack, context };
}
const egal = (a, b) => JSON.stringify(a) === JSON.stringify(b);
/** Pentru fiecare fel al suitei: un vector bun (nu INVALID, fara retea; VALID cand exista) si un frate stricat: INVALID, fara retea, facut din
* vectorul bun, cu ACELASI context si alt artefact (deci artefactul e stricat, nu increderea celui care verifica), o singura regula cand se poate. */
export function alegeVectorii() {
const { j } = indexul();
const feluri = Object.keys(j.artifacts);
const ales = {};
for (const fel of feluri) {
const ale = j.vectors.filter((v) => v.artifact === fel && !v.requires.length);
const bun = ale.find((v) => v.verdict === 'VALID') || ale.find((v) => v.verdict === 'PARTIAL');
if (!bun) throw new Error(`${fel}: no vector that is not INVALID and needs no network`);
const vb = vectorul(bun.id); const ib = imparte(fel, vb.input);
const candidati = ale.filter((v) => v.verdict === 'INVALID').map((v) => vectorul(v.id))
.filter((x) => x.base === bun.id).map((x) => ({ x, i: imparte(fel, x.input) }))
.filter(({ i }) => egal(i.context, ib.context) && !egal(i.pack, ib.pack));
// o singura regula, si nu una de forma (un artefact care nu se poate citi deloc spune putin despre compunere), cand se poate
const deForma = (r) => /[.-]form$/.test(r);
const rau = candidati.find(({ x }) => x.expected.failed.length === 1 && !deForma(x.expected.failed[0])) || candidati.find(({ x }) => x.expected.failed.length === 1) || candidati[0];
if (!rau) throw new Error(`${fel}: no INVALID vector made from ${bun.id} with the same verifier inputs and another artifact`);
ales[fel] = { bun: { id: bun.id, v: vb, ...ib }, rau: { id: rau.x.id, v: rau.x, ...rau.i } };
}
return ales;
}
/** Starea Shield, facuta de uneltele reale: un jurnal al sidecar-ului (doua inregistrari), burafu lui, planul planificatorului pe un inventar
* cu un schimb de cheie si o semnatura clasice si o scanare cu expunere HNDL, si executia lui uscata. Fratele stricat: o inregistrare a executiei
* schimbata dupa scriere (lantul executiei rupt, nimic altceva). */
export async function stareaShield() {
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'org-shield-'));
try {
const { caleaSidecarului } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href);
const SIDE = caleaSidecarului();
const side = (args) => execFileSync(process.execPath, [SIDE, ...args], { encoding: 'utf8', stdio: 'pipe' });
const log = path.join(T, 'audit.log'); const bin = path.join(T, 'node'); fs.writeFileSync(bin, 'BIN');
side(['record', '--kind', 'runtime', '--artifact', bin, '--attested', sha(fs.readFileSync(bin)), '--host', 'org-prod-1', '--log', log, '--at', '2026-10-20T08:00:00Z']);
side(['record', '--kind', 'deployment', '--name', 'checkout-api', '--version', '4.2.1', '--content-file', bin, '--log', log, '--at', '2026-10-20T08:05:00Z']);
const bf = path.join(T, 'b.json'); side(['bundle', '--log', log, '--out', bf, '--host', 'org-prod-1', '--at', '2026-10-20T08:10:00Z']);
const bundle = JSON.parse(fs.readFileSync(bf, 'utf8'));
const { planeaza } = await import(pathToFileURL(path.join(AICI, 'plan-migrare.mjs')).href);
const plan = planeaza({ inventory: [
{ assetType: 'algorithm', name: 'ECDH', primitive: 'key-agree', quantumVulnerable: true, ref: 'a:1' },
{ assetType: 'algorithm', name: 'RSA-2048', primitive: 'signature', quantumVulnerable: true, ref: 'a:2' },
{ assetType: 'algorithm', name: 'ML-KEM-768', primitive: 'kem', quantumVulnerable: false, ref: 'a:3' },
], scan: { domain: 'exemplu.test', findings: [{ id: 'hndl-exposed', severity: 'high', title: 'No post-quantum key exchange' }] } });
const { executa } = await import(pathToFileURL(path.join(AICI, 'executa-migrare.mjs')).href);
const x = await executa(plan, { consent: 'all', execute: false, out: path.join(T, 'exec') });
const execution = JSON.parse(fs.readFileSync(x.file, 'utf8'));
if (!(Array.isArray(execution.records) && execution.records.length >= 2)) throw new Error('the dry execution wrote fewer than two records');
const stricata = JSON.parse(JSON.stringify(execution)); stricata.records[1].record.verdict = 'OK';
return { bun: { bundle, plan, execution }, rau: { bundle, plan, execution: stricata }, rauRegula: 'shield.execution-chain', plan };
} finally { fs.rmSync(T, { recursive: true, force: true }); }
}
/** Raportul de conformitate PQ pe planul real, legat de plicul lui AIP-23; fratele stricat: raportul schimbat dupa legare. */
export async function raportulPQ(plan) {
const { raportConformitate } = await import(pathToFileURL(path.join(AICI, 'raport-conformitate.mjs')).href);
const { report, envelope } = raportConformitate({ plan, organization: 'Example Organization', risk: 'high', date: '2026-10-20' });
const schimbat = JSON.parse(JSON.stringify(report)); schimbat.organization = 'Another Organization';
return { bun: { report, envelope }, rau: { report: schimbat, envelope }, rauRegula: 'pqreport.binding' };
}
/**
* Pachetul bun al unei organizatii cu un artefact din fiecare fel (23 ale suitei + 2 ale planului de control), contextul derivat al celui care
* verifica, si ce se asteapta pentru fiecare (verdictul, regulile, starile nivelurilor din vector). `stricate[fel]` e fratele stricat al fiecaruia.
*/
export async function pachetulBun() {
const ales = alegeVectorii();
const artifacts = [], byArtifact = {}, asteptat = {}, stricate = {};
const idDe = (fel) => fel + '-1';
for (const [fel, { bun, rau }] of Object.entries(ales)) {
const id = idDe(fel);
artifacts.push({ id, artifact: fel, input: bun.pack });
if (Object.keys(bun.context).length) byArtifact[id] = bun.context;
asteptat[id] = { verdict: bun.v.expected.verdict, failed: bun.v.expected.failed, levels: bun.v.expected.levels, vector: bun.id };
stricate[fel] = { id, artifact: { id, artifact: fel, input: rau.pack }, failed: rau.v.expected.failed, levels: rau.v.expected.levels, vector: rau.id };
}
const sh = await stareaShield(); const pq = await raportulPQ(sh.plan);
artifacts.unshift({ id: 'shield-console-1', artifact: 'shield-console', input: sh.bun });
stricate['shield-console'] = { id: 'shield-console-1', artifact: { id: 'shield-console-1', artifact: 'shield-console', input: sh.rau }, failed: [sh.rauRegula], vector: null };
asteptat['shield-console-1'] = { verdict: 'VALID', failed: [] };
artifacts.push({ id: 'pq-compliance-report-1', artifact: 'pq-compliance-report', input: pq.bun });
stricate['pq-compliance-report'] = { id: 'pq-compliance-report-1', artifact: { id: 'pq-compliance-report-1', artifact: 'pq-compliance-report', input: pq.rau }, failed: [pq.rauRegula], vector: null };
asteptat['pq-compliance-report-1'] = { verdict: 'VALID', failed: [] };
for (const f of FELURI_PROPRII) if (!stricate[f]) throw new Error('no broken sibling for ' + f);
return {
pack: { v: 1, kind: PACK_KIND, organization: 'Example Organization', createdAt: '2026-10-20T09:00:00Z', artifacts },
context: { v: 1, kind: CONTEXT_KIND, byArtifact },
asteptat, stricate, ales,
};
}
/** pachetul cu artefactul `id` inlocuit (sau scos, cu null) */
export function inlocuieste(pack, id, artefact) {
const p = JSON.parse(JSON.stringify(pack));
const i = p.artifacts.findIndex((a) => a.id === id); if (i < 0) throw new Error('no artifact ' + id);
if (artefact === null) p.artifacts.splice(i, 1); else p.artifacts[i] = JSON.parse(JSON.stringify(artefact));
return p;
}

View File

@ -0,0 +1,562 @@
#!/usr/bin/env node
'use strict';
// AERE Quantum Security Control Plane - JUDECATA ORGANIZATIEI (B2, roadmap master punctele 41 si 43; 2026-10-20). Planul de control
// "toate domeniile intr-un loc": o organizatie aduce un PACHET DE DOVEZI (prezentari de identitate, plicuri AIP-23, lanturi de incredere AI,
// chitante de plata, jurnale de masini, starea Shield, raportul de conformitate PQ), iar aici fiecare artefact e judecat de VERIFICATORUL
// PUBLICAT al felului lui, si se scoate un verdict pe domeniu si un rezumat care nu e mai tare decat cel mai slab domeniu cerut.
//
// DE CE UN MODUL VECIN, NU O COMANDA NOUA IN consola.mjs: consola are un contract publicat (iesirea `aere-control-plane-console` forma 2,
// steagurile si codurile ei) pe care se sprijina proba ei, vizorul web si poarta publicarii; judecata organizatiei are alta intrare (un
// pachet + contextul celui care se bazeaza pe el), alta iesire, si cheama alte programe. Domeniul Shield e judecat CHIAR de consola.mjs
// (evalueaza), deci regulile ei traiesc intr-un singur loc.
//
// DE CE PRIN ADAPTORUL SUITEI DE CONFORMITATE: tools/aere-conformance/implementare-aere.mjs (publicat in aere-quantum/conformance/) pune
// deja cele saptesprezece verificatoare publicate in aceeasi forma de verdict, fara nicio regula a lui; un al doilea adaptor scris aici ar
// diverge in tacere de primul. Il chemam ca PROGRAM (--batch), deci "verificatorul nu porneste" se vede ca atare si iese NEMASURAT.
//
// PRINCIPIILE, si sunt valoarea modulului:
// 1. niciun verdict din pachet nu e crezut: se recalculeaza; un verdict scris in pachet care difera de masuratoare e numit ca suspect;
// 2. intrarile VERIFICATORULUI (ceasul, emitentii si producatorii de incredere, audienta, nonce-ul, cheia emitentului aleasa, citirile
// proprii ale lantului) sunt ale celui care se bazeaza pe pachet, nu ale organizatiei: vin din CONTEXT; una luata din pachet nu mai
// poate da VALID (organizatia si-ar alege singura increderea); ceasul nu se ia NICIODATA din pachet;
// 3. un domeniu fara artefacte e NOT_PRESENTED, nu verde; un verificator care nu porneste e NOT_MEASURED, nu INVALID si nu VALID;
// 4. reteaua (finalitatea pe lant) se citeste numai cu --online; fara el, artefactele care o cer sunt numite si verdictul spune ca nu a masurat.
//
// node organizatie.mjs --pack pack.json [--context context.json] [--require shield,identity,...] [--at <RFC 3339>] [--online] [--json]
// iesire 0 = rezumat VALID; 1 = INVALID; 2 = NOT_MEASURED (sau nu s-a putut rula); 3 = PARTIAL. Iesirea e in engleza.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import crypto from 'node:crypto';
import { spawn } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const RAD = path.resolve(AICI, '..', '..');
const sha256 = (s) => '0x' + crypto.createHash('sha256').update(s).digest('hex');
const obiect = (x) => !!x && typeof x === 'object' && !Array.isArray(x);
export const VERSIUNE = 'aere-organization-judgement/1 (2026-10-20)';
export const PACK_KIND = 'aere-organization-evidence-pack';
export const CONTEXT_KIND = 'aere-organization-context';
export const OUT_KIND = 'aere-organization-judgement';
/** Domeniile, in ordinea in care se arata. "cloud" nu e un domeniu al lui: ce intorc produsele Cloud se judeca in domeniul artefactului. */
export const DOMENII = [
{ id: 'shield', title: 'Security and post-quantum migration (Shield)' },
{ id: 'identity', title: 'Identity' },
{ id: 'compliance', title: 'Compliance' },
{ id: 'ai', title: 'AI and agents' },
{ id: 'payments', title: 'Payments and settlement' },
{ id: 'devices', title: 'Machines' },
{ id: 'audit', title: 'Audit logs' },
{ id: 'proofs', title: 'Proofs and data' },
];
const ID_DOMENII = DOMENII.map((d) => d.id);
/** felul artefactului -> domeniul. Felurile suitei de conformitate trebuie sa fie TOATE aici (proba o deriva din reguli.mjs); doua sunt ale
* planului de control insusi: starea Shield (judecata de consola.mjs) si raportul de conformitate PQ (raport-conformitate.mjs + plicul lui). */
export const DOMENIUL = {
'shield-console': 'shield',
'identity-presentation': 'identity', 'sdjwt-presentation': 'identity', 'sdjwt-vc-presentation': 'identity',
'compliance-presentation': 'compliance', 'travel-rule-binding': 'compliance', 'travel-rule-message': 'compliance', 'travel-rule-receipt': 'compliance',
'pq-compliance-report': 'compliance',
'ai-trust-bundle': 'ai', 'agent-ledger': 'ai',
'payment-risk': 'payments', 'payment-receipt': 'payments', 'market-timeline': 'payments',
'device-bundle': 'devices', 'device-operation-log': 'devices', 'device-reputation': 'devices',
'audit-log': 'audit', 'audit-inclusion': 'audit', 'audit-consistency': 'audit',
envelope: 'proofs', 'dataset-record': 'proofs', 'dataset-answer': 'proofs', 'dataset-consistency': 'proofs', 'dataset-sample': 'proofs',
};
export const FELURI_PROPRII = ['shield-console', 'pq-compliance-report'];
/** Cine aduce fiecare intrare a verificatorului, pe fel. Cheile sunt EXACT cele pe care le citeste adaptorul suitei (proba le deriva din
* sursa lui si din cei 791 de vectori, si cade pe o cheie neclasificata):
* artifact numai din pachet (artefactul organizatiei);
* stapled semnat de un tert (o lista de stare, o revocare, o politica de firmware): din pachet SI din context, unite (listele
* CONTEXTULUI intai, apoi ale pachetului: un verificator judeca numai primele N); o valoare care nu e lista: contextul castiga;
* verifier alegerea celui care verifica (increderea, provocarea, asteptarile, citirile lui proprii): din context; daca lipseste
* acolo si o aduce pachetul, se foloseste, dar artefactul nu mai poate fi VALID (`self-supplied`);
* verifier-only numai din context; din pachet se arunca, numit (lantul de citit, cheile KEM private ale beneficiarului, plafonul de timp);
* clock ceasul: contextul pe artefact sau pe fel, apoi --at, apoi `now` global al contextului, apoi ceasul masinii; din pachet NICIODATA.
* Revizia B2 (2026-10-21): nu mai exista fel "tot inputul e artefactul". Cronologia de piata si reputatia erau date intregi verificatorului,
* deci organizatia isi alegea singura ARBITRUL pietei (constants.arbiter: o rezolvare a disputei de catre ea insasi iesea VALID, iar constantele
* aduse de cel care verifica erau ignorate) si FAPTELE din care se calculeaza reputatia (fapte inventate, fara jurnal, iesea VALID). Cheile pe
* care le citeste modulul insusi se deriva acum din sursa lui (proba) si se clasifica pe nume, la fel ca toate celelalte; '*': A ramane numai
* pentru o cheie pe care modulul NU o citeste pe nume (un membru in plus), ca modulul care cere forma exacta sa o refuze in continuare. */
const A = 'artifact', S = 'stapled', V = 'verifier', O = 'verifier-only', C = 'clock';
const AUDIT = { log: A, head: A, proof: A, newer: A, older: V, signer: V };
const SDJWT = { sdJwt: A, now: C, issuerKey: V, issuerAlg: V, classicalHalf: V, audience: V, nonce: V, expectedIssuer: V, requireKeyBinding: V, requireExp: V,
maxAgeS: V, expectedTyp: V, expectedVct: V, statusListJwt: S, statusListKey: V, statusListAlg: V, requireStatus: V };
const TRAVEL = { binding: A, presentation: A, message: A, receipt: A, now: C, registries: V, originatorId: V, nonce: V, me: V, seen: V, beneficiaryId: V,
maxAgeS: V, statusLists: S, kem: O };
const DATASET = { record: A, commitment: A, answer: A, proof: A, newer: A, served: A, older: V, k: V, seed: V };
export const INTRARI = {
envelope: { envelope: A, raw: A, chain: O },
'payment-risk': { policy: A, inputs: A },
'payment-receipt': { receipt: A, policy: A, inputs: A, tx: V, txReceipt: V, chain: O },
// constants: ARBITER si ARBITRATION_TIMEOUT citite de cel care verifica din contractul pietei in care are incredere; chainId: lantul pe care se
// citeste plicul executiei (ca `chain` la plic); certified: citirea de sub ancora, pe care adaptorul o face el insusi
'market-timeline': { chainId: O, market: A, jobId: A, constants: V, job: A, events: A, result: A, spec: A, envelope: A, certified: O, '*': A },
'ai-trust-bundle': { bundle: A, chain: O },
'audit-log': AUDIT, 'audit-inclusion': AUDIT, 'audit-consistency': AUDIT,
'agent-ledger': { ledger: A, policy: A, policyHash: V, anchors: V, notBefore: V, revocations: S, maxTime: O },
'identity-presentation': { presentation: A, now: C, audience: V, nonce: V, trustedIssuers: V, maxAgeS: V, request: V, statusLists: S, revocations: S },
'device-bundle': { bundle: A, softwareAttestation: A, now: C, nonce: V, audience: V, trustedManufacturers: V, anchors: V, maxAgeS: V,
statusLists: S, firmwarePolicies: S, statusRecords: S },
'sdjwt-presentation': SDJWT, 'sdjwt-vc-presentation': SDJWT,
'travel-rule-binding': TRAVEL, 'travel-rule-message': TRAVEL, 'travel-rule-receipt': TRAVEL,
'compliance-presentation': { presentation: A, now: C, policy: V, audience: V, nonce: V, request: V, statusLists: S, revocations: S },
'device-operation-log': { log: A, now: C, clockSkewS: V, trustedManufacturers: V, readings: V, beacons: V, otherRecords: V, statusLists: S, statusRecords: S },
// facts: ce a DERIVAT verificatorul de operare din jurnal (citirea celui care verifica); params si claimed sunt afirmatia (fereastra ei si cifrele)
'device-reputation': { facts: V, params: A, claimed: A, '*': A },
'dataset-record': DATASET, 'dataset-answer': DATASET, 'dataset-consistency': DATASET, 'dataset-sample': DATASET,
'shield-console': { bundle: A, plan: A, execution: A },
'pq-compliance-report': { report: A, envelope: A },
};
/** Regulile planului de control pentru felurile lui proprii (restul regulilor sunt ale verificatoarelor, numite de suita). */
export const REGULI_PROPRII = {
'shield.bundle-form': ['integrity', 'the Shield state carries a sidecar bundle (kind aere-verify-layer-bundle) with at least one entry'],
'shield.audit-chain': ['integrity', 'the audit chain recomputed from its entries is intact (the console does not read chainOk from the bundle)'],
'shield.envelope-integrity': ['integrity', 'every AIP-23 envelope in the bundle: sha256 of its statement is its statementHash'],
'shield.self-report': ['integrity', 'what the bundle declares about its own chain (chainOk) is what the measurement gives'],
'shield.execution-chain': ['integrity', 'the migration execution chain (execution.json) recomputed is intact'],
'shield.plan-form': ['integrity', 'the migration plan has the planner\'s list of actions'],
'pqreport.binding': ['integrity', 'the compliance report is the one bound to the AIP-23 compliance envelope (canonical sha256 == evidenceHash)'],
// revizia B2, 2026-10-21: plicul nu e semnat, deci legarea singura lasa un rezumat rescris peste constatarile lui; si o executie rupta de planul ei
'pqreport.derivation': ['integrity', 'the report is what the report generator gives for its own findings, date, risk and CNSA choice, and the envelope\'s statement is the one it writes'],
'shield.execution-plan': ['integrity', 'the execution presented with a plan is the executor\'s run of that plan: one record per automatic action, in the plan\'s order, between its start and end records, and the end record counts them'],
};
/** Felurile pe care adaptorul le judeca CITIND LANTUL cu --online (plicul AIP-23 al lor); proba le deriva din sursa adaptorului. Ceilalti (o
* masina, un jurnal de operare) isi judeca notarizarea pe CITIRILE celui care verifica (anchors, readings), aduse in context: --online nu le ajuta. */
export const CITESC_LANTUL = ['envelope', 'payment-receipt', 'market-timeline', 'ai-trust-bundle', 'pq-compliance-report'];
// ---- unde stau celelalte piese: langa planul de control intr-o copie publica (../conformance), sau in depozitul de dezvoltare (tools/)
function dosarulSuitei() {
const c = [path.join(AICI, '..', 'conformance'), path.join(RAD, 'tools', 'aere-conformance')];
return c.find((d) => fs.existsSync(path.join(d, 'implementare-aere.mjs'))) || c[c.length - 1];
}
export const caleaAdaptorului = () => (process.env.AERE_ORG_ADAPTOR ? path.resolve(process.env.AERE_ORG_ADAPTOR) : path.join(dosarulSuitei(), 'implementare-aere.mjs'));
/** Forma verdictului (verdict.mjs al suitei) si tabla verificatoarelor (reguli.mjs), incarcate o data; o lipsa intoarce motivul, nu arunca. */
let SUITA = null;
async function suita() {
if (SUITA) return SUITA;
const d = dosarulSuitei();
try {
const V = await import(pathToFileURL(path.join(d, 'verdict.mjs')).href);
let R = null; try { R = await import(pathToFileURL(path.join(d, 'reguli.mjs')).href); } catch { R = null; }
SUITA = { ok: true, V, R };
} catch (e) { SUITA = { ok: false, motiv: `the verdict form of the conformance suite is not at ${d} (${String(e.message).split('\n')[0]})` }; }
return SUITA;
}
function verificatorul(fel, R) {
if (fel === 'shield-console') return { id: 'control-plane-console', module: 'control-plane/consola.mjs' };
if (fel === 'pq-compliance-report') return { id: 'compliance-report', module: 'control-plane/raport-conformitate.mjs, and verify-proof.mjs (AIP-23 reference verifier) for its envelope' };
if (!R || !R.VERIFICATOARE) return { id: '?', module: '?' };
const [id, v] = Object.entries(R.VERIFICATOARE).find(([, x]) => x.artifacts.includes(fel)) || [];
if (!v) return { id: '?', module: '?' };
let module = v.sursa === 'verify-proof' ? 'verify-proof.mjs (AIP-23 reference verifier)' : '?';
if (R.MODULE && R.MODULE[v.sursa]) module = fileURLToPath(R.MODULE[v.sursa].url).split(/[\\/]/).slice(-2).join('/');
return { id, module, through: 'conformance/implementare-aere.mjs' };
}
// ---- adaptorul suitei, chemat ca program: un verdict pe rand, in ordine; orice altceva e "nu a pornit" pentru TOATE artefactele lotului
function ruleaza(cmd, args, timeoutMs, env) {
return new Promise((gata) => {
let out = '', err = '', c, expirat = false;
try { c = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'], env }); } catch (e) { gata({ code: null, out, err: String(e.message) }); return; }
const t = setTimeout(() => { expirat = true; c.kill(); }, timeoutMs);
c.stdout.on('data', (d) => { out += d; }); c.stderr.on('data', (d) => { err += d; });
c.on('error', (e) => { clearTimeout(t); gata({ code: null, out, err: String(e.message) }); });
c.on('close', (code) => { clearTimeout(t); gata({ code, out, err, expirat }); });
});
}
const NIVELURI = ['integrity', 'authenticity', 'anchoring', 'finality'];
/**
* Un verdict al suitei e crezut ca VERDICT numai daca are forma ei si verdictul lui e CHIAR cel pe care regula suitei (verdict.mjs) il da pentru
* nivelurile lui: INVALID <=> o regula refuzata (si `failed` = regulile nivelurilor); VALID numai cu integritatea PASSED si fara o afirmatie
* nemasurata (o cauza din `cauzePartiale`). Revizia B2 (2026-10-21): pana azi un "VALID" cu integritatea nemasurata trecea drept VALID.
*/
export function formaVerdictului(j, cauzePartiale) {
if (!(obiect(j) && ['VALID', 'INVALID', 'PARTIAL'].includes(j.verdict) && Array.isArray(j.failed) && Array.isArray(j.levels) && j.levels.length === 4)) return false;
if (!j.levels.every((l, i) => obiect(l) && l.level === NIVELURI[i] && ['PASSED', 'FAILED', 'NOT_MEASURED'].includes(l.state) && Array.isArray(l.rules)
&& l.rules.every((x) => typeof x === 'string') && (l.state === 'FAILED') === (l.rules.length > 0) && (l.state !== 'NOT_MEASURED' || typeof l.cause === 'string'))) return false;
if (!Array.isArray(cauzePartiale)) return false;
const failed = [...new Set(j.levels.flatMap((l) => l.rules))].sort();
const partial = j.levels.some((l) => l.state === 'NOT_MEASURED' && cauzePartiale.includes(l.cause));
const v = failed.length ? 'INVALID' : partial || j.levels[0].state !== 'PASSED' ? 'PARTIAL' : 'VALID';
return j.verdict === v && JSON.stringify([...j.failed].sort()) === JSON.stringify(failed);
}
/** Un lot catre adaptor: { linii, motiv } (motiv = de ce lotul nu a dat cate un rand pe artefact) */
async function unLot(items, { adaptor, online, timeoutS }) {
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'aere-org-'));
try {
const lista = items.map((x, k) => { const f = path.join(T, k + '.json'); fs.writeFileSync(f, JSON.stringify({ artifact: x.artifact, input: x.input })); return { file: f, online: !!online }; });
const lf = path.join(T, 'list.json'); fs.writeFileSync(lf, JSON.stringify(lista));
const r = await ruleaza(process.execPath, [adaptor, '--batch', lf], timeoutS * 1000, process.env);
const linii = r.out.split('\n').filter((l) => l.trim());
const coada = (r.err || '').trim().split('\n').slice(-1)[0].slice(0, 200);
const motiv = linii.length === items.length ? null : `the adapter gave ${linii.length} verdict(s) for ${items.length} artifact(s) (${r.expirat ? `it did not finish in ${timeoutS} s` : 'exit ' + r.code})${coada ? ': ' + coada : ''}`;
return { linii, motiv };
} finally { fs.rmSync(T, { recursive: true, force: true }); }
}
/** Randurile unui lot, citite in ORDINEA artefactelor: un verdict dat pentru alt fel decat cel cerut in locul lui inseamna ca ordinea raspunsurilor
* nu se poate crede, deci niciun raspuns al lotului (revizia B2: un adaptor care raspundea invers dadea artefactului stricat verdictul altuia). */
function citesteLotul(linii, items, cauzePartiale) {
const js = linii.map((l) => { try { return JSON.parse(l); } catch { return undefined; } });
const k = js.findIndex((j, i) => obiect(j) && typeof j.verdict === 'string' && j.artifact !== items[i].artifact);
if (k >= 0) return items.map(() => ({ error: `the adapter answered for ${JSON.stringify(js[k].artifact)} in the place of ${items[k].artifact} (position ${k}): the order of its answers cannot be trusted, so none is` }));
return js.map((j) => {
if (j === undefined) return { error: 'the adapter gave a line that is not JSON' };
if (formaVerdictului(j, cauzePartiale)) return j;
return { error: obiect(j) && typeof j.error === 'string' ? 'the verifier gave no verdict: ' + j.error : 'the adapter gave something that is not a verdict of the suite\'s form (or a verdict its own levels do not give)' };
});
}
async function adaptorul(items, { adaptor, online, timeoutS, cauzePartiale }) {
if (!items.length) return [];
if (!fs.existsSync(adaptor)) return items.map(() => ({ error: `the conformance adapter is not at ${adaptor}: no verifier was run` }));
const o = { adaptor, online, timeoutS };
const lot = await unLot(items, o);
if (!lot.motiv) return citesteLotul(lot.linii, items, cauzePartiale);
if (items.length === 1) return [{ error: lot.motiv }];
// un lot care nu s-a terminat (un artefact care opreste sau tine adaptorul pe loc) nu are voie sa ia cu el verdictele celorlalte: fiecare artefact
// e judecat atunci SINGUR, cu acelasi plafon de timp (revizia B2: un esantion lent adus de pachet facea NOT_MEASURED tot lotul)
const rez = new Array(items.length); let urm = 0;
await Promise.all(Array.from({ length: Math.min(2, items.length) }, async () => {
while (urm < items.length) {
const k = urm++; const u = await unLot([items[k]], o);
const unu = u.motiv ? { error: u.motiv } : citesteLotul(u.linii, [items[k]], cauzePartiale)[0];
rez[k] = unu.error ? { error: `the batch of ${items.length} did not complete (${lot.motiv}); judged alone: ${unu.error}` } : unu;
}
}));
return rez;
}
// ---- intrarile unui artefact: ce e al organizatiei, ce e semnat de un tert, ce e al celui care verifica, ceasul
const CEAS_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
export function rezolvaIntrarile(fel, intrare, ctx, ceas) {
const cls = INTRARI[fel];
const note = { fromContext: [], selfSupplied: [], dropped: [], ignored: [], contextIgnored: [], stapled: {}, now: null };
if (!obiect(cls)) throw new Error(`no classification of the inputs of ${fel}`);
// fara prototip: o cheie `__proto__` din pachet ramane o cheie (si ajunge la verificator ca atare), nu schimba obiectul
const input = Object.create(null);
// cine castiga o intrare a verificatorului adusa de amandoi: contextul (o singura hotarare, citita de ambele bucle)
const castigaContextul = (k) => Object.hasOwn(ctx, k);
// '*': la felurile pe care modulul le citeste INTREGI (si le cere exact forma), o cheie necunoscuta e a artefactului si ajunge la el; cheile pe
// care le citeste sunt toate numite, deci '*' nu poate acoperi una a celui care verifica (proba o cere)
const clasa = (k) => (Object.hasOwn(cls, k) ? cls[k] : Object.hasOwn(cls, '*') ? cls['*'] : null);
for (const [k, v] of Object.entries(intrare)) {
const c = clasa(k);
if (c === null) note.ignored.push(k);
else if (c === A) input[k] = v;
else if (c === S) { input[k] = v; note.stapled[k] = 'pack'; }
else if (c === V) { if (!castigaContextul(k)) { input[k] = v; note.selfSupplied.push(k); } }
else note.dropped.push(k); // verifier-only si clock: o valoare din pachet nu se foloseste niciodata
}
for (const [k, v] of Object.entries(ctx)) {
const c = clasa(k);
if (c === V && !castigaContextul(k)) continue;
if (c === V || c === O || c === C) { input[k] = v; note.fromContext.push(k); }
else if (c === S) {
// listele CELUI CARE VERIFICA intai: verificatoarele judeca numai primele N elemente ale unei liste (64 de liste de rechemare la o masina),
// iar cu listele pachetului in fata, 64 de liste curate aduse de organizatie impingeau rechemarea adusa de cel care verifica afara din
// fereastra si INVALID devenea PARTIAL (revizia B2, 2026-10-21, masurat pe vectorul sold-once.manufacture-revoked)
if (Array.isArray(input[k]) && Array.isArray(v)) { input[k] = [...v, ...input[k]]; note.stapled[k] = 'context+pack'; }
else { input[k] = v; note.stapled[k] = 'context'; }
} else note.contextIgnored.push(k); // un context nu poate scrie artefactul organizatiei
}
if (cls.now === C) {
if (Object.hasOwn(input, 'now') && note.fromContext.includes('now')) note.now = { value: input.now, source: 'context' };
else { input.now = ceas.now; note.now = { value: ceas.now, source: ceas.source }; }
}
return { input, note };
}
// ---- felurile proprii: starea Shield (consola.mjs) si raportul de conformitate PQ
/** Executia (execution.json al executorului) e rularea ACESTUI plan? null daca da, altfel motivul. Executorul scrie un rand `start` cu numarul de
* actiuni ale planului, cate un rand pentru fiecare actiune `auto-aere`, in ordinea planului (si cand nu are consimtamant: SKIPPED), si un rand `end`
* care le numara (executa-migrare.mjs, `executa`). */
export function legaturaExecutiei(plan, execution) {
const recs = (Array.isArray(execution) ? execution : execution.records).map((e) => (obiect(e) ? e.record : null));
const start = recs[0], end = recs[recs.length - 1];
if (!(obiect(start) && start.type === 'start')) return 'the execution does not begin with the executor\'s start record';
if (recs.length < 2 || !(obiect(end) && end.type === 'end')) return 'the execution does not end with the executor\'s end record (cut short, or not written by the executor)';
const actiuni = plan.actions, auto = actiuni.filter((a) => obiect(a) && a.method === 'auto-aere').map((a) => a.ref);
if (start.actionsInPlan !== actiuni.length) return `the execution says its plan had ${start.actionsInPlan} action(s); the plan presented with it has ${actiuni.length}`;
const facute = recs.slice(1, -1);
if (!facute.every((r) => obiect(r) && typeof r.ref === 'string')) return 'a record between start and end is not an action record';
const refs = facute.map((r) => r.ref);
if (JSON.stringify(refs) !== JSON.stringify(auto)) {
const lipsa = auto.filter((x) => !refs.includes(x)), straine = refs.filter((x) => !auto.includes(x));
return 'the execution is not the run of the plan presented with it: ' + [lipsa.length ? `no record for ${lipsa.slice(0, 3).join(', ')}${lipsa.length > 3 ? ` and ${lipsa.length - 3} more` : ''}` : '',
straine.length ? `records for actions the plan does not have automatic: ${straine.slice(0, 3).join(', ')}${straine.length > 3 ? ` and ${straine.length - 3} more` : ''}` : '',
!lipsa.length && !straine.length ? 'the records are not one per automatic action in the plan\'s order' : ''].filter(Boolean).join('; ');
}
const s = end.summary, n = (v) => facute.filter((r) => r.verdict === v).length;
if (!(obiect(s) && s.total === facute.length && s.ok === n('OK') && s.failed === n('FAILED') && s.dryRun === n('DRY-RUN') && s.skipped === n('SKIPPED-no-consent') && s.notExecutable === actiuni.length - auto.length)) return 'the end record of the execution does not count its own records';
return null;
}
async function judecaShield(input, V) {
const acc = V.acumulator();
const b = input.bundle;
if (!(obiect(b) && b.kind === 'aere-verify-layer-bundle' && Array.isArray(b.entries) && b.entries.length > 0)) {
V.pica(acc, 'integrity', 'shield.bundle-form', 'not a sidecar bundle with entries (kind aere-verify-layer-bundle, entries[] not empty)');
for (const l of ['authenticity', 'anchoring', 'finality']) V.nemasurat(acc, l, 'not-reached', 'the bundle could not be read');
return { v: V.verdictUnic('shield-console', acc), details: null };
}
const { evalueaza } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href);
const st = await evalueaza({ bundle: b, plan: input.plan === undefined ? null : input.plan, execution: input.execution === undefined ? null : input.execution });
if (!st.auditChain.ok) V.pica(acc, 'integrity', 'shield.audit-chain', `the audit chain is broken at seq ${st.auditChain.brokenAtSeq}: ${st.auditChain.reason}`);
if (st.envelopes.bad.length) V.pica(acc, 'integrity', 'shield.envelope-integrity', st.envelopes.bad.map((x) => `seq ${x.seq} (${x.kind}): ${x.reason}`).join('; '));
if (st.selfReportSuspect) V.pica(acc, 'integrity', 'shield.self-report', st.selfReportSuspect);
if (st.execution && !st.execution.chainOk) V.pica(acc, 'integrity', 'shield.execution-chain', `the execution chain is broken at seq ${st.execution.brokenAtSeq}: ${st.execution.reason}`);
if (st.migration && st.migration.total === null) V.pica(acc, 'integrity', 'shield.plan-form', st.migration.error);
// executia si planul prezentate impreuna trebuie sa fie aceeasi rulare (revizia B2: o executie din care lipsea o actiune, sau executia altui plan,
// iesea VALID langa planul acesta); numai cand amandoua se pot citi, altfel au deja regula lor
if (st.execution && st.execution.chainOk && st.migration && st.migration.total !== null) { const m = legaturaExecutiei(input.plan, input.execution); if (m) V.pica(acc, 'integrity', 'shield.execution-plan', m); }
V.trece(acc, 'integrity', `the console recomputed the audit chain (${st.auditChain.count} entries, head ${st.auditChain.head || '-'}) and ${st.envelopes.total} envelope digest(s)`
+ (st.migration && st.migration.total !== null ? `; plan: ${st.migration.total} action(s)` : '') + (st.execution ? `; execution: ${st.execution.actions} action(s)` : ''));
// ce poarta burafu si consola NU judeca: o semnatura, o notarizare. Purtate, sunt o afirmatie nemasurata aici (PARTIAL)
const semnate = b.entries.filter((e) => obiect(e) && obiect(e.proof) && e.proof.signature).map((e) => e.seq);
const notarizate = b.entries.filter((e) => obiect(e) && obiect(e.proof) && e.proof.notarization).map((e) => e.seq);
if (semnate.length) V.nemasurat(acc, 'authenticity', 'claim-not-checked', `entries ${semnate.join(',')} carry a signature, which the console does not judge: present each as an \`envelope\` artifact`);
else V.nemasurat(acc, 'authenticity', 'not-judged', 'no entry carries a signature; a signed head is judged as an `audit-log` artifact with its head');
const fin = notarizate.length ? ['claim-not-checked', `entries ${notarizate.join(',')} claim a notarization, which the console does not read: present each as an \`envelope\` artifact and judge with --online`]
: ['not-judged', 'the head is anchored by notarizing its envelope, judged as the artifact `envelope`'];
for (const l of ['anchoring', 'finality']) V.nemasurat(acc, l, fin[0], fin[1]);
return {
v: V.verdictUnic('shield-console', acc),
details: { console: { auditChain: { ok: st.auditChain.ok, count: st.auditChain.count, head: st.auditChain.head }, envelopes: { total: st.envelopes.total, intact: st.envelopes.intact },
migration: st.migration, execution: st.execution } },
};
}
/** Un verdict al suitei, pus inapoi intr-un acumulator (ca sa i se adauge o regula si sa se refaca verdictul in aceeasi forma). */
function inAcumulator(V, acc, v) {
for (const l of v.levels) {
if (l.state === 'FAILED') for (const r of l.rules) V.pica(acc, l.level, r, l.reason);
else if (l.state === 'NOT_MEASURED') V.nemasurat(acc, l.level, l.cause, l.reason);
else V.trece(acc, l.level, l.reason);
}
}
// ---- judecata
const RANG = { INVALID: 0, NOT_MEASURED: 1, NOT_PRESENTED: 1, PARTIAL: 2, VALID: 3 };
export const celMaiSlab = (vs) => vs.reduce((a, b) => (RANG[b] < RANG[a] ? b : a));
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$/;
const nemasuratArtefact = (baza, motiv) => ({ ...baza, verdict: 'NOT_MEASURED', verifierVerdict: null, failed: [], levels: null, error: motiv });
function citesteContextul(context) {
if (context == null) return { ok: true, global: {}, byKind: {}, byArtifact: {}, now: null, require: null };
if (!obiect(context) || context.kind !== CONTEXT_KIND) return { ok: false, motiv: `the context is not an ${CONTEXT_KIND}` };
for (const k of ['byKind', 'byArtifact']) if (context[k] !== undefined && !(obiect(context[k]) && Object.values(context[k]).every(obiect))) return { ok: false, motiv: `context.${k} must map names to objects` };
if (context.now !== undefined && !(typeof context.now === 'string' && CEAS_RE.test(context.now))) return { ok: false, motiv: 'context.now must be an RFC 3339 time' };
if (context.require !== undefined && !(Array.isArray(context.require) && context.require.length && context.require.every((d) => ID_DOMENII.includes(d)))) return { ok: false, motiv: `context.require must list domains among ${ID_DOMENII.join(', ')}` };
return { ok: true, byKind: context.byKind || {}, byArtifact: context.byArtifact || {}, now: context.now || null, require: context.require || null };
}
/**
* Judeca un pachet de dovezi al unei organizatii.
* @param {object} pack pachetul (aere-organization-evidence-pack)
* @param {object} [o.context] contextul celui care se bazeaza pe pachet (aere-organization-context): ceas, incredere, provocari, cerinte
* @param {string[]} [o.require] domeniile cerute (peste context.require); implicit TOATE
* @param {string} [o.at] ceasul celui care verifica (RFC 3339), daca nu il da contextul; implicit ceasul masinii
* @param {boolean} [o.online] citeste lantul pentru finalitate (prin adaptor si verificatorul de referinta); implicit NU
*/
export async function judecaPachetul(pack, { context = null, require = null, at = null, online = false, adaptor = caleaAdaptorului(), timeoutS = null, packSha256 = null } = {}) {
if (require !== null && !(Array.isArray(require) && require.length && require.every((d) => ID_DOMENII.includes(d)))) throw new Error(`--require must list domains among ${ID_DOMENII.join(', ')}`);
if (at !== null && !(typeof at === 'string' && CEAS_RE.test(at))) throw new Error('--at must be an RFC 3339 time');
const ctx = citesteContextul(context);
const ceas = at ? { now: at, source: '--at' } : ctx.ok && ctx.now ? { now: ctx.now, source: 'context' } : { now: new Date().toISOString(), source: 'this machine' };
const cerute = require ? { domains: require, source: '--require' } : ctx.ok && ctx.require ? { domains: ctx.require, source: 'context' } : { domains: ID_DOMENII, source: 'default: every domain' };
const iesire = {
v: 1, kind: OUT_KIND, version: VERSIUNE,
organization: obiect(pack) && typeof pack.organization === 'string' ? pack.organization : null,
pack: { sha256: packSha256, createdAt: obiect(pack) && typeof pack.createdAt === 'string' ? pack.createdAt : null, artifacts: obiect(pack) && Array.isArray(pack.artifacts) ? pack.artifacts.length : null },
clock: ceas, network: { online: !!online, needed: [], readings: [], unmeasured: [], finality: [] }, required: cerute,
summary: null, domains: [], artifacts: [], unjudged: [], selfReport: { ignored: [], suspect: [] },
};
const opreste = (motiv) => {
iesire.domains = DOMENII.map((d) => ({ domain: d.id, title: d.title, verdict: 'NOT_PRESENTED', artifacts: [], failed: [], reason: 'nothing was judged' }));
iesire.summary = { verdict: 'NOT_MEASURED', weakest: [], notPresented: [], outsideRequired: {}, reason: motiv };
return iesire;
};
if (!ctx.ok) return opreste('the context cannot be read: ' + ctx.motiv);
if (!(obiect(pack) && pack.kind === PACK_KIND && Array.isArray(pack.artifacts))) return opreste(`the pack cannot be read: not an ${PACK_KIND} with an artifacts list`);
const S0 = await suita();
if (!S0.ok) return opreste(S0.motiv);
const { V, R } = S0;
// 1) forma fiecarui artefact, domeniul lui, intrarile rezolvate
const rezultate = []; const deJudecat = []; const vazute = new Set(); const pePozitie = new Map();
pack.artifacts.forEach((a, k) => {
const fel = obiect(a) && typeof a.artifact === 'string' ? a.artifact : null;
const id = obiect(a) && typeof a.id === 'string' && ID_RE.test(a.id) ? a.id : null;
const domeniu = fel && Object.hasOwn(DOMENIUL, fel) ? DOMENIUL[fel] : null;
if (!domeniu) { iesire.unjudged.push({ id: id || `#${k}`, artifact: fel, reason: fel ? `no verifier here judges the kind ${fel}` : 'the artifact names no kind' }); return; }
const baza = { id: id || `#${k}`, artifact: fel, domain: domeniu, verifier: verificatorul(fel, R), inputSha256: sha256(JSON.stringify(obiect(a) ? (a.input ?? null) : null)) };
const nm = (motiv) => { const r = nemasuratArtefact(baza, motiv); rezultate.push(r); pePozitie.set(k, r); };
if (!id) { nm('the artifact has no id (letters, digits, . _ : / -, at most 128)'); return; }
if (vazute.has(id)) { nm(`the id ${id} appears more than once in the pack: only its first artifact is judged`); return; }
vazute.add(id);
if (!obiect(a.input)) { nm('input is not an object'); return; }
const cx = { ...(obiect(ctx.byKind[fel]) ? ctx.byKind[fel] : {}), ...(obiect(ctx.byArtifact[id]) ? ctx.byArtifact[id] : {}) };
const { input, note } = rezolvaIntrarile(fel, a.input, cx, ceas);
const r = { ...baza, inputs: note };
rezultate.push(r); pePozitie.set(k, r); deJudecat.push({ r, input });
});
// 2) un singur lot catre adaptor (felurile suitei si plicul raportului PQ), apoi felurile proprii
const lot = [];
for (const x of deJudecat) {
if (x.r.artifact === 'shield-console') continue;
if (x.r.artifact === 'pq-compliance-report') { x.plic = lot.length; lot.push({ artifact: 'envelope', input: { envelope: x.input.envelope === undefined ? null : x.input.envelope } }); continue; }
x.lot = lot.length; lot.push({ artifact: x.r.artifact, input: x.input });
}
const verdicte = await adaptorul(lot, { adaptor, online, timeoutS: timeoutS || (online ? 1800 : 600), cauzePartiale: V.CAUZE_PARTIALE });
for (const x of deJudecat) {
let v = null, eroare = null;
try {
if (x.r.artifact === 'shield-console') { const s = await judecaShield(x.input, V); v = s.v; if (s.details) x.r.details = s.details; }
else if (x.r.artifact === 'pq-compliance-report') {
const { verifyReport, verificaDerivarea } = await import(pathToFileURL(path.join(AICI, 'raport-conformitate.mjs')).href);
const leg = obiect(x.input.report) ? verifyReport(x.input.report, x.input.envelope) : { ok: false, reason: 'the artifact carries no report' };
const pv = verdicte[x.plic];
if (pv.error && leg.ok) eroare = 'the envelope of the report was not judged: ' + pv.error;
else {
const acc = V.acumulator();
if (pv.error) for (const l of NIVELURI) V.nemasurat(acc, l, 'not-reached', 'the envelope of the report was not judged: ' + pv.error);
else inAcumulator(V, acc, pv);
if (!leg.ok) V.pica(acc, 'integrity', 'pqreport.binding', leg.reason);
else {
V.trece(acc, 'integrity', 'the report is the one bound to the envelope (canonical sha256 == evidenceHash)');
// legat nu inseamna facut de generator: plicul nu e semnat, deci un rezumat rescris si legat din nou trecea (revizia B2, 2026-10-21)
const dv = verificaDerivarea(x.input.report, x.input.envelope);
if (dv.ok === false) V.pica(acc, 'integrity', 'pqreport.derivation', dv.reason);
else if (dv.ok === null) V.nemasurat(acc, 'integrity', 'unmeasurable', dv.reason);
else V.trece(acc, 'integrity', 'the report is what the report generator gives for its own findings, and the envelope\'s statement is the one it writes');
}
v = V.verdictUnic('pq-compliance-report', acc);
if (obiect(x.input.report)) x.r.details = { report: { organization: x.input.report.organization ?? null, date: x.input.report.date ?? null,
finding: obiect(x.input.report.summary) ? x.input.report.summary.verdict ?? null : null } };
}
} else { const a = verdicte[x.lot]; if (a.error) eroare = a.error; else v = a; }
} catch (e) { eroare = 'the verifier could not run: ' + String(e.message).split('\n')[0].slice(0, 200); }
if (eroare) { Object.assign(x.r, { verdict: 'NOT_MEASURED', verifierVerdict: null, failed: [], levels: null, error: eroare }); continue; }
Object.assign(x.r, { verifierVerdict: v.verdict, verdict: v.verdict, failed: v.failed, levels: v.levels });
// intrarile verificatorului aduse de organizatie: verificatorul a spus VALID, dar pe increderea aleasa de ea; nu e VALID aici
const proprii = x.r.inputs.selfSupplied;
if (v.verdict === 'VALID' && proprii.length) {
x.r.verdict = 'PARTIAL';
x.r.capped = `the verifier's own inputs (${proprii.join(', ')}) came from the organization's pack, not from the relying party's context: the verifier said VALID on trust the organization chose itself`;
}
}
// 3) reteaua: ce a ramas nemasurat fiindca nu s-a citit lantul (consola nu citeste lantul nici online). Revizia B2 (2026-10-21), trei lucruri:
// - raportul PQ isi are nivelurile de la plicul lui, citit de adaptor ca orice plic: pana azi lipsea din `needed`;
// - o masina sau un jurnal de operare isi judeca notarizarea pe CITIRILE celui care verifica (context: anchors, readings), nu pe lant: --online
// nu le ajuta, deci stau in `readings`, nu in `needed` (pana azi rezumatul spunea "(no --online)" si cu --online dat);
// - finalitatea citita isi numeste lantul, si un testnet e spus ca testnet (adaptorul il scrie in motivul nivelului; o forma necunoscuta: chain null)
for (const r of rezultate) {
if (!r.levels || r.artifact === 'shield-console') continue;
const lantul = r.levels.filter((l) => l.level === 'anchoring' || l.level === 'finality' || r.artifact === 'ai-trust-bundle');
if (lantul.some((l) => l.state === 'NOT_MEASURED' && l.cause === 'claim-not-checked')) (CITESC_LANTUL.includes(r.artifact) ? iesire.network.needed : iesire.network.readings).push(r.id);
if (online && r.levels.some((l) => (l.level === 'anchoring' || l.level === 'finality') && l.state === 'NOT_MEASURED' && l.cause === 'unmeasurable')) iesire.network.unmeasured.push(r.id);
const fin = r.levels.find((l) => l.level === 'finality');
if (fin && fin.state === 'PASSED') {
const m = /\(chain (\d+)(, a testnet)?; the finality is that chain's\)/.exec(String(fin.reason || ''));
iesire.network.finality.push({ id: r.id, chain: m ? Number(m[1]) : null, testnet: m ? !!m[2] : null });
}
}
// 4) domeniile: fiecare cat cel mai slab artefact al lui; fara artefacte, NOT_PRESENTED
iesire.artifacts = rezultate;
iesire.domains = DOMENII.map((d) => {
const ale = rezultate.filter((r) => r.domain === d.id);
if (!ale.length) return { domain: d.id, title: d.title, verdict: 'NOT_PRESENTED', artifacts: [], failed: [], reason: 'no artifact of this domain in the pack' };
const verdict = celMaiSlab(ale.map((r) => r.verdict));
const slabe = ale.filter((r) => r.verdict === verdict).map((r) => r.id);
return { domain: d.id, title: d.title, verdict, artifacts: ale.map((r) => r.id), failed: [...new Set(ale.flatMap((r) => r.failed))].sort(),
reason: `${ale.length} artifact(s); the weakest (${verdict}): ${slabe.join(', ')}` };
});
// 5) rezumatul: nu e mai tare decat cel mai slab domeniu CERUT; un domeniu cerut si neprezentat e NOT_MEASURED pentru rezumat
const cer = iesire.domains.filter((d) => cerute.domains.includes(d.domain));
const vs = cer.map((d) => (d.verdict === 'NOT_PRESENTED' ? 'NOT_MEASURED' : d.verdict));
let verdict = celMaiSlab(vs);
const weakest = cer.filter((d, i) => RANG[vs[i]] === RANG[verdict]).map((d) => d.domain);
const notPresented = cer.filter((d) => d.verdict === 'NOT_PRESENTED').map((d) => d.domain);
const motive = [`${cer.length} required domain(s) (${cerute.source}); weakest: ${weakest.join(', ')}`];
if (notPresented.length) motive.push(`not presented: ${notPresented.join(', ')}`);
if (iesire.unjudged.length && verdict === 'VALID') { verdict = 'PARTIAL'; weakest.length = 0; motive.push(`${iesire.unjudged.length} artifact(s) of the pack no verifier here judges: the pack claims more than was measured`); }
else if (iesire.unjudged.length) motive.push(`${iesire.unjudged.length} artifact(s) not judged (unknown kind)`);
const outsideRequired = Object.fromEntries(iesire.domains.filter((d) => !cerute.domains.includes(d.domain) && d.verdict !== 'NOT_PRESENTED').map((d) => [d.domain, d.verdict]));
if (Object.values(outsideRequired).includes('INVALID')) motive.push(`INVALID outside the required domains: ${Object.entries(outsideRequired).filter(([, x]) => x === 'INVALID').map(([d]) => d).join(', ')}`);
const N = iesire.network;
if (N.needed.length) motive.push(`${N.needed.length} artifact(s) claim a notarization that was not read${online ? '' : ' (no --online)'}`);
if (N.readings.length) motive.push(`${N.readings.length} artifact(s) claim a notarization that only the relying party's own readings check (the context's anchors or readings; --online does not read them)`);
if (N.unmeasured.length) motive.push(`the chain could not be read for ${N.unmeasured.length} artifact(s)`);
const pePlasa = N.finality.filter((f) => f.testnet === true), neNumit = N.finality.filter((f) => f.chain === null);
if (pePlasa.length) motive.push(`${pePlasa.length} artifact(s) are final only on a testnet (chain ${[...new Set(pePlasa.map((f) => f.chain))].join(', ')})`);
if (neNumit.length) motive.push(`${neNumit.length} artifact(s) are final on a chain the verifier did not name`);
iesire.summary = { verdict, weakest, notPresented, outsideRequired, reason: motive.join('; ') };
// 6) verdictele scrise de pachet despre el insusi: ignorate; cele care difera de masuratoare sunt numite
const spune = (unde, x, masurat) => { if (typeof x !== 'string') return; iesire.selfReport.ignored.push({ where: unde, says: x }); if (x !== masurat) iesire.selfReport.suspect.push({ where: unde, says: x, measured: masurat }); };
spune('pack.verdict', pack.verdict, verdict);
if (obiect(pack.summary)) spune('pack.summary.verdict', pack.summary.verdict, verdict);
pack.artifacts.forEach((a, k) => {
const r = pePozitie.get(k); if (!obiect(a) || !r) return;
spune(`artifact ${r.id}: verdict`, a.verdict, r.verdict);
if (obiect(a.expected)) spune(`artifact ${r.id}: expected.verdict`, a.expected.verdict, r.verdict);
});
return iesire;
}
export const codulIesirii = (v) => ({ VALID: 0, INVALID: 1, NOT_MEASURED: 2, PARTIAL: 3 }[v] ?? 2);
function text(o) {
const L = [];
L.push(`ORGANIZATION JUDGEMENT${o.organization ? ' - ' + o.organization : ''}: ${o.summary.verdict}`);
L.push(` ${o.summary.reason}`);
L.push(` pack ${o.pack.sha256 || '-'}, ${o.pack.artifacts ?? '?'} artifact(s); clock ${o.clock.now} (${o.clock.source}); network: ${o.network.online ? 'read (--online)' : 'not read'}`
+ (o.network.needed.length ? `; needed by ${o.network.needed.join(', ')}` : '') + (o.network.readings.length ? `; the relying party's readings needed by ${o.network.readings.join(', ')}` : '')
+ (o.network.unmeasured.length ? `; unmeasured for ${o.network.unmeasured.join(', ')}` : '')
+ (o.network.finality.length ? `; final on ${o.network.finality.map((f) => `${f.chain === null ? 'an unnamed chain' : 'chain ' + f.chain + (f.testnet ? ' (a testnet)' : '')} (${f.id})`).join(', ')}` : ''));
for (const d of o.domains) L.push(` ${d.domain.padEnd(11)} ${(d.verdict === 'NOT_PRESENTED' ? 'NOT PRESENTED' : d.verdict).padEnd(13)} ${d.artifacts.length ? d.artifacts.length + ' artifact(s)' + (d.failed.length ? '; refused by ' + d.failed.join(', ') : '') : ''}${o.required.domains.includes(d.domain) ? '' : ' (not required)'}`);
const rele = o.artifacts.filter((r) => r.verdict !== 'VALID');
if (rele.length) L.push(' artifacts not VALID:');
for (const r of rele) {
const nm = (r.levels || []).filter((l) => l.state !== 'PASSED').map((l) => `${l.level} ${l.state}${l.cause ? ' (' + l.cause + ')' : ''}`).join(', ');
L.push(` ${r.id} (${r.artifact}): ${r.verdict}` + (r.failed.length ? ` - refused by ${r.failed.join(', ')}` : '') + (r.error ? ` - ${r.error}` : '') + (r.capped ? ` - ${r.capped}` : '') + (nm && !r.failed.length && !r.capped ? ` - ${nm}` : ''));
}
for (const r of o.artifacts) {
const n = r.inputs; if (!n) continue;
const parti = [];
if (n.dropped.length) parti.push(`dropped from the pack: ${n.dropped.join(', ')}`);
if (n.ignored.length) parti.push(`not an input of the verifier: ${n.ignored.join(', ')}`);
if (n.contextIgnored.length) parti.push(`context keys ignored: ${n.contextIgnored.join(', ')}`);
if (parti.length) L.push(` ${r.id}: ${parti.join('; ')}`);
}
for (const u of o.unjudged) L.push(` not judged: ${u.id} (${u.artifact || '?'}): ${u.reason}`);
for (const s of o.selfReport.suspect) L.push(` WARNING: the pack says ${s.where} = ${s.says}; measured ${s.measured} (ignored)`);
return L.join('\n');
}
async function main() {
const a = process.argv.slice(2);
const get = (f) => { const i = a.indexOf(f); return i >= 0 ? a[i + 1] : null; };
const pf = get('--pack');
if (!pf) { console.error('usage: node organizatie.mjs --pack pack.json [--context context.json] [--require shield,identity,...] [--at <RFC 3339>] [--online] [--json]'); return 2; }
let brut; try { brut = fs.readFileSync(pf); } catch (e) { console.error('the pack cannot be read: ' + e.message); return 2; }
let pack; try { pack = JSON.parse(brut.toString('utf8')); } catch (e) { pack = { kind: 'not JSON: ' + e.message }; }
const cf = get('--context'); let context = null;
if (cf) { try { context = JSON.parse(fs.readFileSync(cf, 'utf8')); } catch (e) { console.error('the context cannot be read: ' + e.message); return 2; } }
const rq = get('--require');
const o = await judecaPachetul(pack, { context, require: rq ? rq.split(',').map((x) => x.trim()).filter(Boolean) : null, at: get('--at'), online: a.includes('--online'), packSha256: sha256(brut) });
console.log(a.includes('--json') ? JSON.stringify(o, null, 1) : text(o));
return codulIesirii(o.summary.verdict);
}
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
main().then((c) => { process.exitCode = c; }).catch((e) => { console.error(e.message); process.exitCode = 2; });
}

View File

@ -0,0 +1,182 @@
// Proba vederii judecatii organizatiei in consola web (consola-web/index.html), intr-un Chromium ADEVARAT (playwright-core + Chromium-ul
// instalat de Playwright), pe telefon (390x844, isMobile, cu media queries aplicate) si pe desktop (1440x900), cu reteaua taiata. Iesirile
// redate sunt REALE: organizatie.mjs pe pachetul derivat din producatori (fixturi-organizatie.mjs): pachetul bun, acelasi cu un artefact de
// identitate stricat, acelasi fara domeniul masinilor, si unul cu un verdict scris de pachet care minte. Cere, pe fiecare latime: pagina cat
// ecranul si fara derulare orizontala dupa fiecare redare; verdictul din antet = rezumatul, in culoarea lui; cate o caseta pe domeniu, cu
// verdictul DOMENIULUI din iesire; fiecare artefact cu verificatorul lui si amprenta intreaga a dovezii; regula refuzata numita; un domeniu
// neprezentat niciodata verde; un pachet brut UNCHECKED; trecerea inapoi la o stare a consolei; nicio eroare de pagina; nicio cerere la retea.
// node proba-organizatie-web.mjs [cale index.html] iesire 0 = toate cum trebuia, 1 = una rosie, 2 = nu s-a putut rula (fara browser)
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { createRequire } from 'node:module';
import { judecaPachetul, PACK_KIND, CONTEXT_KIND } from './organizatie.mjs';
import { pachetulBun, inlocuieste, vectorul, imparte } from './fixturi-organizatie.mjs';
import { evalueaza } from './consola.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const PAGINA = path.resolve(process.argv[2] || path.join(AICI, 'consola-web', 'index.html'));
const EXE = process.env.CHROME || path.join(os.homedir(), 'AppData', 'Local', 'ms-playwright', 'chromium-1228', 'chrome-win64', 'chrome.exe');
let chromium;
try { ({ chromium } = createRequire(import.meta.url)('playwright-core')); } catch { /* mai jos */ }
if (!chromium || !fs.existsSync(EXE)) { console.log(`NEMASURAT: lipseste playwright-core (npm i playwright-core, in dosarul acesta sau mai sus) sau Chromium (${EXE}); CHROME=<chrome> pentru altul`); process.exit(2); }
let rele = 0, bune = 0; const cer = (n, c, d = '') => { console.log(` [${c ? 'OK ' : 'RAU '}] ${n}${c || !d ? '' : ' -- ' + d}`); if (c) bune++; else rele++; };
// iesirile reale ale judecatii
let F, bun, stricat, faraMasini, minte, consola;
try {
F = await pachetulBun();
bun = await judecaPachetul(F.pack, { context: F.context });
const s = F.stricate['identity-presentation'];
stricat = await judecaPachetul(inlocuieste(F.pack, s.id, s.artifact), { context: F.context });
let p = F.pack; for (const a of F.pack.artifacts.filter((x) => ['device-bundle', 'device-operation-log', 'device-reputation'].includes(x.artifact))) p = inlocuieste(p, a.id, null);
faraMasini = await judecaPachetul(p, { context: F.context });
const pm = JSON.parse(JSON.stringify(F.pack)); pm.summary = { verdict: 'VALID' };
minte = await judecaPachetul(pm, { context: F.context });
consola = await evalueaza({ bundle: F.pack.artifacts.find((a) => a.id === 'shield-console-1').input.bundle });
} catch (e) { console.log('NEMASURAT: iesirile de proba nu s-au putut face: ' + e.message); process.exit(2); }
// revizia B2 (2026-10-21): iesiri care incearca vederea. Doua EDITATE (nu le scrie unealta: un rezumat VALID peste un domeniu cerut INVALID, un
// domeniu VALID peste un artefact INVALID); doua reale cu text lung sau cu marcaj adus de pachet; trei reale despre retea.
const clona = (x) => JSON.parse(JSON.stringify(x));
let falsRezumat, falsDomeniu, lung, marcaj, inchis, citiri, testnet;
try {
falsRezumat = clona(stricat); falsRezumat.summary.verdict = 'VALID';
falsDomeniu = clona(stricat); falsDomeniu.domains.find((d) => d.domain === 'identity').verdict = 'VALID';
const mic = (artifacts, extra = {}) => ({ v: 1, kind: PACK_KIND, artifacts, ...extra });
lung = await judecaPachetul(mic([{ id: 'o', artifact: 'k'.repeat(400), input: {} }], { organization: 'O'.repeat(300), verdict: 'V'.repeat(400) }), { require: ['identity'] });
marcaj = await judecaPachetul(mic([{ id: 'o', artifact: '<img src=x onerror="window.__xss=3">', input: {} }], { organization: '<img src=x onerror="window.__xss=1">', verdict: '<img src=x onerror="window.__xss=2">' }), { require: ['identity'] });
const n = vectorul('envelope/notarized-28001.valid'), ni = imparte('envelope', n.input);
const vechi = process.env.AERE_CONFORMANCE_RPC_28001; process.env.AERE_CONFORMANCE_RPC_28001 = 'http://127.0.0.1:9';
try { inchis = await judecaPachetul(mic([{ id: 'notar', artifact: 'envelope', input: ni.pack }]), { context: { v: 1, kind: CONTEXT_KIND, byArtifact: { notar: ni.context } }, online: true, require: ['proofs'] }); }
finally { if (vechi === undefined) delete process.env.AERE_CONFORMANCE_RPC_28001; else process.env.AERE_CONFORMANCE_RPC_28001 = vechi; }
const dvz = vectorul('device-bundle/notarization-not-read.partial'), di = imparte('device-bundle', dvz.input);
citiri = await judecaPachetul(mic([{ id: 'dev', artifact: 'device-bundle', input: di.pack }]), { context: { v: 1, kind: CONTEXT_KIND, byArtifact: { dev: di.context } }, online: true, require: ['devices'] });
// finalitatea pe un testnet: forma pe care o scrie unealta cand citeste 28001 (proba-organizatie --online o masoara pe lant)
testnet = clona(bun); testnet.network = { online: true, needed: [], readings: [], unmeasured: [], finality: [{ id: 'envelope-1', chain: 28001, testnet: true }] };
} catch (e) { console.log('NEMASURAT: iesirile de proba ale reviziei nu s-au putut face: ' + e.message); process.exit(2); }
if (!(inchis.network.unmeasured.length === 1 && citiri.network.readings.length === 1 && lung.unjudged.length === 1 && marcaj.unjudged.length === 1)) {
console.log(`NEMASURAT: iesirile de proba ale reviziei nu sunt cele asteptate (${JSON.stringify(inchis.network)}, ${JSON.stringify(citiri.network)})`); process.exit(2);
}
const RULE = F.stricate['identity-presentation'].failed[0];
if (!(bun.summary.verdict === 'PARTIAL' && stricat.summary.verdict === 'INVALID' && faraMasini.summary.verdict === 'NOT_MEASURED' && minte.selfReport.suspect.length && consola.verdict === 'OK')) {
console.log(`NEMASURAT: iesirile de proba nu sunt cele asteptate (${bun.summary.verdict}, ${stricat.summary.verdict}, ${faraMasini.summary.verdict}, ${minte.selfReport.suspect.length}, ${consola.verdict})`); process.exit(2);
}
const CLS = { VALID: 'ok', PARTIAL: 'warn', NOT_MEASURED: 'warn', NOT_PRESENTED: 'dim', INVALID: 'bad' };
const browser = await chromium.launch({ executablePath: EXE, headless: true });
try {
for (const vp of [{ w: 390, h: 844, mobil: true }, { w: 1440, h: 900, mobil: false }]) {
const et = `${vp.w}px`;
const ctx = await browser.newContext({ viewport: { width: vp.w, height: vp.h }, deviceScaleFactor: 1, isMobile: vp.mobil, hasTouch: vp.mobil });
const page = await ctx.newPage();
const erori = []; const cereri = []; const eroriRedare = [];
page.on('console', (m) => { if (m.type() === 'error' && !/ERR_(BLOCKED|FAILED|INTERNET|NAME)|net::|Failed to load resource/.test(m.text())) erori.push(m.text().slice(0, 160)); });
page.on('pageerror', (e) => erori.push('pageerror: ' + String(e.message).slice(0, 160)));
await page.route('**/*', (r) => { const u = r.request().url(); if (u.startsWith('file:')) return r.continue(); cereri.push(u); return r.abort(); });
await page.goto(pathToFileURL(PAGINA).href, { waitUntil: 'load' });
const lipeste = async (o) => {
try { await page.fill('#in', typeof o === 'string' ? o : JSON.stringify(o), { timeout: 10000 }); await page.click('#render', { timeout: 5000 }); }
catch (e) { cer(`${et}: campul si butonul Render se pot folosi`, false, String(e.message).split('\n')[0].slice(0, 140)); return null; }
const st = await page.evaluate(() => {
const q = (s) => document.querySelector(s); const qa = (s) => [...document.querySelectorAll(s)];
return {
iw: window.innerWidth, sw: document.documentElement.scrollWidth, perr: q('#perr').hidden ? null : q('#perr').textContent,
vtext: q('#vtext').textContent, vcls: q('#verdict').className, hostlab: q('#hostlab').textContent,
org: !q('#orgsec').hidden, consola: !q('#sec-chain').hidden && !q('#sec-ladder').hidden && !q('#sec-mig').hidden,
alert: q('#alert').textContent, tiles: qa('#tiles .tile').map((t) => t.textContent),
retea: (() => { const t = qa('#tiles .tile').find((x) => /^Network/.test(x.textContent)); return t ? { text: t.textContent, stripe: t.querySelector('.stripe').className, v: t.querySelector('.v').textContent } : null; })(),
host: q('#host').textContent, xss: window.__xss === undefined ? null : window.__xss, injectate: qa('#alert *, #host *, #domains *, #tiles *').filter((e) => ['IMG', 'SCRIPT', 'IFRAME'].includes(e.tagName)).length,
carduri: qa('#domains .dcard').map((c) => ({ domain: c.dataset.domain, verdict: c.dataset.verdict, np: c.classList.contains('np'),
pill: c.querySelector('.dhead .pill') ? { text: c.querySelector('.dhead .pill').textContent, cls: c.querySelector('.dhead .pill').className } : null,
reguli: c.querySelector(':scope > .arules') ? c.querySelector(':scope > .arules').textContent : '',
randuri: [...c.querySelectorAll('.arow')].map((r) => ({ id: r.dataset.id, verdict: r.dataset.verdict, text: r.textContent,
dig: r.querySelector('.adig .hval') ? r.querySelector('.adig .hval').textContent : '', reguli: r.querySelector('.arules') ? r.querySelector('.arules').textContent : '',
lata: r.scrollWidth > r.clientWidth + 1 })) })),
};
});
if (st.perr) eroriRedare.push(st.perr.slice(0, 160));
return st;
};
const lat = (s, nume) => cer(`${et}: ${nume}: pagina cat ecranul (${s && s.iw}) si fara derulare orizontala (${s && s.sw})`, s && s.iw === vp.w && s.sw <= vp.w, JSON.stringify(s && { iw: s.iw, sw: s.sw }));
// 1) pachetul bun
const a = await lipeste(bun);
if (a) {
lat(a, 'judecata buna');
cer(`${et}: verdictul din antet e rezumatul (${bun.summary.verdict}), in culoarea lui, si eticheta spune "organization"`, a.vtext === bun.summary.verdict && a.vcls.includes(CLS[bun.summary.verdict]) && a.hostlab === 'organization', `${a.vtext} ${a.vcls} ${a.hostlab}`);
cer(`${et}: sectiunile consolei ascunse, sectiunea domeniilor aratata`, a.org && !a.consola, `org ${a.org}, consola ${a.consola}`);
const peDomeniu = bun.domains.every((d) => { const c = a.carduri.find((x) => x.domain === d.domain); return c && c.verdict === d.verdict && c.pill && c.pill.cls.includes(CLS[d.verdict]); });
cer(`${et}: cate o caseta pe domeniu (${bun.domains.length}), fiecare cu verdictul domeniului din iesire, in culoarea lui`, a.carduri.length === bun.domains.length && peDomeniu, JSON.stringify(a.carduri.map((c) => [c.domain, c.verdict, c.pill && c.pill.text])));
const toate = a.carduri.flatMap((c) => c.randuri);
const cuDovada = bun.artifacts.every((r) => { const x = toate.find((y) => y.id === r.id); return x && x.verdict === r.verdict && x.dig === r.inputSha256 && x.text.includes(r.verifier.module); });
cer(`${et}: fiecare artefact (${bun.artifacts.length}) cu verdictul lui, verificatorul care l-a judecat si amprenta intreaga a dovezii`, toate.length === bun.artifacts.length && cuDovada, `${toate.length} randuri`);
cer(`${et}: niciun rand de artefact mai lat decat caseta lui`, toate.every((r) => !r.lata), toate.filter((r) => r.lata).map((r) => r.id).join(','));
cer(`${et}: motivul rezumatului e aratat`, a.alert.includes(bun.summary.reason.slice(0, 40)), a.alert.slice(0, 120));
cer(`${et}: o iesire reala nu e numita contradictorie (nicio alarma falsa a corectiei de mai jos)`, !/contradicts itself/.test(a.alert), a.alert.slice(0, 160));
}
// 2) un artefact de identitate stricat
const b = await lipeste(stricat);
if (b) {
lat(b, 'judecata cu un artefact stricat');
const ci = b.carduri.find((c) => c.domain === 'identity');
const ri = ci && ci.randuri.find((r) => r.id === F.stricate['identity-presentation'].id);
cer(`${et}: rezumatul INVALID in culoarea rea; domeniul identity INVALID (rau), cu regula ${RULE} numita pe caseta si pe artefact`,
b.vtext === 'INVALID' && b.vcls.includes('bad') && ci && ci.verdict === 'INVALID' && ci.pill.cls.includes('bad') && ci.reguli.includes(RULE) && ri && ri.reguli.includes(RULE),
JSON.stringify({ v: b.vtext, ci: ci && { v: ci.verdict, p: ci.pill, r: ci.reguli } }));
}
// 3) un domeniu neprezentat
const c = await lipeste(faraMasini);
if (c) {
lat(c, 'judecata fara domeniul masinilor');
const cd = c.carduri.find((x) => x.domain === 'devices');
cer(`${et}: domeniul neprezentat aratat NOT PRESENTED, palid, niciodata in culoarea buna; caseta "Domains presented" spune 7/8`,
cd && cd.np && cd.verdict === 'NOT_PRESENTED' && cd.pill.text === 'NOT PRESENTED' && !cd.pill.cls.includes('ok') && !cd.randuri.length && c.tiles.some((t) => /Domains presented7\/8/.test(t)) && c.vtext === 'NOT MEASURED',
JSON.stringify({ cd, tiles: c.tiles, v: c.vtext }));
}
// 4) un verdict scris de pachet care minte
const d = await lipeste(minte);
if (d) cer(`${et}: verdictul scris de pachet e aratat ca suspect si ignorat, nu ca verdict`, d.vtext === minte.summary.verdict && /Suspect self-report, ignored: the pack says pack\.summary\.verdict = VALID/.test(d.alert), d.alert.slice(0, 160));
// revizia B2: o iesire editata nu e aratata mai bine decat o dau propriile ei randuri
const g1 = await lipeste(falsRezumat);
if (g1) cer(`${et}: o iesire editata cu rezumatul VALID peste un domeniu cerut INVALID: antetul arata INVALID (rau) si spune ca iesirea se contrazice`,
g1.vtext === 'INVALID' && g1.vcls.includes('bad') && /contradicts itself/.test(g1.alert), JSON.stringify({ v: g1.vtext, c: g1.vcls, a: g1.alert.slice(0, 120) }));
const g2 = await lipeste(falsDomeniu);
if (g2) { const ci = g2.carduri.find((c) => c.domain === 'identity');
cer(`${et}: o iesire editata cu domeniul identity VALID peste un artefact INVALID: caseta lui arata INVALID (rau), nu VALID`,
ci && ci.verdict === 'INVALID' && ci.pill.cls.includes('bad') && !ci.pill.cls.includes('ok') && g2.vtext === 'INVALID' && /contradicts itself/.test(g2.alert), JSON.stringify({ ci: ci && [ci.verdict, ci.pill], v: g2.vtext })); }
// tot ce vine din pachet e text: un marcaj nu devine element
const g3 = await lipeste(marcaj);
if (g3) cer(`${et}: un marcaj adus de pachet (numele organizatiei, felul, verdictul scris) e aratat ca text, nu devine element si nu ruleaza`,
g3.xss === null && g3.injectate === 0 && g3.host.includes('<img src=x onerror') && /<img src=x onerror="window.__xss=3">/.test(g3.alert), JSON.stringify({ xss: g3.xss, n: g3.injectate, host: g3.host.slice(0, 60) }));
// reteaua: "read" in culoarea buna numai cand s-a citit tot ce se cerea; citirile celui care verifica si un testnet sunt spuse
const g4 = await lipeste(inchis), g5 = await lipeste(citiri), g6 = await lipeste(testnet);
if (g4 && g5 && g6) cer(`${et}: caseta retelei: lantul necitit e "partly read" (galben), citirile celui care verifica si finalitatea pe testnet sunt numite, niciuna in verde`,
g4.retea && g4.retea.v === 'partly read' && g4.retea.stripe.includes('s-warn') && /could not be read for 1/.test(g4.retea.text)
&& g5.retea && g5.retea.stripe.includes('s-warn') && /relying party's own readings/.test(g5.retea.text)
&& g6.retea && g6.retea.stripe.includes('s-warn') && /testnet \(chain 28001\)/.test(g6.retea.text), JSON.stringify([g4.retea, g5.retea, g6.retea]));
// text lung fara spatii adus de pachet: nicio derulare orizontala (pana azi felul necunoscut de 400 de caractere facea pagina de 3138 px pe telefon)
const g7 = await lipeste(lung);
if (g7) lat(g7, 'text lung fara spatii adus de pachet (felul, verdictul scris, numele organizatiei)');
// 5) un pachet brut
const e = await lipeste(F.pack);
if (e) {
lat(e, 'pachetul brut');
cer(`${et}: un pachet brut lipit e UNCHECKED, cu nota, si nicio caseta de domeniu judecata`, e.vtext === 'UNCHECKED' && e.vcls.includes('bad') && /checks nothing/.test(e.alert) && !e.carduri.length, JSON.stringify({ v: e.vtext, n: e.carduri.length }));
}
// 6) inapoi la o stare a consolei: vederea consolei revine, a organizatiei se ascunde
const f = await lipeste(consola);
if (f) cer(`${et}: o stare a consolei dupa o judecata a organizatiei: sectiunile consolei revin, domeniile se ascund, eticheta spune "host"`, f.vtext === 'OK' && f.consola && !f.org && f.hostlab === 'host', JSON.stringify({ v: f.vtext, c: f.consola, o: f.org }));
cer(`${et}: nicio iesire reala nu a dat un mesaj de eroare la redare (campul de eroare ascuns de fiecare data)`, !eroriRedare.length, eroriRedare.join(' | '));
cer(`${et}: nicio eroare de consola sau de pagina`, !erori.length, erori.join(' | '));
cer(`${et}: nicio cerere in afara fisierului a ajuns la retea (${cereri.length} cerute, toate refuzate: fonturile)`, cereri.every((u) => /fonts\.(googleapis|gstatic)\.com/.test(u)), cereri.join(', '));
if (process.env.CAPTURI) {
fs.mkdirSync(process.env.CAPTURI, { recursive: true });
for (const [n, o] of [['bun', bun], ['stricat', stricat], ['fara-masini', faraMasini]]) { await lipeste(o); await page.screenshot({ path: path.join(process.env.CAPTURI, `organizatie-${n}-${vp.w}.png`), fullPage: true }); }
}
await ctx.close();
}
} finally { await browser.close(); }
console.log(`\norganizatie-web: ${bune}/${bune + rele} cum trebuia (Chromium ${path.basename(path.dirname(path.dirname(EXE)))})`);
process.exitCode = rele ? 1 : 0;

View File

@ -0,0 +1,511 @@
// Proba judecatii organizatiei (organizatie.mjs; B2, roadmap master 41 si 43). Pe artefacte PRODUSE de uneltele reale (fixturi-organizatie.mjs:
// vectorii suitei de conformitate, ceruti egali cu amprenta lor din INDEX.json, plus starea Shield si raportul PQ facute acum de sidecar, de
// planificator, de executor si de raport-conformitate.mjs), nu scrise de mana. Masoara:
// - acoperirea, DERIVATA: fiecare fel judecat de adaptorul suitei are domeniu si clasificare; fiecare cheie pe care adaptorul o citeste (din
// sursa lui) si fiecare cheie din cei 791 de vectori e clasificata;
// - compunerea: pachetul bun (un artefact din fiecare din cele 25 de feluri, contextul derivat) da pentru fiecare artefact EXACT verdictul,
// regulile si starile nivelurilor din vectorul lui; domeniul e cel mai slab artefact; rezumatul e cel mai slab domeniu cerut;
// - cate un artefact stricat din fiecare fel, fiecare refuzat pentru regula lui, si numai domeniul lui atins;
// - paznicii: domeniul neprezentat, verificatorul care nu porneste (adaptorul lipsa, verificatorul de referinta lipsa, un adaptor care da
// mai putine verdicte sau unul incoerent), increderea adusa de organizatie, ceasul pachetului, listele de stare adaugate de cel care
// verifica, verdictele scrise de pachet, reteaua (--online), felul necunoscut, id-ul repetat, cheile KEM private, contextul care ar
// scrie artefactul, starea Shield in fiecare fel in care se strica, linia de comanda si codurile ei;
// - atacurile reviziei adversariale (2026-10-21), fiecare aratat ROSU pe codul de dinainte: arbitrul pietei si faptele reputatiei alese de
// organizatie, 64 de liste ale pachetului care imping afara rechemarea celui care verifica, un raport PQ rescris si legat din nou, raportul PQ
// lipsa din retea, o masina trimisa la --online, finalitatea pe testnet nenumita, un adaptor care raspunde in alta ordine, un "VALID" pe care
// nivelurile lui nu il dau, un artefact lent care lua cu el tot lotul, o executie Shield rupta de planul ei, ordinea ceasului din README.
// AERE_ORG_MODUL=<copie> judeca o copie (controlul negativ planteaza in ea); AERE_ORG_DOAR=<a,b> ruleaza numai randurile care contin a sau b.
// node proba-organizatie.mjs [--online] iesire 0 = toate cum trebuia, 1 = una rosie, 2 = nu s-a putut rula
// --online citeste si testnetul 28001 (un plic notarizat acolo, finalitatea lui); fara el randul acela e scris NEMASURAT, nu numarat.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { spawnSync } from 'node:child_process';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { pachetulBun, inlocuieste, vectorul, imparte, indexul, dosarulSuitei } from './fixturi-organizatie.mjs';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const MODUL = process.env.AERE_ORG_MODUL ? path.resolve(process.env.AERE_ORG_MODUL) : path.join(AICI, 'organizatie.mjs');
const M = await import(pathToFileURL(MODUL).href);
const ONLINE = process.argv.includes('--online');
const DOAR = (process.env.AERE_ORG_DOAR || '').split(',').map((x) => x.trim()).filter(Boolean);
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
const nemasurat = (m) => { console.log('NEMASURAT: ' + m); process.exit(2); };
let F;
try { F = await pachetulBun(); } catch (e) { nemasurat('fixturile nu s-au putut face: ' + e.message); }
const SUITA = dosarulSuitei(); if (!SUITA) nemasurat('lipsesc vectorii suitei de conformitate');
const ADAPTOR = path.join(SUITA, 'implementare-aere.mjs');
// verificatorul de referinta AIP-23, din acelasi loc din care il ia suita (AERE_VERIFY_PROOF, altfel cel al depozitului de dezvoltare): fara el,
// plicurile ar iesi NOT_MEASURED (corect) si randul compunerii rosu dintr-un motiv care nu e al judecatii
const { VERIFY_PROOF } = await import(pathToFileURL(path.join(SUITA, 'reguli.mjs')).href);
if (!fs.existsSync(VERIFY_PROOF)) nemasurat(`verificatorul de referinta AIP-23 nu e la ${VERIFY_PROOF}: AERE_VERIFY_PROOF=<verify-proof.mjs de la aere.network/tools, cu fisierele lui alaturi si npm install facut>`);
const ctx = (byArtifact) => ({ v: 1, kind: M.CONTEXT_KIND, byArtifact });
const judeca = (pack, o = {}) => M.judecaPachetul(pack, { context: F.context, ...o });
const art = (o, id) => o.artifacts.find((x) => x.id === id);
const dom = (o, d) => o.domains.find((x) => x.domain === d);
const stari = (levels) => (levels || []).map((l) => l.state);
const stariAsteptate = (lv) => ['integrity', 'authenticity', 'anchoring', 'finality'].map((l) => lv[l]);
const T = fs.mkdtempSync(path.join(os.tmpdir(), 'proba-org-'));
const randuri = [];
const rand = (nume, f) => randuri.push([nume, f]);
let O0 = null; // judecata pachetului bun, facuta o data
const bun = async () => (O0 ||= await judeca(F.pack));
// ---- acoperirea, derivata
rand('acoperire: fiecare fel al suitei are domeniu si clasificare, si numai ele (plus cele doua ale planului de control)', async () => {
const R = await import(pathToFileURL(path.join(SUITA, 'reguli.mjs')).href);
const aleSuitei = Object.values(R.VERIFICATOARE).flatMap((v) => v.artifacts).sort();
const aleNoastre = Object.keys(M.DOMENIUL).filter((k) => !M.FELURI_PROPRII.includes(k)).sort();
const domenii = M.DOMENII.map((d) => d.id);
const ok = aleSuitei.length >= 23 && eq(aleSuitei, aleNoastre) && eq(Object.keys(M.INTRARI).sort(), Object.keys(M.DOMENIUL).sort())
&& Object.values(M.DOMENIUL).every((d) => domenii.includes(d)) && domenii.every((d) => Object.values(M.DOMENIUL).includes(d));
return [ok, `suita ${aleSuitei.length} feluri, aici ${aleNoastre.length}; lipsa aici: ${aleSuitei.filter((x) => !aleNoastre.includes(x)).join(',') || '-'}; in plus: ${aleNoastre.filter((x) => !aleSuitei.includes(x)).join(',') || '-'}`];
});
/** cheile pe care adaptorul le citeste, pe fel, din SURSA lui: ramurile de sus ale lui judeca() (doua spatii), `input.X`, listele `for (const k of [..])`,
* "tot inputul" (destructurat intreg sau dat intreg verificatorului: atunci si modulul si functia lui), si daca ramura citeste lantul (`plic(`, `online`) */
function cheileAdaptorului(src, feluri) {
const i0 = src.indexOf('async function judeca('), i1 = src.indexOf('async function unul(');
if (i0 < 0 || i1 < 0) return null;
const harta = {};
for (const b of src.slice(i0, i1).split(/\n {2}(?:\} )?(?:else )?if \((?=artifact)/).slice(1)) {
const cap = b.split('\n')[0]; // conditia ramurii e pe primul ei rand
const ale = [...cap.matchAll(/artifact === '([a-z0-9-]+)'/g)].map((m) => m[1]);
const pref = cap.match(/artifact\.startsWith\('([a-z-]+)'\)/); if (pref) ale.push(...feluri.filter((f) => f.startsWith(pref[1])));
const chei = new Set([...b.matchAll(/\binput\.([A-Za-z_]\w*)/g)].map((m) => m[1]));
for (const m of b.matchAll(/for \(const k of \[([^\]]*)\]\) if \(input\[k\]/g)) for (const q of m[1].matchAll(/'([A-Za-z]+)'/g)) chei.add(q[1]);
const tot = /\}\s*=\s*input\s*;/.test(b) || /\(input\)/.test(b);
const modul = (b.match(/incarca\('(\w+)'\)/) || [])[1] || null, fn = (b.match(/\bM\.(\w+)\(/) || [])[1] || null;
const lant = /\bplic\(|\bonline\b/.test(b);
for (const f of ale) harta[f] = { chei: [...new Set([...(harta[f]?.chei || []), ...chei])], tot: tot || !!harta[f]?.tot, modul, fn, lant: lant || !!harta[f]?.lant };
}
return harta;
}
/** cheile pe care le citeste MODULUL caruia adaptorul ii da tot inputul: `<param>.X` in sursa lui si listele `exact(<param>, [...])` */
function cheileModulului(src, fn) {
const p = (src.match(new RegExp('function ' + fn + '\\((\\w+)\\)')) || [])[1];
if (!p) return null;
const chei = new Set([...src.matchAll(new RegExp('\\b' + p + '\\.([A-Za-z_]\\w*)', 'g'))].map((m) => m[1]));
for (const m of src.matchAll(new RegExp('exact\\(' + p + ', \\[([^\\]]*)\\]\\)', 'g'))) for (const q of m[1].matchAll(/'([A-Za-z]\w*)'/g)) chei.add(q[1]);
return [...chei];
}
rand('acoperire: fiecare cheie pe care adaptorul o citeste (derivata din sursa lui) e clasificata pe felul ei, si la felurile date intregi fiecare cheie a modulului', async () => {
const feluri = Object.keys(M.DOMENIUL).filter((k) => !M.FELURI_PROPRII.includes(k));
const h = cheileAdaptorului(fs.readFileSync(ADAPTOR, 'utf8'), feluri);
const R = await import(pathToFileURL(path.join(SUITA, 'reguli.mjs')).href);
const deModul = (f) => { const x = h[f]; if (!x || !x.tot || !x.modul || !x.fn) return null; try { return cheileModulului(fs.readFileSync(R.caleModul(x.modul), 'utf8'), x.fn); } catch { return null; } };
// controlul pozitiv al metodei: chei care SIGUR sunt citite trebuie gasite, altfel nicio lipsa de mai jos nu inseamna nimic (revizia B2: si cheile
// modulelor date intregi, arbitrul pietei si faptele reputatiei, pe care organizatia le alegea singura)
const mt = deModul('market-timeline'), rp = deModul('device-reputation');
const pozitiv = h && h['identity-presentation']?.chei.includes('trustedIssuers') && h['travel-rule-message']?.chei.includes('kem')
&& h['device-reputation']?.tot && h['market-timeline']?.tot && h.envelope?.chei.includes('raw') && feluri.every((f) => h[f])
&& mt && ['constants', 'job', 'events'].every((k) => mt.includes(k)) && rp && ['facts', 'params', 'claimed'].every((k) => rp.includes(k));
if (!pozitiv) return [false, 'derivarea din sursa adaptorului sau a modulelor nu a gasit cheile care sigur exista (metoda moarta)'];
const lipsa = []; let nModul = 0;
for (const f of feluri) {
const cls = M.INTRARI[f];
if (!(cls && typeof cls === 'object')) { lipsa.push(`${f}: fara clasificare pe chei`); continue; }
for (const k of h[f].chei) if (!Object.hasOwn(cls, k)) lipsa.push(`${f}.${k}`);
if (h[f].tot) {
const km = deModul(f); if (!km) { lipsa.push(`${f}: cheile modulului nu s-au putut deriva`); continue; }
nModul += km.length;
for (const k of km) if (!Object.hasOwn(cls, k)) lipsa.push(`${f}.${k} (modulul)`);
if (cls['*'] !== 'artifact') lipsa.push(`${f}: dat intreg modulului, dar o cheie necunoscuta nu ajunge la el ca a artefactului`);
} else if (Object.hasOwn(cls, '*')) lipsa.push(`${f}: '*' la un fel pe care adaptorul nu il da intreg`);
}
return [!lipsa.length, `${feluri.length} feluri, ${feluri.reduce((n, f) => n + h[f].chei.length, 0)} chei citite de adaptor, ${nModul} de modulele date intregi; neclasificate: ${lipsa.join(', ') || '-'}`];
});
rand('acoperire: felurile judecate citind lantul cu --online sunt exact cele ale adaptorului (derivate din sursa lui), plus raportul PQ prin plicul lui', async () => {
const feluri = Object.keys(M.DOMENIUL).filter((k) => !M.FELURI_PROPRII.includes(k));
const h = cheileAdaptorului(fs.readFileSync(ADAPTOR, 'utf8'), feluri);
const derivate = [...feluri.filter((f) => h[f] && h[f].lant), 'pq-compliance-report'].sort();
const pozitiv = derivate.includes('envelope') && derivate.includes('ai-trust-bundle') && !derivate.includes('identity-presentation');
return [pozitiv && eq(derivate, [...M.CITESC_LANTUL].sort()), `derivate ${derivate.join(',')}; aici ${[...M.CITESC_LANTUL].sort().join(',')}`];
});
rand('acoperire: fiecare cheie de intrare din toti vectorii suitei e clasificata (inputurile reale ale producatorilor)', async () => {
const { j, dir } = indexul(); const lipsa = [];
for (const v of j.vectors) {
const x = JSON.parse(fs.readFileSync(path.join(dir, ...v.id.split('/')) + '.json', 'utf8'));
try { imparte(v.artifact, x.input); } catch (e) { lipsa.push(e.message); }
}
return [!lipsa.length && j.vectors.length >= 791, `${j.vectors.length} vectori; ${lipsa.slice(0, 3).join('; ') || 'toti clasificati'}`];
});
rand('vectorii folositi sunt cei scrisi de generator (amprenta din INDEX.json, ceruta la citire)', async () => {
const ids = Object.values(F.ales).flatMap((x) => [x.bun.id, x.rau.id]);
return [ids.length === 46 && new Set(ids).size === 46, `${ids.length} vectori (un bun si un frate stricat pentru fiecare din cele 23 de feluri ale suitei)`];
});
// ---- compunerea, pe pachetul bun
rand('pachetul bun: fiecare artefact are exact verdictul, regulile si starile nivelurilor din vectorul lui (25 de feluri)', async () => {
const o = await bun(); const rele = [];
for (const [id, e] of Object.entries(F.asteptat)) {
const r = art(o, id);
if (!r || r.verdict !== e.verdict || !eq(r.failed, e.failed) || (e.levels && !eq(stari(r.levels), stariAsteptate(e.levels)))) rele.push(`${id}: ${r ? r.verdict + ' ' + r.failed.join(',') + (r.error ? ' ' + r.error : '') + (r.capped ? ' capped' : '') : 'lipsa'} fata de ${e.verdict}`);
}
return [!rele.length && o.artifacts.length === 25, `${o.artifacts.length} artefacte; ${rele.join('; ') || 'toate ca vectorii lor'}`];
});
const domeniulAsteptat = (o, d) => M.celMaiSlab(o.artifacts.filter((r) => r.domain === d).map((r) => F.asteptat[r.id].verdict));
rand('pachetul bun: fiecare domeniu e cel mai slab artefact al lui (asteptat din vectori)', async () => {
const o = await bun();
const rele = o.domains.filter((d) => d.verdict !== domeniulAsteptat(o, d.domain)).map((d) => `${d.domain} ${d.verdict}`);
return [!rele.length && o.domains.length === 8, rele.join(', ') || o.domains.map((d) => d.domain + ' ' + d.verdict).join(', ')];
});
rand('pachetul bun: rezumatul (toate domeniile cerute, implicit) e cel mai slab domeniu, si il numeste', async () => {
const o = await bun();
const asteptat = M.celMaiSlab(o.domains.map((d) => domeniulAsteptat(o, d.domain)));
const slabe = o.domains.filter((d) => domeniulAsteptat(o, d.domain) === asteptat).map((d) => d.domain);
return [o.summary.verdict === asteptat && asteptat !== 'VALID' && eq(o.summary.weakest, slabe) && o.required.domains.length === 8,
`${o.summary.verdict} (asteptat ${asteptat}), cel mai slab ${o.summary.weakest.join(',')}`];
});
rand('pachetul bun, cerute numai domeniile VALID: rezumatul VALID; domeniul necerut e numit alaturi', async () => {
const o0 = await bun(); const valide = o0.domains.filter((d) => d.verdict === 'VALID').map((d) => d.domain);
const o = await judeca(F.pack, { require: valide });
return [o.summary.verdict === 'VALID' && valide.length >= 6 && Object.keys(o.summary.outsideRequired).length === 8 - valide.length && M.codulIesirii(o.summary.verdict) === 0,
`${o.summary.verdict}, cerute ${valide.length}, in afara: ${JSON.stringify(o.summary.outsideRequired)}`];
});
// ---- cate un artefact stricat din fiecare fel
for (const [fel, s] of Object.entries(F.stricate)) {
rand(`stricat ${fel}: refuzat pentru ${s.failed.join(',')}, domeniul lui INVALID, celelalte neatinse, rezumatul INVALID`, async () => {
const o0 = await bun();
const o = await judeca(inlocuieste(F.pack, s.id, s.artifact));
const r = art(o, s.id); const d = dom(o, r.domain);
const altele = o.domains.filter((x) => x.domain !== r.domain).every((x) => x.verdict === dom(o0, x.domain).verdict);
const ok = r.verdict === 'INVALID' && eq(r.failed, s.failed) && (!s.levels || eq(stari(r.levels), stariAsteptate(s.levels)))
&& d.verdict === 'INVALID' && eq(d.failed, s.failed) && altele && o.summary.verdict === 'INVALID' && eq(o.summary.weakest, [r.domain]);
return [ok, `${r.verdict} ${r.failed.join(',')}${r.error ? ' ' + r.error : ''}; domeniul ${d.domain} ${d.verdict}; rezumat ${o.summary.verdict} (${o.summary.weakest.join(',')})${s.vector ? '; vectorul ' + s.vector : ''}`];
});
}
// ---- paznicii judecatii
rand('domeniu neprezentat: NOT_PRESENTED (nu verde), iar rezumatul cu el cerut e NOT_MEASURED si il numeste', async () => {
let p = F.pack; for (const a of F.pack.artifacts.filter((x) => M.DOMENIUL[x.artifact] === 'devices')) p = inlocuieste(p, a.id, null);
const o = await judeca(p); const d = dom(o, 'devices');
return [d.verdict === 'NOT_PRESENTED' && !d.artifacts.length && o.summary.verdict === 'NOT_MEASURED' && o.summary.notPresented.includes('devices') && o.summary.weakest.includes('devices'),
`devices ${d.verdict}; rezumat ${o.summary.verdict}, neprezentate ${o.summary.notPresented.join(',')}`];
});
rand('domeniu neprezentat si necerut: rezumatul nu e atins de el', async () => {
const o0 = await bun(); const valide = o0.domains.filter((d) => d.verdict === 'VALID' && d.domain !== 'devices').map((d) => d.domain);
let p = F.pack; for (const a of F.pack.artifacts.filter((x) => M.DOMENIUL[x.artifact] === 'devices')) p = inlocuieste(p, a.id, null);
const o = await judeca(p, { require: valide });
return [o.summary.verdict === 'VALID' && dom(o, 'devices').verdict === 'NOT_PRESENTED', `${o.summary.verdict}`];
});
rand('adaptorul suitei lipsa: artefactele lui NOT_MEASURED (nu VALID, nu INVALID), numite; Shield tot judecat; rezumatul NOT_MEASURED', async () => {
const o = await judeca(F.pack, { adaptor: path.join(T, 'nu-exista', 'implementare-aere.mjs') });
const prinAdaptor = o.artifacts.filter((r) => !M.FELURI_PROPRII.includes(r.artifact));
const ok = prinAdaptor.length === 23 && prinAdaptor.every((r) => r.verdict === 'NOT_MEASURED' && /conformance adapter is not at/.test(r.error || ''))
&& art(o, 'shield-console-1').verdict === 'VALID' && o.summary.verdict === 'NOT_MEASURED';
return [ok, `${prinAdaptor.filter((r) => r.verdict === 'NOT_MEASURED').length}/23 NOT_MEASURED; shield ${art(o, 'shield-console-1').verdict}; raport ${art(o, 'pq-compliance-report-1').verdict}; rezumat ${o.summary.verdict}`];
});
rand('verificatorul de referinta AIP-23 lipsa: ce cere plicuri iese NOT_MEASURED cu motivul numit, restul neatins', async () => {
const vechi = process.env.AERE_VERIFY_PROOF; process.env.AERE_VERIFY_PROOF = path.join(T, 'nu-exista', 'verify-proof.mjs');
let o; try { o = await judeca(F.pack); } finally { if (vechi === undefined) delete process.env.AERE_VERIFY_PROOF; else process.env.AERE_VERIFY_PROOF = vechi; }
const o0 = await bun();
const cazute = o.artifacts.filter((r) => r.verdict === 'NOT_MEASURED');
const ok = ['envelope-1', 'pq-compliance-report-1'].every((id) => art(o, id).verdict === 'NOT_MEASURED') && cazute.every((r) => /reference verifier/.test(r.error || ''))
&& o.artifacts.filter((r) => r.verdict !== 'NOT_MEASURED').every((r) => r.verdict === art(o0, r.id).verdict) && art(o, 'identity-presentation-1').verdict === 'VALID';
return [ok, `${cazute.length} NOT_MEASURED: ${cazute.map((r) => r.id).join(', ')}`];
});
const stub = (nume, corp) => { const f = path.join(T, nume); fs.writeFileSync(f, `import fs from 'node:fs'; const a = process.argv.slice(2); const lista = JSON.parse(fs.readFileSync(a[a.indexOf('--batch') + 1], 'utf8'));\nconst felul = (x) => JSON.parse(fs.readFileSync(x.file, 'utf8')).artifact;\n${corp}\n`); return f; };
// un verdict in forma suitei (kind, artifact = felul cerut), cu starile date; `cauza` pe nivelurile nemasurate
const VERD = (verdict, failed, stari, cauza = 'not-judged') => `({ v: 1, kind: 'aere-conformance-verdict', artifact: felul(x), verdict: ${JSON.stringify(verdict)}, failed: ${JSON.stringify(failed)}, levels: ${JSON.stringify(['integrity', 'authenticity', 'anchoring', 'finality'].map((l, i) => ({ level: l, state: stari[i], rules: stari[i] === 'FAILED' ? failed : [], ...(stari[i] === 'NOT_MEASURED' ? { cause: typeof cauza === 'string' ? cauza : cauza[i] } : {}), reason: 'x' })))} })`;
const REALUL = JSON.stringify(path.join(SUITA, 'implementare-aere.mjs'));
rand('un adaptor care da in lot mai putine verdicte decat artefacte: lotul nu e crezut, fiecare artefact e judecat SINGUR si are verdictul lui', async () => {
// adaptorul real, care in lot (mai mult de un artefact) pierde ultimul verdict; singur, raspunde intreg
const f = stub('putine.mjs', `import { spawnSync } from 'node:child_process'; const r = spawnSync(process.execPath, [${REALUL}, ...a], { encoding: 'utf8', maxBuffer: 1 << 28 });\nconst l = r.stdout.split('\\n').filter((x) => x.trim()); process.stdout.write((lista.length > 1 ? l.slice(0, -1) : l).join('\\n') + '\\n');`);
const o0 = await bun(); const o = await judeca(F.pack, { adaptor: f });
const rele = o.artifacts.filter((r) => r.verdict !== art(o0, r.id).verdict || (r.failed || []).join() !== (art(o0, r.id).failed || []).join()).map((r) => `${r.id} ${r.verdict}${r.error ? ' ' + r.error.slice(0, 80) : ''}`);
return [!rele.length && o.artifacts.length === 25, `${o.artifacts.length} artefacte; altfel decat judecata directa: ${rele.join('; ') || '-'}`];
});
rand('un adaptor care da un verdict incoerent (VALID cu o regula refuzata): NOT_MEASURED, nu VALID', async () => {
const f = stub('incoerent.mjs', `for (const x of lista) console.log(JSON.stringify(${VERD('VALID', ['identity.form'], ['FAILED', 'NOT_MEASURED', 'NOT_MEASURED', 'NOT_MEASURED'])}));`);
const o = await judeca(F.pack, { adaptor: f });
const prin = o.artifacts.filter((r) => !M.FELURI_PROPRII.includes(r.artifact));
return [prin.every((r) => r.verdict === 'NOT_MEASURED') && o.summary.verdict === 'NOT_MEASURED', `${prin.filter((r) => r.verdict === 'NOT_MEASURED').length}/${prin.length} NOT_MEASURED`];
});
rand('un adaptor care spune VALID cu integritatea nemasurata, sau peste o afirmatie nemasurata: verdictul nu e cel pe care il dau nivelurile lui, NOT_MEASURED', async () => {
const f1 = stub('fara-integritate.mjs', `for (const x of lista) console.log(JSON.stringify(${VERD('VALID', [], ['NOT_MEASURED', 'NOT_MEASURED', 'NOT_MEASURED', 'NOT_MEASURED'])}));`);
const f2 = stub('afirmatie.mjs', `for (const x of lista) console.log(JSON.stringify(${VERD('VALID', [], ['PASSED', 'NOT_MEASURED', 'NOT_MEASURED', 'NOT_MEASURED'], [null, 'not-judged', 'claim-not-checked', 'not-judged'])}));`);
const a = await judeca(F.pack, { adaptor: f1 }), b = await judeca(F.pack, { adaptor: f2 });
const prin = (o) => o.artifacts.filter((r) => !M.FELURI_PROPRII.includes(r.artifact));
const ok = [a, b].every((o) => prin(o).length === 23 && prin(o).every((r) => r.verdict === 'NOT_MEASURED' && /not a verdict of the suite's form/.test(r.error || '')));
return [ok, `integritate nemasurata: ${prin(a).filter((r) => r.verdict === 'NOT_MEASURED').length}/23; afirmatie nemasurata: ${prin(b).filter((r) => r.verdict === 'NOT_MEASURED').length}/23 NOT_MEASURED`];
});
rand('finalitatea citita isi numeste lantul: una pe un testnet e spusa ca atare in iesire si in rezumat, nu doar in motivul nivelului (adaptor de proba)', async () => {
// fara retea: un adaptor care raspunde coerent, cu finalitatea in forma adaptorului real ("(chain 28001, a testnet; the finality is that chain's)")
const f = stub('final.mjs', `for (const x of lista) { const v = ${VERD('VALID', [], ['PASSED', 'PASSED', 'PASSED', 'PASSED'])}; v.levels[3].reason = 'the post-quantum certificate of the covering anchor verified (chain 28001, a testnet; the finality is that chain\\'s)'; console.log(JSON.stringify(v)); }`);
const n = notarizat(); const o = await M.judecaPachetul(n.pack, { context: n.context, adaptor: f });
return [art(o, 'notar').verdict === 'VALID' && eq(o.network.finality, [{ id: 'notar', chain: 28001, testnet: true }]) && /final only on a testnet \(chain 28001\)/.test(o.summary.reason),
`${art(o, 'notar').verdict}; ${JSON.stringify(o.network.finality)}; ${o.summary.reason}`];
});
rand('un adaptor care raspunde in alta ordine: niciun raspuns al lotului nu e crezut (artefactul stricat nu primeste verdictul altuia)', async () => {
const f = stub('invers.mjs', `import { spawnSync } from 'node:child_process'; const r = spawnSync(process.execPath, [${REALUL}, ...a], { encoding: 'utf8', maxBuffer: 1 << 28 });\nprocess.stdout.write(r.stdout.split('\\n').filter((x) => x.trim()).reverse().join('\\n') + '\\n');`);
const s = F.stricate['identity-presentation'];
const o = await judeca(inlocuieste(F.pack, s.id, s.artifact), { adaptor: f });
const prin = o.artifacts.filter((r) => !M.FELURI_PROPRII.includes(r.artifact));
return [prin.length === 23 && prin.every((r) => r.verdict === 'NOT_MEASURED' && /order of its answers/.test(r.error || '')) && art(o, s.id).verdict === 'NOT_MEASURED' && dom(o, 'identity').verdict !== 'VALID',
`${prin.filter((r) => r.verdict === 'NOT_MEASURED').length}/23 NOT_MEASURED; identitatea stricata ${art(o, s.id).verdict}`];
});
rand('un artefact care tine adaptorul pe loc nu ia cu el verdictele celorlalte din lot (fiecare e judecat singur, cel lent NOT_MEASURED)', async () => {
// un esantion al unui set de zece milioane de inregistrari, cu k ales de pachet: adaptorul nu termina in plafonul de timp dat
const v = vectorul('identity-presentation/direct.valid'), iv = imparte('identity-presentation', v.input);
const ds = vectorul(F.ales['dataset-sample'].bun.id), id = imparte('dataset-sample', ds.input);
const lent = JSON.parse(JSON.stringify({ ...id.pack, ...id.context })); lent.commitment.size = 1e7; lent.k = 1e7;
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'kyc', artifact: 'identity-presentation', input: iv.pack }, { id: 'lent', artifact: 'dataset-sample', input: lent }] };
const o = await M.judecaPachetul(pack, { context: ctx({ kyc: iv.context }), timeoutS: 8 });
const k = art(o, 'kyc'), l = art(o, 'lent');
return [k.verdict === v.expected.verdict && l.verdict === 'NOT_MEASURED' && /did not finish in 8 s/.test(l.error || '') && /judged alone/.test(l.error || ''), `kyc ${k.verdict}${k.error ? ' ' + k.error.slice(0, 100) : ''}; lent ${l.verdict} ${String(l.error || '').slice(0, 160)}`];
});
rand('pachet fara context: intrarile verificatorului aduse de organizatie nu dau VALID (self-supplied, numite); ceasul pachetului aruncat', async () => {
// fiecare artefact cu inputul INTREG al vectorului (si increderea, si ceasul, in pachet), fara context
const p = JSON.parse(JSON.stringify(F.pack)); const asteptPartial = [];
for (const a of p.artifacts) {
const e = F.asteptat[a.id]; if (!e.vector) continue;
const v = vectorul(e.vector); a.input = v.input;
const c = imparte(a.artifact, v.input).context;
if (Object.keys(c).some((k) => M.INTRARI[a.artifact][k] === 'verifier') && e.verdict === 'VALID') asteptPartial.push(a.id);
}
const o = await M.judecaPachetul(p, { context: null, at: '2026-10-20T09:00:00Z' });
const rele = asteptPartial.filter((id) => { const r = art(o, id); return r.verdict === 'VALID' || !r.inputs.selfSupplied.length; });
const cuNow = p.artifacts.filter((a) => a.input && Object.hasOwn(a.input, 'now')).map((a) => art(o, a.id));
const ceasOk = cuNow.length > 5 && cuNow.every((r) => r.inputs.dropped.includes('now') && r.inputs.now.source === '--at');
return [asteptPartial.length >= 10 && !rele.length && ceasOk && o.summary.verdict !== 'VALID',
`${asteptPartial.length} artefacte cu incredere adusa de pachet; VALID totusi: ${rele.join(',') || '-'}; ceasul aruncat pe ${cuNow.length}`];
});
rand('atac: emitentii de incredere ai organizatiei nu inlocuiesc pe ai celui care verifica (identity.issuer-trusted)', async () => {
const b = vectorul('identity-presentation/direct.valid'), m = vectorul('identity-presentation/direct.issuer-trusted');
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'kyc', artifact: 'identity-presentation', input: b.input }] }; // pachetul isi aduce increderea lui
const o = await M.judecaPachetul(pack, { context: ctx({ kyc: imparte('identity-presentation', m.input).context }) });
const r = art(o, 'kyc');
return [r.verdict === 'INVALID' && eq(r.failed, m.expected.failed) && r.inputs.fromContext.includes('trustedIssuers') && !r.inputs.selfSupplied.includes('trustedIssuers'), `${r.verdict} ${r.failed.join(',')}`];
});
rand('ceasul pachetului nu se foloseste niciodata: prezentarea veche pe ceasul celui care verifica (identity.freshness)', async () => {
// pachetul isi aduce ceasul de cand prezentarea era proaspata; cel care verifica nu da ceas in context, ci il ruleaza la ora lui (--at)
const b = vectorul('identity-presentation/direct.valid'), m = vectorul('identity-presentation/direct.freshness');
const { now: _faraCeas, ...c } = imparte('identity-presentation', m.input).context;
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'kyc', artifact: 'identity-presentation', input: { ...imparte('identity-presentation', b.input).pack, now: b.input.now } }] };
const o = await M.judecaPachetul(pack, { context: ctx({ kyc: c }), at: m.input.now });
const r = art(o, 'kyc');
return [r.verdict === 'INVALID' && eq(r.failed, m.expected.failed) && r.inputs.dropped.includes('now') && r.inputs.now.source === '--at' && b.input.now !== m.input.now,
`${r.verdict} ${r.failed.join(',')}; now ${r.inputs.now && r.inputs.now.source}`];
});
rand('o lista de stare adusa de cel care verifica se adauga celor din pachet: revocarea retinuta de organizatie e gasita', async () => {
const b = vectorul('identity-presentation/direct.valid'), m = vectorul('identity-presentation/direct.credential-revoked-second-list');
const ib = imparte('identity-presentation', b.input);
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'kyc', artifact: 'identity-presentation', input: ib.pack }] }; // numai lista curata
const o = await M.judecaPachetul(pack, { context: ctx({ kyc: { ...ib.context, statusLists: [m.input.statusLists[1]] } }) });
const r = art(o, 'kyc');
return [r.verdict === 'INVALID' && eq(r.failed, m.expected.failed) && r.inputs.stapled.statusLists === 'context+pack', `${r.verdict} ${r.failed.join(',')}; listele: ${r.inputs.stapled.statusLists}`];
});
rand('atac: 64 de liste de rechemare curate in pachet nu imping afara rechemarea adusa de cel care verifica (device.manufacture-revoked)', async () => {
// verificatorul masinilor judeca primele 64 de liste; cu listele pachetului inainte, rechemarea celui care verifica era a 65-a si INVALID devenea PARTIAL
const b = vectorul('device-bundle/sold-once.valid'), m = vectorul('device-bundle/sold-once.manufacture-revoked');
const ib = imparte('device-bundle', b.input), im = imparte('device-bundle', m.input);
const curata = ib.pack.statusLists[0], revoca = im.pack.statusLists;
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'dev', artifact: 'device-bundle', input: { ...im.pack, statusLists: Array.from({ length: 64 }, () => curata) } }] };
const o = await M.judecaPachetul(pack, { context: ctx({ dev: { ...im.context, statusLists: revoca } }) }); const r = art(o, 'dev');
return [!!curata && revoca.length > 0 && r.verdict === 'INVALID' && eq(r.failed, m.expected.failed) && r.inputs.stapled.statusLists === 'context+pack', `${r.verdict} ${r.failed.join(',')}; listele ${r.inputs.stapled.statusLists}`];
});
rand('atac: arbitrul pietei e al celui care verifica: o disputa rezolvata de organizatia care se numeste singura arbitru nu e VALID (market.arbiter)', async () => {
const b = vectorul('market-timeline/s2-slashed.valid'), m = vectorul('market-timeline/s2.arbiter');
const inp = JSON.parse(JSON.stringify(m.input)); inp.constants.arbiter = inp.events.find((e) => e.name === 'DisputeResolved').from; // arbitrul ales de pachet
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'mt', artifact: 'market-timeline', input: inp }] };
const cu = art(await M.judecaPachetul(pack, { context: ctx({ mt: { constants: b.input.constants } }) }), 'mt');
const fara = art(await M.judecaPachetul(pack, { context: null }), 'mt');
return [cu.verdict === 'INVALID' && eq(cu.failed, m.expected.failed) && cu.inputs.fromContext.includes('constants') && fara.verdict === 'PARTIAL' && fara.inputs.selfSupplied.includes('constants'),
`cu constantele celui care verifica: ${cu.verdict} ${cu.failed.join(',')}; fara context: ${fara.verdict} (din pachet: ${fara.inputs.selfSupplied.join(',')})`];
});
rand('atac: faptele reputatiei sunt ale celui care verifica (derivate de el din jurnal): cifrele pe faptele altui jurnal nu sunt VALID (reputation.figures)', async () => {
const h = vectorul('device-reputation/honest.h1.valid'), s = vectorul('device-reputation/sparse.h1.valid');
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'rep', artifact: 'device-reputation', input: h.input }] }; // organizatia isi aduce faptele
const cu = art(await M.judecaPachetul(pack, { context: ctx({ rep: { facts: s.input.facts } }) }), 'rep');
const fara = art(await M.judecaPachetul(pack, { context: null }), 'rep');
return [cu.verdict === 'INVALID' && eq(cu.failed, ['reputation.figures']) && fara.verdict === 'PARTIAL' && fara.inputs.selfSupplied.includes('facts'),
`cu faptele celui care verifica: ${cu.verdict} ${cu.failed.join(',')}; fara context: ${fara.verdict} (din pachet: ${fara.inputs.selfSupplied.join(',')})`];
});
rand('atac: un raport PQ cu rezumatul rescris peste propriile constatari si legat din nou de plic nu e VALID (pqreport.derivation)', async () => {
const a = F.pack.artifacts.find((x) => x.artifact === 'pq-compliance-report');
const RC = await import(pathToFileURL(path.join(AICI, 'raport-conformitate.mjs')).href);
// constructorul de plicuri din acelasi loc din care il ia raport-conformitate.mjs: langa planul de control (copia publica) sau in tools/
const pk = [path.join(AICI, '..', 'proof-kinds', 'proof-kinds.mjs'), path.join(AICI, '..', '..', 'tools', 'proof-kinds', 'proof-kinds.mjs')].find((p) => fs.existsSync(p));
if (!pk) return [false, 'constructorul de plicuri (proof-kinds.mjs) nu e nici langa planul de control, nici in tools/'];
const { buildProof } = await import(pathToFileURL(pk).href);
const rep = JSON.parse(JSON.stringify(a.input.report));
const expuse = rep.findings.filter((f) => f.exposedToday === true).length;
rep.summary.verdict = 'no quantum-vulnerable public-key algorithm in what was measured'; rep.summary.exposedToday = 0;
const env = buildProof('compliance', { subject: rep.organization, policy: rep.version, result: rep.summary.verdict, evidenceHash: RC.sha256Canonic(rep), createdAt: rep.date + 'T00:00:00Z' });
const o = await M.judecaPachetul({ v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'r', artifact: 'pq-compliance-report', input: { report: rep, envelope: env } }] }, { context: null });
const r = art(o, 'r');
return [expuse > 0 && RC.verifyReport(rep, env).ok && r.verdict === 'INVALID' && eq(r.failed, ['pqreport.derivation']) && art(await bun(), a.id).verdict === 'VALID',
`${expuse} constatari expuse azi sub un rezumat "fara algoritmi vulnerabili", legat: ${RC.verifyReport(rep, env).ok}; ${r.verdict} ${r.failed.join(',')}`];
});
rand('un raport PQ al carui plic cere o notarizare e numit in network.needed fara --online', async () => {
const a = JSON.parse(JSON.stringify(F.pack.artifacts.find((x) => x.artifact === 'pq-compliance-report')));
a.input.envelope.notarization = { chainId: 28001, notary: '0x' + '11'.repeat(20), txHash: '0x' + '22'.repeat(32), block: 1, firstSeen: 1 };
const o = await M.judecaPachetul({ v: 1, kind: M.PACK_KIND, artifacts: [a] }, { context: null }); const r = art(o, a.id);
return [r.verdict === 'PARTIAL' && eq(o.network.needed, [a.id]) && /notarization that was not read \(no --online\)/.test(o.summary.reason), `${r.verdict}; ${JSON.stringify(o.network)}`];
});
rand('o masina a carei notarizare se judeca pe citirile celui care verifica: numita in network.readings, nu in needed, si nimic nu cere --online cand e dat', async () => {
const v = vectorul('device-bundle/notarization-not-read.partial'), i = imparte('device-bundle', v.input);
const o = await M.judecaPachetul({ v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'dev', artifact: 'device-bundle', input: i.pack }] }, { context: ctx({ dev: i.context }), online: true });
return [art(o, 'dev').verdict === 'PARTIAL' && eq(o.network.readings, ['dev']) && !o.network.needed.length && !/no --online/.test(o.summary.reason) && /relying party's own readings/.test(o.summary.reason),
`${art(o, 'dev').verdict}; ${JSON.stringify(o.network)}`];
});
rand('ceasul: un `now` pe artefact sau pe fel al contextului, apoi --at, apoi `now` global al contextului (cum spune README-ul)', async () => {
const v = vectorul('identity-presentation/direct.valid'), i = imparte('identity-presentation', v.input); const { now: _n, ...c } = i.context;
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'kyc', artifact: 'identity-presentation', input: i.pack }] };
const a = await M.judecaPachetul(pack, { context: { ...ctx({ kyc: c }), now: '2026-01-01T00:00:00Z' }, at: v.input.now });
const b = await M.judecaPachetul(pack, { context: { ...ctx({ kyc: { ...c, now: v.input.now } }), now: '2026-01-01T00:00:00Z' }, at: '2026-01-02T00:00:00Z' });
const d = await M.judecaPachetul(pack, { context: { ...ctx({ kyc: c }), now: v.input.now } });
const ok = a.clock.source === '--at' && art(a, 'kyc').inputs.now.source === '--at' && art(b, 'kyc').inputs.now.source === 'context' && art(b, 'kyc').inputs.now.value === v.input.now
&& d.clock.source === 'context' && art(d, 'kyc').verdict === 'VALID';
return [ok, `--at peste now global: ${a.clock.source}; now pe artefact peste --at: ${art(b, 'kyc').inputs.now.source}; numai now global: ${d.clock.source} ${art(d, 'kyc').verdict}`];
});
rand('verdictele scrise de pachet sunt ignorate, iar cele care difera de masuratoare sunt numite', async () => {
const s = F.stricate['identity-presentation'];
const p = inlocuieste(F.pack, s.id, { ...s.artifact, verdict: 'VALID', expected: { verdict: 'VALID' } }); p.summary = { verdict: 'VALID' }; p.verdict = 'VALID';
const o = await judeca(p);
const unde = o.selfReport.suspect.map((x) => x.where);
return [o.summary.verdict === 'INVALID' && art(o, s.id).verdict === 'INVALID' && ['pack.verdict', 'pack.summary.verdict', `artifact ${s.id}: verdict`, `artifact ${s.id}: expected.verdict`].every((w) => unde.includes(w)),
`rezumat ${o.summary.verdict}; suspecte: ${unde.join(' | ')}`];
});
const notarizat = () => { const v = vectorul('envelope/notarized-28001.valid'); const i = imparte('envelope', v.input); return { v, pack: { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'notar', artifact: 'envelope', input: i.pack }] }, context: ctx({ notar: i.context }) }; };
rand('fara --online: un plic notarizat e PARTIAL, finalitatea "claim-not-checked", si e numit ca cerand reteaua', async () => {
const n = notarizat(); const o = await M.judecaPachetul(n.pack, { context: n.context });
const r = art(o, 'notar');
return [r.verdict === 'PARTIAL' && r.levels[3].cause === 'claim-not-checked' && o.network.online === false && eq(o.network.needed, ['notar']) && /notarization that was not read/.test(o.summary.reason),
`${r.verdict}, ${r.levels[3].cause}; network ${JSON.stringify(o.network)}`];
});
rand('cu --online catre un capat inchis: adaptorul chiar citeste (finalitatea nemasurabila, numita), nu mai e "claim-not-checked"', async () => {
const n = notarizat(); const vechi = process.env.AERE_CONFORMANCE_RPC_28001; process.env.AERE_CONFORMANCE_RPC_28001 = 'http://127.0.0.1:9';
let o; try { o = await M.judecaPachetul(n.pack, { context: n.context, online: true }); } finally { if (vechi === undefined) delete process.env.AERE_CONFORMANCE_RPC_28001; else process.env.AERE_CONFORMANCE_RPC_28001 = vechi; }
const r = art(o, 'notar');
return [r.verdict === 'PARTIAL' && r.levels[3].cause === 'unmeasurable' && o.network.online === true && !o.network.needed.length && eq(o.network.unmeasured, ['notar']) && /the chain could not be read for 1 artifact/.test(o.summary.reason) && !o.network.finality.length,
`${r.verdict}, ${r.levels[3].cause}; ${JSON.stringify(o.network)}`];
});
rand('un fel pe care nu il judeca nimeni aici: nejudecat, numit, si rezumatul nu mai poate fi VALID', async () => {
const o0 = await bun(); const valide = o0.domains.filter((d) => d.verdict === 'VALID').map((d) => d.domain);
const p = JSON.parse(JSON.stringify(F.pack)); p.artifacts.push({ id: 'oracol-1', artifact: 'quantum-oracle', input: { says: 'VALID' } });
const o = await judeca(p, { require: valide });
return [o.summary.verdict === 'PARTIAL' && o.unjudged.length === 1 && o.unjudged[0].id === 'oracol-1' && o.artifacts.length === 25, `${o.summary.verdict}; nejudecate ${o.unjudged.map((u) => u.id).join(',')}`];
});
rand('un id repetat: primul judecat, al doilea NOT_MEASURED si numit; domeniul nu mai e VALID', async () => {
const p = JSON.parse(JSON.stringify(F.pack)); const a = p.artifacts.find((x) => x.id === 'identity-presentation-1'); p.artifacts.push(JSON.parse(JSON.stringify(a)));
const o = await judeca(p); const doi = o.artifacts.filter((r) => r.id === 'identity-presentation-1');
return [doi.length === 2 && doi[0].verdict === 'VALID' && doi[1].verdict === 'NOT_MEASURED' && /more than once/.test(doi[1].error) && dom(o, 'identity').verdict === 'NOT_MEASURED',
`${doi.map((r) => r.verdict).join(',')}; identity ${dom(o, 'identity').verdict}`];
});
rand('pachet necitibil sau context necitibil: NOT_MEASURED cu motivul, niciun domeniu judecat', async () => {
const a = await M.judecaPachetul({ kind: 'altceva' }); const b = await M.judecaPachetul(F.pack, { context: { kind: 'altceva' } });
return [a.summary.verdict === 'NOT_MEASURED' && /pack cannot be read/.test(a.summary.reason) && b.summary.verdict === 'NOT_MEASURED' && /context cannot be read/.test(b.summary.reason)
&& a.domains.every((d) => d.verdict === 'NOT_PRESENTED'), `${a.summary.reason} | ${b.summary.reason}`];
});
rand('cheile KEM private nu se citesc din pachet: aruncate si numite; fara cele din context mesajul nu se judeca (NOT_MEASURED)', async () => {
const v = vectorul('travel-rule-message/sealed.valid'); const i = imparte('travel-rule-message', v.input);
const pack = { v: 1, kind: M.PACK_KIND, artifacts: [{ id: 'msg', artifact: 'travel-rule-message', input: v.input }] }; // cheile private in pachet
const { kem, ...faraKem } = i.context;
const o = await M.judecaPachetul(pack, { context: ctx({ msg: faraKem }) }); const r = art(o, 'msg');
const o2 = await M.judecaPachetul(pack, { context: ctx({ msg: i.context }) }); const r2 = art(o2, 'msg');
return [kem && r.verdict === 'NOT_MEASURED' && r.inputs.dropped.includes('kem') && r2.verdict === 'VALID' && r2.inputs.fromContext.includes('kem'), `fara context: ${r.verdict} (${r.error || ''}); cu context: ${r2.verdict}`];
});
rand('un context nu poate scrie artefactul organizatiei: cheia lui e ignorata si numita', async () => {
const s = F.stricate['identity-presentation'];
const c = JSON.parse(JSON.stringify(F.context)); c.byArtifact['identity-presentation-1'].presentation = s.artifact.input.presentation;
const o = await judeca(F.pack, { context: c }); const r = art(o, 'identity-presentation-1');
return [r.verdict === 'VALID' && r.inputs.contextIgnored.includes('presentation'), `${r.verdict}; ignorate ${r.inputs.contextIgnored.join(',')}`];
});
// ---- starea Shield, in fiecare fel in care se strica (consola.mjs o judeca; aici se masoara ca refuzul ajunge cu regula lui)
const shieldCu = async (f) => { const i = JSON.parse(JSON.stringify(F.pack.artifacts.find((a) => a.id === 'shield-console-1').input)); await f(i); return art(await judeca(inlocuieste(F.pack, 'shield-console-1', { id: 'shield-console-1', artifact: 'shield-console', input: i })), 'shield-console-1'); };
rand('Shield: o intrare a jurnalului schimbata (lantul rupt) -> shield.audit-chain', async () => {
const r = await shieldCu((i) => { i.bundle.entries[0].proof.statement.host = 'ATACATOR'; i.bundle.chainOk = undefined; });
return [r.verdict === 'INVALID' && r.failed.includes('shield.audit-chain'), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: lant refacut coerent, dar un plic cu statementHash mincinos -> numai shield.envelope-integrity', async () => {
const { caleaSidecarului } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href); const { hashIntrare } = await import(pathToFileURL(caleaSidecarului()).href);
const r = await shieldCu((i) => { const b = i.bundle; b.entries[0].proof.statement.host = 'ATACATOR'; let prev = '0x' + '00'.repeat(32); for (const e of b.entries) { e.prev = prev; e.hash = hashIntrare(e.seq, e.prev, e.proof); prev = e.hash; } b.head = prev; });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.envelope-integrity']), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: burafu declara chainOk:false peste un lant intreg -> shield.self-report', async () => {
const r = await shieldCu((i) => { i.bundle.chainOk = false; });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.self-report']), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: un buraf fara intrari -> shield.bundle-form (nimic nu e dovada unei desfasurari)', async () => {
const r = await shieldCu((i) => { i.bundle.entries = []; });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.bundle-form']), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: un plan fara lista planificatorului -> shield.plan-form', async () => {
const r = await shieldCu((i) => { i.plan = { ceva: [1] }; });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.plan-form']), `${r.verdict} ${r.failed.join(',')}`];
});
// executia refacuta dintr-o lista de inregistrari, cu lantul executorului insusi (un lant intreg: ce se masoara e legatura cu planul, nu lantul)
const executiaDin = async (recs) => { const { lantulExecutiei } = await import(pathToFileURL(path.join(AICI, 'executa-migrare.mjs')).href); const l = lantulExecutiei(); for (const r of recs) l.adauga(r); return { version: 'x', records: l.lista() }; };
rand('Shield: o executie din care lipseste o actiune automata a planului (lant refacut intreg) -> shield.execution-plan', async () => {
const r = await shieldCu(async (i) => { const recs = i.execution.records.map((e) => e.record); const k = recs.findIndex((x) => x.ref); recs.splice(k, 1); i.execution = await executiaDin(recs); });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.execution-plan']), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: executia unui plan, alaturi de alt plan (fara actiunile automate) -> shield.execution-plan', async () => {
const r = await shieldCu((i) => { i.plan = { ...i.plan, actions: i.plan.actions.filter((a) => a.method !== 'auto-aere') }; });
const g = await shieldCu((i) => { i.plan = { ...i.plan, actions: [] }; });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.execution-plan']) && g.verdict === 'INVALID' && eq(g.failed, ['shield.execution-plan']), `alt plan: ${r.verdict} ${r.failed.join(',')}; plan gol: ${g.verdict} ${g.failed.join(',')}`];
});
rand('Shield: o executie al carei rand de sfarsit nu isi numara inregistrarile -> shield.execution-plan', async () => {
const r = await shieldCu(async (i) => { const recs = i.execution.records.map((e) => e.record); recs[recs.length - 1] = { ...recs[recs.length - 1], summary: { ...recs[recs.length - 1].summary, ok: 3, dryRun: 0 } }; i.execution = await executiaDin(recs); });
return [r.verdict === 'INVALID' && eq(r.failed, ['shield.execution-plan']), `${r.verdict} ${r.failed.join(',')}`];
});
rand('Shield: o intrare care poarta o semnatura pe care consola nu o judeca -> PARTIAL, numita', async () => {
const { caleaSidecarului } = await import(pathToFileURL(path.join(AICI, 'consola.mjs')).href); const { hashIntrare } = await import(pathToFileURL(caleaSidecarului()).href);
// semnatura sta in afara declaratiei (statementHash neatins), dar intra in hash-ul intrarii: lantul se reface coerent, ca de o gazda care semneaza
const r = await shieldCu((i) => { const b = i.bundle; b.entries[1].proof.signature = { scheme: 'ml-dsa-65', publicKey: 'AA', signature: 'AA' }; let prev = '0x' + '00'.repeat(32); for (const e of b.entries) { e.prev = prev; e.hash = hashIntrare(e.seq, e.prev, e.proof); prev = e.hash; } b.head = prev; });
return [r.verdict === 'PARTIAL' && r.levels[1].cause === 'claim-not-checked', `${r.verdict} ${r.levels && r.levels[1].cause}`];
});
// ---- linia de comanda
rand('linia de comanda: --json si codurile de iesire (0 VALID, 1 INVALID, 2 NOT_MEASURED, 3 PARTIAL)', async () => {
const o0 = await bun(); const valide = o0.domains.filter((d) => d.verdict === 'VALID').map((d) => d.domain).join(',');
const pf = path.join(T, 'pack.json'), cf = path.join(T, 'context.json'), sf = path.join(T, 'stricat.json');
fs.writeFileSync(pf, JSON.stringify(F.pack)); fs.writeFileSync(cf, JSON.stringify(F.context));
const s = F.stricate['payment-receipt']; fs.writeFileSync(sf, JSON.stringify(inlocuieste(F.pack, s.id, s.artifact)));
const cli = (...a) => { const r = spawnSync(process.execPath, [MODUL, ...a], { encoding: 'utf8', maxBuffer: 64 << 20 }); let j = null; try { j = JSON.parse(r.stdout); } catch { /* text */ } return { c: r.status, j, out: r.stdout }; };
const a = cli('--pack', pf, '--context', cf, '--require', valide, '--json');
const b = cli('--pack', sf, '--context', cf, '--json');
const c = cli('--pack', pf, '--context', cf, '--require', 'devices,shield');
const d = cli('--pack', pf, '--context', cf);
const e = cli('--pack', pf, '--context', cf, '--require', 'nu-e-domeniu');
const ok = a.c === 0 && a.j && a.j.kind === M.OUT_KIND && a.j.summary.verdict === 'VALID' && /^0x[0-9a-f]{64}$/.test(a.j.pack.sha256)
&& b.c === 1 && b.j.summary.verdict === 'INVALID' && c.c === 0 && /ORGANIZATION JUDGEMENT/.test(c.out) && d.c === 3 && /PARTIAL/.test(d.out) && e.c === 2;
return [ok, `coduri ${a.c},${b.c},${c.c},${d.c},${e.c}`];
});
rand('linia de comanda: un domeniu cerut si neprezentat iese cu 2 (NOT_MEASURED)', async () => {
const p = inlocuieste(F.pack, 'shield-console-1', null); const pf = path.join(T, 'fara-shield.json'), cf = path.join(T, 'context2.json');
fs.writeFileSync(pf, JSON.stringify(p)); fs.writeFileSync(cf, JSON.stringify(F.context));
const r = spawnSync(process.execPath, [MODUL, '--pack', pf, '--context', cf, '--require', 'shield', '--json'], { encoding: 'utf8', maxBuffer: 64 << 20 });
let j = null; try { j = JSON.parse(r.stdout); } catch { /* */ }
return [r.status === 2 && j && j.summary.verdict === 'NOT_MEASURED' && eq(j.summary.notPresented, ['shield']), `cod ${r.status}`];
});
// ---- reteaua adevarata (numai cu --online): un plic notarizat pe testnetul 28001, finalitatea lui citita
let neRulat = null;
if (ONLINE) {
rand('cu --online pe testnetul 28001: plicul notarizat e VALID, ancorarea si finalitatea PASSED, iar iesirea spune ca finalitatea e a unui testnet (28001)', async () => {
const n = notarizat(); const o = await M.judecaPachetul(n.pack, { context: n.context, online: true }); const r = art(o, 'notar');
return [r.verdict === 'VALID' && r.levels[2].state === 'PASSED' && r.levels[3].state === 'PASSED' && !o.network.needed.length
&& eq(o.network.finality, [{ id: 'notar', chain: 28001, testnet: true }]) && /final only on a testnet \(chain 28001\)/.test(o.summary.reason),
`${r.verdict} ${stari(r.levels).join(',')}; finality ${JSON.stringify(o.network.finality)}`];
});
} else neRulat = 'randul care citeste testnetul 28001 (finalitatea unui plic notarizat): NEMASURAT fara --online, nenumarat';
let bune = 0, rele = 0;
try {
for (const [nume, f] of randuri) {
if (DOAR.length && !DOAR.some((d) => nume.includes(d))) continue;
let ok = false, detaliu = '';
try { [ok, detaliu] = await f(); } catch (e) { ok = false; detaliu = 'a aruncat: ' + String(e.stack || e.message).split('\n').slice(0, 2).join(' '); }
console.log(` [${ok ? 'OK ' : 'RAU '}] ${nume}${ok ? '' : ' -- ' + detaliu}`);
if (ok) bune++; else rele++;
}
} finally { fs.rmSync(T, { recursive: true, force: true }); }
if (neRulat && !DOAR.length) console.log(' ' + neRulat);
console.log(`\norganizatie: ${bune}/${bune + rele} cum trebuia${DOAR.length ? ' (numai randurile cerute: ' + DOAR.join(', ') + ')' : ''}`);
process.exitCode = rele ? 1 : 0;

View File

@ -178,3 +178,46 @@ export function verificaRaport(raport, plic) {
return h === String(e).toLowerCase() ? { ok: true } : { ok: false, reason: 'the report is NOT the one bound to the envelope', fingerprint: h, inEnvelope: e };
}
export { verificaRaport as verifyReport };
// DERIVAREA (revizia B2, 2026-10-21): legarea de plic spune numai CA raportul e cel legat, nu si ca e ce da generatorul. Plicul nu e semnat, deci
// cine tine raportul ii poate rescrie rezumatul ("no quantum-vulnerable public-key algorithm") peste constatari care spun altceva, il poate lega
// din nou, si legarea iese buna (masurat: judecata organizatiei dadea VALID si arata rezumatul rescris). Aici raportul se FACE INCA O DATA din
// propriile constatari, cu acelasi generator: fiecare constatare devine actiunea din care generatorul ar fi scris-o (natura ei, refacuta prin
// `natura`), iar rezumatul, termenele, zilele, expunerea, regimurile, limitele si declaratia plicului trebuie sa iasa EXACT cele primite.
// Ce nu se poate reface de aici: ca actiunile erau chiar cele ale planului real (asta e inventarul si scanarea, nu raportul).
const ACTIUNE_PE_NATURA = { 'key-exchange': { source: 'inventory', primitive: 'key-agree' }, signature: { source: 'inventory', primitive: 'signature' }, 'key-use-unknown': { source: 'inventory', primitive: '' } };
const json = (v) => JSON.parse(JSON.stringify(v === undefined ? null : v));
/** prima cale la care doua valori JSON difera, ca motivul sa spuna CE nu se reface */
function primaDiferenta(a, b, cale = '') {
if (canonicJson(a) === canonicJson(b)) return null;
if (a && b && typeof a === 'object' && typeof b === 'object' && Array.isArray(a) === Array.isArray(b)) {
const chei = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
for (const k of chei) { const d = primaDiferenta(a[k], b[k], cale + (Array.isArray(a) ? `[${k}]` : (cale ? '.' : '') + k)); if (d) return d; }
}
return cale || '(the whole)';
}
/**
* Raportul e ce da generatorul acesta pentru propriile lui constatari, data, risc si CNSA? { ok: true } | { ok: false, reason } | { ok: null, reason }
* (null = alt generator: nu se poate reface de aici, deci nu se poate spune nici da, nici nu).
*/
export function verificaDerivarea(raport, plic) {
if (!raport || typeof raport !== 'object' || Array.isArray(raport)) return { ok: false, reason: 'the report is not an object' };
if (raport.version !== VERSIUNE) return { ok: null, reason: `the report says it was made by ${JSON.stringify(raport.version)}; this generator is ${VERSIUNE} and does not re-derive another` };
if (!Array.isArray(raport.findings) || !raport.findings.every((f) => f && typeof f === 'object' && !Array.isArray(f))) return { ok: false, reason: 'the report has no list of findings' };
const actiuni = [], cnsaCategories = Object.create(null);
for (const f of raport.findings) {
actiuni.push({ ref: f.ref, asset: f.asset, problem: f.problem, urgency: f.urgency, method: f.method, product: f.product, target: f.target, blocker: f.blocker, scope: f.scope,
...(Object.hasOwn(ACTIUNE_PE_NATURA, String(f.nature)) ? ACTIUNE_PE_NATURA[f.nature] : { source: null }) });
for (const r of Array.isArray(f.regimes) ? f.regimes : []) if (r && r.regime === 'cnsa-2.0' && r.assumed === false && typeof r.category === 'string') cnsaCategories[f.ref] = r.category;
}
const p = raport.plan && typeof raport.plan === 'object' ? raport.plan : {};
let d;
try { d = raportConformitate({ plan: { version: p.version, summary: p.summary, actions: actiuni }, organization: raport.organization, risk: raport.risk === null ? undefined : raport.risk, date: raport.date, cnsa: raport.cnsa === true, cnsaCategories }); }
catch (e) { return { ok: false, reason: 'the report cannot be made again from its own findings: ' + String(e.message).slice(0, 160) }; }
const unde = primaDiferenta(json(d.report), json(raport));
if (unde) return { ok: false, reason: `the report is not what the report generator gives for its own findings (it differs at ${unde})` };
const st = plic && plic.statement && typeof plic.statement === 'object' ? plic.statement : null;
const undeP = primaDiferenta(json(d.envelope.statement), json(st));
if (undeP) return { ok: false, reason: `the envelope's statement is not the one the generator writes for this report (it differs at statement.${undeP})` };
return { ok: true };
}