Sinetti Admin Guideoperator prose · keep in the same commit as the product

Sinetti Admin Guide

Everything the Sinetti admin controls, from the one-page picture down to the exact command. Written for the person who holds the admin login, the deploy desk and the scheduled jobs.

0How to read this guide

The guide has three depths. Use the switch in the top bar; your choice is remembered.

DepthWhat you getReading time
SummaryThe whole-system schema and, for every area, a short In short box: what it is, why it exists, what you do there.≈ 10 minutes
How it worksAdds the mechanism for each area: a schema, the moving parts, the rules that bite, what the screens show.≈ 1 hour
ProceduresAdds step-by-step procedures with exact screens, buttons, commands, what you should see, and how to recover.Reference — open what you need

Marks used throughout: Screen a task done in the app's Admin menu · Desk a task done from a terminal on the operator machine · Gap something the product does not do yet, stated plainly so nobody assumes otherwise.

Names follow the project vocabulary: Sinetti admin = you (a login with the admin flag, plus access to the operator machine); customer = an organisation under a bilateral agreement; member = a customer's user; the app = the production Hugging Face Space; operator machine = the Windows PC that holds the repository, the deploy desk and the local scheduled tasks.

Where facts come from. Code wins. This file is the map; the runbooks under docs/ are the territory. An admin-behaviour change must update the matching runbook, this guide, and any in-app catalog/copy in the same commit (see .cursor/rules/admin-operator-docs-freshness.mdc). Numbers that live outside the repository (live job settings, secrets) are described by where to read them — Environment, Data ops, Sources freshness — never quoted from memory. A cheap mechanical checklist lives in tests/test_admin_operator_docs.py.

1The whole system in one page

In short

Three streams meet in one app: data (what customers see), code (what the app does) and people (who gets in). Each stream runs mostly by itself, and each has exactly one gate where nothing moves until the Sinetti admin decides: promote a data release, approve a deploy, grant or withdraw access. Everything else in this guide is detail about one of those three streams, or about watching them.

DATA — what customers see Public registersISCC · REDcert · INS… Refresh jobsnightly + weekly Candidateprivate dataset repo PROMOTE Active releasepointer + 2 previous fetch publish review activate serves Trust Review checks every candidate · advisory findings land in Admin → Data Trust CODE — what the app does main branchmerged work Pinned commiton the deploy desk Release gatefull tests · 45–90 min APPROVE Publishrebuild ≈ 8 min · smoke pin test passed live deploys PEOPLE — who gets in Agreementsigned bilaterally Organisationplan · seats · limits Invitemember sets password ACCESS Every requestplan · rate · budget configure invite login allowed checked Sinetti appHugging Face Spacelogin-gatedno anonymous view Persistent statecustomer work · history reads · writes
Three streams, one app. Warm pills are the only points where a human decision is required. Data refreshes itself but never reaches customers until promoted; code never reaches production without a passed gate and a recorded approval; nobody logs in without an organisation you configured. Customer work (watchlists, saved views, alerts, audit trails) lives in persistent state, apart from all three streams — which is why a deploy or a data promotion never touches it.
You decideProductionAutomatic step

The areas, and where each is explained

PEOPLE · 2

Logins, MFA and sessions

Who can sign in, how admins prove identity twice, how to cut a session off.

on event
PEOPLE · 3

Customers, plans and seats

Turn a signed agreement into an organisation with features, registers and seats.

per customer
PEOPLE · 4

Pause, offboard, delete

Four ways to withdraw access, from reversible to permanent.

on event
PEOPLE · 5

Limits: AI, rate, export

Monthly AI budgets in dollars; request-rate and CSV-export ceilings; the export audit.

monthly review
PEOPLE · 6

Customer isolation

What is private per member, shared per organisation, or global — and why admin cannot cross it.

know it
DATA · 7

Where data comes from

One nightly cloud job and a set of weekly tasks on the operator machine.

daily glance
DATA · 8

How data goes live

Candidate → release → active pointer. Promote, roll back, self-heal.

weekly-ish
DATA · 9

Is the data right?

Data ops briefing, Data Trust, Trust Review findings, the Admin QA inbox.

daily glance
DATA · 10

Curation

Material names, company identity merges, related companies, market templates, site footprints.

weekly
DATA · 11

Knowledge and law

The documents Ask can cite, source freshness, the regulatory change monitor.

weekly
DATA · 12

Email and Ask safety

What the app sends, how to preview it, and the abuse-incident inbox.

daily glance
APP · 13

Shipping a release

One resumable command: scope check → gate → approval → dry run → publish.

per release
APP · 14

Health and monitoring

Two health endpoints, the uptime probe, the monitors that email you.

when paged
APP · 15

State, backup, recovery

What is irreplaceable, where it lives, how to back it up and prove a restore.

daily 06:15, ran once 00:09

Your rhythm

WhenWhatWhere
Daily
≈ 5 min
Read the morning briefing: is each register ok, did queues move, any trust findings. Glance at the pending-access badge and open Ask-safety incidents. Read the admin daily brief email if it arrived.Admin → Data ops · Admin → Customers (badge) · Admin → Ask → Safety
Weekly
≈ 30 min
Confirm the weekly local tasks ran (nothing tagged overdue). Triage unresolved Trust Review findings. Mark new material names reviewed. Clear the regulatory review queue. Decide whether the newest data candidate deserves promotion.Admin → Data ops → Sources · Data Trust · Materials · Ask → Regulatory monitor · §8
MonthlyAccess review: every admin and every customer member still justified. AI spend against each agreement. Law-layer refresh result. Confirm backups exist and are recent.Access, Organisations (each organisation's Members and AI caps and the People table's AI spend) · §15
On eventNew customer → §3. Someone leaves → §4. New app version → §13. Alert email → §14. Something looks wrong → §19.
Five rules that outrank everything else.
  1. Serving data is released, never edited. No file copy into the app's data folder, ever; only promote and rollback.
  2. A receipt file is not proof of health. A job that was killed writes nothing, so yesterday's receipt still looks green. Check the age of the evidence.
  3. Offboarding is not deletion, and deletion is not reversible. Pick the weakest action that achieves the goal.
  4. Never roll the app back past a security control that has been used (session revocation, offboarding, purge). An older version would not enforce it.
  5. Mandates and consumption never ship. Every release passes -Exclude consumption,mandates; a refusal there is the system working.

2Logins, MFA and sessions

Where: Admin → Customers → Access tab · account menu → Settings → Security · Space secrets

In short

Nobody sees anything without a login, and nobody gets a login unless you created it. There are two kinds of account: a handful of owner-provisioned accounts defined in a Space secret (you, and any early user), and invited accounts that customers' members create from an emailed link. Admins must also pass a six-digit code sent to their email. A session lasts four hours, stays signed in when the browser window is closed, and can be cut off at any moment. If the login configuration is missing or broken, the app locks everyone out rather than letting anyone in.

The Sinetti app limits simultaneous password checks and rapid sign-in attempts. A caller asked to wait can retry shortly; five failed passwords still pause that account from the same network source for fifteen minutes. The Sinetti operator should check the auth runbook when several customers share a hosting proxy.

The Sinetti app keeps browser sessions in a protected cookie and checks browser writes against that session. Signing out or switching accounts clears private browser state. New password hashes use stronger protection; invited accounts upgrade on successful sign-in, while the Sinetti operator must replace older roster hashes in the account secret. A password reset also cancels pending sign-in steps. Company sign-in, when configured, must finish in the browser that started it.

Password5 misses → 15 min lock Admin account?flag on the user EMAILED CODE Sessionsigned · 4 hours yes no — members go straight to a session Every request re-checks the session signature valid · session not revoked · account active (not disabled / offboarded) · organisation trial or active · not expired
A session is not a one-time pass. Because every request re-checks account and organisation state, disabling a member or suspending an organisation takes effect on that person's very next click — there is no waiting for the four hours to run out.

Two kinds of account

Owner-provisioned (roster)Invited
Lives inThe Space secret SINETTI_USERS (a JSON list with password hashes, never passwords)The app's own database, created when the invitee sets a password
Created byYou, from the operator machine, followed by a Space restartYou send an invite (or approve an access request); the member does the rest
Can be adminYes — "admin": true in the recordNot through the app Gap — invites always create non-admin members
Forgot passwordNo self-service; you re-issue a hashSelf-service emailed link, valid 1 hour, single use; resets every open session
Use it forYourself and a second adminEvery customer member — this is the normal path

When the same person could match both, the invited account wins. A roster user who has no organisation is placed in the owner organisation at the next start — and the owner organisation sees everything. Never leave a customer's member there; move them to their own organisation immediately (§3).

Admin verification (emailed code)

Verified against docs/auth-runbook.md and the hosted marker probe, 25 September 2026. The code on main uses the emailed code. The hosted marker reported that day names aed958ce55bc396b2221458c70de896f3fb31d2d, which already contains that sign-in. The marker is the deployment script's reported source commit, not an independent runtime hash, and this guide has not watched a live administrator login.

An admin's password alone opens nothing. Sinetti emails a six-digit code and shows Check your email. The code lasts ten minutes, works once, and a newer code cancels it. A new code can be sent at most once a minute and five times an hour. Five wrong codes lock the step for fifteen minutes. The code goes to the account's email, else an email-shaped username, else SINETTI_ADMIN_EMAIL. Roster administrators in SINETTI_USERS should carry an "email" field. With no address anywhere, the screen says the code could not be sent. Mail uses the same path as password reset. If delivery is down, administrator sign-in is down until it is back; ordinary members are unaffected.

Sensitive actions — disable, offboard, reactivate, session revocation, permanent deletion — use the same code. The dialog offers Email me a code; enter it within five minutes. On the operator machine, scripts\run_fastapi_v2.ps1 sets SINETTI_LOCAL_ADMIN_MFA_BYPASS=1, so administrators sign in with the password alone. Quotes pasted around SINETTI_ADMIN_EMAIL are ignored.

Authenticator enrolment is the historical path. It applied before the emailed-code commit. On a build that reports marker aed958ce, do not scan a QR code or store recovery codes. scripts\break_glass_reset_admin_mfa.py resets that old factor only; it does not send an email code. A missing SINETTI_MFA_ENCRYPTION_KEY mattered for the authenticator. Do not delete it until an administrator has signed in with an emailed code on the build you are changing.

The app refuses any change that would leave zero active admins, and refuses self-demotion and self-offboarding. Keep two administrators, each with a working email. A sole administrator with no deliverable address has no in-app way in.

What fails closed

  • Missing SINETTI_SESSION_SECRET or an empty user list → nobody can log in. There is no anonymous mode to fall back to.
  • Two roster records with the same user_id → the whole roster is discarded, not just the duplicate. Validate the JSON before saving the secret.
  • A member whose organisation is suspended, offboarded or past its agreement end date is refused at login with “{organisation}'s access is paused — contact Sinetti.”
  • Login answers never reveal whether an account exists: every failure is the same “Invalid username or password.”

Procedures

Add an owner-provisioned (roster) userDesk · 10 min + Space restart
  1. On the operator machine, produce a password hash using a hidden prompt; keep the password out of shell history and process arguments.
    python -c "from getpass import getpass; from sinetti_services.user_directory import hash_password; print(hash_password(getpass('Password: ')))"
    one line starting pbkdf2_sha256$600000$…
  2. Add a record to the JSON list. user_id is the permanent key that owns the person's data — choose it once and never change it.
    {"user_id": "u_second_admin", "username": "jane", "password_pbkdf2": "pbkdf2_sha256$600000$…",
     "display_name": "Jane Doe", "admin": true, "email": "jane@example.com"}
  3. Check the whole list parses as JSON and that no user_id repeats.
  4. Save it in both places: the Space (Settings → Variables and secrets → SINETTI_USERS) and the operator machine's .env.
  5. Restart the Space. Hand over username and password by a channel other than email.
  6. If this is a customer's member rather than an admin, open Admin → Customers → Organisations, pick their organisation, open Members and AI caps and add their user_id — otherwise they sit in the all-seeing owner organisation.
Sign in as an admin (emailed code)Screen · 2 min
  1. Enter the administrator password. The screen shows Check your email. No session exists yet.
  2. Enter the six-digit code. Expect the app. A code older than ten minutes, already used, or replaced by a newer send is refused.
  3. If nothing arrives: confirm the roster record has "email", that SINETTI_ADMIN_EMAIL is set, and that the configured Infomaniak or customer SMTP transport delivers mail (§12). The server log line is email_code_mail_failed. Production and private installations fail closed on unsent mail and never log codes, even with local SQLite storage. Customer SMTP requires authenticated STARTTLS with a verified certificate; the customer platform team mounts the password secret and, when needed, its private CA as described in the auth runbook.
Historical authenticator enrolment. Builds from before the emailed-code commit asked for a QR code, a first confirmation, and ten recovery codes. Do not follow those steps on a build that reports marker aed958ce.
An administrator cannot get inScreen / Desk
  1. After the emailed-code release is deployed: there is no authenticator to reset. Put a reachable address on the roster record (or fix SINETTI_ADMIN_EMAIL), restart the Space, and sign in again. Five wrong codes still lock the step for fifteen minutes — wait it out.
  2. Historical authenticator lockout (builds from before emailed codes): the locked-out admin uses a recovery code, then re-enrols. Otherwise a second admin opens Admin → Customers → Access → Users → More → Reset MFA, types a reason, confirms, and enters their own code. That control does not send an email code.
    “Account state updated.” The person enrols afresh at next login.
  3. Sole administrator locked out on production (lost phone and lost recovery codes): from the operator machine, dry-run then apply. Expect about five minutes of downtime. The script pauses the Space, keeps an untouched backup under %LOCALAPPDATA%\Sinetti\break-glass\<timestamp>\, resets the factor, uploads, checks the upload byte for byte, and restarts even when a step fails. It refuses during the nightly window (00:00–02:00 UTC) and while any Hugging Face job is running.
    python scripts\break_glass_reset_admin_mfa.py --user-id u_admin
    python scripts\break_glass_reset_admin_mfa.py --user-id u_admin --apply
    the next password login shows the enrolment screen. First used 19 September 2026.
Cut off sessions — one person, yourself, or everyoneScreen / Desk
  1. One member: Admin → Customers → Access → Users → More → Revoke sessions → reason → Confirm session revocation. Their next click lands on the login page.
  2. Your own other devices: Settings → Security → Revoke on one device, or Revoke other sessions.
  3. Everyone, immediately (suspected compromise): replace the Space secret SINETTI_SESSION_SECRET with a new long random string and restart the Space. Every existing token stops verifying. Everyone logs in again; nothing else is lost.
Change or reset a passwordScreen / Desk
  1. Invited member: they press Forgot password? on the login page. The confirmation text is identical whether or not the address exists. The emailed link lasts one hour; using it logs out all their sessions. There is deliberately no admin screen that reveals a reset link — if mail is broken, fix mail (§12).
  2. Roster user: hash a new password (as in Add a roster user), replace password_pbkdf2 in the secret, restart the Space. Gap Nothing in the roster path is known to end their open sessions, so revoke them as well to be sure.
Rotate the MFA encryption keyDesk · production, until emailed codes are deployed

This still matters on production while authenticator factors are stored. No automated command exists. Back up the identity store and note the current key version, deploy code able to read the old key while writing the new, re-encrypt every stored authenticator, confirm each admin can still log in and /api/ready is ready, and only then retire the old key. Pasting a new key into the secret locks every admin out. After the emailed-code release is live, login no longer reads those factors; leave the key in place until that sign-in has been proved, then treat leftover auth_mfa_* rows as historical.

3Customers: organisations, plans and seats

Where: Admin → Customers (tabs Organisations, Plans and Access)

Admin → Customers → Email delivery answers “did my customers get mail?” without opening the database: emails sent in the last 7 days (with how many failed), members with a verified address out of all members, Market lists with a daily summary switched on, and customer alert attempts recorded. Below sit the most recent emails (date, kind, masked recipient). A row marked Not sent failed at the mail provider; the next daily run retries it. If members have no verified address, ask them to open Settings → Notifications and confirm it with the code, or invite them so the sign-in address is proven. The screen is read-only.

On a private installation (Docker), the server creates no Market list alerts and sends no daily summaries: change alerts come from Sinetti's own nightly data refresh, which does not run there yet. Email delivery therefore shows how many members have a verified address and lists what the installation does email (sign-in and security codes, address-confirmation codes, access requests and user reports to the administrator address). It shows no sent totals or history, because no list mail exists. Market monitoring on that installation says the same: monitors are saved lists, the Inbox stays empty, and no email option is offered. The hosted app is unchanged.

Contracts for a customer deal live next door in Admin → Customers → Legal: the Data Processing Agreement, privacy policy, terms of service and the counsel packet, each shown straight from docs/legal/ with a Download .md button for sending to counsel or a customer. All four are DRAFTS until counsel has reviewed them; edit the files in git, never on the page.

In short

Sinetti takes no online payment. A customer signs a bilateral agreement; you then mirror that agreement in the app as an organisation holding a plan. The plan decides which features are unlocked, which registers are visible and the monthly AI budget; the organisation carries the number of seats. Each member belongs to exactly one organisation. Nothing on the Organisations or Plans tab bills anybody; they only mirror the agreement.

Organisations is a list on the left (Everyone, then Customers, then Staff — the owner organisation) and the selected organisation on the right: plan, seats, contract, and the people in it with how they joined, when they were last seen (Zurich time) and what they spent on AI this month. A box at the top, Needs a decision, appears only when something does: people who joined through an access request naming a company but sit in the staff organisation, or a customer organisation with no members or on Trial with no contract end. Everyone shows all people grouped by organisation, with filters. The list folds to a rail of initials with the button at its top; the browser remembers that. Plans shows every plan as a column so they compare side by side. The address /admin/billing?org=<org_id> opens one organisation.

Plan (template) features: DD · Company research ·CSV export · PDF deep-dives Market balances · Batch check registers: ISCC · REDcert · INS · …AI budgets: chat · narrative · research Organisation (the customer) status: trial · active · suspended · offboardedseats: the live ceiling on membersagreement period end (optional) Members each in exactly one organisationseat order = order they were addedadding elsewhere moves them one plan belong to What a member can do right nowunlocked features · visible registers · seat granted at login · AI budget · rate & export limits recomputed after every admin change
The plan is a reusable template; the organisation is the customer. Seats live on the organisation — a plan's “seat limit” is only the default copied when the plan is first applied, so to sell two more seats you edit the organisation, not the plan.

What members choose for themselves on first login

The first time a member opens Sinetti they see a three-step welcome. It asks their work (sourcing and trading, compliance, certification body, market analysis, or something else), which materials and regions they follow, and which tour to start. Their answers change three things and nothing else: the questions Ask Sinetti suggests, one Market monitoring list called My materials, and, only if they tick the box, an email when that list changes. Nobody is sent mail unless they choose it. Every member lands on the same Home page.

Where to see it: Admin → Customers → Organisations → the organisation → Members and AI caps: the table has a Work column, “—” for a member who has not chosen. Members change their own answer in Settings → Your work. Admin accounts never see the welcome. There is nothing for you to configure, and nothing to recover: if a member's role is wrong, they edit it themselves.

The eight features a plan can unlock

In the plan editorWhat the member gains
Counterparty DDThe due-diligence profile on a holder
Company researchOnline business-footprint research (spends AI + search budget)
CSV ExportDownloading filtered results as a file (still subject to export limits, §5)
PDF Deep-DivesThe parsed-certificate deep-dive views
Market BalancesThe market-balances add-on. Not part of either seeded plan, and its code is never deployed (§13)
Batch checkCheck an official ISCC or REDcert sustainability declaration, in Excel or as a text-layer PDF, a Nabisy proof of sustainability PDF, or a Nabisy account-statement Excel. A single declaration or proof is one cargo. An account statement is one verdict per row: the scheme column is a BLE code, so the certificate line misses unless the row also carries an ISCC or REDcert certificate number. The REDcert raw-materials declaration has no single gCO2eq/MJ total, so that line stays a miss until a figure is printed. A Nabisy proof adds the country printed on the form, or the on-site-inspection consent on a downloaded Teilnachweis, judged against the BLE list (Stand 2026-08). The account statement uses Anbauland, because that export does not record inspection consent. Scanned PDFs are refused. Off on the seeded plans. Tick it on the agreement’s plan. Set SINETTI_BATCH_CHECK_ENABLED=0 to leave the add-on out of a deployment. The file is read for that organisation and is not stored.
Research dataResearch-derived data next to the certificates: research-confirmed related companies, the ownership line, Production units, research nameplate capacities, researched plants on the Producers map and list, Company research. On every plan by default (existing plans received it once on 29 September 2026). Untick it to make that customer certificates-only. On Admin → Customers → Plans, every plan without it, and on Organisations every customer organisation on such a plan, carries a Certificates only badge (no badge when the plan is unknown, or for the owner organisation). The global research data switch on Research → Coverage still overrides every plan. Takes effect on the member's next page load
Plant tableThe third Producers panel next to List and Map: one row per researched plant plus one row per certified producer not researched yet, sortable and filterable by column. Needs Research data: the plan editor disables the box without it and the server refuses the combination. Not part of either seeded plan; the owner organisation always has it. Without it the Producers page shows List and Map only. No download

Verified against the plan-editor feature keys, 17 October 2026. Batch check stays off the seeded plans. Until a plan includes it, the nav item is absent and a direct link does not open a locked page.

A member who opens a locked feature sees a polite in-place message — “{Feature} is not included in your plan — contact your administrator.” — never an error page. Registers outside the plan simply do not exist for that member: certificates, counts, maps and exports are all filtered at one choke point.

Seeded plans and the owner organisation

