{"scope":"salon","collecting":true,"platform":"https://primer.tech/.well-known/reviews-network.json","salonId":"cmj2w4mlb0004fti9apf9cftw","algo":"sha256-canonicaljson-v5","invitationRule":"inv-v2","head":{"seq":3,"entryHash":"450a5338de3562d94c1cce93e8dfb8c0e065c55ca10932cae9f1e6b2af4ea909","createdAt":"2026-08-31T17:44:15.610Z"},"entries":3,"exportUrl":"https://www.salontransilvania.ro/api/public/reviews/log?salon=cmj2w4mlb0004fti9apf9cftw","policyUrl":"https://www.salontransilvania.ro/despre-recenzii","anchors":[],"anchorAge":{"firstAnchoredAt":null,"firstConfirmedAt":null,"anchoredDaysCount":0,"confirmedDaysCount":0,"pendingDaysCount":0},"anchorHonestyNote":"This register has no anchored days yet. Nothing has been submitted to an OpenTimestamps calendar, so nothing here is externally timestamped — only internally hash-linked. Trust here accumulates with time and is not declared.","funnel":{"invited":3,"responded":1,"published":1,"publishedFromInvitation":1,"publishedOrganic":0,"withdrawnReviews":0,"negativeFinal":0,"negativeSurviving":0},"keyAnchors":[{"seq":2,"kid":"primer-salon-2026-08-prod","publicKeyX":"xS_gLW0UVkSZLhkvJ-OmnvR7j87ZC2Rcu2S9mW9cXec","scope":"salon","custody":"platform","supersedesKid":null,"activatedAt":"2026-08-31T17:02:20.503Z"}],"keyAnchorsUnresolved":[],"endpoints":{"jwksUrl":"https://www.salontransilvania.ro/.well-known/jwks.json","anchorsUrl":"https://primer.tech/api/public/reviews/anchors","exportUrl":"https://www.salontransilvania.ro/api/public/reviews/log?salon=cmj2w4mlb0004fti9apf9cftw","networkUrl":"https://primer.tech/.well-known/reviews-network.json"},"verifier":{"npmPackage":"primer-verify","repoUrl":"https://github.com/iclaudiumihaila/primer-verify","specUrl":"https://github.com/iclaudiumihaila/primer-verify/blob/d5b28e3d1371411f67a9cf1f766c9a51d8a62d00/SPEC.md","specVersion":"1.1.0"},"claimSources":{"collecting":"platform","funnel":"platform"},"disclosure":{"derivedFrom":"append-only review ledger + immutable Review rows","editableInputs":"none","invitationPolicy":"Every customer who completes an appointment AND has agreed to receive messages FROM THIS SALON is invited to review that visit, on that agreement (art. 6(1)(a) GDPR). Within that population nobody chooses who is asked: the invitation goes out automatically, on the same platform-wide rules for every salon. THE AGREEMENT IS PER SALON, AND THAT IS STATED RATHER THAN ASSUMED: it is recorded against this salon alone, so an agreement a customer gave to a different salon does not admit them here, and an objection they sent to a different salon does not remove them from here. Each register asks for itself and is answered for itself. THE QUALIFICATION IS PUBLISHED RATHER THAN OMITTED: that agreement is a record the salon itself can switch off, so under this basis a salon CAN remove somebody from the population. Every such removal is counted against the salon that performed it and shown on that salon's own register screen, together with the other three levers it has — its block list, an objection its staff relay at the counter, and recording a finished visit as an absence, which ends an invitation already sent, is refused once a review has been written and cannot be undone. Those counts are not part of the public figures below, which publish the pipeline and not a roll of the individuals who asked this salon to stop. This is the narrower basis — it asks fewer people than the salon serves, and saying so is the point. WHICH VISITS: every finished visit — closed at the till, closed with the finish button, or closed automatically at least 2 hours after its end time — unless it was cancelled or recorded as an absence, in which case no invitation is sent and any invitation already sent stops working. Who closed the visit decides nothing and is not a gate: this register publishes no closer for any single visit, and the source is recorded on the salon's own appointments screen. THIS RULE IS VERSIONED: it is inv-v2, declared as invitationRule beside these documents' hash rule, and disclosure.invitationRules publishes what every earlier version said and from which date this one applies — the previous rule invited only the visits a person had closed, and visits finished under it are not invited retroactively. At most one invitation every 30 days per person, at most 3 unanswered invitations ever, and never again after a STOP. ONE PERSON, ONE REVIEW: a customer whose review is still published at this salon is never invited again, however often they come back. There is no renewal invitation at all: it is switched off platform-wide, and no salon can switch it on. The gate is the published review, not the act of having written one — but a removal that is about the PERSON does not put them back in the pool: when a review is removed at the reviewer's own request, or under the right to erasure, the same narrow objection a STOP records is written for that person at that salon, so no further review invitation is ever sent to them there. Their booking confirmations and reminders are untouched — those are a different purpose. A removal on a ground about the CONTENT, such as a takedown or a court order, leaves the person's standing exactly as it was. These limits are platform-wide and identical for every salon — they are not configurable by a salon.","invitationUnit":"The unit of this policy is the PERSON: invited and responded in this document's funnel are counts of distinct people, one invitation per person per visit, and the limits stated in invitationPolicy are per person. A customer with three invited visits is one invited person, never three.","invitationRules":"The current invitation rule is inv-v2, and it is declared as invitationRule above. It says WHICH VISITS are asked about; invitationPolicy says which PEOPLE may be asked and on what legal ground, and the two are separate questions. What each rule changed: inv-v1 — only visits a PERSON closed were invited — at the till, or with the finish button. A visit nobody closed by hand, which the automatic close caught instead, was never asked about, so a register could shrink the population it published simply by not closing tickets. inv-v2 — every finished visit is invited — at the till or automatically, at least 2 hours after its end time, unless it was cancelled or recorded as an absence. Who closed the visit stops deciding anything: the source (completionSource) is no longer a gate, it is published for no single visit here, and it stays on the salon's own appointments screen. Applies to visits finished after 2026-08-30; visits finished before that date were governed by inv-v1 and are not invited retroactively. THE RULE FOR ONE VISIT IS THE RULE IN FORCE WHEN THAT VISIT FINISHED, never the rule this document declares — a register whose older entries were collected under inv-v1 is correct behaviour and not a fork, exactly as a chain that mixes hash rules is. AND ONE THING THAT IS NOT A RULE CHANGE: nothing here touches how an entry is hashed or what a payload contains, so the hash rule is unaffected and a reader should not look for a new one.","hashRules":"The current hash rule is sha256-canonicaljson-v5, and it is declared as algo above. Every rule a live chain may honestly carry is published here — sha256-canonicaljson-v2, sha256-canonicaljson-v3, sha256-canonicaljson-v4, sha256-canonicaljson-v5 — because THE ALGO GATE IS A MEMBERSHIP TEST, NOT EQUALITY WITH THE NEWEST RULE: a chain legitimately mixes rules, one per entry, and branding every pre-current entry unverifiable the day a new rule shipped would falsely unverify the honest back-catalogue. What each rule changed: sha256-canonicaljson-v2 — the base rule: sha256 over canonical JSON, with supersedesReviewId inside the ENTRY preimage so a reader can tell a corrected review from a hidden one from the export alone. sha256-canonicaljson-v3 — adds four economic flags to the PUBLISH/SUPERSEDE payload — paidVisit, paymentMethod, returningClient, visitCount. Present-as-null changes the canonical string, so the add could not be unconditional: that is why it is a new rule and not a field. sha256-canonicaljson-v4 — keeps those four flags but forces them present-as-null for an ANONYMOUS author, because an exact visit count re-identifies a hidden author on a small register. A v4 NAMED payload is byte-identical to a v3 one. sha256-canonicaljson-v5 — adds one field, synthetic (bool|null): true when the row was written by a declared seed path. Absent under every earlier rule, and absent means \"written before the flag existed\", never \"asserted genuine\". NONE of the rules after the base one touches the ENTRY preimage — all of them change only the PUBLISH/SUPERSEDE payload the payloadHash is taken over, which is why an entry's link structure re-derives identically under every rule. The rule for ONE entry is the rule stamped ON that entry (its hashAlgo field), never the rule this document declares: a permalink showing an entry under an older rule inside a register declaring the newest one is correct behaviour and not a fork. AND ONE THING THAT IS NOT A RULE CHANGE: the funnel no longer publishing an absolute count of visits is a change to what these DOCUMENTS represent, not to how a ledger entry is hashed — no funnel field has ever entered an entry preimage or a payload, so there is no rule after sha256-canonicaljson-v5 and a reader should not look for one. AND A SECOND THING THAT IS NOT A RULE CHANGE, on the same law one level down: NO REVIEW PUBLISHED FROM THIS BUILD ONWARD CARRIES A COUNT OF THAT PERSON'S VISITS. visitCount is still a field of the sha256-canonicaljson-v3/sha256-canonicaljson-v4/sha256-canonicaljson-v5 payload and is still hashed — the rule did not move, and no new rule exists — but this platform no longer derives or stores it, so a new entry commits visitCount as null for EVERY author, named as well as anonymous, exactly as the sha256-canonicaljson-v4 coarsening already committed it for anonymous ones. Entries published BEFORE this build keep the integer they committed to and re-derive byte-for-byte: the payload is rebuilt from the review row, so changing what an old entry publishes would have broken every proof already anchored — which is why this is a change to what is WRITTEN and not to how anything is hashed. A null therefore means \"this platform does not publish a person's visit count\", never \"this customer had no visits\", and a mixed register — old entries with a number, new ones with null — is correct and expected. AND THE ENTRY RULE ITSELF, so a reader need not leave this document to recompute one: entryHash = sha256(canonicalJson({algo, seq, salonId, reviewId, kind, payloadHash, prevHash, supersedesReviewId, createdAt})) — those fields, that order, nothing else. Two shapes an independent reimplementation gets wrong, named here because they were measured rather than imagined: the preimage key is algo, while the EXPORTED entry names the same value hashAlgo — one value, two field names, and a verifier threads the exported hashAlgo back in as algo. And salonId is INSIDE the preimage but is NOT a field of the exported entry: it comes from the register's own address, the salon the export was fetched for, which is exactly what binds an entry to its register.","synthetic":"Each review published under hash rule sha256-canonicaljson-v5 carries a 'synthetic' field in its signed payload: true when the row was written by a declared seed path, false on a real write. A production register can never carry true — the write door refuses a declared-synthetic write against a production tenant before the transaction. The field is ABSENT on every entry published under an earlier rule (v2, v3, v4), and absent means 'written before the flag existed', NEVER 'asserted genuine': back-filling it would rewrite payloads already committed and already anchored. It is a platform assertion, not a third-party-verifiable fact — you can verify that the value we committed to is the value we hash, not that we classified it honestly.","signing":"This document is signed by a key held and operated by Primer, not by the salon. What the signature proves is that Primer served these exact bytes for this exact origin — never that their contents are true. The key's fingerprint is written into this salon's own append-only chain as a KEY_ANCHOR entry, so a republication under a different key is detectable rather than invisible.","anchor":"Daily Merkle root over the per-salon chain heads, submitted to public OpenTimestamps calendars. SUBMITTED means a calendar accepted the digest; it is NOT a Bitcoin confirmation. CONFIRMED means the proof carries a Bitcoin attestation naming a block height. Note for anyone checking by hand: GET <calendar>/timestamp/<digest> returns 404 for genuine digests — the calendar indexes the commitment at the pending attestation, not the file digest — so verification is done from the proof, never by re-querying the calendar. TWO CALENDARS, TWO ROLES, because one word is doing two jobs: the calendar field published beside this note is the SUBMISSION endpoint the digest was handed to, which is a POOL address; the ATTESTING calendar is the one named inside the .ots proof at its pending attestation, and it is routinely a different host, because a pool answers from one of its members. A verifier that finds the two disagreeing has found an ordinary hand-off, not a contradiction. We publish the submission endpoint because that is the fact we hold; the attesting calendar is READ OUT OF THE PROOF, which is the checkable artefact and is served at the otsUrl beside this block. Neither name is evidence on its own: what a proof commits to is a Bitcoin block, never a calendar.","signatureAbsence":"When this document cannot be signed it is served UNSIGNED and says so (signature: null, signatureUnavailable), never withheld and never faked. The reason is machine-readable and names the actual gap: NO_SIGNING_KEY_CONFIGURED means this deployment has no key at all; SIGNING_KID_NOT_CONFIGURED means it has one but no key id to publish it under; SIGNING_KEY_INVALID means the configured key is not base64(PKCS#8 DER) of an Ed25519 key. Three different things to go and fix, so they are three different codes. In every case the chain, the counters and the anchors below are unaffected and remain fully verifiable from the public export; only the origin binding is missing.","funnel":"funnel is the collection pipeline — invited, responded, published (broken out as publishedFromInvitation and publishedOrganic, which sum to published), withdrawnReviews, and the negativeFinal/negativeSurviving pair. THE UNITS ARE NOT ALL THE SAME, and mixing them without saying so is how a response rate quietly understates itself as a salon's clientele becomes more loyal: invited and responded are counts of DISTINCT PEOPLE, not of visits and not of messages — a customer with three invited visits is one invited person, so the response rate is people over people and cannot exceed one. published, publishedFromInvitation, publishedOrganic and withdrawnReviews are counts of REVIEWS. NO ABSOLUTE COUNT OF VISITS IS PUBLISHED HERE ANY MORE: this funnel used to open with a count of completed visits, and that figure is gone from every public document as of this build — its absence means \"this build no longer publishes it\", never \"this register has none\". It is still computed, and it is still on the salon's own register screen, where the three anti-manipulation gaps beside it are legible to the people who can act on them. Every figure that remains is a PLATFORM ASSERTION: it is computed over Appointment and Review rows this export deliberately does not carry (the export publishes hashes only, so one erasure has one place to chase), so a third party cannot recompute it from public data. It is published labelled, and it is the figure that would embarrass us if we were selecting: an invited population far larger than the responded one stands out here. The published split exists because published is NOT a subset of invited and never was: the organic door (a recognised device on the salon's own site) and legacy rows publish a review without an invitation ever sent, so publishedFromInvitation counts the invitation-door reviews and publishedOrganic the rest — reading published ⊄ invited as a red flag is reading the shape, not the numbers. negativeFinal and negativeSurviving are published as a PAIR so a reader can read \"two of two negatives still standing\" off two integers without trusting a rate they cannot recompute."},"documentOrigin":"https://www.salontransilvania.ro","signedAt":"2026-08-31T20:51:51.492Z","signedBy":{"scope":"salon","custody":"platform","kid":"primer-salon-2026-08-prod"},"signature":{"alg":"EdDSA","kid":"primer-salon-2026-08-prod","origin":"https://www.salontransilvania.ro","signedAt":"2026-08-31T20:51:51.492Z","canonicalization":"sha256-canonicaljson-v2","sig":"qA7Racaif_CMov0EaTz9Au1e9iVvRWJioYus-A9LTWH1xLD8zajlapyEq9JMTlACYtnwhK1voVS01NvQ7ZGEDQ"}}