PlanFeaturesRegistersSeats (default)AI $/month (chat · narrative · research)
trialPDF Deep-DivesISCC, REDcert, INS, 2BSvs, RSB22 · 1 · 1
design_partnerDD, Company research, CSV Export, PDF Deep-DivesISCC, REDcert, INS, 2BSvs, RSB525 · 10 · 15

These are starting points, not commercial truth: the signed agreement is authoritative and you shape a plan to match it. The special organisation owner ignores plans entirely — all features, all registers — but it does not bypass AI or rate metering; only the admin flag on the person does that.

Rules that surprise people

  • Seats are checked when you add people, and again at login. Saving a members list or sending an invite that would take an organisation past its seats (members plus open invites) is refused with a message naming the seat count; raise the seats, remove a member or revoke an invite first. Removing members from an already over-seated organisation still works. At login the arrival order still applies: if an organisation has 5 seats and 6 members, the sixth-added member is refused with “All 5 seats for {organisation} are in use — contact your administrator.”
  • Members are picked from known users. The drawer shows one row per member (name and user id, self-declared work, AI spend against each cap) under “Members · N of S seats used”. The cross at the end of a row removes that member, Add member offers active known users who are not members yet, and Save stores the list exactly as shown. A row in red reading “not a known user” is a member id with no login behind it (a typo or a deleted login); remove it. New people join through Access → Invite, not here. Removing a member ends their access at once; their private work stays stored under the old organisation.
  • The contract end date stops sign-in. Contract ends in the organisation's edit panel (Rename or Change plan; blank = no end date) is the last day members can sign in; from the next day (UTC) login answers “{organisation}'s access is paused — contact Sinetti.” The organisation list shows “until 31 Dec 2026”, the Contract tile reads “Until 31 Dec 2026”, and a passed date turns the status pill to Expired (never for the owner organisation, which login does not check). Changing the plan keeps the date; clear the field and Save to remove it. Sessions already open are not cut at midnight; suspend the organisation for that.
  • Adding someone who belongs elsewhere moves them, silently, and they lose sight of their previous organisation's private work (§6). Today a move is “remove from one organisation, add to the other” in Members and AI caps; the one-step Move to organisation action (which also re-stamps AI budgets and writes a receipt) is announced on the People table but not built yet.
  • AI budgets follow the plan. They are stamped onto members when the plan is assigned, and Save plan with changed AI budgets re-stamps every member of every organisation on that plan, except a member whose cap you typed by hand in the organisation's Members and AI caps section (§5). A member added later has no plan budget until the organisation is saved with its plan again or the plan's budgets change.
  • An invite that names a new organisation creates it with 5 seats and status active — not trial — on the plan picked in the invite bar. An organisation created from the Organisations tab (+ New organisation) starts as trial.
  • An invite or an approved request never changes an existing organisation's plan. When the organisation id is already known, the invite bar shows its current plan as read-only text (“{plan} · unchanged”) with a Change in Organisations link; sending the invite, redeeming it and approving a request all leave the plan, seats and AI budgets as they are. Change a plan only in Organisations (Change plan). The new member receives that organisation's plan AI budgets when they set their password.
  • Regulatory registers are a deliberate grant. The plan editor has two register groups: Certification schemes (ISCC, REDcert, INS, 2BSvs, RSB) and Regulatory registers (LCFS, EPA, CFR, Oregon, BC). The seeded plans stay certification-only; tick a regulatory register on the agreement's plan to show it. RenovaBio has no box: it is added automatically to a plan that holds LCFS, CFR, Oregon and BC, and the line under the boxes says whether this plan includes it.
  • Gap The plan editor validates feature and register names (an unknown name is refused with “unknown scheme key”), but not AI-budget names typed through the API. Through the screen you pick from boxes, so this only matters for scripted changes.

The customer edition

Customer administrator recovery (SIN-0909 baseline). The customer platform team appoints the initial administrator through customer-edition installation and bootstrap. The Sinetti app retains that user's organisation-scoped authority in /state/customer-admin.json, includes it in customer-state backup, and checks it against the restored database and retained installation identity before publishing a restore. An exact bootstrap repeat preserves credentials and authority; missing or different authority on an initialized account is refused. The customer platform team retains edition.json, installation configuration and recovery secrets separately. This baseline still requires its exact-image and customer-host acceptance; see the private installation runbook.

Customer administrator controls. The Sinetti app checks the protected customer-side appointment on every session probe; a database operator flag does not grant customer administrator access. The customer Access page lists the appointed organisation's members and invitations. Designated administrator peers are protected; ordinary-member actions follow their current account state. Customer invitations use an email address and name, and customer reactivation retains the appointed organisation. Environment exposes only the expiring-soon window. An action requesting fresh verification uses the account's emailed code and retries the captured action once; leaving or cancelling the verification clears the pending action. Billing, plans, operator requests, permanent deletion, models and colour administration are outside these customer controls.

The customer edition is the reduced build installed on a customer-controlled server (Sinetti Private, Docker). The full edition keeps every module; the customer edition leaves the surfaces below out of its registration and its browser. Nothing is deleted from the code base, and a trimmed surface can return later as a released result.

  • What the customer gets. Browse, Verify, holder pages, watchlists and saved views, maps and Certified Sites with capacities, feedstock and fuel deep-dives, audit facts, certificate PDFs through live links, login with an e-mailed code, and customer-side organisation administration (access, setup, e-mail delivery) — for ISCC and REDcert only.
  • What the customer does not get. Counterparty Profile, CB reputation and the CB deep-dive, Business Footprint and DD history, Ask, every other admin screen, operator monitoring (change monitor, pending release, source freshness, refresh plan, job health, data ops), consumption, mandates and market balances, and every other register: INS, 2BSvs, RSB, RenovaBio, LCFS, EPA, CFR, Oregon, BC and NEa.
  • How the browser follows. GET /api/capabilities carries an edition block on the customer edition only: the name, the two schemes it serves and the list of absent surfaces. The browser narrows the plan's registers to those two schemes and closes the Ask and market-balances switches from it. The full edition sends no edition block and behaves as before.
  • The block hides; it does not enforce. Plan scope still filters every certificate, count, map and export on the server, and the customer build omits the trimmed routes from its registration. Set the customer's plan to ISCC and REDcert only (§3, Procedures).
  • The browser follows the block in these places: the masthead drops Monitoring, Certification Bodies and every Admin entry except Environment (setup) and Customers (access); those routes send the visitor to Browse; the holder page drops the Scheme risk block, the company research button, the Research status line and the Dutch REV account line; the High risk chip is gone from Browse cards, Verify, certificate pages and Geography (the register's own ISCC blocklist signal stays); the Browse cards drop the Company research button and the Also.. (Counterparty Fit) link and drawer, and Geography drops its Company research panel; no freshness banner and no Source dates link appear, and the Browse export dialog leaves out the source date line, because the monitoring routes are not served; Ask and the Consumption entries are closed. The register-derived decision summary on the holder page stays, and so does the Alerts entry, because the watchlist Lists live there.
  • The scheme chips follow the served data. The Scheme group in the Browse filter drawer lists the registers present in the rows the server returns, so on an installation whose data and plan are ISCC and REDcert only it shows those two. Verified in the built bundle on 23 October 2026 on a desk that still holds every register: all registers listed, because nothing is filtered there. Gap No customer data candidate exists on the operator machine yet, so the two-chip result has not been seen end to end; check it on the first customer installation. Browse also still makes one request to the unserved freshness route on page load; it answers 404 and nothing shows.

Procedures

Onboard a customer, end to endScreen · 20 min
  1. Before touching the app, run the two-customer acceptance gate if this is the first real customer or the first since a security-relevant release (procedure in §15).
  2. Shape the plan. Admin → Customers → Plans. Reuse design_partner (Edit under its name), or press + New plan: give it an ID and name, tick features, certification schemes and any regulatory registers the agreement covers, enter AI budgets (blank = unlimited), Save plan.
  3. Invite the first member. Access tab → Invite: email, name, an organisation id (type a new one, e.g. acme; a New org name field and the Plan list appear), pick the plan, Send invite. The plan is applied only because the organisation is new.
    “Invite created”. If the note says email dry-run, no email left the building — copy the set-password link shown and fix mail (§12).
  4. Set the seats and the contract end. Organisations tab → the new organisation → Change plan → Seats = the number in the agreement, Contract ends = the agreement's last day (blank when it has none) → Save.
  5. Invite the remaining members with the same organisation id. Invites last 7 days; Resend issues a fresh link, Revoke kills one.
  6. Set limits if the agreement differs from defaults: AI caps and request/export ceilings (§5).
  7. Verify as the customer. Log in as a non-admin member of that organisation (not with your admin account — admins bypass metering and see admin chrome). Check: only agreed registers appear, locked features show the plan message, an export works, another organisation's saved views are absent.
  8. Record it outside the app. The agreement reference, who provisioned, and when, go in your contract records. Sinetti stores no contract, price or payment detail by design.
Approve or reject an access requestScreen · 2 min
  1. A badge on Admin → Customers shows pending requests (from the public request form). Open the Access tab → Requests & invites.
  2. Set the organisation in the Invite form above the table first — the approval uses it. The plan from that form applies only if the approval creates a new organisation; for an existing organisation the form shows its current plan as unchanged and the approval leaves it alone.
  3. Press Approve (an invite is issued to the requester) or Reject.
Approving is a commercial act: it creates access under a plan. Reject anything without an agreement behind it.
Change a customer's plan, seats or membersScreen · 2 min
  1. Admin → Customers → Organisations → the organisation → Rename or Change plan.
  2. Change Plan, Seats, Status or Contract ends, and Save in the panel. To edit members, open Members and AI caps: the cross at the end of a row removes a member, Add member → Add adds a known user, and nothing is stored until Save members. (An AI cap typed in the same table is the exception: it saves on leaving the field.)
  3. Save. The change applies on each member's next request. Assigning a plan also re-stamps AI budgets on current members.
  4. After adding members to an existing customer, re-select the plan and save once more so the newcomers receive AI budgets.

4Pause, offboard, delete

Where: Admin → Customers → Access → Users (people) · Admin → Customers → Organisations (organisations)

In short

There are four strengths of “stop”. Disable a person or suspend an organisation to pause — memberships stay intact and one click resumes. Offboard when the relationship has ended — access is quarantined, every session dies, but all of the customer's work is kept. Permanently delete only after offboarding, only after a verified backup, and only when you mean it: it cannot be undone. Every one of these leaves a receipt naming who acted and when. All but suspending an organisation also ask for a typed reason and a fresh emailed verification code. On production, until the emailed-code release is deployed, that code is still the authenticator.

Activenormal use Pausedperson: Disabledorganisation: Suspended Offboardedmembership + plan quarantinedall customer work kept Permanently deletedprivate records gone pauseresume offboardreactivate purgeno way back Every arrow: typed reason · explicit confirmation · emailed code from the last 5 minutes · security event + ownership receipt. Refused outright: acting on yourself or your own organisation · removing the last active admin · reactivating into an organisation with no free seat.
Pick the leftmost state that achieves the goal. Pausing is for a late invoice or a security doubt; offboarding is for the end of an agreement; deletion is for a contractual or legal erasure request.
ActionUse whenEffect on accessEffect on their dataUndo
Disable a personOne person must stop; their place in the organisation staysRefused on next request; sessions revokedNoneReactivate
Suspend an organisationCommercial or operational pauseAll members refused at login and on next requestNone; memberships and plan stay liveReactivate suspension
Offboard a personThey left the customerAccount quarantined, membership removed, sessions revokedKeptReactivate, choosing a target organisation
Offboard an organisationThe agreement endedRoster and plan moved to quarantine; every member session revokedKeptReactivate on the organisation — restores roster and plan together
Permanently deleteErasure is required—Every private record in every registered category removed; shared register data untouched; a minimal receipt keptNone
Do not “fix” an offboarded organisation with the ordinary editor. Changing status, plan or members on an offboarded organisation is refused (“Use the dedicated organisation reactivation route”). The Reactivate action checks seat capacity and restores plan and roster in one step. Old sessions never come back; everyone logs in again.
Rollback boundary. Once anyone has been offboarded or purged in production, an app version that predates these controls must never be deployed: it would ignore the quarantine and hand access back. The release gate carries a dedicated stage (“identity and offboarding safety acceptance”) for this reason.

Procedures

Disable, offboard or reactivate a personScreen · 2 min
  1. Admin → Customers → Access → Users. The row button is Disable (or Reactivate); More opens Revoke sessions, Reset MFA, Offboard.
  2. Choose the action, type a reason (3+ characters — write something a stranger would understand in a year), confirm.
  3. If prompted, press Email me a code, enter it, and press Verify and retry. On production, until the emailed-code release is deployed, this is still the authenticator code.
    “Account state updated. Event n · k sessions revoked.” Ask the person to refresh: they must land on the login page.
  4. To reactivate an offboarded person, pick the Target organisation in the dialog. Refused if that organisation has no free seat.
Roster accounts (defined in the Space secret) cannot be managed from this screen; the refusal message is deliberately generic. Remove or edit them in the secret instead.
Suspend, offboard or reactivate an organisationScreen · 2 min
  1. Admin → Customers → Organisations → the organisation → Suspend or offboard.
  2. Suspend pauses at once; Reactivate suspension resumes. Neither asks for more. Every status change from here or the edit panel's Status list is recorded like offboarding: the organisation keeps who changed it, when and “Status trial → suspended in Admin → Billing”, and an ownership receipt repeats it.
  3. Offboard opens the reason dialog → Confirm offboarding → emailed code if asked (authenticator on production until that release is deployed).
    Status offboarded; an event number and the count of revoked sessions.
  4. To bring the customer back: Reactivate → Confirm reactivation. Roster and plan return together.
  5. Note the event number in your own records. Never copy credentials or customer content there.
Permanently delete a customer's or a person's dataScreen · irreversible
This destroys data and cannot be undone. It is available only for an already-offboarded organisation or person, and only when the operator machine has a backup location configured (SINETTI_BACKUP_DIR).
  1. Open the offboarded organisation (Organisations → the organisation → Suspend or offboard) or person (Access → Users → More) and choose Permanently delete.
  2. Preview permanent deletion — read the per-category counts. The preview expires; if anything changes meanwhile the app demands Refresh preview.
  3. Create and verify backup — wait for “Backup verified.” A stale preview invalidates the backup too, by design.
  4. Type the exact identifier shown to confirm, then Delete permanently. Enter the emailed code if asked (authenticator on production until that release is deployed).
  5. Deletion runs category by category under a ten-minute execution lease. The worker renews it while it is alive. If the browser closes mid-way, reopen the same panel: Resume deletion continues the same request. If the worker was killed, wait about ten minutes for the lease to lapse, then resume. A message execution lease is no longer owned means another operator holds it, or the lease lapsed during a real stall — do not start a second purge of the same subject.
    A receipt: completion time, administrator, backups verified. The receipt names the subject only as a one-way digest.

5Limits: AI spend, request rate, exports

Where: Admin → Customers → Organisations (the People table's AI spend column, each organisation's Members and AI caps and Data access limits) and Plans (Default AI caps under the table)

In short

Two meters protect the business. The AI meter prices every assistant answer, narrative rewrite and company-research run in real dollars and stops a member at their monthly cap. The data-access meter caps how fast anyone can page through the data and how many rows they may export per day and per month — the practical defence against scraping, in place of a captcha. Both follow the same rule: a personal setting beats the default, the default beats the built-in value. Both refuse when in doubt; admins bypass both but are still recorded.

1 · Personal settingthis member (or this organisation) 2 · Default rowshown as “*” — applies to everyone 3 · Built-in valuein code — last resort none set none set A setting that exists but is blank means “unlimited” and still wins — blank is a decision, not an absence.
The first box that holds a setting decides. This is why a cap you typed by hand on one member survives every later plan change: a hand-typed value is a personal setting, and plan budgets never overwrite those.

AI meter

Metered featureWhat spendsBuilt-in capWhat the member reads at the cap
ChatAsk Sinetti answers$5.00 / month“Monthly Ask Sinetti allowance used - resets date.”
NarrativeAI rewrite of narratives$2.00 / month“Monthly narrative-refinement allowance used; resets date.”
ResearchCompany research (model + web search at $0.005 per query)$3.00 / month“Monthly Company research allowance used - resets date.”
  • The month is the calendar month in UTC; caps reset on the 1st. A warning state begins at 80 % of the cap.
  • Before calling the model, the app reserves the worst-case cost; afterwards it records the exact cost and releases the rest. If the reservation cannot be made — no identity, storage trouble, cap reached — the call never happens. You are never billed for a request the meter could not see.
  • Prices per model are constants in the code. A model with no price is refused rather than metered at zero. Review the table against the provider's current prices when you change models.
  • Plan budgets are stamped on members at plan assignment, and again on every member of the plan's organisations when you change the plan's AI budgets; they are marked as plan-owned. A cap you type by hand is marked as yours and is never overwritten by a plan again. Gap No screen hands a member back to plan control.
  • The Customers table's AI spend column reads this month's spend for the whole customer against the sum of its members' caps (personal cap, else the default). When any member's cap is unlimited, the column shows the spend alone.

Data-access meter

LimitBuilt-in valueNotes
Requests per 5 minutes — per member2 000An older installation may still hold 120 in its default row; check the screen, not this table
Requests per 5 minutes — per organisation10 000All members together
Export rows per day5 000
Export rows per month20 000
Rows in a single file5 000Hard ceiling. Periodic unlimited lifts the daily/monthly/rate allowances, never this

A member over a limit sees only “We couldn't complete this action. Please try again later or contact support.” — no numbers, no countdown, no headers. The limits are invisible on purpose: a scraper learns nothing about where the edge is.

Every export attempt — issued, refused or failed — is written to the export audit: who, when, which filters, how many rows, a fingerprint of the exact selection. Never the rows themselves. Audit entries are kept 365 days; limit changes are logged with before and after values.

Procedures

Set or lift an AI capScreen · 1 min
  1. Admin → Customers → Organisations → the person's organisation → Members and AI caps. The table shows each member's spend and an editable cap per feature.
  2. Type a dollar amount in the person's cell and click away — it saves on leaving the field, without Save. Blank on a person = follow the default. The default lives in Default AI caps under the Plans table (Plans tab); blank there = unlimited for everyone without a personal cap.
  3. For invited members, set budgets through their plan instead (Plans → Edit → AI budgets → Save plan): the new budgets reach every current member of the plan's organisations, except members with a hand-typed cap.
Set request and export limits for a customer or a memberScreen · 2 min
  1. Admin → Customers → Organisations → the organisation → Data access limits.
  2. Under Organisation limits, fill Requests / 5 min, Daily export rows, Monthly export rows (blank = inherit) or tick Periodic unlimited → Save limits. Reset inheritance returns to defaults.
  3. For one member, press Override in the members table and do the same.
  4. The usage line (“Used this month: …”) tells you whether a complaint of “it stopped working” is a limit.
Investigate an exportScreen · 5 min
  1. Admin → Customers → Organisations → the organisation → Data access limits → open Export audit — n events.
  2. Read When · User · Outcome · Rows · Reason · Filename; page with Older / Newer.
  3. A run of refused with a rate reason from one member is the signature of automated harvesting: consider Disable while you ask the customer.

6Customer isolation

In short

Register data is shared by everyone — it is public information, enriched. What customers do with it is not shared. Personal work belongs to a member inside their organisation; research belongs to the organisation; nothing crosses between customers, and the admin flag does not open another customer's private research. The browser never says which organisation it belongs to — the server works it out from the login — so there is nothing for anyone to forge.

ScopeWhat lives thereWho can see it
Private to a member
(organisation + person)
Watchlist pins, saved views, alert dismissals, notification preferences and delivery recordsThat member only — not colleagues in the same organisation
Shared in an organisationCounterparty DD history; company-research briefs and their evidenceEvery member of that organisation
GlobalCertificates, pathways, alerts raised from register changes, the cache of public company-registry lookupsEveryone, filtered by their plan's registers
  • Moving a member between organisations does not move their work. They start empty in the new one; if they return, the old work reappears. No screen copies records across.
  • No organisation, no access. A login with no active organisation cannot hold a session; private features answer tenant_required.
  • Rows from before isolation existed that could not be attributed to anyone are parked in a quarantine that no login can reach. They are kept, not served.
  • Company research shipped with the app is a read-only seed used only while an organisation has no research of its own. One customer's research is never a fallback for another.

The two-customer acceptance gate (§15) proves these rules on two throwaway organisations. Run it before each real customer.

7Where data comes from

Where: Admin → Data ops (tabs Refresh · Sources) · Hugging Face Jobs · Windows Task Scheduler on the operator machine

In short

Register data is collected by machines, not by you. One nightly cloud job refreshes ISCC and REDcert (2BSvs and RSB join it once the job is re-registered with them — see Activating 2BSvs and RSB in docs/daily-refresh-runbook.md); a handful of weekly tasks on the operator machine refresh the registers that block cloud addresses or change slowly (INS, the North-American registers, the Dutch account list). Each run validates what it fetched, rebuilds the app database, has it reviewed, and publishes a candidate to a private dataset repository. Your job is to notice when a run did not happen — and a run that was killed leaves no trace except silence.

CLOUD — every night ISCC · REDcertpublic register downloads Nightly refresh jobHugging Face · 00:00 UTC · plans for 3 h 30, hard stop 4 h 30 fetch OPERATOR MACHINE — every week INS · LCFS/EPA · CFROregon/BC · NEa Weekly scheduled tasksWindows Task Scheduler · PC must be on fetch INS is local because the Italian register refuses cloud addresses (verified) — not a bug to fix. Private dataset repository release channel: one atomic commitwith a checksum manifest (≤ 1 GB) side channel: hosted REDcert PDFs(unbounded, own commits) = the newest candidatenot yet what customers see review · publish same steps
Two origins, one destination. Every run ends by publishing a complete candidate release; nothing a refresh job does changes what customers see. That happens only in §8.

What one nightly run does, in order

  1. Hydrate the last verified release into a throwaway workspace (the job never works on the app's own data).
  2. Refresh ISCC (certificates, compliance list, blocklist) and REDcert. Each source must pass a contract check — expected columns, plausible row count, parseable dates. A source that fails keeps yesterday's data (kept_previous) and the run is marked partial; one bad source never poisons the rest. Every ISCC certificate without a PDF link gets one here: withdrawn and suspended certificates take the link from the compliance block; the rest cost one register search each, newest in-force first, capped by ISCC_PDF_LINK_MAX_LOOKUPS (default 500). The map rides the data release. Receipt key refresh.ISCC.pdf_links. A miss is retried the next night. The PDF queue only takes certificates that already have a link. Hand backlog: python scripts\resolve_iscc_pdfs.py.
  3. Rebuild the app database from that workspace (including whatever PDF artifacts hydrate already had).
  4. Trust Review that candidate (§9).
  5. Publish a register candidate to the dataset repository and restart the app so it notices it. A receipt and job_heartbeat.json are flushed after this step. Hugging Face SIGKILL cannot write a final receipt; the heartbeat is the in-run pulse. A running receipt is not a successful night.
  6. Optional tail — geocode, certificate PDFs, ISCC audit reports, REDcert PDF hosting — only if time remains (the process keeps a 75-minute reserve for the second rebuild, review, publish and mail). New tail work is not started inside that reserve. Certificate PDFs take new documents first. The 1,200-PDF cap is split evenly across schemes that still have candidates (ISCC and REDcert on this job); unused share spills back, so a straight ISCC-first prefix cannot leave REDcert with an empty night. Audit reports take known-template and URL-identity repairs first, then new documents. In both stages, previous failed parses may use at most 20% of the cap. Parser-version repairs do not count against that retry share. A cap of 0 skips that stage.
  7. Second publish if the tail changed artifacts and time remains. If the tail is killed, the register candidate from the first publish stays.
The job's settings are frozen at registration. Schedule, caps, timeout and register list are baked into the scheduled job when it is registered; editing a file or a Space variable changes nothing. To change any of them you register the job again — and then make sure the old one is no longer active, because exactly one nightly job may run. As registered on 24 September 2026: certificate PDFs 1,200/night, audit reports 100/night, REDcert PDF hosting 300/night, timeout 4 hours, hardware cpu-upgrade (8 vCPU, 32 GB). Re-registered 29 September 2026: the job plans against a 3 h 30 budget and the hard stop moved to 4 h 30, so the stop only fires on a stuck run. The previous nightly was 100 PDFs and 2 hours on cpu-basic (16 GB); that machine ran out of memory when PDF work ran long. Read the live values from the active job, not from this page.

Verified against the nightly PDF retry policy, the registered nightly job settings, and the INS weekly task registration (register_ins_weekly_task.ps1: caps 1,200, four-hour cutoff), 25 September 2026.

The nightly job downloads the Space code revision at the start of its bootstrap and records it as config.space_revision on last_daily_refresh.json. The existing Data ops last-run caption shows its first ten characters; Space unknown means the receipt has no usable recorded revision, including older receipts, malformed values and private code rehearsals. A parser fix on the operator machine runs in the cloud only after that code is deployed. The revision is the code the job downloaded, not the dataset release or proof that the app has activated it. After deploying this receipt change, the Sinetti operator must re-register the nightly job once to install the new bootstrap, then check its next receipt; until then the revision stays unknown. A job error or missing receipt still requires checking the true job status on Hugging Face.

Receipt and caption behavior verified against local bootstrap, receipt and Data ops tests, 9 October 2026. Deployment and job re-registration remain separate operator actions.

Pending certificate PDFs (policy approved 10 October 2026). Once compatible code is deployed, a positively diagnosed scanned certificate PDF or a REDcert document positively confirmed unavailable may remain pending during otherwise checked publication. Pending documents remain in the backlog and failed/remaining counts; they are not successful parses. The Sinetti operator must inspect the per-document evidence and the final activation receipt. Missing or malformed evidence, unknown errors, identity or date conflicts, refused replacements, lost material evidence, audit failures, worker failures and failed quality or Trust Review still hold publication of that night's PDF results. Since 23 October 2026 (SIN-0904) they no longer hold the night's register data: the nightly activates its register-only commit, and the activation receipt shows tier "core". A PDF left for the next night because time ran out is not a hold. A hold requires investigation or correction, never a weaker check. This local policy approval does not authorize deployment, another paid run or a schedule change. See the daily refresh runbook.

Preparing an additional PDF drain

The Sinetti operator can prepare a bounded proposal with scripts/prepare_pdf_drain.py; the detailed procedure is in docs/pdf-drain-runbook.md. Preparation is local-only: it starts no job, enables no schedule and reads no credentials. It does not change the nightly settings or enable automatic promotion. Recurring drain scheduling stays off.

Two authorised publishing trials finished on 25 September 2026. Both published a reviewed core revision and a reviewed tail revision, left auto-activation off, and kept the ISCC, REDcert and INS register files unchanged. The first selected 50 certificates and added no new parses. The second, on the repaired parser, selected 100: 79 were re-reads that stayed parsed, and 4 previously unsupported certificates became parsed (349 pathway rows). Sixteen stayed unsuccessful. Receipts, durations and costs are in docs/pdf-drain-runbook.md. Another paid run needs a new explicit approval with an exact job count and cost ceiling. Do not promote while any cloud job is running.

The prepared drain requires Trust Review of the retained certificate sources before both publications, without recording a new register fetch. It preserves the existing business overrides and Trust Review history, while keeping its report, heartbeat, history and PDF cache separate. A failed or incomplete required review stops publication; findings still need the normal manual decision. Concurrent promotion remains blocked while another cloud job runs.

A completed job or an overall ok report is not proof that PDF work was saved. The Sinetti operator must verify tail_publish.status = ok, the published revision, and useful PDF results retained by the next baseline. A hard interruption before publication can lose work and require a repeat. There is no recurring drain schedule. Turn one on only after a later trial shows durable parsed-document progress, and stop or reduce it when the backlog clears.

Verified against both 25 September 2026 trial receipts in docs/pdf-drain-runbook.md. Recurring drain remains disabled.

Morning cloud OCR job

Publication policy approved on 9 October 2026: the nightly data refresh and morning OCR run should each make their checked release live after finishing. This is being rolled out under SIN-0481; both live schedules were still set to candidate-only at the last configuration check. After enablement, failed data checks keep the previous release live, and a failed website restart triggers recovery to the previous release. The Sinetti operator confirms success from the final activation receipt and the live revision under System health. A completed upload alone does not prove customers received the update. Application-code deployments remain separate.

Scanned ISCC and REDcert certificates cannot be read by the nightly: its machine has no OCR engine. A separate Hugging Face job reads them each morning at 07:00 local time (06:00 in winter), after the nightly has published. It takes up to 60 ISCC and 20 REDcert certificates by default; an explicit REDcert cap of zero disables that lane. Known unchanged scans stay out of nightly text-parser retries. An unsuccessful OCR attempt is retried once when its OCR reader or applicable ISCC layout family improves, then its new ledger entry closes it again. Successful OCR and accepted exceptions remain protected; new document URLs, positive file changes and reviewed rereads remain eligible. The job keeps only readings the approved quality rules accept, merges both lanes, and publishes one candidate release. Production changes only when the Sinetti operator promotes a release. The job refuses an unpinned OCR engine and refuses publication if another job moved the dataset revision. Register it with python scripts\register_hf_cloud_ocr_job.py --apply after the deploy that ships it; the runbook section “Morning cloud OCR job” has the settings, rehearsal and checks.

Verified against scripts/run_iscc_cloud_ocr.py, iscc_cloud_ocr.py and scripts/register_hf_cloud_ocr_job.py, 29 September 2026. Not registered yet: ships with the next deploy.

The calendar

JobRuns onWhen (local time)What it does
Daily refreshHugging Facenightly, 00:00 UTCISCC + REDcert → candidate (2BSvs + RSB after activation)
Morning cloud OCRHugging Facedaily, 05:00 UTC (07:00 summer / 06:00 winter)Scanned ISCC and REDcert certificates → one candidate. Registered after the deploy that ships it
Sinetti PC daily HF syncOperator machinedaily 03:00 + at logonMirrors production receipts and state to the machine; stages the newest candidate locally. Never promotes. The register script's intended slot is 04:30; the installed trigger is still 03:00.
Sinetti PC daily test runOperator machinedaily 03:00Runs the release gate itself against main (--keep-going), so a red stage is found the next morning and a green night leaves proofs a same-day deploy can inherit. Runbook: docs/nightly-suite-runbook.md
Sinetti PC 15-min resume interrupted gateOperator machineevery 15 minutesIf a reboot kills release.ps1 mid-stage, the next tick resumes that same candidate. It does not start a new release
Sinetti PC daily trust monitorOperator machinedaily 05:30Emails you the daily workload to clear, and when a refresh or review went missing or failed (§14)
Sinetti PC daily customer-state backupOperator machinedaily 06:15Copies production's customer state, as mirrored nightly into data\prod-mirror, into C:\Users\azizl\sinetti-backups\customer-state and keeps the newest 14 stamps. SQLite files are snapshotted, so an open writer does not leave a torn copy. The task does not read the production bucket and does not upload. Receipt: data\refresh\last_customer_state_backup.json. One stamp, 20260925T205902Z, was uploaded once to the private dataset Couscouskabyle/sinetti-customer-state-backups.
Sinetti PC daily regulatory monitorOperator machinedaily 06:30Checks laws, North American guidance indexes and five registers for changes (§11)
Sinetti PC weekly US refreshOperator machineSat 07:00California LCFS + US EPA registers
Sinetti PC weekly CFR refreshOperator machineSun 07:30Canadian Clean Fuel Regulations list
Sinetti PC weekly OR-BC refreshOperator machineSun 08:30Oregon + British Columbia registers
Sinetti RenovaBio weekly refreshHugging FaceSun 10:00Brazil ANP RenovaBio certificates → candidate. Shown in Browse and on holder pages since stage 2 (30 Sep 2026) (runbook docs/renovabio-refresh-runbook.md). Registered after the next deploy by the cloud-jobs procedure below (register_hf_weekly_register_jobs.py, all four cloud registers or --registers renovabio alone)
Sinetti PC weekly NEa refreshOperator machineSun 09:15Dutch account holders — a change still needs a human commit
Sinetti PC weekly INS refreshOperator machineMon 10:00INS register + three capped INS PDF stages
Sinetti PC quarterly company groupsOperator machine1 Jan / Apr / Jul / Oct, 06:45Places new holders into corporate groups and records ownership, draining every queue slice by slice (no per-quarter cap; stops after 66 h and resumes next run). Log data/refresh/company_group_drain.log. Needs the operator signed in to Claude Code. See §10
Law-layer monthly refreshOperator machineevery 4 weeks, Mon 07:30Re-fetches regulation texts Ask can cite
Consumption Eurostat monthlyOperator machineevery 4 weeks, Mon 05:00Market-balances statistics (not deployed)

If the operator machine is off on Monday, INS simply ages and the app says so honestly in its freshness line — visible degradation, never silent. The three INS PDF stages default to a cap of zero in the runner, so an unset machine never parses an INS PDF. Registering the weekly task (scripts/register_ins_weekly_task.ps1) raises each cap to 1,200 when .env omits it or sets it lower — the same drain budget as the nightly certificate-PDF cap — and sets a four-hour cutoff. Re-register after pulling that script; the cutoff is stored on the task, and the caps are stored in .env. Leave the uncached switches unset unless you deliberately want the local task to refetch from GSE. With INS_PDF_SITE_REEXTRACT=1 the site stage also re-reads documents that already have sites, and replaces them only when nothing is lost. A criteria batch that cannot improve is a Trust Review finding, not a failed run that withholds the whole publication.

How to tell a run really happened

  • Admin → Data ops shows a card per register with a status pill (ok · error · held · not due · no data), what changed (“+12 new · 40 renewed · 3 withdrawn”, and suspended, delisted and terminated on their own — a termination is the holder’s exit and is not a withdrawal), and how the PDF queues moved. Below the briefing, Parse inflow, next 12 months counts certificates still in force onto the day each one ends: one renewal PDF that day, and for ISCC one summary audit report. The numbers are read from the serving release when you open the page. A section bar stays pinned at the top while you scroll the long Data ops pages (Refresh, Sources) and the other long admin pages (Data Trust, Regulatory monitor, System health): the hub tabs plus one pill per section, with the pill of the section in view highlighted; click a pill to jump to that section (a closed section opens). Right after the three summary tiles, Documents still to read — day by day shows line charts, one point per day a refresh ran, ending at the data the live app serves: the last point is the promoted data release’s own count, so the charts move when new data is promoted, and a nightly count on data not yet promoted is not drawn. They keep Digital and Scanned documents apart. A digital PDF has a text layer the parser reads directly; a scanned PDF is an image that needs OCR before anything can be read. Digital certificate PDFs waiting has one small chart per register, each with one line per reason a digital PDF is still waiting (layout not read yet, because the parser does not know the layout; waiting for a check; download to retry), each chart with its own scale. Scanned certificate PDFs waiting keeps one line per register; under the scanned chart a line says how many scanned ISCC certificates the morning OCR run has already tried (and still could not read) against how many it has not tried yet — ISCC and REDcert report the morning OCR queue; INS reads “no OCR job”. ISCC audit reports waiting draws a Digital and a Scanned line. Hover a day for the numbers: on the scanned and audit charts the split into parser, check and retry (scanned days read “needs OCR”); the 30 days / 90 days / All switch sets every chart. A day without a point had no refresh for that register (INS runs weekly). Days recorded before 9 October 2026 were not split when written: they are estimates, shaded and labelled, worked out by sorting the documents that were waiting that day by their reason today (a scan that was later read counts as digital on the days before). In the digital charts only the “layout not read yet” line is estimated on shaded days; check and retry were recorded exactly. From the next nightly data refresh each day stores its own split and is exact. For ISCC and REDcert the waiting counts include only certificates still in force, the same rule as Data Trust Accounted; INS is unchanged. The charts mark the day this rule started with a vertical line labelled “Current certificates only”: the drop on that day is the rule change, not reading, and hovering that day says how many waiting PDFs of ended certificates stopped counting. Under those charts, Holders on the map — in force only counts, per scheme, the holders with a certificate, pathway or registration in force that draw a point on the maps (one unit per holder and country, so a holder in two countries counts twice). One card lists the certification schemes, one the regulatory programmes: a bar for the share on the map, located against in force, the percentage and how many are not on the map (no plant, address or postcode point, so missing from Browse, Certified Sites and the deep-dive maps). The count is taken on the data the live app serves, so it moves when new data is promoted; each nightly data refresh also records its own count, and once two nights exist each card draws a Not on the map — day by day line. The Queue tables section further down carries the full daily movement per register, and Register history shows the last 14 nights (a link shows all of them). Under the three summary tiles, Serving on sinetti.ch compares each register's data on the live app with the newest release in the dataset repository. It recommends a release only when every served register remains present with a valid date that has not moved backwards, and at least one register is newer or newly added. The command to run on your PC is pre-filled with that release (Copy button); the app never moves production itself. When newer data waits, the card headline names the register and the date of its waiting data (for example Newer ISCC waiting 25 Oct 26 · 02:00), and the line under it says what promoting changes and which registers on the same schedule have nothing newer; the table lists only each register’s schedule, live date and status. When the newest release holds newer data but the code deployed on sinetti.ch cannot pull it (for example a register folder that code does not know), the card headline reads Needs a code deploy first, a red strip quotes the reason, and no command is offered until a deploy ships code that can read it; promote_release_on_space.py --list marks the same release. An invalid or regressed candidate is shown as needing review, and an equal-date release with possible PDF work needs manual review. A green newest-data line requires trustworthy evidence or the already served release. While the server is running on partial data (a slow start left company identities out, so holder pages show no company links), the same card shows a terracotta strip saying so and roughly how many seconds remain until it reloads by itself; nothing needs doing unless it is still there after a few minutes, then restart the server.
  • Admin → Data ops → Sources tags each source on schedule, overdue n days or manual. “Overdue” is computed purely from how old the newest evidence is — the one signal a killed job cannot fake.
  • For the nightly job, the authority is the job's own status on Hugging Face (completed / error). In August 2026 every night for two weeks died of a timeout and wrote no receipt; the app's log kept showing the last good night.

Procedures

Check the nightly job's true statusDesk · 2 min
  1. From the operator machine, list recent job runs and the active schedule:
    python -c "from huggingface_hub import HfApi; a=HfApi(); [print(j.id, j.status.stage, j.created_at) for j in list(a.list_jobs())[:8]]"
    python -c "from huggingface_hub import HfApi; [print(j.id, j.schedule, 'SUSPENDED' if j.suspend else 'ACTIVE') for j in HfApi().list_scheduled_jobs()]"
    last night's run COMPLETED, and exactly one nightly data refresh job ACTIVE. The morning cloud OCR job and the weekly register jobs are separate schedules.
  2. ERROR with “Job timeout” → the run outgrew its time budget: re-register with a longer timeout or lower caps. ERROR exit 137 → out of memory. Any other error → open the job's log on Hugging Face.
Change the nightly job's settings (re-register)Desk · 15 min
  1. Preview first — this prints the full job definition and creates nothing:
    python scripts\register_hf_daily_refresh_job.py --dry-run
  2. Register with every setting spelled out. A flag you omit falls back to its default, and the default for every PDF cap is 0 — a careless re-registration silently switches parsing off.
    python scripts\register_hf_daily_refresh_job.py --schedule "0 0 * * *" --state-dir /data/sinetti `
      --bucket-volume Couscouskabyle/iscc-app-storage --refresh-schemes iscc,redcert `
      --geocode-schemes iscc,redcert,ins --allow-partial --timeout 270m --flavor cpu-upgrade `
      --pdf-delta-max-pdfs 1200 --pdf-delta-prefer-new-documents `
      --audit-delta-max-pdfs 100 --audit-delta-prefer-new-documents `
      --redcert-pdf-max-fetches 300
  3. Suspend the previous job in the Hugging Face Jobs screen, then confirm exactly one is active (previous procedure).
  4. Watch the next night's duration. Raise caps only with clear headroom under the timeout.
Rehearse a refresh now, with every outward effect offDesk · minutes to about 2 h

Use this after a refresh change, instead of waiting for the scheduled slot. Real work: downloads, schema checks, PDF parsing, corpus bands, rebuild, Trust Review, composed emails, and the shape of the single-commit release. Withheld: the dataset commit, activation, Space upload and restart, mail delivery, paid geocoding, and the Hub control-branch write. A scratch state directory is used. The run refuses during 00:00–02:00 UTC and while another session holds data-release.

  1. List the jobs, then run one or all:
    .\.venv\Scripts\python.exe scripts\rehearse_refreshes.py --list
    .\.venv\Scripts\python.exe scripts\rehearse_refreshes.py us
    .\.venv\Scripts\python.exe scripts\rehearse_refreshes.py all --keep-workspace
    a PASS or FAIL line per job. rehearsal: true on a receipt means it did not publish.
  2. A PC rehearsal cannot reproduce a cloud timeout, an out-of-memory kill, or the Space's symlink layout. For that, scripts\rehearse_hf_nightly.py uploads the current checkout to dataset branch rehearsal-code (never the Space, never a main) and runs one job with the live scheduled job's settings. It is a paid cloud job: state the cost ceiling and get approval before launching it. Runbook: docs/rehearsal-runbook.md.
Check, run or re-create a weekly task on the operator machineDesk · 5 min
  1. State and last result:
    Get-ScheduledTask -TaskName "Sinetti*" | Get-ScheduledTaskInfo | Format-Table TaskName,LastRunTime,LastTaskResult,NextRunTime
  2. Run one now: Start-ScheduledTask -TaskName "Sinetti PC weekly INS refresh", then read its log, e.g. Get-Content data\refresh\ins_local\runner.log -Tail 20.
  3. Re-create on a new machine with the matching script in scripts\: register_ins_weekly_task.ps1, register_us_weekly_task.ps1, register_orbc_weekly_task.ps1, register_nea_weekly_task.ps1, register_local_hf_sync_task.ps1, register_trust_monitor_task.ps1, register_regulatory_monitor_task.ps1, register_law_monthly_task.ps1, register_nightly_suite_task.ps1, register_company_group_task.ps1, register_resume_interrupted_gate_task.ps1, register_customer_state_backup_task.ps1. Gap there is no registration script for the CFR task in the repository. Re-register the company-group task after changing -Model (current: claude-sonnet-5).
  4. Always verify with Get-ScheduledTask — a registration script can print success after Windows refused the task.
  5. An INS run whose receipt says upload.status = skipped-pdf-stage-error withheld publication on purpose: one of the three PDF stages failed. Read that stage in data\refresh\ins_local\last_ins_local_refresh.json.
Move the US, CFR, Oregon + BC and Brazil (RenovaBio) weekly refreshes to Hugging FaceDesk · 15 min, once

Approved 28 September 2026 (brief docs/superpowers/briefs/2026-09-28-weekly-registers-to-hf-jobs-brief.md). Not done yet the US, CFR and OR-BC rows above still run on the operator machine, and the Brazil RenovaBio row is cloud-only from the start (runbook docs/renovabio-refresh-runbook.md). The cloud jobs run the deployed Space code, so this can only start after a deploy that includes the four refresh scripts.

  1. Preview: python scripts\register_hf_weekly_register_jobs.py. It covers all four cloud registers (US, CFR, OR-BC, RenovaBio), prints each job's schedule (UTC) with secrets masked and registers nothing. --registers renovabio narrows it to that one register.
  2. Register: add --apply. It refuses while the deployed Space lacks a script, and leaves alone any register that already has an active scheduled job.
  3. After each register's first cloud slot, confirm the job reads COMPLETED in HfApi().list_jobs() and the register shows fresh data under Data ops → Sources. Then disable that register's machine task (RenovaBio has none), e.g. Disable-ScheduledTask -TaskName "Sinetti PC weekly US refresh".
  4. Switch back: Enable-ScheduledTask on the machine task, and suspend the cloud job in the Hugging Face Jobs page.

8How data goes live

Where: operator machine · Hugging Face Jobs · /api/ready

In short

What customers see is a release: one database file plus its manifest, stored under its revision id and never modified afterwards. A one-line active pointer names the release being served. Going live means moving that pointer — nothing is copied over anything, so going back is just moving it again. The app reads the pointer once, at start: a new release appears after the next restart, never mid-session. If the active release turns out broken at start, the app heals itself by stepping back to the previous good one, and as a last resort serves the data it was shipped with.

Candidatedataset repo commit Stagedchecksums verified Guardsschema · counts ·no register shrank PROMOTE Releaseimmutable, by revision Active pointerread once, at app start 2 previous releaseskept for rollback rollback · self-heal Bundled datashipped with the app last resort The pointer can be moved by: you (manual promotion / rollback), the app at start (self-heal), or automation after a streak of clean nights — if switched on. Each move records who made it. This whole store exists twice: once in production's storage bucket, once on the operator machine. Promoting one does not promote the other.
Going live is a pointer move. Because releases are never edited, “roll back” cannot half-fail, and a bad release cannot damage a good one.

Who may move the pointer

MoverWhenGuards applied
You — manual promotionAfter reviewing a candidateExact revision match (checked three times), deep validation of the database. The shrink guard is not applied: a human may knowingly promote a smaller register.
You — rollbackThe new release misbehavesMoves to the newest retained release other than the current one
The app — self-healAt every start, if the active release fails validationSteps back to the newest retained release that validates. If none does, serves bundled data for that process only and changes nothing on disk.
Automation — green streakOnly if enabled by a setting; off by defaultN consecutive valid nightly candidates and corpus counts in band and the shrink guard: no register missing, no register with even one certificate fewer, total not down more than 20 %. Refuses to replace a pointer that a human set.

The strict shrink rule exists because a real accident was a 0.65 % loss — 507 certificates — that a percentage threshold would have waved through. Registers do shrink legitimately; that judgement is reserved for a person.

Reading /api/ready

FieldHealthyMeaning when not
data.release.sourcereleasesbundled = the pointer is missing, unreadable, or every release failed validation. The app is up but serving the older shipped data; data.release.reason says why.
data.release.revisionthe revision you last promotedA different revision = someone or something else moved the pointer; null = bundled.
data.oktruefalse with validation: critical = the database being served fails its own manifest. The endpoint answers HTTP 503.
Never open, copy or replace the database file under data/ by hand. That pair is the shipped fallback; it lags the active release and answers questions wrongly without any error. Inspection goes through the app's own loader; going live goes through promote.

Procedures

Promote a reviewed candidate in productionDesk · 15 min + restart
  1. Identify the candidate. In the dataset repository's history, note the full 40-character commit id of the nightly you want. Confirm in Admin → Data ops that this night was ok for each register and read its Trust Review line.
  2. Review it. Stage it on the operator machine and look at it in the local app (next procedure). Check the counts per register against the active release; a drop needs an explanation you believe.
  3. Preview the production promotion — rechecks the exact selected commit against the live source dates, then prints the job and creates nothing. Missing, older or invalid source evidence refuses by default; ordinary force flags do not bypass this check. Equal-date PDF work can be selected manually after review. It also runs the code deployed on the Space against the release's file list (about ten seconds): when that code would refuse the release, it stops with REFUSING: the deployed app would refuse this release: … — the release needs a code deploy first, and a cloud job would only end in ERROR. --list marks such a release “needs a code deploy first” instead of recommending it:
    python scripts\promote_release_on_space.py --hub-revision <40-char-commit-id> --dry-run
  4. Run it. This launches a one-off cloud job that mounts production's storage, pulls exactly that revision, validates it and moves the pointer, and the script watches the job to completion. A failed start, log read or restart prints a plain sentence. If a newer nightly published meanwhile, the revision no longer matches and the job refuses — the current release keeps serving. To promote the commit the Data ops card recommends, resolved at the moment you run it, use --recommended instead of --hub-revision. It refuses when nothing is newer. It cannot be combined with --allow-data-rollback.
    python scripts\promote_release_on_space.py --hub-revision <40-char-commit-id>
    python scripts\promote_release_on_space.py --recommended
    a job id; in its log PROMOTE: {… "status": "activated" …}.
  5. Restart the Space (Hugging Face → Space → Settings → Restart). The running app never swaps data mid-session.
  6. Verify: https://couscouskabyle-iscc-app.hf.space/api/ready shows status: ready, data.release.source: releases and your revision.
  7. Intentional rollback only: after reviewing an older release, use its full commit id and --allow-data-rollback on the dry run and real command. It cannot accompany --recommended, still refuses invalid evidence, and does not disable the job's revision checks. Local preflight is not a cloud lock; verify the served revision and source dates after restart.
Whether production also promotes by itself depends on a Space variable (SINETTI_AUTO_ACTIVATE_NIGHTS, and the nightly job's SINETTI_PROD_AUTO_ACTIVATE). Read the current values in the Space and job settings. The pointer file records by: manual-promotion, manual-rollback, boot-self-heal or auto-activation — that is how you tell what moved it last. When the nightly data refresh holds production activation for more than a day, the admin gets one reminder mail per day (“production data activation still held since …”) naming the reason, the live revision and the newest published revision, until activation succeeds.
Stage and promote on the operator machineDesk · 10 min
  1. Pull the newest candidate without touching what the local app serves:
    python scripts\local_hf_sync_once.py --force
    data\refresh\local_hf_sync.json shows source: dataset, space.status: pending-promotion and the revision you expect.
  2. Promote exactly that revision, then restart the local app:
    python scripts\promote_data_release.py --hub-revision <40-char-commit-id>
    status: activated. Any other status ends with decision: kept-current — nothing was half-applied.
  3. error-locked means another promotion holds the lock (or died holding it); error-revision-mismatch means the staged candidate is not the one you named — sync again.
The company-group job's local rebuild (scripts\rebuild_local_release.py) is a different command. It starts from the retained local release with the freshest register data, recomputes only the company layers, and refuses when another session holds data-release, when any register would be older than the base, or when a register would shrink by more than 2%. It does not read this desk's bundled register files.
Roll serving data backDesk · 5 min + restart
  1. On the operator machine: python scripts\promote_data_release.py --rollback, then restart the app. This reaches two releases back; older ones need a fresh sync and promote.
  2. In production: promote the previous revision id with the production procedure above, then restart the Space. If the active release is outright broken you need do nothing — self-heal steps back at the next start.
  3. This is data rollback. Rolling the app version back is a different mechanism (§13); never claim one from the other.
After a PDF-parsing campaign: the promote checklistDesk · 1–2 h

Whenever parsed-PDF counts advance outside the nightly (a repair campaign, an OCR batch), finish all of this in the same sitting. Skipping the later steps leaves Data Trust current while the Product page, Certificate formats, the count guard and the served release silently drift.

  1. Commit the updated parsed-PDF archive data/cert_pdf_details.sqlite.gz.
  2. Rebuild the structured layer and the app database:
    $env:APP_BACKEND = 'local'
    .\.venv\Scripts\python.exe scripts\build_cert_pdf_structured_db.py --source-db data\cert_pdf_details.sqlite `
      --work-db data\cert_pdf_structured.sqlite --artifact data\cert_pdf_structured.sqlite.gz --replace
    .\.venv\Scripts\python.exe -c "from scripts.rebuild_sinetti_app_artifact import rebuild_bundled_app_database; print(rebuild_bundled_app_database())"
  3. Run the follow-ons in one command and commit data/cert_format_catalog.json and data/corpus_counts.json. It rebuilds the certificate-format catalogue when it was not built from the archive you just committed, then refreshes the corpus count bands. Exit 1 = a count left its band: investigate, never widen a band to make it pass. The band refresh records which artifact it measured, so run it on the desk holding the corpus you mean to set bands from. A register sitting at zero (certificates_oregon, certificates_bc, ...) means that register left the artifact entirely. Forgetting the catalogue is now caught: the test gate fails until the catalogue matches the committed archive. Admin → Certificate formats says the same thing on the page: the status line at the right of the tab row turns to the warning colour and reads "Out of date — rebuild by hand after a PDF-parsing promote", with an "Out of date" strip below, when the catalogue does not match the archive the server reads. Ask knowledge, Research coverage and Site footprint each carry a one-line note saying they update only by hand.
    .\.venv\Scripts\python.exe scripts\finish_details_promote.py
  4. Stage and activate the rebuilt pair through promote — never by copying files.
  5. Open Admin → Data Trust: snapshot date, per-register rows and backlog totals must match your campaign notes. Spot-check Product and Certificate formats.
Audit-report-only progress needs steps 1, 3 and 5 only.

9Is the data right?

Where: Admin → Data ops · Admin → Data Trust (tabs Overview · Formats · Admin QA · User reports)

In short

Every candidate is inspected by Trust Review, an automatic reviewer that files findings — “this register looks stale”, “these identities disagree”, “coverage fell”. Findings are advice for you, kept in a store of their own: they never reach customers, never block anything by themselves, and never send email. Your part is to read the morning briefing, give each finding a disposition with a reason, and know the state of the PDF-parsing backlog. A separate monitor emails you the daily workload, and when a refresh, a review, a repeated application error, or the PC daily test run failed (§14).

Candidateafter each refresh Trust Review — 5 checksfreshness · status evidence ·identity · coverage · publication Findings storeown file · admin only DISPOSITION state + reason,kept as history filesyou read Job monitor · daily 05:30“did the run and its review happen?” One alert emailthen one recovery email missing / failed runs on its own —a job cannot report its own absence
Two different questions, two different mechanisms. Is the data plausible? → findings, read on screen, never emailed. Did the machinery run? → the monitor, which emails. The findings store is deliberately disconnected from customer pages: if it is corrupt or locked, only the admin evidence degrades — Browse, exports, /api/health and /api/ready carry on unchanged.

The screens

Map coordinate delivery (checked 8 October 2026). The Sinetti operator reads coordinate_workspace in the Data ops refresh receipt. Newly recovered holder, address and site points reach Browse, Producers / Certified Sites and the deep-dive maps after the rebuilt data candidate is accepted and activated. An unchanged checkout baseline protects nightly positions; an actual later hand correction supersedes them. Town/postcode fallback evidence stays approximate. A workspace error preserves unreadable inputs and stops the candidate: restore a verified usable copy, keep the receipt and rerun the isolated job. Interrupted coordinate/provenance writes recover from their transaction journal; do not delete it. A successful receipt alone does not establish activation or complete address coverage. Procedure: docs/daily-refresh-runbook.md, “Coordinate delivery and hand corrections”.

Map coverage accounting (checked 8 October 2026). The raw refresh report now includes coordinate_coverage: the familiar distinct World holder total, separate certificate-address and physical-site counts, pending/ambiguous/negative work, and changes on retained identities. New holders are shown separately from lost coordinates. A stored singleton pin avoids an unnecessary paid request; another unresolved address remains in scope. Town/postcode evidence is approximate and leaves the site task open. An advisory audit error or damaged prior inventory says coverage/history unavailable and preserves the last readable report. The Sinetti operator can propose zero-cost recovery in a separate bundle using verified published caches and strict local place evidence; acceptance and activation remain separate. The large postcode index is never loaded by nightly accounting. Procedure: docs/daily-refresh-runbook.md, “Coverage inventory and zero-cost catch-up”.

ScreenWhat it answersCan you change anything?
Data ops
Admin → Data ops
The morning briefing: per-register run status, certificate changes, PDF and audit queue movement (opening → +added → −done → closing), the next-12-month parse inflow counted from certificate end dates, backlog line charts over days, history tables, the raw refresh report, the geocoding budget, and previews of tonight's emailsNo — read only
Data Trust · Overview
Admin → Data Trust
In-force certificate PDFs per register as Accounted (read or a named exception) and Read, with parser and manually verified reads shown separately; the list of certificates not accounted and why; then how all certificate PDFs add up: parsed, no pathways found, accepted exceptions, remaining work split into parser backlog, data-trust review and acquisition retry; audit-report coverage; plus three review queues (below)Yes — entity identity, related companies, finding dispositions
FormatsEvery distinct certificate layout per register (“ISCC Type 1” …) with up to five sample PDFs, tagged Good / Needs action and Machine / Human work. Unfamiliar digital annex tables wait at the top, one shape at a time: the annex page of one specimen, shared by every certificate with that tableYes — materials annex, or empty template. Skip leaves it in the queue. The gallery underneath stays a reference
Admin QAThe inbox of individual parsing and interpretation issues; stage decisions, preview their effect, write the input the next re-parse consumesYes — staged, then explicitly written
User reports
Admin → Data Trust
Customer data problems (from a dossier row or the dossier’s “Tell us” line) and product feedback (footer Feedback), each with the holder, field, shown value, serving releases and the customer’s noteYes — close each report with an outcome and a short note; the customer sees it under Activity. It never creates a finding or changes data

“Balanced operating lanes” and “Acquisition retry preview” on the Overview are planning aids. The lane caps are read from the nightly receipt, not from a number baked into the page. They suggest cadence and show what a retry would pick; they cannot start or schedule anything. Reason codes on this page and on Certificate formats use plain labels (for example “Ticked box could not be read”).

A customer report of a wrong or missing record starts with docs/enterprise/hosted-bilateral/data-correction-procedure.md. The report lands in User reports and does not itself create a finding. Record the intake, the reply to the customer, and the source-versus-served evidence before you change anything.

Verified live PDF coverage

The Sinetti operator reads three separate results: current source checks, production reconciliation, and reciprocal monitor liveness. The existing Accounted and read screen describes the selected artifact; a local parse or published candidate does not establish production completeness. Reconciliation needs the verified active revision/hash, full expected inventory including required annexes, and independent code, source, API and customer evidence. Missing evidence is unknown. INS live recovery/publication is owner-deferred while ISCC and REDcert are prioritized; INS remains visibly unresolved in the full target, with its local support and history retained.

Every expected current document needs a successful source check within seven days, including successes and accepted exceptions. Full current bytes or an authoritative digest establish freshness; unchanged URL, HEAD or file length does not. Source-only checks can acquire new PDFs without earlier Read/customer proof, while production coverage stays unknown. A changed file immediately supersedes old current proof, even if earlier good rows are preserved after a failed replacement. Source blockers stay in the denominator, are rechecked weekly and never count as Read. Keep diagnosed Failure A–E labels separate from OCR residual Types.

The new lanes default off. SINETTI_PDF_SOURCE_AUDIT_ENABLED=1 and SINETTI_PDF_COVERAGE_MONITOR_ENABLED=1 are separate explicit opt-ins; audit caps default to zero. An independent request and its SHA256 must be configured through SINETTI_PDF_COVERAGE_INPUT_REQUEST and SINETTI_PDF_COVERAGE_INPUT_REQUEST_SHA256. Local code changes leave existing jobs, flags and schedules as configured. Enabling, source runs, publication, activation and deployment are separate authorized operations; Environment has no control for these flags.

The exact audit caps are SINETTI_PDF_SOURCE_AUDIT_MAX_CHECKS, SINETTI_PDF_SOURCE_AUDIT_ISCC_MAX, SINETTI_PDF_SOURCE_AUDIT_REDCERT_MAX and SINETTI_PDF_SOURCE_AUDIT_INS_MAX. Inspect refresh/pdf_coverage/source-audit.json, exactly {state, report, receipt_binding}, against both full six-field inventories and their independent inventory pins. Latest negative observations and unbound changed requirements remain holds despite retained older success. Only genuine processing adds report_json.pdf_source_freshness; operational job reports use source_version_audit. An unchanged source check creates no generic parse run. The shipped acquisition adapter checks full bytes; independently required ISCC audit reports use verified bytes in both existing sequential and overlap parser routes under existing audit caps. Required REDcert/INS audit-report roles remain explicit unknown/pending in the full denominator because their processing routes are unsupported; unrelated audit reports are not added.

Source-audit JSON reads allow 16 MiB without truncation; narrow heartbeat/hosted-check reads remain 1 MiB. Actual full-population payload capacity is unmeasured. The 14-date/two-activated-INS operational gate remains open after the local code merge: current full-population proof, measured capacity, actual deployed/scheduled operation, source/customer evidence and reciprocal liveness remain necessary.

Proposed targets are acquired text PDFs within 24 hours, daily-lane scans within 48 hours, and INS discovery within seven days followed by 48-hour processing. Measure successful checks and queue ages inside the existing runtime/caps before claiming capacity. Local fixtures prove behavior and arithmetic only. Operational acceptance needs 14 consecutive Zurich scheduled dates with completions at most 26 hours apart, plus two distinct genuine scheduled weekly INS accepted revisions activated and reconciled. In-deadline arrivals lower literal coverage without alone resetting operational consistency. Visible, evidence-bound weekly-checked blockers may coexist with operational consistency; literal 100% sustained additionally needs zero incomplete/blocker cases at every counted checkpoint. Expiry is not recovery, and checkpoints do not prove continuous completeness between runs.

Findings: states and what they mean

StateUse it when
NewNobody has looked yet. A resolved finding that comes back returns here with its history intact.
InvestigatingYou are looking into it
Known gapReal, understood, accepted for now
Fix plannedReal, and work is scheduled
False positiveThe check is wrong for this case
ResolvedGone. Findings that stop appearing are resolved automatically.

Each finding type is also classed Blocking or Advisory. The class matters only to automation: a candidate carrying a blocking-class finding is not promoted by itself. It never stops you. Unclassified types count as blocking. Priorities are critical · high · normal · information.

Procedures

The five-minute morning checkScreen
  1. Admin → Data ops. Read the date on “Last-run briefing” first — a briefing from two days ago is itself the finding.
  2. Each register card: pill ok? Do the change numbers look like a normal day? A queue whose closing number never falls means parsing is switched off or stuck.
  3. Follow a card's trust link — it opens Data Trust already filtered to that register's unresolved findings.
  4. Expand Email previews if you want to see exactly what subscribers and you will be sent tonight. Nothing can be sent from there.

Material evidence held: a failed or incomplete reparse keeps the last verified source and creates a review item. Unavailable or unreadable documents do not mean no materials. Dataset publication stops when source owners, certificate periods or material rows disagree between the register, raw extraction and served release. The Sinetti operator reviews the actual source, repairs and reparses it, then rebuilds all three layers. The last accepted release remains available; deleting a review item or changing a count band is not a repair. A refused re-read where the kept file and the new read both have no material rows (for example "no materials declared" read again as an unsupported layout) loses nothing: it stays listed for review but no longer counts toward the 50-document hold ceiling and never holds publication. The 50-document ceiling counts only documents newly held in the last three days, and never a failed download: older holds keep their own certificate on its last verified reading but do not stop the night's publication. A reading held for more than 14 days is named on a Held PDF readings line in the daily admin email until it is reviewed.

Data Trust starts with Accounted and read, for in-force certificates only. Accounted is certificates that are read or closed as an accepted exception with a named reason; Read includes parser reads and manually verified PDFs, shown in separate columns. An exception counts as accounted, never as read, is bound to the exact PDF file, and reopens when a new file arrives. A cover that prints more pages than its official PDF holds (“Page 1 of 2” on a one-page file) is never counted as declaring no materials: it shows as Official PDF misses a printed page until reviewed, then as the exception Page missing from the official PDF. The target is accounted 100% after every morning OCR run. Not accounted lists each certificate with why: Needs a decision (the only rows that need you), Not tried yet, Download retry (three mornings, then closed) or Reader fix open with its backlog item. 2BSvs and RSB read “Not read — frozen scheme”. The same list downloads as CSV.

For a certificate that needs a decision, select Review manually to compare its source PDF with editable pathway and site rows. Existing material names and scopes are suggested as the admin types; free text remains available for the exact printed wording. A partial save keeps the certificate open. Marking it manually verified requires a reason and confirmation that every row and site annex was checked. The review is tied to the PDF SHA-256, and review history records each save, reviewer and time. These corrections stay in the operator review record; run the normal PDF promotion process before they appear in customer-serving pathways.

Trust Review findings follow. Open Document overview, audit reports and reconciliation for document coverage, operating lanes and retry work. Entity identity and related-company reviews each have their own disclosure below. Related-company review shows ten pairs per page, with x–y of N pairs, Previous and Next; all candidates remain available. After a decision removes the last row on the last page, the screen returns to the last valid page.

Review a Trust Review findingScreen · 3 min each
  1. Admin → Data Trust → Trust Review findings appears right under the tab row, including when the snapshot is unavailable. The snapshot date, artifact and provenance sit at the right end of that tab row; Unverified snapshot shows in warning colour. Data ops findings links land here and preserve their scheme and lifecycle filters. Filter by Scheme, Priority, Check, Lifecycle state (start with Unresolved).
  2. Review finding. Read the claim, the evidence and its source dates, how many records are affected, first and last seen, recurrence, and earlier decisions. Open investigation route jumps to the affected records.
  3. Under Review decision choose a State, write a Reason (3+ characters; write for the person reading it next year), Save review decision.
  4. If the screen says the finding changed while you were reading, do not force it: read the fresh evidence and decide again.
  5. To change how a whole type of finding treats automation, use Activation classification → Blocking or Advisory → reason → Save classification. This applies to every finding of that type.
Inspect a PDF coverage cycle or liveness failureDesk · evidence review

The PC combines authenticated hosted ISCC/REDcert source checks with verified local INS receipt/current/latest evidence. Missing local INS proof stays unknown; hosted INS rows cannot replace it. The pinned operational proof binds both actual envelope hashes and separate lane capacity receipts. Preserve the original immutable INS producer request and its file pins; see the Data Trust runbook for the exact composition digest.

An evidence-bound requirement with no PDF hash remains in the denominator and never becomes Read or literal 100%. Only complete, current blocker proof or in-deadline acquisition evidence permits operational evaluation. Parser support alone is insufficient: the existing strong Read contract accepts certificate/material/site annex roles; other required roles, including parsed ISCC audit reports, remain unknown.

  1. Verify the actual Sinetti PC daily trust monitor task at 05:30 Zurich time, preserving that local slot through daylight-saving changes. Confirm the deployed hosted schedule separately. The PC consumes --pdf-coverage-invocation-id only with independently pinned scheduler_execution proof from the request's named operational_evidence file matching the current launcher PID, its actual OS birth in launcher_started_at, and original scheduled provenance. Strings, manual runs, --now, copied/stale proof or registration alone cannot qualify. No real wrapper/proof producer is installed, enabled or verified by this merge.
  2. Read the immutable local receipt under <state>/refresh/pdf_coverage/receipts/ and coverage-only latest.json: its byte hash and previous-receipt link, source/inventory and stable production bindings, pending ages, unresolved cohort, notifications and pass/fail/unknown targets. Actual Ready must name the same active revision/hash before and after reconciliation; movement is retried once, then remains unknown. Immutable production app/manifest/details and current independent code/contract/source/API/customer proof are required; stale mirrors or candidate attachment do not supply it.
  3. Inspect the PC's narrow export refresh/pdf_coverage/monitor-heartbeat.json, independent hosted hosted-checks/ and hosted-check-latest.json, and separate local liveness-outcomes/ and liveness-latest.json for actual authenticated SDK export/readback. Only refresh/pdf_coverage/hosted-check.json is exported for the PC to read. Full local receipts and audit state are not uploaded. Hosted validation checks the authenticated heartbeat and receipt-hash link without full PC receipt verification. Strong completed bootstrap reconciliation can export an unknown-coverage heartbeat without qualifying acceptance.
  4. The declared hosted slot is 02:00 Zurich time in summer, 01:00 Zurich time in winter, preceding that day's 05:30 Zurich time PC cycle; verify the actual schedule before relying on this order. The next PC execution finalizes the prior outcome; current pending evidence is never counted. A newer bound negative check invalidates a prior positive by appending immutable evidence. Actual stored incident results, including required recovery/due reminder or genuine no-notification-needed disposition, must bind the canonical receipt before qualification. Required delivery failure remains unknown; retained failed/reserved/unknown delivery and refresh/trust_monitor_reminder.json cannot silently become no-duty success. The PC report uses pdf_coverage_monitor; enabled unknown records monitor-error and exits 2.
  5. A persistence, hash, authentication, notification, export/readback or hosted-check failure keeps acceptance unknown. A heartbeat written before failed readback may remain remotely; preserve the immutable receipt and do not assume rollback. Enabled hosted preflight failure retains acquired/refreshed candidate data and recovery evidence, but reports partial and returns nonzero; the existing production auto-activation guard holds activation whenever overall status is not ok. Missing global proof or deferred INS cannot waive that opt-in hold. Default-off behavior is unchanged. Repair the named incident through the existing reminder route. Do not hand-edit proof or backfill missed dates; the next qualifying scheduled cycle starts day one.
  6. Use the Data Trust runbook for the complete evidence and acceptance procedure; daily refresh, local INS and rehearsal describe their separate lanes.
Work the Admin QA inboxScreen
  1. Data Trust → Admin QA. Filter by Scheme, Issue family, Severity. Numbers on the Overview link straight into the right filter.
  2. Select an issue. Read the plain-language description, the suggested action and, where offered, Open PDF evidence.
  3. Choose: Accept suggestion, Defer, Mark false positive or Send to parser/rule backlog. Use Edit suggestion to correct the target first. Decisions are only staged.
  4. Pending resolutions → Approve all pending for publish.
  5. Publish preview shows the expected effect. Write rebuild input file… → Confirm — write rebuild input hands the decisions to the next PDF re-parse. Nothing changes for customers until that re-parse is promoted.

Company research request backend (SIN-0561). Admin requests queue for PC pickup even while the PC is offline. Ingested means saved on the PC, not researched or landed. Full-pass estimates require a fresh complete catalog and exact configured subscription/model. If an acknowledgement conflicts with missing journal history, or receipt verification is interrupted, PC pickup and execution stop for operator reconciliation and remain stopped on later ticks and direct commands. The fifteen-minute task definition registers disabled and has not been run. The approved holder Research line shows per-kind dates and provenance, due/review/queue state, standing priority and Research now to admins only. Research → Coverage adds actual bounded queue preview, held reasons, recent per-company outcomes and producer changes. Queue next due preserves current due/hold checks; Full pass first requires a fresh exact estimate and unchecked approval, with targeted, selected-tier or every-in-force scope. These actions only enqueue PC work; the existing globe remains separate. The existing daily admin brief can show recent producer changes with review labels, including plant and capacity evidence additions. The PC whole-dossier path retains ownership/relationships and plant research separately; partial sections do not complete the dossier or full-pass target. Fresh estimates count both stages; legacy plant dates are never invented. Organisation-owned contact research remains separate. See Company research Admin runbook.

10Curation

Where: Admin → Materials · Data Trust (identity queues) · Customers → Market templates · Research (tabs Coverage · Site footprint) · Environment

In short

Registers name the same thing a hundred ways. Curation is where your judgement becomes part of the product: which long material names share a short label and a family, which two company records are one legal entity, which companies are related, which competitors belong to a market template. These decisions are hundreds of small owner calls that no competitor can download — treat the screens as an asset, not housekeeping.

ScreenWhat you decideWhen it takes effect
MaterialsA prioritised To do list (big names still in Other, spelling twins including curated spelling groups, label-vs-family disagreements, long names without a short label); which family each material belongs to and how names roll up; custom families; short display labels; a read-only view of the curated spelling groups, and an All names table you can filter by kind and group by spelling. Opening the page applies the existing automatic suggestions, including names with a single certificate, and writes each change to the auto-apply log. New curated-group copies remain in Spelling twins to align for an explicit Accept; assigned conflicting families appear separately for reviewSaved on open (automatic suggestions) or on save (curated-group copies and your own edits, after an impact preview when families move). Users see the change after the next data publish, normally the nightly data refresh — not at once
Entity identity review
Data Trust · one table
MERGE or DO NOT MERGE two records across registers that look like one legal entity but were unsafe to merge automatically. The table shows legal names, whether each side is in force, and the flags. Rows badged Applied provisionally · company-group job are same-company merges the job researched on the web and already recorded because the two names are plainly one (one name on two registers); the cards are merged and the row shows the source links and note: CONFIRM keeps it, REVERT removes the merge. Rows badged Awaiting your decision · company-group job are merges the job judged ambiguous and did not record: ACCEPT records the merge in your name, REJECT writes nothing. Either way the pair is never researched againWith the next successful data rebuild — not at once
Related companies review
Data Trust · one table
CONFIRM RELATED (with a group label), SAME ENTITY or REJECT. Each row shows the legal names, in-force state, flags, and the shared fact for the pair. Undecided pairs from the local parent-join report also appear with “same ultimate parent”; previously decided pairs stay out. SAME ENTITY also joins the two holders’ register records, so their Browse cards merge. The full cluster remains in data/refresh/company_parent_join_review.json. Rows badged Applied provisionally · company-group job are group memberships (two or more source domains) or ownership lines (an official or own-site source) the job researched on the web and already wrote, so customers see them: CONFIRM keeps the row, REVERT removes exactly what was written. Rows badged Awaiting your decision · company-group job rest on thinner evidence and are not shown until you decide (an ownership answer of “unknown” never reaches this queue: it claims nothing, is not shown to customers and is researched again after 90 days): ACCEPT writes exactly the proposed row in your name, REJECT writes nothing. The right-hand cell names the group or the ownership line; every row shows the source links and the researcher’s note. A rejected or reverted pair is never researched again. The daily admin mail counts both tiers under “Workload to clear”Next rebuild
Company groups (automatic)
quarterly task “Sinetti PC quarterly company groups” (1 Jan / Apr / Jul / Oct, 06:45)
Nothing to click. Each quarter the job (run by scripts/company_group_drain.ps1 until every queue is empty, one commit per slice) places holders not yet decided (by shared first name word, and, since 28 September 2026, holders whose name carries an existing group's name further in, such as TOTSA TotalEnergies Trading) (more than 50% the same ultimate parent), joins holders whose recorded ultimate parent already matches, and records whether that parent is listed, privately held, state-owned or a cooperative. Clusters it will not join automatically are written to data/refresh/company_parent_join_review.json. Research uses headless Claude Code on the operator's subscription, model claude-sonnet-5, and strips ANTHROPIC_API_KEY so it never bills the API. Sign in once with claude then /login in a normal terminal; a dead sign-in shows as researcher failed: Failed to authenticate. Your REJECT on a pair wins over the group. Receipt: data/refresh/company_group_job.json. A failed merge is retried; the job does not throw away an unmerged slice. Runbook: docs/company-groups-runbook.mdLocal rebuild the job runs, then the next code deploy (the override files are code-owned)
Market templates
Customers
Which organisations are offered a ready-made competitor cohort; holders pinned into or excluded from itAt once
Site footprintAgree or correct the automatic classification of a site's surroundings (industrial / office-residential / unclear) — the China pilot. The separate reviewed satellite site-assessment workflow is under development and remains disabled; these existing classifications are not image-backed findings. Scans run by hand (scripts\scan_site_footprints.py) and each server keeps its own scan store; a server with none says “No site-footprint scans exist on this server yet”.At once; feeds classifier accuracy
Research coverage
Research → Coverage
The research data switch at the top: Shown or Hidden. Hidden means customers see certificate data only — no research-confirmed related companies, ownership line, Production units box, research nameplate capacities, researched plants on the Producers map and list, Company research or Ask's research offer. Your own related-company pair decisions and same-company joins stay. Nothing is deleted; the band turns terracotta and names who switched it off. While it is hidden, every admin page shows a terracotta Research hidden pill beside Ask that links back here. To hide research for one customer only, untick Research data on that customer's plan instead. Below it, Company research shows due tiers, held companies, PC status/subscription, recent per-company runs, independent section outcomes and producer changes including plant additions. Partial means the dossier is unfinished. Choose a tier and a bounded next 12–200 due queue; Preview queue shows the actual PC order. Full pass… estimates the selected tier, all targeted companies or every in-force holder, then requires explicit approval. Stale/unavailable estimates disable queueing. Queued and ingested are distinct from researched/landed. The facility table remains below, with evidence and capacity sorting. Runbook: docs/company-groups-runbook.md (research data switch)Switch: every customer's next page load, no restart. Stored in the persistent state folder, so it survives a redeploy
EnvironmentAssistant model routing (leave on automatic unless diagnosing); the “expiring soon” window in days; a read-only list of which API keys are configuredOn save
Material families are assigned, not guessed at display time. Since 22 September 2026, opening Admin → Materials applies the existing automatic suggestions and logs them. Curated-group copies added on 9 October 2026 require the Sinetti admin to review the sibling, Accept for that name, then save. The copy fills missing values; it does not replace an assigned short label or family. Names marked Keep in Other keep that family. Different assigned families in these new curated checks require a separate explicit review decision; the prior punctuation-based automatic behavior is unchanged. After saving, confirm the short label and family under All names; Admin → Materials shows your saved values at once, while Browse, deep-dives, Ask and the next customer package show them after the next data publish (SIN-1043: the data refresh prepares the map from your saved values and every release carries it; the app no longer changes it). Names that first appear in a refresh arrive in Other and in the review list with that release. If saving is refused, read the message and correct or discard the pending edits; unrelated materials and distinct grades still cannot share a label. Two rules from the domain: a name in brackets after a material is its origin or its acronym, never a spelling variant to merge away; and animal by-product categories are ordered grades (category 1 worst, 3 cleanest) that must never be merged.

Ownership second opinions. The Sinetti operator can recheck a bounded slice of pending, known-form ownership proposals once through the stored ChatGPT subscription login. The independent researcher sees the original form and parent, but no original sources, website or notes. The existing proposal note shows First and Second form/parent summaries and the outcome. Matching form and normalized parent need a new source domain and the existing source-trust checks before the row becomes provisional; Confirm/Revert stays the same. Disagreement, unknown, invalid or reused-source answers remain pending. Failed and interrupted attempts are also spent once. The original answer and complete second evidence remain in data/company_group_proposals.json; no new review control is added. The manual procedure is in docs/company-groups-runbook.md.

How a material name travels

Every register spells materials its own way, in its own language. Before a name reaches a screen, Sinetti takes it through the same steps, in the same order, as the strip at the top of Admin → Materials, then reads one more thing from it. Each step has one answer source. When a chip or a filter looks wrong, find which step gave the wrong answer — that tells you where to fix it.

You decide it on Admin → Materials Set in code — ask a developer

1 · Register names — where they come from

ISCCregister CSV + certificate PDF annex rows + audit reports
REDcertregister + certificate PDF annex ticks
INSItalian free text, e.g. Olio da cucina usato (UCO)
2BSvs · RSBregister free text (not read from PDFs yet)
LCFS · CFR · Oregon · BC · RenovaBiopathway feedstock and fuel names

Steps 2–5, in order — one example traced

code
2 · Spelling groups — are these spellings one material?
Registers write the same material several ways. Sinetti puts them in one spelling group and counts them as one material, keeping each original wording underneath.
Used cooking oil (UCO) used cooking oil(UCO) Olio da cucina usato (UCO)
→
Used cooking oil (UCO)spelling group · 3 spellings
  • Automatic, for every name: capitals, punctuation and spacing are ignored (rows 1–2).
  • Curated list, 25 groups: different words for the same material — Italian, acronym vs full name (row 3).
  • Never: a different origin or ABP category in brackets. Soapstock (Soybean) stays apart from Soapstock (Sunflower).
▼
Admin → Materials
3 · Families — which shelf does it sit on?
Colour, icon, filter group. Unplaced names wait in Other. A fuel goes to its fuel family, never to the feedstock in its bracket.
Used cooking oil (UCO) → family UCO
HVO (Used cooking oil (UCO)) → family HVO
▼
Admin → Materials
4 · Short labels — what does the chip say?
Display only. Empty = the full register name is shown; the full name always stays on hover. Names that share a label show as one chip on a holder card.
Used cooking oil (UCO) → UCO
▼
code
5 · Kind — product or feedstock?
Read from the words before the first bracket only (HVO, Biodiesel, Ethanol, SAF … = product).
UCO → feedstock  ·  HVO (…) → product

Where users see the result

Browse cards, list, Filters
3 family4 label
Feedstocks / Fuels deep-dives supply chain table, maps
3 family4 label
Holder page materials, pathways, dossier export
2 spelling3 family4 label
Geography deep-dive
2 spelling5 kind
Producers · Market monitoring · watchlist mail
3 family4 label
Daily digest · pathway change alerts
2 spelling
Ask Sinettiown word list (“animal fats” asks cat 1/2 or cat 3), then reads families
3 family

How much each step groups

16,5321 · register wordings
→
13,0152 · spelling groups
→
313 · built-in families
(+ your custom ones)
→
25 · kinds
product · feedstock
1,1214 · wordings with a short label

Short labels group too, but only on screen and only where a label exists. Names that share a label show as one chip on a holder card and one line in its materials list: 630 different co-processed oil names all read Copro oil. Families, filters and deep-dive counts ignore labels, so a label never moves a holder in or out of a result. That is why it sits beside the ladder, not on it.

Counts: material names on certificate pathway rows in the active release; labels from the local material map, 2026-10-08.

The same levels, one real example — each box sits inside the next

5 · Kind 3 · Family 2 · Spelling group Register wording (× pathway rows) chip4 · Short label — what the chip says
Feedstock
Family UCO 284 wordings · 179 spelling groups
Used cooking oil (UCO) entirely of veg. origin UCO (veg.)24 spellings · 3,324 rows
Used cooking oil (UCO) entirely of veg. origin2,396
used cooking oil (UCO) entirely of veg. origin428
Used Cooking Oil (UCO) entirely of veg. origin333
Used cooking oil(UCO) entirely of veg. origin31
+ 20 more spellings
Used cooking oil (UCO) UCO35 spellings · 2,248 rows
Used cooking oil (UCO)1,327
UCO294
UCO - Used cooking oil55
+ 32 more spellings
Refined oil (Used cooking oil (UCO) entirely of veg. origin) Refined oil (UCO (veg.))11 spellings · 157 rows
Refined oil (Used cooking oil (UCO) entirely of veg. origin)84
+ 10 more spellings
+ 176 more spelling groups
+ other feedstock families (POME, Animal fat cat 3, …)
Product
Family HVO 443 wordings · 359 spelling groups
HVO HVO1 spelling · 276 rows
HVO276
HVO (Used cooking oil (UCO)) full name3 spellings · 34 rows
HVO (Used cooking oil (UCO))26
HVO (used cooking oil (UCO))7
HVO (USED COOKING OIL (UCO)1
HVO (Food waste) full name3 spellings · 41 rows
HVO (Food waste)25
HVO (food waste)15
+ 1 more spelling
+ 356 more spelling groups
+ other fuel families (Biodiesel, Ethanol, SAF, …)

Read it inside-out. A register wording lands in a spelling group (step 2); the group sits in a family (step 3); the family is a feedstock or a product (step 5). HVO (Used cooking oil (UCO)) contains the words “used cooking oil” but sits in the HVO box on the product side — UCO is what the HVO was made from, not what it is. A UCO filter therefore does not return it. Wordings and row counts: certificate pathway rows in the active release; families from the local material map, 2026-10-08.

QuestionAnswer kept inWho changes itSymptom when wrong
2 · Spelling groupdata/models/material_aliases.jsonDeveloper, by commitSame material shown as two rows in Geography or a holder's pathways
3 · FamilyMaterial map + family list (Admin → Materials)AdminHolder missing from a family filter, wrong chip colour, name stuck in Other
4 · Short labelMaterial map (Admin → Materials)AdminChip shows a long or odd name, or two materials share one chip
5 · Kind (product or feedstock)Code word listDeveloperFuel counted as feedstock in Geography
Brackets are information, never noise. Soapstock (Soybean) and Soapstock (Sunflower) are two materials. Animal fats (category 1) and (category 3) are different grades. No question above ever merges them.

Counts and examples: certificate pathway rows and the local material map, 8 October 2026. Spelling groups and kind are shown read-only in Admin → Materials → All names (tick Group spellings; the Kind filter is next to Family).

A fuel is filed under its fuel family, never under the feedstock in its bracket. “HVO (UCO)” is HVO; “Biodiesel (UCO)” is Biodiesel. A UCO filter therefore returns UCO handlers, not the HVO and biodiesel producers that buy from them. Since 2026-09-24 the app enforces this at start-up: the first start after the deploy files every fuel-led name (biodiesel/FAME/UCOME, HVO/HEFA/SAF, ethanol, biomethane/bio-LNG, bio-LPG/naphtha/propane/butane, bio heating oil, biomethanol) under its fuel family and creates the three owner families Bio-LPG / naphtha, Bio heating oil and Biomethanol if missing; every later start only lifts fuel names still sitting in Other, so a family you set by hand is never overridden. Each move appears in the Materials change log. Rename or recolour the three families freely — the app creates them once and never rewrites them.
Straw, husks, wood residues and similar lignocellulosic byproducts have their own family. Since 2026-09-25 the app files the owner-approved list (Straw, Husks, Chaff, Grass, Wood chips, Wood chips from harvesting, Forestry processing/forest-biomass residues, Damaged trees, Fruit tree cuttings, Nut shells, Bean shells, Grape marc(s)/wine lees, Agricultural harvesting residues, Shells/husks: soy hulls, Non-food cellulosic materials from food/forage crops, Unused feed/fodder from ley) into Agri & forestry residues at start-up if they are still sitting in Other, and creates that family if missing. Every later start only lifts names still in Other, so a family you set by hand is never overridden. Names the owner marked uncertain at the time (an ambiguous "Sugar beet residues", "Darnel", "Intermediate crop") were deliberately left out — place those yourself if you agree with a reading.
After a deploy that introduces a new built-in family, nothing is assigned to it — production keeps its existing material map, and nothing errors. Run the seed-assignment script against production's state (dry run first): python scripts/apply_seed_family_assignments.py, then --apply. A dry run reporting zero changes is the proof it is done.

How a company name travels

Where it is decided: Admin → Data Trust (Entity identity review, Related companies review) · the quarterly company-groups job · the rest is set in code and read-only. Real Neste example; counts as of 8 October 2026.

The same company is printed in a dozen ways across the registers, and companies belong to families. Sinetti works through five questions in a fixed order. The first three decide whether two names are one company. The last two place different companies in a family. Each question has one answer source — when a card, a related list or a header looks wrong, find which question gave the wrong answer.

You decide it, or can reverse it, in Admin Set in code — ask a developer

1 · Register names — where they come from

ISCCname and address in one string: Neste Oyj, Keilaranta 21, 02150 Espoo, Finland
REDcert · INS · 2BSvs · RSBname only, e.g. NESTE Oyj
LCFS · EPA · CFR · Oregon · BC · RenovaBioname, usually with a structured address
Research filesgroup, ownership and links we recorded from public sources

Steps 2–6, in order — Neste traced

code
2 · Holder key — is it spelled the same?
The name before the first comma, lower case, letters and digits only. Registers that spell a name identically share a key, so the join across registers is already done here.
Neste Oyj, Keilaranta 21, …NESTE OyjNeste Oyj
nesteoyjISCC · INS · EPA · LCFS
  • Never edited. Watchlists, alerts and every override are keyed on it.
▼
Data Trust
3 · Legal entity — are two different keys one legal person?
Same legal person, spelled differently by the registers. Joined into one entity that keeps every original name.
nesterenewablefuelsBC · “Neste Renewable Fuels”nesterenewablefuelsoyLCFS · “…Oy”
Neste Renewable Fuels Oyautomatic · BC left the legal form out
nesteoilsingaporeBC · “Neste Oil Singapore”nestesingaporepteltdEPA · INS · ISCC · LCFS · Oregon
Neste Singapore Pte Ltdowner decision 22 Sep 2026 · earlier name, same Tuas plant · 6 registers
  • Automatic: same name apart from the legal form one register leaves out or spells differently, or the same full legal name with no conflicting address.
  • You: Entity identity review — MERGE or DO NOT MERGE for what the rules would not join on their own (a renamed company, the same plant under an old name). Takes effect at the next data rebuild.
  • Never: a holding company with its operating company, or a parent with its subsidiary. Those are step 4.
▼
Data Trust
4 · Related companies — different legal entities, one family?
Never merged. Found by a shared address and shared name stem, a shared street, or a holding company naming its operating company. Shown to customers only once confirmed.
Bioro NV ↔ Cargill NV — same street in Ghent · confirmed “Cargill — Ghent site”
  • You: Related companies review — CONFIRM RELATED (with a group label), SAME ENTITY or REJECT.
▼
quarterly job · reversible via API
5 · Corporate group — more than 50 % under one parent?
Researched from public sources, with the source recorded. Members become related pairs automatically.
Group “Neste” · parent Neste Oyj · 21 holder keys → 19 legal entities
  • Not members: joint ventures and minority stakes. They stay labelled links (box below).
  • You: nothing to click each quarter. A wrong placement is reversed through the admin API — there is no screen for it yet (procedure Review or reverse an autonomous company correction) — and takes effect at the next rebuild.
▼
same job
6 · Ownership line — listed, private, state-owned?
One fact per ultimate parent, shown under the address. A subsidiary reads “Listed group · Subsidiary of Neste Oyj”.
Neste Oyj → Listed · Nasdaq Helsinki · NESTE

Where users see the result

Browse cards in the World view or with 2+ registers picked: one card per legal entity. With one register picked: one card per holder key.
2 key3 entity4 related5 group
“· N related” counts confirmed related companies in force today
Browse map
2 key3 entity
Holder page name and aliases · ownership line · Related companies · Production units
3 entity4 related5 group6 ownership
Production units pools every plant of the group, joint ventures at the recorded share
Watchlist · alert mail
2 key3 entity
A watch follows the whole entity once it is confirmed across registers
Dossier export
3 entity4 related5 group
Ask Sinettimatches holder names across registers by name tiers
3 entity
Research data switch Admin → Research → Coverage. Hidden removes research-confirmed related companies, the ownership line and Production units from customer screens. Your own pair decisions (step 4) and same-company joins (step 3) stay.

How much each step groups

40,5102 · holder keys
(after the spelling join)
→
40,2643 · legal entities
236 fuse 494 keys
→
9665 · corporate groups
holding 3,448 holder keys
→
3486 · ultimate parents with an ownership line
137 listed · 172 private · 18 state-owned · 18 cooperative · 3 unclear
625labelled links
(JV, minority, …)

Related (step 4) and links sit beside the ladder, not on it. A related pair or a link never merges two companies and never changes a count; it only adds a line on the holder page and a number on the card. Counts: entities from the active release on this desk; groups, links and ownership from the committed decision files, 8 October 2026.

The same levels, one real example — each box sits inside the next

5 · Corporate group 3 · Legal entity 2 · Holder key Register wording
Group “Neste” · parent Neste Oyj · Listed · Nasdaq Helsinki NESTE  · 21 holder keys · 19 legal entities · 7 registers
Legal entity NESTE Oyj1 holder key · 36 register rows · confirmed across 4 registers
nesteoyjholder key
Neste Oyj, Keilaranta 21, 02150 Espoo, FinlandISCC
Neste Oyj, Jalostamontie 36, 06850 Kulloo, FinlandISCC
NESTE OyjINS
Neste OyjEPA · LCFS
Legal entity Neste Singapore Pte Ltd2 holder keys fused · 67 register rows · 6 registers
nestesingaporepteltdholder key · 27 rows
Neste Singapore Pte LtdEPA · LCFS · Oregon
Neste Singapore PTE. LTD.INS
Neste Singapore Pte Ltd, 1 Tuas South Lane …ISCC
nesteoilsingaporeholder key · 40 rows
Neste Oil SingaporeBC
Legal entity Neste Renewable Fuels Oy2 holder keys fused · 10 register rows
nesterenewablefuelsoyholder key
Neste Renewable Fuels OyLCFS
nesterenewablefuelsholder key
Neste Renewable FuelsBC
+ 16 more legal entitiesNeste Netherlands B.V. · Neste Germany GmbH · Neste US Inc. · Neste Terminal Rotterdam · NEWPORT BIODIESEL INC · UAB Neste Lietuva · …
each has its own holder key and register wordings

Read it inside-out. A register wording lands in a holder key (step 2); keys that are one legal person share an entity (step 3); entities under one parent form a group (step 5). Neste Oil Singapore and Neste Singapore Pte Ltd are two keys but one company — so a watch on either follows both. Neste Netherlands B.V. is a different company in the same group — a watch on it does not follow Neste Oyj.

Certification bodies run a separate, shorter chain

A CB is identified by legal entity, never by brand. The brand only groups entities for search and display; no number is ever computed at brand level.

SGS Germany GmbH, Emstek, GermanySGS Germany GmbH, Emstek, DE238 distinct strings on certificates
SGS Germany GmbHCB legal entity · home country DE · 153 in the register
SGSbrand · 15 in the register

Neste Oyj's ISCC certificates read SGS Germany GmbH (DE); its INS certificate reads SGS ITALIA SPA (IT): two CBs, one brand. DEKRA Certification GmbH, …B.V. and …Sp. z o.o. are three CBs. A bare “SGS” stays unmapped — an owner decision of 26 July 2026. Set in code (data/cb_reputation.json). Users see it in the holder dossier and under Market intelligence → Certification bodies; reputation grades are admin-only.

QuestionAnswer kept inWho changes itSymptom when wrong
2 · Holder keyComputed from the name (certlogic.holder_norm)Developer — changing it re-keys every watchlistOne company on two cards when a single register is picked
3 · Legal entityRules in code + owner decisions in data/entity_identity_overrides.json (92 joins, 4 separate)Admin → Data Trust → Entity identity review; next rebuildOne company on two cards in the World view — or two companies on one card
4 · RelatedRules in code + the decisions in data/related_company_overrides.jsonAdmin → Data Trust → Related companies reviewMissing, or wrong, “· N related”
5 · Corporate groupgroups in data/related_company_overrides.json (966)Quarterly job; owner. Reverse through the admin API (no screen yet)A subsidiary missing from the related list; plants missing from Production units
6 · Ownership linedata/company_ownership.jsonSame job; ownerHeader shows no ownership line
Labelled linksrelations in data/related_company_overrides.json (625)Same job; ownerA joint venture shown as a group company, or not shown
Certification bodydata/cb_reputation.jsonDeveloper, by commitTwo spellings of one CB counted twice
Same company, related and group are three different things. Merge two names only on evidence of one legal person. A holding company over several operating companies stays related — merging it with each would chain them into one. A group needs more than 50 % under one parent: joint ventures and minority stakes stay labelled links. Never merge a parent with its subsidiary to tidy a list.

Procedures

Review or reverse an autonomous company correctionAdmin API · operator report

Corrections to existing memberships and duplicate groups keep the more-than-50% ultimate-parent rule. Every new autonomous decision stores its sources, supporting facts, reasoning, actor, model, policy, time and complete before/after groups. Uncertain cases are saved as deferred. The journal does not add evidence retrospectively to older job decisions.

  1. The Sinetti admin uses an authenticated API session: GET /api/admin/data-trust/company-resolutions?limit=50&offset=0 lists newest events first (maximum 200 per page); GET /api/admin/data-trust/company-resolutions/<event_id> opens full evidence. There is no new history screen yet. The local operator can export all decisions and reversals with python scripts/company_resolution.py --history --report history.md.
  2. To reverse an applied decision, the Sinetti admin sends POST /api/admin/data-trust/company-resolutions/<event_id>/revert with {"rationale":"Reason for the correction"}. The actor is the signed-in admin. Success returns ok: true, a linked reversal event and applies: next_rebuild. The original evidence stays intact.
  3. A 409 means an affected group changed since the original decision; a 422 can mean a current Admin rejection prevents restoration. Review the latest evidence and prepare a fresh proposal. Do not overwrite later work or edit the journal. Repeating a successful reversal returns the same receipt.
  4. The Sinetti operator preserves the changed code-owned override and history report in the normal commit/merge workflow, refreshes registry context with scripts/company_registry_review.py, then follows the company-layer rebuild and restart procedure. A hosted API reversal must also be preserved in the code-owned source before a later deploy. Customer links change on the next successful rebuild; a receipt alone does not prove merge or deployment.

Verified against the journal service, Admin routes and CLI, 27 September 2026. Proposal format, isolated-checkout apply, exact paths and recovery: docs/company-groups-runbook.md → Reviewing and reversing autonomous corrections. These commands make no research-provider calls.

Weekly: review new material namesScreen · 10 min
  1. Admin → Materials opens on To do and applies suggestions before you click. Read the note that says what it applied. What remains is names with no suggestion: pick a Family, add a Short label where a chip reads badly, then Save (Review impact first if families moved). The auto-apply log is GET /api/setup/material-map/auto-log.
  2. For names that genuinely belong nowhere, Keep in Other removes them from the list. Since 2026-09-25 this choice is saved on the server (Admin → Materials "kept in Other" list), not in the browser — it follows you across devices and survives a cleared browser profile, and it also keeps that name out of the daily admin mail's material-backlog row.
  3. All names → New since last review shows names that appeared since you last looked; Mark all reviewed once you have. A start-up guard (2026-09-25) re-baselines this list once if it ever holds under 10% of the corpus — e.g. after a data reset — so a stale or empty seen-list never flags the whole corpus as "new" in one go.
Decide an entity-identity pairScreen · 2 min each
  1. Admin → Data Trust → Entity identity review. One table: both legal names, in-force state, flags, and why the pair was flagged.
  2. Merge only when you would defend “same legal entity” to the customer: same registration, not merely same group or same street. Sister companies belong in Related companies, not here.
  3. Write the Decision reason (what evidence), press MERGE or DO NOT MERGE. The grouping changes at the next data rebuild.

11Knowledge and law

Where: Admin → Ask → Knowledge · Admin → Data ops → Sources · Admin → Ask → Regulatory monitor

In short

Ask Sinetti answers from two things: the register data, and a library of documents — scheme rules, EU and national law, guidance — that it can quote with a link. That library is built on the operator machine and ships inside the app, so a new document reaches customers only with the next deploy. ISCC and REDcert documents are checked automatically every week and regulation every four weeks; new or changed files are picked up and indexed without anyone adding them by hand. Separately, a daily monitor watches the laws behind Sinetti's country logic and opens a review case when an official text changes; it never changes any calculation by itself.

ScreenWhat it showsActions
Ask knowledgeDocument library inventory: title, categories, chunk count, published version, download date, link to source or “private copy” (cited without a link). Availability in Ask also depends on source classification and the caller’s scheme access. Groups: ISCC, Canada, REDcert, Nabisy, Other.None — read-only. ISCC and REDcert documents are refreshed by the weekly scheme-document refresh; other documents are added on the operator machine.
SourcesEvery public endpoint Sinetti pulls from, with when Sinetti last pulled, when the source last published, and the schedule tag (on schedule / overdue / manual)None — jump links
Regulatory monitorHealth per country, the review queue, and for each case the exact before/after text with the official source and a fingerprintCheck now; record a decision; link an implementation

A regulatory case moves New → Reviewing → Action required / No action / Superseded, and from Action required to Implemented once you link the revision that changed Sinetti. Every step needs a written reason; the evidence is immutable.

Dated watches (procedure verified 2026-09-26). The same morning task also checks a short list of pages that are not due every day: a URL, the first day to look, and the text that counts as news. The list covers France and the other mandate countries (Germany, the Netherlands, Belgium, Italy, Spain, Sweden, and Finland). A hit is an ordinary review case. It does not change a mandate rate. The runbook section "Dated watches" names each page, the signal, and the window.

North American watch (procedure verified 2026-09-26). The daily monitor checks California LCFS, EPA, Canada CFR, Oregon and BC registers, plus official rules and guidance indexes. It compares content before parsing and verifies downloads at least weekly. The legal action email carries legal-source review cases and source failures. A separate Register monitor email groups pathway/register value changes, data quality or extraction changes, uncertain differences, and register check failures. It names each register in plain words, describes each observation in full once, folds Sinetti-side row renames (the register did not change) and re-issued pathways into single blocks, counts large changes instead of listing them, and then keeps unpublished observations as one line each under “Still waiting for a data release” until the active data release was built after them. Parser enrichment is not automatically a publisher change. “Not yet in a data release” is not confirmation of live publication: verify the source, use Data ops → Refresh now, review the candidate, then follow the publication workflow. Rules/index alerts never change legal interpretations automatically.

Check <state>/refresh/north_american_monitor/latest.json for source outcomes and events.jsonl for before/after evidence. A failed check is unknown, not unchanged; a receipt older than 30 hours is overdue. Keep the last good baseline during recovery. A British Columbia Laws line that only says the act or regulation is current to a new date is not a review case. Gateway timeouts (HTTP 502, 503 and 504) and a dropped connection are tried again up to three times inside that request's time budget. A source that failed yesterday is checked again the next morning, even when the failure was after 06:30. The daily legal action mail keeps naming a legal source whose latest check is still a failure, and an open legal case stays until marked. The separate register mail keeps an observed difference eligible while a refresh is pending and the latest check has not read the file unchanged. If a change is uncertain, compare the official source and full stored evidence before treating it as a business update. A 06:30 run that never starts is named by the 05:30 job monitor when the last receipt is missing or older than 30 hours. EUR-Lex discovery allows 90 seconds. A publisher that keeps failing stays a visible failure. Oregon's Secretary of State rules page rejects this host; leave that source visibly failed and retry on the normal schedule. The Oregon DEQ program index does not replace the rule text. An official https page with no law-allowlist probe is on the weekly check; a change opens a review case and does not write an optimizer value. Drafts, discovery feeds that are not yet proven, and sources with no official URL stay off that check. Re-register with scripts/register_regulatory_monitor_task.ps1 after updating the operator checkout; the task action must include --include-registers. This task needs the operator machine online. Merged code is not cloud monitoring. The runbook covers verification, retry and the separate cloud-authority option.

Agreement coverage in Ask

Ask applies the caller's scheme access before reading or counting evidence. This applies to signed-in callers, including administrators. The agreement must permit every scheme covered by a mixed source. Sources without a reliable scheme classification are excluded. An explicit request for an excluded scheme receives the line “X is not in your plan”; Ask can still answer the permitted part.

Until their sources are classified, Ask cannot provide material-label lookups, RED default values, trade codes, fuel conversion and specification facts, or market, mandate and GHG-value calculations through its reference tools. A document listed in Knowledge is therefore not necessarily available to that caller.

Ask also omits holder production units and Certified Sites reads because they include unclassified capacity evidence. This applies to every signed-in caller, including administrators with access to all schemes. The ordinary holder pages and Browse maps remain available.

For callers with limited scheme access, Ask also omits saved views, monitor sets and activity history whose mixed content cannot be reliably scoped, plus the shared briefing and product vocabulary. Nothing is deleted. Reduced coverage is intentional; an unavailable source does not mean the underlying information is absent.

Questions where Ask found nothing good enough to cite are logged on the server (the “miss log”). That list is your shopping list for documents worth adding.

Procedures

Handle a regulatory review caseScreen · 10 min
  1. Admin → Ask → Regulatory monitor. Check Last successful check is this morning; a stale time means the daily task did not finish.
  2. Pick a case in the Review queue. Compare Previous accepted observation with Current official observation; open the Official source.
  3. Write the Admin decision reason — the legal or market reasoning — and press Reviewing, Action required, No action or Superseded.
  4. When the change has been built and merged: enter the Revision and an Implementation note, Link revision.
  5. To re-check one country or instrument now: choose the Check scope, Check now. Overlapping requests reuse the run already in progress.
Refresh the document library by handDesk · 15 min · small embedding cost
  1. See what changed without downloading: python scripts/refresh_law_docs.py --probe-only
  2. Refresh (downloads only changed documents, updates the index incrementally): python scripts/refresh_law_docs.py
  3. Run the retrieval check. Never lower its threshold to make it pass: python scripts/law_rag_smoke.py
  4. Commit the changed index files (knowledge/_manifest.json, knowledge/law_probe_state.json, rag_index.npy, rag_index.jsonl, rag_catalog.json). The documents themselves stay out of git.
  5. The new library reaches customers at the next deploy (§13).
Rebuilding the index calls a paid embedding service: cents for an incremental update, a few dollars for a full rebuild. Live evaluation of Ask answers is a separate, paid run that needs an explicit decision each time, with the scenario count and cost ceiling stated first.

12Email and Ask safety

Where: Admin → Data ops → Email previews · Admin → Ask → Safety · Space secrets

In short

The app sends four kinds of email: invitations and password resets, watchlist alerts to members, a daily brief to you, and operational alerts from the monitors. On Hugging Face only one transport works — the mail provider's web API — because ordinary mail ports are blocked there. If it is not configured, the app does not fail: it quietly runs in dry-run, which looks like success and delivers nothing. Ask safety is a small inbox of questions that tripped the assistant's abuse rules.

EmailToSent whenSafeguards
Invite · password resetThe personYou invite; they ask for a resetLinks are single-use and expire (7 days · 1 hour). Reset links are never shown to an admin.
Watchlist alert / digestMembers who opted inA watched holder changes (immediately or as a digest, the member's choice)Re-checked at send time: account active, holder still on their watchlist, register still in their plan. One delivery per event.
Market daily summaryMembers who ticked “Email me” on a Market listAfter the nightly data refresh, on days with changes on their listsOne email per person per day: each change appears once and the person's matching lists are named at the top. Nothing on quiet days. Sent only to a verified address; a member proves an address in Settings → Notifications with a six-digit code mailed to it (10 minutes, one use). Invited accounts are verified already. Every send, and every failed send, is recorded and listed on Admin → Customers → Email delivery.
Admin daily briefYouAfter the nightlyPreviewable on Data ops. An error that kept coming back is named: where it happened, and the first line of the error. A server error is the exception type plus the route — the full exception text is not stored. One-off errors are listed the same way and do not by themselves make the brief say ATTENTION.
Operational alertsYouA refresh or review is missing or failed; a regulatory case opened; a new access requestOne alert, then one recovery — never a stream

Settings the mail needs (Space secrets): INFOMANIAK_API_TOKEN, SINETTI_INFOMANIAK_MAILBOX_UUID, SINETTI_EMAIL_FROM, SINETTI_PUBLIC_BASE_URL (the address put into invite and reset links — wrong value, dead links), and SINETTI_ADMIN_EMAIL / SINETTI_ACCESS_NOTIFY_EMAIL for mail addressed to you.

Dry-run is the trap. With no transport configured, an invite reports “Invite created (email dry-run — copy the set-password link)”. Treat that phrase as an incident, not a convenience.

When a customer questions an alert (“this certificate is not withdrawn”), check two sources before answering: the delivery record with the ordinary certificate row, and the register's own compliance status. The ordinary row keeps its original dates even after the scheme suspends or withdraws the certificate, so either source alone can mislead. A voluntary termination (the holder left while the certificate was still valid) is not a red flag and is not in the default Flags priority or the watchlist alert mail. The daily subscriber digest lists each one in its own section, under the red flags, and says the exit was voluntary and is not an enforcement action. It also names a new certificate for that holder when one starts within three days, or when the holder comes back within 90 days. That section is not counted in the red-flag subject. The admin overnight brief lists the same exits. INS, California LCFS, EPA, Canada CFR, Oregon, and BC land on the same Flags page and in the same daily digest; each weekly refresh still keeps its own staging log. The Saturday US mail is the operator brief only. Withdrawn is the authority cancellation. Delisted is Sinetti's own inference that a certificate disappeared between snapshots.

Watchlist digests draw the holder's country flag and the scope and material chips, and a reinstatement says which end-state the certificate came back from.

Ask safety

Question retention and customer purge. Permanently deleting an organisation also deletes its Ask abuse incidents, export cohorts and organisation-tagged retrieval-miss entries through the guarded customer purge (section 4). Other organisations' records remain. Abuse incidents expire 180 days after creation, including dismissed incidents; repeated hits do not extend that deadline. A hit after the deadline starts a new incident with its own 180-day lifetime. Alert deduplication stays within the same organisation. Export cohorts remain usable for one hour. A cleanup worker runs when the app starts and hourly thereafter: it deletes expired cohorts, expired abuse incidents and retrieval misses at 90 days. Miss entries retain up to 500 characters of question text for diagnosis and carry the organisation resolved by the server.

Where a hit came from. The abuse rules read the typed question and also the page context the browser sends and the earlier turns it sends back. A hit outside the question shows its source as a prefix on the rule name, page_context: or history:, in the Safety inbox and in the alert email. A high-severity hit in any of the three is refused the same way and a watch hit is only recorded. Document passages, tool results and page context also reach the model inside labelled data blocks that it is told never to obey; that needs no admin action.

Older retrieval misses without an organisation tag cannot safely be attributed to a customer: an organisation purge leaves those rows for their 90-day expiry. Rows with missing or invalid dates require operator review and are preserved. If cleanup reports ask_retention_failed in server logs, fix access to the named store; the next hourly pass retries it. Before treating a customer purge as complete, confirm all three Ask categories completed in its deletion receipt. A failed category requires the existing preview/Resume deletion procedure; never clear a shared log manually.

Questions that match abuse rules, or sessions that hit the tool-call ceiling, open an incident. Alert emails never contain the question; the full text is readable only on this screen. There is one action: read, optionally note, Dismiss. Repeated incidents from one member are a conversation with the customer, and a reason to consider Disable (§4).

13Shipping a release

Where: operator machine — scripts\release.ps1 · the deploy desk C:\Users\azizl\sinetti-deploy

In short

A release is one exact commit, tested whole, approved by a named person for a stated reason, and only then published. One command drives it and can be re-run at any time: each run does the next safe step and stops. The long test run happens in the background, so nothing depends on a terminal staying open. Publishing replaces the app's code only — never the data, never customer state — and if production does not come up healthy on the new version, the previous version is restored automatically and the attempt is written down.

Scope checkno mandates Preflightfast checks Pin deskexact commit Release gatebackground APPROVE Dry runtouches nothing LIVE Publishto production first run does these four, then returns at once re-run to poll · 45–90 min what Publish does Upload · 1 commit Restart app New version seen≈ 8 min · max 15 Data ready Signed-in smoke Approval used any failure → previous version restored + receipt
Two human decisions, asked for separately: Approve records who and why and proves the release with a dry run; Live publishes. Passing -Live without an approval does nothing. The outcome of every live attempt is one of four words — published, refused, rolled_back, failed — written to a receipt, and the receipt, not the terminal, is the truth.

What the release gate runs

#StageProves
1Repository hygieneNo whitespace damage or conflict markers
2Runtime manifestEvery file the app imports at runtime is in the shipment; scheduled-job scripts too
3Identity and offboarding safety acceptanceLogin, MFA, session revocation, offboarding, purge still hold — the reason rollback has a boundary
4Python testsThe full backend suite — the long one
5–7Frontend typecheck · tests · production buildThe interface compiles, passes, and builds
8Browser smokeA real browser logs in and walks the built app

The gate refuses to start on a dirty folder, refuses if another gate is running, and fails if the commit or folder changes underneath it. A stage whose inputs are byte-identical to an earlier green run may be inherited instead of re-run; the status shows which stages ran and which were inherited.

Rules that bite

  • Once a commit is pinned, never chase main. New work landing meanwhile is for the next release. Change the candidate only if you name another commit yourself, or to repair this exact candidate.
  • The approval is single-use and is spent before publication. A live attempt that fails and rolls back has still used it; the retry needs a new approval id. This is what stops a replay.
  • -Exclude consumption,mandates is an assertion, not a filter. If the candidate touches those areas the workflow refuses before pinning anything. The fix is a candidate without those commits — never dropping the flag. Independently, the consumption code is left out of the shipment.
  • Publishing ships code and the document library, not data. A data change goes live through §8; a new built-in material family needs its seed step (§10).
  • Never roll back past a used security control (§4).
  • The footer release mark. Every user sees the code release and the data date (for example v13 · data 23 Oct) in the left column of the app footer; on a phone the version sits left and the data date right, either side of the centred links. Nothing shows on a desk or dev server. For an admin the mark also shows the data publish number (D155: the live data release’s position among all publishes, since several can land on one day) and is a button: it opens a small panel with the code release and commit, the data publish number and revision, and when each register was last read (Zurich time), with “Copy for support”. The old CalVer names (2026.10.13) display as their running number (v13). The name comes from -ReleaseName at publish; tag the published commit v plus that name afterwards, and python scripts/deploy_state.py warns if they differ.
  • Never run the gate inside a chat or an awaited shell — if the window dies, orphaned test processes remain. Always detached, then poll.
  • The 03:00 nightly suite is the gate. It runs scripts/release_check.py --no-resume --keep-going in ..\sinetti-nightly, so one red stage does not hide the later ones, and a green stage still leaves a proof. A night another gate already holds the lock is skipped, not a failure and not a pass. The 05:30 mail includes a “PC daily test run” section when the newest verdict is failed or older than 48 hours. Runbook: docs/nightly-suite-runbook.md.
  • A reboot mid-gate is resumed, not abandoned. Task “Sinetti PC 15-min resume interrupted gate” runs scripts\resume_interrupted_gate.ps1 every 15 minutes and catches up after the machine comes back. Two resumes are allowed; a third mid-stage kill stops as gate_failed. Re-register with scripts\register_resume_interrupted_gate_task.ps1 after changing the launcher. The task has no “run at logon” trigger.
  • Secret and dependency scan. Before a pilot release, run python scripts/security_check.py as in docs/security/security-scanning-runbook.md. The scan covers every tracked file in an isolated snapshot of the pinned commit. A pass that only looked at the latest diff is not evidence. Offline mode is ineligible for release.

What publishing needs on the operator machine

In the deploy desk's .env: a Hugging Face write token (HF_TOKEN), SINETTI_DATA_REPO_ID (so the record that an approval was used survives a rollback), a login for the smoke test (SINETTI_SMOKE_USERNAME / SINETTI_SMOKE_PASSWORD), and the map key for production builds (VITE_MAPTILER_KEY_HF). Any of these missing → refusal before anything is built.

Procedures

Ship a release with the resumable commandDesk · 1–2 h elapsed, 10 min of attention
  1. Choose the commit and get its full 40-character id (git rev-parse main). Work from a folder checked out at exactly that commit.
  2. Start. This verifies scope, runs fast checks, claims and pins the deploy desk, starts the gate in the background, and returns.
    powershell -ExecutionPolicy Bypass -File scripts\release.ps1 `
      -Sha <40-char-commit-id> -Baseline main -Exclude consumption,mandates
    phase gate_running. A refusal prints REFUSING: … and changes nothing.
  3. Poll whenever you like — either repeat the same command or, to look without advancing:
    powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Sha <40-char-commit-id> -Status
    eventually gate result: passed and phase awaiting_approval. gate_failed means nothing was published; read the log under .worktrees\_gate-logs\.
  4. Approve. This writes and commits the approval on the deploy desk (that file and nothing else), runs the pre-release Trust Review decision as Proceed, then performs a dry run.
    powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Sha <40-char-commit-id> `
      -Exclude consumption,mandates -ApprovalId release-20260919-01 -Reviewer <your-admin-id> `
      -Reason "What this release is for, in plain words" -Approve
    phase dry_run_passed.
  5. Go live — same line plus -Live and the release name -ReleaseName — the next running number, for example 14 (no leading v; without it the command refuses):
    powershell -ExecutionPolicy Bypass -File scripts\release.ps1 -Sha <40-char-commit-id> `
      -Exclude consumption,mandates -ApprovalId release-20260919-01 -Reviewer <your-admin-id> `
      -Reason "What this release is for, in plain words" -Approve -Live -ReleaseName 14
    after about 10–20 minutes, phase published and a line deploy receipt: ….
  6. Verify yourself: open the app, check /api/ready is ready with the expected data revision, click through Browse and one Admin screen.
  7. If the phase stays publishing, the outcome is ambiguous — the command could not match a receipt to this approval. Do not retry blindly: open the newest file in the deploy desk's data\refresh\deploy_provenance\ and read outcome and observed_oid first.
Read a deploy receiptDesk · 2 min
FieldMeaning
intended_shaThe commit you meant to publish
observed_oidWhat production was independently seen to be running afterwards. Empty only when observation failed — and then the outcome can never be published.
outcomepublished · refused (stopped before anything external) · rolled_back (went out, failed a check, previous version restored) · failed (needs investigation)
release_check_receipt_sha256Fingerprint of the gate result this publication relied on
original_error, space_run_log_tailWhy it failed, captured before the restart destroyed the log
The same release, step by step (for diagnosis)Desk
powershell -ExecutionPolicy Bypass -File scripts\pin_deploy_desk.ps1 -Sha <commit> -Provision -NpmCi
powershell -ExecutionPolicy Bypass -File scripts\run_release_check_detached.ps1 -Worktree C:\Users\azizl\sinetti-deploy -ForceKillOrphans
powershell -ExecutionPolicy Bypass -File scripts\poll_release_check.ps1 -Worktree C:\Users\azizl\sinetti-deploy -Tail 30

Poll exit codes: 0 still running · 1 passed · 2 failed · 3 no gate found for this desk. (Yes — 1 is the good one.)

14Health, monitoring and alerts

Where: Admin → System health (/admin/system-health) · /api/health · /api/ready · your inbox · external uptime service

In short

Two public addresses tell you how production is. /api/health only says the process is alive. /api/ready says whether it is fit to serve: data valid, storage writable, admin identity configured — and answers HTTP 503 when not. Watchers that can email you: an outside uptime service probing /api/health, the job monitor (a missed refresh or review, a repeated application error, the daily workload), and the regulatory monitor (a law changed). The application-error mail depends on the daily local sample; a burst earlier in the day can be missed until an opt-in catch-up is run.

Using System health

Open Admin → System health to inspect the app that is running now. The page reads /api/ready when opened and again only when you choose Check again; there is no continuous refresh. A degraded HTTP 503 still carries the readiness details. Serving data, writable persistence and administrator identity determine the overall readiness result. Entity links, corpus loading and frontend build information are useful diagnostics but do not decide that result. Expand the saved-data stores to inspect every returned check; the list opens itself when a store reports a problem. Treat a check as Healthy only when it explicitly passes. Unknown means the response lacks reliable evidence; Unavailable means the request failed. An unavailable frontend modification time does not prove the interface is fresh. After a failed check, the previous result is not current evidence.

Running versions shows the revision reported by this running app and the active data release. An absent code revision reads Unknown; bundled data without a revision has no recorded release ID. Copy the code revision only when one is known. Open Data ops takes you to the serving-data and release workflow; System health itself does not promote or roll back data.

Recent errors is a separate 30-day history. Filter locally between app-server and browser reports, then expand a group for its recorded type, count, first and last seen, and safe support reference. An error recorded yesterday does not prove a current fault or its cause; green readiness does not prove the error was resolved. An empty history means the ledger was read and no groups were found. If the error history is Unavailable, do not interpret it as zero errors. Readiness and error history load independently, so either can remain useful when the other cannot be reached. The administrator-only metadata endpoint /api/admin/ops/system-health returns safe grouped identifiers and the running revision; it excludes raw event text, customer paths, stack traces and credentials. A ledger-read failure returns ok: false with a safe message and errors: null, while a successful empty read returns ok: true with errors: [].

Reading /api/ready

BlockHealthyUnhealthy → likely cause
statusready (HTTP 200)degraded (HTTP 503) — one of the next three is false
dataok: true — the serving data passed validationvalidation: critical + reason → the served database failed validation. release.source: bundled identifies the bundled fallback; review the release reason and §8 separately, even if data validation passed
persistenceevery check okunavailable → storage not mounted or file missing. schema incomplete → a migration did not finish. contended → busy; only a fault if it persists
identityok: trueadministrative_identity_degraded → administrator sign-in configuration is not operational (§2)
frontendstale: false with a known bundle and usable modification timesInformational only; never makes the app degraded. mtime_unavailable means freshness was not established. On the operator machine, compare the reported folder and bundle with the desk being checked

Who watches what

WatcherRunsEmails you whenDoes not cover
External uptime probe
UptimeRobot, free plan, five minutes
Outside Hugging Face/api/health stops answering 200 “ok”A live app serving wrong or fallback data. Simulated DOWN and UP emails were received on 25 September 2026. It does not prove an application-error email.
Job monitorOperator machine, daily 05:30A register's refresh or its review is missing past its allowance (ISCC/REDcert, and 2BSvs/RSB after activation: 1 day + 6 h · INS: 7 days + 12 h), or ended unusable; a PDF queue grows for several nights; a burst of application errors; the regulatory monitor receipt is missing or older than 30 hoursAnything when the machine is off
Regulatory monitorOperator machine, daily 06:30An official text changed, including an official https page that has no law-allowlist probe, and the open case stays until it is marked, with the previous and current lines; a source whose latest check is still a failure, until a later check succeeds; weekly health on Mondays. The separate Register monitor email covers register observations and check failures with its own receiptA run that never starts. The 05:30 job monitor names that
Nightly test suiteOperator machine, daily 03:00Does not send its own mail. The 05:30 job monitor names a failed verdict, or one older than 48 hoursA skipped night (another gate was running) is neutral

The job monitor sends one alert per incident and one recovery message when a genuinely newer good run arrives — never a stream. Do not close an incident by hand: fix the job and let the next good run close it. A mail titled “PDF queue is still growing” means the nightly parse cap is not shrinking that scheme’s queue. The register refresh can still have succeeded. The mail names the last night the depth fell, how many certificate PDFs that night parsed, and what is still waiting (new documents, an older parser version, earlier failures, and ISCC audit templates the parser now recognises).

Its 05:30 slot is deliberate: the evidence it reads is the copy mirrored to the machine at 03:00, after the nightly data refresh has finished, so it normally shows the same night. When that mirror run was skipped or the machine was off, the copy is the previous night's, which stays inside its allowance only until about 09:00 — keep the task between roughly 04:45 and 09:00 or a skipped mirror raises a false “missing”. If a reminder disagrees with the Hugging Face jobs page, start “Sinetti PC daily HF sync” by hand before believing it. Since 21 September 2026 the monitor first pulls a fresh copy itself (about 15 seconds), so it normally reads the same night even when the mirror task ran early or was skipped.

Since 23 September 2026 the monitor mails you every day. The mail opens with Workload to clear: how many certificate PDFs are still to parse per scheme (ISCC, REDcert, INS) and how many ISCC audit reports, each split into parser work, review and not downloaded, with what changed since the previous count. Since 25 September 2026 a fourth row, Material names awaiting a family, counts the register material names still sitting in Other on Admin → Materials that carry certificates (split into 5+ certificates and 1–4), with how many arrived and how many you cleared since the previous mail — every refresh brings new names, and they wait there until you place them. Since 28 September 2026 a fifth row, Companies awaiting group research, counts the certificate holders the corporate-group research has not reached yet (split into those beside a production plant, researched first, and the rest), counted fresh each morning with the job's own selection. It should fall while the quarterly drain runs; a count that only grows means the drain is not running (log data/refresh/company_group_drain.log). On a day with nothing failing the mail is the “Daily admin summary” and the subject carries the two totals; on a failing day the usual “Still failing” list follows the workload. The morning a check first fails, that check’s own alert is the only mail — the daily summary does not also go out and call the desk healthy.

Since 12 October 2026 the mail carries one line, Certificate PDFs, while any in-force certificate is neither read nor an accepted exception (counts per scheme, how many need a decision, accounted share). It reports the published data after the previous morning OCR run and disappears when everything is accounted.

Since 25 September 2026 the mail also says when sinetti.ch serves a late register. Each register is judged against its own schedule — ISCC and REDcert every night (2BSvs and RSB too once the nightly job carries them; until then they are not judged); INS, the US registers (LCFS, EPA), Canada CFR, Oregon and BC every week — and counts as late one day after its refresh was due. When last night's refresh already published newer data that is not live yet, the mail says so and gives the command to run on your PC: python scripts\promote_release_on_space.py --list. It also says when newer data is ready even if nothing is late yet, with a command pinned to that email's exact full dataset commit: python scripts\promote_release_on_space.py --hub-revision <full commit> --restart-space. If the commit cannot be verified, the mail offers review only. When the deployed code cannot serve that release because it needs a newer data-plane version, the headline reads Newer data needs a code deploy first, the mail names both versions and offers review only: deploy newer code, then promote. And it names any Hugging Face cloud job that has been running for 6 hours or more, because every promotion refuses while another cloud job runs. Nothing late, nothing waiting and no long job: no section.

A Trust Review mail lists its five checks (Freshness, Status evidence, Identity, Coverage, Publication) as passed, could not run or found a problem. “Could not complete (data unaffected)” in the subject means a check could not run — a tool problem, nothing wrong found in the data. “Found a blocking problem” means the new data failed a check: do not promote that candidate. ISCC and REDcert share one nightly run, so one cause sends one mail for both.

Application errors. Unhandled server errors and authenticated client error reports are recorded in an internal ledger. System health displays safe groups from its most recent 30 days; individual events remain available through the administrator API. The daily local Trust monitor opens an incident and attempts an operator email when one server error repeats at least three times, or one client error repeats at least five times, within its preceding 15-minute sample. Earlier bursts can be missed. A verified-snapshot catch-up (--catch-up-errors on scripts/monitor_trust_jobs.py, with the three inputs the runbook names) is opt-in and is not a scheduled task. Dry-run changes nothing. A catch-up incident stays open until the verified snapshot covers a complete quiet 15-minute interval after the last event; a quiet daily sample cannot close it. A failed delivery can be retried with --retry-failed-error-delivery on a live catch-up invocation. The receipt names that file's size and modification time (`ledger_bytes`, `ledger_mtime`) and not the rows inside it. On 25 September 2026 five hosted client reports were accepted. Infomaniak accepted both the emergency mail (21:10 UTC) and the recovery mail (21:28 UTC), and the operator confirmed both messages were in the inbox. The live daily monitor was not the sender. See docs/security/evidence/2026-09-25-restore-offmachine-and-error-mail.md and docs/data-trust-runbook.md.

Procedures

PDF monitor liveness is separately opt-in. While healthy, the PC monitor detects failures on its next daily execution, with a maximum 24-hour sweep interval. The independent hosted preflight rejects a missing, invalid or more-than-26-hour-old completed PC heartbeat. With the existing daily hosted cadence, detection is at most 50 hours from the last completed heartbeat while the hosted job runs. Both hosts unavailable has no bounded alert delivery; evidence is unknown on recovery and the streak resets. These are not immediate-alert guarantees. Use the coverage procedure to inspect receipt, export/readback and genuine hosted-check evidence.

You received an alert email — now what10 min
  1. “Down” from the uptime service: open /api/ready. No answer at all → open the Space page on Hugging Face: is it Running, Building, or in error? Restart it. Still failing → read the Space logs; if this began right after a deploy, the deploy should already have rolled back — read its receipt (§13).
  2. “Missing/failed run” from the job monitor: check the job's true status (§7). Fix the cause; the next good run sends the recovery mail.
  3. “PDF queue is still growing”: the register refresh may still have succeeded. Read the reason: it says how many PDFs last night parsed and what is still waiting (new documents, an older parser version, earlier failures, ISCC audit templates). The incident closes when a later night shows the depth fell. The same text appears on the next daily reminder; the first alert is not sent again.
  4. /api/ready degraded on data: restart once — self-heal steps back to a good release at start. If it returns as bundled, promote a known-good revision (§8).
  5. Degraded on persistence: confirm the storage bucket is still attached to the Space and SINETTI_STATE_DIR points at it.
  6. Write down what happened and when. Security-relevant events follow docs/security/incident-response-runbook.md.
Ask the job monitor what it thinks, without sending anythingDesk · 1 min
$env:APP_BACKEND = 'local'
python scripts\monitor_trust_jobs.py --state-dir <state folder> --dry-run

Exit 0 healthy · 1 open incidents · 2 the monitor or its mail transport is itself broken.

15State, backup and recovery

Where: production storage bucket · operator machine backup folder

Verified against the Windows task “Sinetti PC daily customer-state backup”, the isolated restore receipt, and dataset Couscouskabyle/sinetti-customer-state-backups, 25 September 2026. The task is registered for 06:15. It ran once at 00:09 on 26 September 2026 (result 0), stamp 20260925T220919Z. The 06:15 trigger is still ahead. Stamp 20260925T205902Z is a manual copy of the local mirror. That stamp was restored into an isolated folder and uploaded once. Stamp 20260925T220919Z was restored into a separate isolated folder the same night (RPO 5 minutes, RTO 1 minute for that copy). The daily task does not upload, and the production bucket still has no backup series.

In short

Almost everything in Sinetti can be rebuilt: code from git, register data by refreshing, releases by promoting. What cannot be rebuilt is state: customers' watchlists and saved views, accounts and organisations, audit trails, your curation decisions — and the day-by-day history of register changes, which no register publishes and which is the core of the product's value. State lives in one storage bucket attached to production. Backups are a command you schedule on the operator machine; a restore only counts once you have rehearsed it.

ThingIf lostProtected by
App codeRebuild from gitLocal repository; private GitHub mirror of main; the Space repository
Register data and releasesRefresh and promote again; two previous releases retainedDataset repository history; self-heal; bundled fallback
Main state database (intel.db): accounts, organisations, plans, watchlists, alerts, change history, meters, auditsGoneCustomer-state backup (required member)
Saved viewsGoneCustomer-state backup (required member)
Customer administrator authority (customer-admin.json)GoneCustomer-state backup (optional inventory member; required and identity-checked for customer-edition restore)
Admin app settings (settings.json: certificate expiry window)Back to defaultsCustomer-state backup (optional member; the customer edition keeps it in /state)
Findings, Ask-safety incidents, regulatory cases, identity and related-company decisions, timeline setsGoneCustomer-state backup (optional members — recorded as missing if absent)
Ask retrieval-miss questions (ask_rag_misses.jsonl)GoneNew stamps include optional ask_rag_misses.jsonl.enc; requires the separately retained backup key to recover
Refresh history ledgers (change_events.jsonl and siblings)Gone — and irreplaceableGap not in the backup set yet
Geocoded coordinates, parsed-PDF archivesExpensive to rebuild (money and weeks)Files on the operator machine; keep a copy off it
SecretsRegenerate, never restorePassword manager

Production state is reached through SINETTI_STATE_DIR (/data/sinetti), a storage bucket mounted into the Space and into the nightly job. If that variable is unset, the app writes state inside its own container and loses it at every deploy — silently. The bucket can only be written from inside Hugging Face; the operator machine receives a mirror through the daily local sync.

Gap What is still missing: a repeating off-machine copy (the 06:15 task stays on this PC); a backup of the production bucket itself; a named second recovery operator; a rebuild-from-nothing procedure if the Hugging Face account were lost. The local restore drill recorded RPO 8 minutes and RTO 1 minute for stamp 20260925T205902Z. A later restore of stamp 20260925T220919Z recorded RPO 5 minutes and RTO 1 minute. Those figures are those drills, not hosted targets. As of 2026-09-25 21:31 UTC: the 06:15 task ran once at 00:09 on 26 September 2026 (result 0), stamp 20260925T220919Z. The 06:15 trigger remains. Stamp 20260925T205902Z is a manual copy of the local mirror (intel.db 7,278,592 bytes). An isolated restore of that stamp passed, and that same stamp was uploaded once to the private dataset Couscouskabyle/sinetti-customer-state-backups. The hosted file listed the same afternoon was 4,866,048 bytes. OneDrive is not signed in (0x8004de25), so the OneDrive folder is not the off-machine copy. Detail: docs/security/evidence/2026-09-25-restore-offmachine-and-error-mail.md.

Procedures

Back up customer stateDesk · 2 min · before anything risky
$env:APP_BACKEND = 'local'
$env:SINETTI_BACKUP_DIR = 'C:\Users\azizl\sinetti-backups\customer-state'
.\.venv\Scripts\python.exe scripts\backup_customer_state.py --git-sha (git rev-parse --short HEAD)

the path of a new time-stamped folder holding the files and a MANIFEST.json of checksums. A missing required file aborts the backup. Older folders beyond the newest 14 successful stamps are pruned.

Ask miss-log protection (code and isolated tests verified 9 October 2026): new stamps encrypt only the optional question-log member with AES-256-GCM. The Sinetti operator supplies SINETTI_ASK_MISS_BACKUP_KEY, a dedicated random 32-byte key encoded as base64, to the backup and restore processes. Keep the key separately in approved secret custody, available to the recovery operator. Never put it in stamps, Git, receipts or logs, or reuse an API key or session secret. Sinetti does not create or print a key; this change has not configured the live scheduled task. The existing operator-controlled production mirror retains the private plaintext source.

A present log, including an empty one, with missing or invalid key configuration aborts the backup. An absent log needs no key. Supply the retained operator key and retry; do not delete the source log to bypass failure. Historical stamps without the optional member remain readable. Historical stamps are never rewritten by customer purge: live questions are erased through the existing customer process, while backup generations expire under the newest-14 retention policy.

The folder must not be the live state directory C:\Users\azizl\sinetti\data. This PC has no D: drive. The daily task “Sinetti PC daily customer-state backup” (06:15, scripts/register_customer_state_backup_task.ps1) is registered for that folder. It ran once at 00:09 on 26 September 2026 (result 0), stamp 20260925T220919Z. The 06:15 trigger is still ahead. The first stamp in the folder was written by hand. The 00:09 run added 20260925T220919Z, and that stamp was restored into an isolated folder. It was not uploaded. The user environment variable SINETTI_BACKUP_DIR names it; a program that is already running does not see a newly set variable until it is started again. Stamp 20260925T205902Z is a copy of the local mirror. On 25 September 2026 that one stamp was uploaded to the private dataset Couscouskabyle/sinetti-customer-state-backups. The daily task does not repeat that upload. The 30 July 2026 stamp under the older AppData folder is a different series.

Rehearse a restore (the drill)Desk · 15 min · at least before each new customer
  1. Pick a recent backup folder; open its MANIFEST.json.
  2. If it contains ask_rag_misses.jsonl.enc, supply the separately retained SINETTI_ASK_MISS_BACKUP_KEY to the restore process. Ciphertext checksums and log authentication are checked before any target writes. A wrong/missing key, damaged ciphertext or invalid envelope refuses recovery without changing the target. Check key custody and select an intact stamp; do not edit the manifest to bypass a failure. Success recovers ask_rag_misses.jsonl. Older generations can recover erased questions: keep the drill isolated and reapply relevant erasure before returning restored state to service.
  3. Restore into an empty, separate folder — never over live state:
    .\.venv\Scripts\python.exe scripts\restore_customer_state.py `
      --manifest "$env:SINETTI_BACKUP_DIR\<stamp>\MANIFEST.json" --target-dir C:\Users\azizl\sinetti-restore-drills\<date>
  4. Confirm exit 0 and that the checksums of intel.db and saved_views.json match the manifest.
  5. Note how long it took and how old the backup was. File a receipt under data\refresh\ops_drills\ (template: docs/ops-drill-receipt.example.json).
Restoring over live state is refused unless you pass --i-understand-overwrite and the target is on an explicit allow-list. If you ever need it for real, stop the app first and take a fresh backup of the damaged state before overwriting it. If the target folder already holds a database, the restore removes its -wal and -shm files first so an earlier write cannot replay onto the snapshot.
Run the two-customer acceptance gateDesk · 10 min · before each real customer
$env:APP_BACKEND = 'local'
.\.venv\Scripts\python.exe scripts\bilateral_acceptance_gate.py --operator $env:USERNAME --git-sha (git rev-parse --short HEAD)

It creates two throwaway organisations in a temporary folder — it never touches live state — and proves twelve things: isolation between them, plan entitlements, rate and export limits with audit, release identity, admin MFA, session revocation, offboarding and reactivation, lifecycle receipts, guarded permanent deletion, a restore, the shape of a deploy receipt, and the ownership trail.

a PASS line per check, exit 0, and the path of a receipt. Anything else: do not onboard.

17Settings and secrets

In short

Configuration lives in three places: Space secrets (production), the nightly job's registration (frozen when registered), and the operator machine's .env file (never committed). This section lists names and purposes only — values belong in a password manager. On handover, secrets are regenerated, not copied.

Production — Space secrets and variables

NamePurposeIf missing or wrong
SINETTI_SESSION_SECRETSigns login sessionsNobody can log in. Changing it logs everyone out.
SINETTI_USERSOwner-provisioned accounts (hashes)Nobody from the roster can log in; a duplicate id voids the whole list
SINETTI_MFA_ENCRYPTION_KEYProtected stored authenticator secrets (32 random bytes, base64). Emailed-code sign-in does not use it.On an authenticator-era build, a missing key refused admin login and degraded /api/ready. On a build that reports marker aed958ce, login uses email. Never reuse the session secret. Do not delete the key until an administrator has signed in with an emailed code on that build.
SINETTI_STATE_DIRWhere persistent state lives (the mounted bucket)State resets at every deploy — silently
SINETTI_DATA_REPO_IDThe private dataset repository for candidatesApp serves only bundled data
ANTHROPIC_API_KEYAsk Sinetti, narrative, researchAssistant unavailable
SINETTI_PRIVATE_MODE · SINETTI_EGRESS_ALLOW · SINETTI_RELEASE_PUBKEYSPrivate Cloud only: refuse outbound connections except approved hosts; extra trusted release-signing keys. See docs/enterprise/private-cloud-gcp/egress-acceptance.md and offline-releases.mdNever set on this Space: private mode would stop the data pull and mail.
SINETTI_AI_PROVIDERWhere AI requests go: anthropic (default, this Space), or a customer's own vertex / bedrock / azure, or off. Private Cloud only changes it — see docs/enterprise/private-cloud-gcp/ai-routing.mdUnset = Anthropic. A misspelled value turns AI off rather than guessing.
SINETTI_AZURE_AI_ENDPOINT · SINETTI_AI_MODEL_MAPFor azure, the customer platform team sets the customer's Microsoft Foundry resource base URL (https://<resource>.services.ai.azure.com/anthropic) and maps Sinetti model IDs to deployment names. The team builds with ENTERPRISE_AI=1 and grants the app's managed/workload identity invocation permission through Microsoft Entra; no Foundry API key is stored. The Sinetti admin verifies the configured workflows through customer acceptance. This is an AI route; an Azure deployment reference is not built.A missing or invalid endpoint leaves AI unavailable. Correct the endpoint, model map, dependencies or identity permissions and repeat acceptance. Configuration status does not prove a live invocation; none was run for this implementation.
INFOMANIAK_API_TOKEN · SINETTI_INFOMANIAK_MAILBOX_UUID · SINETTI_EMAIL_FROMOutgoing mailDry-run: looks sent, is not
SINETTI_PUBLIC_BASE_URLAddress written into invite and reset linksDead links in emails
SINETTI_ADMIN_EMAIL · SINETTI_ACCESS_NOTIFY_EMAILWhere operational mail goes. After the emailed-code release, SINETTI_ADMIN_EMAIL is also the last-resort destination for an administrator's sign-in codeYou are not told. With no address on the account either, administrator sign-in cannot send a code
SINETTI_AUTO_ACTIVATE_NIGHTSAutomatic data promotion after N clean nightsUnset or 0 = manual promotion only
WEB_CONCURRENCYNumber of app workers (shipped as 2)1 = one slow request blocks everyone
Never set on production: SINETTI_DEV_AUTH_BYPASS and SINETTI_LOCAL_ADMIN_MFA_BYPASS. Both switch protection off for local development and are built to be inert on Hugging Face — do not test that.

Operator machine — .env

NamePurpose
HF_TOKEN (and HF_JOB_TOKEN, HF_CREATE_TOKEN)Publish, roll back, launch and register cloud jobs. A “bucket does not exist” error usually means the token lacks scope, not that the bucket is gone.
SINETTI_SMOKE_USERNAME · SINETTI_SMOKE_PASSWORDThe login the post-deploy smoke test uses
VITE_MAPTILER_KEY · VITE_MAPTILER_KEY_HFSatellite map imagery, baked in at build time (local · production)
GOOGLE_MAPS_KEYGeocoding, passed to the nightly job at registration
INS_PDF_NEW_MAX_PDFS · INS_PDF_CRITERIA_MAX_PDFS · INS_PDF_SITE_MAX_PDFSWeekly INS PDF caps. The runner default is zero (never). register_ins_weekly_task.ps1 raises a missing or lower value to 1,200, the same drain budget as the nightly certificate-PDF cap. Uncached GSE refetch stays off.
INS_PDF_SITE_REEXTRACTLets the weekly INS refresh re-read certificates that already have sites when the site reader has improved, within the site cap. It replaces stored sites only when the new reading keeps everything the old one said. Off unless set to 1.
SINETTI_PDF_SOURCE_AUDIT_ENABLED · SINETTI_PDF_COVERAGE_MONITOR_ENABLEDSeparate source-audit and production/reciprocal-monitor opt-ins, both off unless explicitly set to 1. Audit caps separately default to zero. No Environment-screen controls; local merge does not change live configuration. See verified live coverage.
SINETTI_PDF_COVERAGE_INPUT_REQUEST · SINETTI_PDF_COVERAGE_INPUT_REQUEST_SHA256Independent inventory/evidence request and exact byte-hash pin. Source-only loading permits acquisition before Read proof; production coverage still requires the full strong evidence chain. Missing/invalid proof remains unknown.
SINETTI_PDF_SOURCE_AUDIT_MAX_CHECKS · SINETTI_PDF_SOURCE_AUDIT_ISCC_MAX · SINETTI_PDF_SOURCE_AUDIT_REDCERT_MAX · SINETTI_PDF_SOURCE_AUDIT_INS_MAXSeparate all-unit source-check caps, each default zero; existing processing caps/runtime are retained. A cap is not evidence of measured capacity.
SINETTI_PDF_COVERAGE_DRY_RUNWhen set to 1, withholds the new monitor runtime effects and acceptance. The PC's legacy --now also cannot seed acceptance.
SINETTI_BACKUP_DIRWhere backups go; also required before permanent deletion is allowed
SINETTI_ASK_MISS_BACKUP_KEYDedicated base64 32-byte Ask miss-log backup key; retain separately for recovery
SINETTI_JOB_AUTO_ACTIVATELets a local refresh promote its own clean result on this machine only
SINETTI_DEV_AUTH_BYPASSSkips login locally (you appear as “Admin (dev)”)

18The operator machine

In short

One Windows PC holds the only copy of the repository (it has no remote by design), three special folders — the working folder, the “latest” serving desk and the deploy desk — and the weekly scheduled tasks. Two cleanup scripts exist because improvised cleanup has destroyed live work twice. Both do nothing until you add a flag.

FolderRoleRule
C:\Users\azizl\sinettiThe live working folder; scheduled tasks run from hereMerges never happen here; it only fast-forwards
C:\Users\azizl\sinetti-latestServes a clean copy of main on port 8511 for you to look atNo work in progress; reset to main on every open
C:\Users\azizl\sinetti-deployThe deploy desk, pinned to the release commit“Detached HEAD” there is the pin, not a mistake. Never delete it.
sinetti-*, .worktrees\*Temporary work desksMay share Python and node folders by link — delete links with rmdir, never recursively

Procedures

Look at the latest app locallyDesk · 1–10 min
powershell -ExecutionPolicy Bypass -File scripts\open_latest_main.ps1
powershell -ExecutionPolicy Bypass -File scripts\open_latest_main.ps1 -Path /browse

Reuses the running server when it already serves the current main; otherwise rebuilds and restarts, then opens your browser. It serves this machine's active data release and state. If it refuses because the desk is dirty, it says so and changes nothing.

A restart does not take the app down. Port 8511 is a small front door that passes every request to one of two server ports, 8512 or 8513. The new version starts on the idle one while the old one keeps answering; the front door switches over only once the new one is ready, and the old one stops a few seconds later. The scheduled task Sinetti PC 5-min keep latest main does the same in the background whenever main moves and nobody is using the app. Each server keeps its own copy of the web pages, so rebuilding them for the next version never affects the one you are using.

Rebuild a desk's Python environment off python.orgDesk · 15 min

The Microsoft Store Python breaks the release gate (Playwright cannot see its browser, and python -I tests fail). Rebuild one desk at a time, only when no gate or PC daily test run is using it, and only with the python.org interpreter — never the venv you are about to replace. The old venv is kept outside the checkout. Full steps: docs/runbook.md.

  1. Confirm python.exe -c "import sys; print(sys.base_prefix)" does not contain WindowsApps.
  2. Preview, then apply. Deploy desk first, then the live folder:
    <python.org python> scripts\rebuild_venv.py --target C:\Users\azizl\sinetti-deploy
    <python.org python> scripts\rebuild_venv.py --target C:\Users\azizl\sinetti-deploy --apply
Clean up stuck processes and dead work foldersDesk · 5 min
powershell -ExecutionPolicy Bypass -File scripts\kill_zombies.ps1                 # lists only
powershell -ExecutionPolicy Bypass -File scripts\kill_zombies.ps1 -Kill
powershell -ExecutionPolicy Bypass -File scripts\cleanup_dead_worktrees.ps1      # lists only
powershell -ExecutionPolicy Bypass -File scripts\cleanup_dead_worktrees.ps1 -Apply
  • The first never touches the app on ports 8511–8513 (the front door and its two servers), anything with a live connection, or the refresh, gate, deploy and promotion jobs. Every kill is logged to %LOCALAPPDATA%\Sinetti\kill-zombies.log.
  • The second removes only folders whose work is fully in main, clean, and idle for 12 hours; never the deploy, latest, release or nightly desks; removals go to a trash folder first.
  • Never kill processes by matching “sinetti” in a path, and never bulk-delete work folders by hand.

19When something breaks

In short

Start from the symptom. Most surprises in Sinetti are one of three things: a job that silently did not run, a setting that was never set, or data that was never promoted.

SymptomMost likelyGo to
Nobody can log inSession secret or user list missing/invalid; duplicate user_id§2
Admins cannot log in; members canOn production today: MFA key missing or changed. After the emailed-code deploy: the code was not delivered (no address, or mail dry-run)§2
One member cannot log in: “seats are in use”More members than seats; they were added last§3
One customer cannot log in: “access is paused”Organisation suspended, offboarded, or agreement end date passed§4
Member sees “not included in your plan”Feature not ticked in their plan§3
Member sees “We couldn't complete this action”Rate or export limit reached — or the meter's storage is unavailable§5
Ask says “allowance used”Monthly AI cap reached§5
New member has a tiny AI allowanceAdded after the plan was assigned; budgets never stamped§3
Invite “sent” but never arrivesMail in dry-run§12
Data looks days old although nightly “ran”Job died without a receipt; or candidates exist but none was promoted§7, §8
/api/ready says source: bundledActive pointer or releases broken; app fell back§8
Customer data vanished after a deploySINETTI_STATE_DIR unset or bucket detached§15
New INS certificates never get pathwaysINS PDF caps unset (zero)§7
PDF queues never shrinkNightly caps re-registered at zero§7
New material family shows nothingSeed assignment not run after deploy§10
Ask does not know a document you addedLibrary not deployed yet§11
Release refuses: “excluded release paths changed”Candidate contains mandate or consumption work — working as intended§13
Release stuck in publishingAmbiguous outcome; read the receipt before anything else§13
Retry of a failed publish refusedApproval already spent; use a new approval id§13
False “missing run” email every morningJob monitor task moved out of its 03:05–09:00 slot§14
Local app shows an old interfaceAnother folder's server is answering on that port§18

20Known gaps

In short

Things the product does not do today, collected so that nobody plans around a capability that is not there. Documentation mismatches found on 19 September 2026 were closed in the same sitting as the freshness rule.

Product

  • An invited member cannot be made an admin from the app; invites always create non-admin accounts.
  • No screen returns a hand-capped member to plan-controlled AI budgets.
  • Members added to an organisation after plan assignment receive no AI budget until the plan is re-saved.
  • System health shows grouped recent application errors; individual ledger events remain available through the administrator API.
  • No screens for the certification-body register or the INS place corrections — both are data files maintained through development work.
  • The site-footprint “rescan” exists on the server without a button.
  • Plan changes made by script are validated for feature names only, not register or AI-budget names.

Operations

  • Backups, as of 2026-09-25 21:31 UTC: the daily 06:15 task ran once at 00:09 on 26 September 2026 (result 0), stamp 20260925T220919Z. The 06:15 trigger remains. Stamp 20260925T205902Z is a manual local-mirror copy. An isolated restore of it passed, and that stamp was uploaded once to private dataset Couscouskabyle/sinetti-customer-state-backups. Stamp 20260925T220919Z was restored into a separate isolated folder (RPO 5 minutes, RTO 1 minute). The production bucket itself still has no backup series. The daily task does not upload. Change-history ledgers stay outside the backup set. The local restore drill recorded RPO 8 minutes and RTO 1 minute. No hosted recovery targets, no named second operator, and no rebuild-from-nothing procedure.
  • UptimeRobot DOWN and UP test emails were received on 25 September 2026. The same day the operator confirmed both application-error drill messages in the inbox (emergency 21:10 UTC, recovery 21:28 UTC). The live daily monitor was not the sender, so OPS-02 stays in progress on that limit.
  • Retention and deletion periods, the data-processing agreement and the supplier transfer register are open legal items (PRIV-01, PRIV-02, SUP-01).
  • No registration script in the repository for the CFR weekly task.
  • The monthly law-refresh task was last noted exiting with an error and an empty log when run by the scheduler (August 2026); check its history.
  • MFA key rotation has no tooling. It applies while production still stores authenticator factors.

Documentation

  • An older installation may still hold the Phase-3 default of 120 requests per 5 minutes. The code default is 2 000; read Admin → Customers → Organisations → Data access limits, not a design spec.

21Glossary

TermMeaning
Active pointerThe one-line file naming which release the app serves
CandidateA complete release published by a refresh job, not yet served
Deploy deskThe folder pinned to the exact commit being released
DispositionYour recorded decision on a finding or a review pair, with its reason
Dry-runGoes through the motions and changes nothing. For email: nothing was delivered.
EntitlementWhat a member may do right now, derived from organisation and plan
FindingOne Trust Review observation about a candidate; advisory
Gate (release gate)The full automated test run a commit must pass before approval
HeartbeatEvidence that a scheduled job finished usefully; what the job monitor looks for
Operator machineThe Windows PC holding the repository, desks and scheduled tasks
OrganisationA customer under a bilateral agreement
Owner organisationThe built-in organisation with all features and registers; for you only
PromoteMove the active pointer to a reviewed release
ReceiptA small file a job writes when it finishes. Its absence or age is the signal; its content alone proves nothing.
Release (data)The served database plus its manifest, stored under a revision id and never changed
Release (app)One commit of code, gated, approved and published
RosterThe owner-provisioned accounts kept in a Space secret
SpaceThe Hugging Face container that runs the production app
StateEverything customers and you created inside the app; lives in the storage bucket
Step-upAn emailed six-digit code entered within the last five minutes, required for sensitive actions. On production, until that release is deployed, this is still an authenticator code
Trust ReviewThe automatic reviewer of each candidate

Sources: the runbooks under docs/ (auth, billing, bilateral-acceptance, daily-refresh, ins-local-refresh, data-plane, data-trust, law-layer, ops-harden, local-hf-sync, nightly-suite, security/), docs/OWNER-HANDOVER.md, and the code they describe. This guide is the map; those are the territory.