A Cloudflare Worker that takes submissions, establishes either that whoever submitted can push to the repository or that an active Technical Maintainer is running a non-registerable workflow test, and keeps every durable fact in GitHub.
Live at https://submit.palomar-registry.org.
The Worker keeps nothing between requests. A submission is a directory of JSON
files committed to the private PalomarRegistry/PalomarSubmissionState
repository, and every transition is a commit. Switch the Worker off and the
record is complete; the operator CLI can drive any submission to a terminal
state without it. That is the property that stops this becoming something we
cannot leave.
Stated on the intake form, in these words, because it is not what people assume:
- Public from the moment you submit: the repository, commit, and selected Comparator configuration path. Verification runs in a public Actions workflow whose logs are public.
- Never public unless the author registers: the review, its findings, and the submitter's identity.
- "Private" means not public, not confidential. Reviews are readable by operators, GitHub, and the model provider, and are kept indefinitely.
There is no account. There are two ways in, and a record says which one it came through, because they do not prove the same thing.
A person signs in with GitHub once, so the server can check permissions.push
on the repository being submitted and read the login it is answering for. The
token is discarded immediately and written nowhere; the login is kept, because
it is what the quotas count against. GitHub answered for the same account that
authorised Palomar, so one account both can push and identified itself.
An agent has no browser, and drives a tag at the submitted commit plus a secret
gist carrying the same challenge. Creating a ref needs the same write access;
the gist supplies an identity, because a ref records no author and a third party
cannot ask GitHub who has push. That establishes that someone who can push
submitted the repository and that an account named itself, which are not
provably the same account, so the record carries separately-attested rather
than same-account and the two are not treated as equivalent anywhere.
A Registry-correction agent uses a narrower proof for a different authority.
Corrections do not require write access to the source repository, which they
cannot move. The agent creates only the secret challenge gist; GitHub's numeric
id for its owner must be in src/technical-maintainers.js. The durable proof is
the same technical-team-correction / active-technical-team-membership
authority recorded by the dashboard path. A source-repository tag would prove
an irrelevant capability and is deliberately neither requested nor read.
An access token is the ordinary way back to a submission. On the agent path
there is no link to put it in, so /api/verify returns it in the response body.
On the browser path it is carried after the #, because a browser leaves that
part out of the requests it makes, so it does not reach an access log or a
Referer header.
The status page presents its fragment as an Authorization: Bearer header on
each private request and explicitly omits cookies. The fragment belongs to the
page rather than the browser profile, so two submissions open at once cannot
replace one another's credential. Saying it is "never sent to a server" would
still be wrong, and the page does not say so: it says to treat the link like a
password, which is the part a submitter can act on.
Losing the link is not permanent while a submission remains in the open-work
queue. /submissions, also linked from the submission form, starts a separate
recovery sign-in. GitHub identifies the same numeric account id recorded in the
original proof; Palomar then shows
every matching open submission and rotates one recovery token for each. The
original token remains valid. Numeric identity, not a reusable login, is the
authority: GitHub logins can be renamed and later reused.
After a successful browser OAuth callback, Palomar also sets a signed,
host-only identity cookie for twelve hours. It contains the account's numeric
id, login, and expiry, but no GitHub access token; the OAuth token is still used
only during the callback and discarded. The submission form uses that local
identity to list matching open-work metadata automatically. Listing is
read-only. Choosing Open this submission rotates the recovery capability
for only that submission, so merely loading the form neither writes State nor
invalidates a previously recovered link. An absent or expired identity cookie
falls back to the ordinary /submissions OAuth flow.
For a completed verification failure, the browser reads the public run's job
and check annotations directly from api.github.com so it can show the actual
error beside the run link. Those bounded requests carry neither a GitHub
credential nor the Palomar cookie or fragment, and the site's no-referrer
policy keeps the private status URL out of them.
The browser status page previously used a single __Host- cookie. Although it
was protected from sibling hosts, every tab on this host shared it; opening a
second submission therefore changed what the first tab read. Current status
pages use only their own fragment capability for private reads and actions. The
server still honours /session cookies for legacy clients, but current pages
neither create nor send them.
The short-lived cookie that binds a browser OAuth callback to the tab that started it has the same host-only properties and a different name for every pending intake. Only the callback consumes the exact nonce-derived name, and an ambiguous or case-confusable protected name, or a malformed binding, is refused before it can read or consume pending State.
Push access is not authorship. It does not establish approval from the responsible authors of a substantive formalization, and does not replace the declaration a submitter makes about that.
Browser sign-in requests no GitHub OAuth scopes: public identity and public
repository information are sufficient. Technical Maintainer authority comes
from the reviewed numeric GitHub ids in src/technical-maintainers.js, not
from organization visibility delegated by every submitter. Allowlisted
Technical Maintainers bypass the ordinary per-principal start interval and the
owner and submitter in-flight caps for every submission, independently of the
authorization relationship selected on the form. The durable OAuth proof
records that authority.
When an allowlisted maintainer submits a public repository and pinned commit
without write access, the browser path automatically records it as a technical test.
The explicit technical-test relationship reaches the same path. Its durable
record says test_submission, does not claim push_verified, and carries a
distinct Technical Maintainer proof. Technical tests remain browser-only; the
agent exception is limited to a structurally valid Registry correction and
checks the gist owner's numeric id before admission. The pre-authentication
address throttle still applies because the account is not known until the
OAuth callback or gist verification completes.
Technical Maintainers enter at https://submit.palomar-registry.org/dashboard.
The dashboard includes the protected exceptional Registry-correction editor;
the operational procedure is the PalomarPolicy
maintainer-corrections.md
runbook. The ordinary submission form does not expose this editor.
/dashboard shows aggregate submission throughput, review-round counts,
landing distributions, and bounded model-spend distributions. It never reads a
review or serves a submission id, repository, Comparator path, commit, or
submitter identity. The State reporting workflow derives
reports/dashboard.json in a daily or manually dispatched full sweep; the page
reads that one private file and identifies the exact immutable State
submissions/ tree and latest event included, so lag is visible rather than
disguised as current data.
Dashboard sign-in also requests no GitHub OAuth scopes. The callback requires a
numeric id in the same checked-in Technical Maintainer allowlist, discards the
GitHub token immediately, and issues a signed host-only session lasting fifteen
minutes. Removing a maintainer takes effect no later than that expiry after the
allowlist change is deployed. The signature is domain-separated from
submission-token digests while reusing the existing TOKEN_PEPPER; there is no
additional secret or durable login store.
The OAuth state and session cookie both reject duplicates and are never cached.
Dashboard OAuth initiation and callback share the intake address limiter, so an
unauthenticated loop cannot turn into unbounded GitHub token exchanges. Every
dynamic OAuth response carries the same no-referrer and security headers as the
rest of the Server. The API additionally requires a same-origin browser request;
the OAuth landing page itself remains a top-level cross-site navigation.
The machine-readable aggregate is available at /api/dashboard under the same
session. The Server validates that the stored document has the identity-free
dashboard contract with an exact field-name allowlist and deep value shapes,
instead of trying to spot a few forbidden identity fields. The contract is
represented by versioned aggregate fixtures retained as backward-compatibility
examples, not as evidence about current producer output. Pull-request CI runs
the current consumer against every dashboard fixture from the base commit, so
editing a fixture cannot disguise a backward-incompatible change.
State CI runs its real producer output through this repository's
consumer, while Server CI proves that a candidate still accepts every schema
version retained by its base commit. Together those two directions prevent
either repository from crossing the contract boundary alone, without granting
the public Server repository access to private State. The page and API link
directly to the private Database's one-person Moderator forms for takedown and
restoration. Those links are operator conveniences, not State data.
Dashboard contracts are append-only by schema_version. A new required field,
removed field, changed literal, or narrowed value shape requires a new schema
version. Deploy the Server consumer accepting both versions first; only then
change State to emit the new version. Keep the earlier consumer until no stored
report uses it. Corrections that make a producer conform to its already-declared
schema do not create a new version.
Variables live in wrangler.jsonc. Secrets are set with wrangler secret put
and never appear in the repository:
| Secret | What it is | Reach |
|---|---|---|
OAUTH_CLIENT_ID |
GitHub OAuth App client id, for public identity and submission push-access checks | — |
OAUTH_CLIENT_SECRET |
its client secret | — |
GITHUB_TOKEN |
reads and atomically advances submission State, asks the reviewer to run, and reads public repository metadata for the repository being submitted | PalomarSubmissionState, contents and actions, plus public reads |
SUBMISSION_TOKEN |
starts and reads verification runs, and reads the submitter's public ref and gist while checking a proof | PalomarSubmission, actions, plus public reads |
TOKEN_PEPPER |
so a leaked state repository does not yield live links, and to authenticate short-lived GitHub identity cookies under a domain-separated HMAC key | — |
The checked-in Technical Maintainer ids are the source of truth. Adding or
removing authority is an ordinary reviewed change to
src/technical-maintainers.js. GitHub login renames require no update because
the numeric id is stable; a deleted account can no longer authenticate, so no
scheduled synchronization job or organization-reading credential is needed.
The State token's existing repository Contents: write grant covers the Git
tree, commit, and non-forced reference update used for atomic admission; it
does not need repository-administration permission.
Two GitHub tokens, not one, because a fine-grained token grants the same
permissions to every repository it names. A single token covering both
repositories would carry write access to the verification code itself, and this
server is internet-facing and takes untrusted input: a leaked secret would
become a way to forge mechanical verification. Neither token needs admin
anywhere, and neither can touch PalomarDatabase.
Dispatching a workflow does not return a run id, so the server finds the run it
started by name: the workflow's run-name is Verify submission <id>. The
submission id is public, so anyone able to dispatch that workflow can produce a
run carrying it. The name is therefore matched exactly, and the run is pinned
the first time it is seen and never replaced. The reviewer accepts only the
pinned run id, so the name is not the trust boundary in either place.
Registration is the submitter's decision and nobody else's. /register records
consent together with the digest of the review the submitter was shown, and the
reviewer refuses to register anything whose bytes differ, so a revised review
needs fresh consent rather than inheriting the old. Withdrawing before consent
leaves no public trace of the review or its findings. After consent, the reviewer
still refuses to merge a withdrawn record, but source-preservation or rendering
work that already started may remain public. Registration requires a current
review that identified no blocking problem; an older in-flight review must be
rerun. A technical-team test can reach the same review screen, but its
registration control is disabled with an explanation. /register and the
reviewer independently refuse it, and State validation reports any test record
edited by hand to carry consent or registration state. Test submissions also
cannot request an automated metadata-repair pull request.
Withdrawing also empties the record, in the same write that closes it. What the submission was and what happened to it stay; what identified the person who sent it does not. The submitter's login goes, and so do both of the free-text fields the submitter wrote: the notes, and whatever was typed to evidence an authorization relationship. Either of those can name people who never submitted anything, which is why neither survives the submission they were written for. A second event on the record's own timeline says that they went. Replacing an earlier submission of the same repository closes it the same way and scrubs it the same way.
The numeric GitHub principal is the exception, and it has to be. Recovery
intersects index/principals/<digest>.json with the reviewer's queue and then
verifies the numeric principal stored in every record it selected, before it
filters out the closed ones; a withdrawn id stays queued until the reviewer's
next pass drops it. A record with no principal would therefore not read as
"closed, ignore it" but as a locator naming somebody else's submission, which
fails closed and would take the whole recovery page down for as long as the id
is queued. Replacement checks the same field before refusing to replace
something already closed.
Retaining it is a deliberate trade rather than a claim that what remains is
anonymous. A GitHub account id is stable and publicly resolvable: the provider
will hand back the account for an id through an endpoint that needs no
permissions at all, so anyone who can read the state repository can still get
from a withdrawn record to the account that sent it. What the pepper buys is
that listing index/principals/ or index/rate/ enumerates nobody, because
those file names are digests rather than ids; it does not make the id inside
the record anonymous. The scrub removes the direct login and both free-text
fields, and keeps the one bare identifier the integrity checks above fail
closed without.
It is not erasure. The state repository's git history retains the record as it was before the withdrawal, and what happens to that is a question for the state repository's retention policy. Scrubbing the current tree is what stops every later read, sweep, clone, and index rebuild carrying it forward.
submissions/<id>/state.json # the record: status, source, authorization, run, consent
submissions/<id>/review.json # the private review, once delivered
index/tokens/<digest>.json # original or recovery token digest to submission id
index/principals/<digest>.json # private submission locator for one submitter
index/rate/<digest>.json # how long this submitter waits before starting
# again; the digest is the only thing naming
# them, and the body names nobody
index/inflight.json # admission slots, released by cron reconciliation
index/open.json # the reviewer's queue: added here, pruned there
index/review-timing.json # how long recent reviews took, for the estimate
pending/<digest>.json # a one-time intake nonce; an OAuth submission may
# retain its proved identity until the choice,
# and a record older than fifteen minutes is
# refused on read and swept on the next pass
index/inflight.json has exactly one top-level field, open, and no versioned
pre-launch variants. Each entry has exactly id, owner, submitter, and
at. The id is the current 12-character lowercase submission id; owner and
submitter are GitHub logins (owner may be null); and at is a UTC timestamp
at whole-second precision. Duplicate ids, missing or extra fields, noncanonical
timestamps, and a missing file stop admission and reconciliation visibly. They
are not coerced to an empty list or treated as if the repository owner were the
submitter.
The index enforces at most two active verifications for one repository owner and at most one for one submitter. It deliberately has no global admission cap: existing work by unrelated people cannot make intake refuse everyone. The edge address throttle and the submitter's exponentially increasing start interval remain independent controls.
index/open.json is likewise required. The server consumes only its
schema_version: 1 marker and an open array of unique current submission ids.
Every other top-level field belongs to the reviewer: the server preserves it on
append without interpreting its shape or timestamp precision. A missing or
malformed queue is never replaced as though it were empty.
Recovery first reads the authenticated principal's pepper-keyed locator,
then intersects its submission ids with the current reviewer queue. It reads
only that person's current records rather than fanning out across the shared
queue, checks every stored numeric principal id, and atomically rotates their optional
recovery_token_sha256 pointers. Repeated recovery therefore costs current
work rather than registry history and retains at most one recovery pointer per
submission. It neither invalidates the original pointer nor makes a login name
an authority.
Each locator contains exactly schema_version: 1 and a unique submissions
array. Admission appends to it atomically with the new submission; terminal ids
may remain because recovery intersects it with index/open.json before reading
records. Submissions from an OAuth-verified Technical Maintainer use the same
locator without acquiring the ordinary submitter backoff or in-flight caps.
The pure intake normalization and validation live in src/intake-contract.js;
admission caps and rate-record projection live in src/admission-contract.js;
the checks for these indexes, the current review marker, and the
submitter-visible review projection live in src/state-contract.js; and exact
request credential transport and origin classification live in
src/request-credentials.js. These four modules do not perform I/O. Validated
shared-index reads, reviewer-queue appends, admission-slot release and
reconciliation writes, verification reconciliation, and abandoned-intake
cleanup live in src/submission-lifecycle.js. The Worker in src/index.js
remains the composition root: it decides which routes require credentials and
owns body decoding, responses, authorization, request-path record and rate
reads and optimistic writes, the admission-slot append, and route-level
ordering.
Admission uses GitHub's Git Data API as a transaction boundary. The Worker
reads the pending proof, rate record, capacity index, reviewer queue, and the
two create-only paths at one exact main commit. One derived tree then consumes
the proof and writes the submission record, capacity reservation, reviewer
queue entry, token index, and rate update. A non-forced ref update publishes all
of them together. If another writer moved main, the update is rejected and
the complete decision is recomputed from the new head; unreachable losing tree
and commit objects never become State. Policy refusals publish only the proof
deletion. Damaged or unavailable State publishes nothing and leaves an agent's
proof retryable within its reported attempt budget. If a ref-update response
and the follow-up reachability checks are all unavailable, the Worker reports
the outcome as unknown instead of claiming that the proof survived.
A browser submission pauses after OAuth when that submitter already has open work. The choice page includes fresh links to all of it. When a matching repository is selected for replacement, withdrawing the earlier record, exchanging any admission slot, consuming the pending OAuth proof, admitting the new record, and updating both queues happen in the same transaction. A rate or capacity refusal writes none of those changes, so “start the new one” cannot strand the earlier submission halfway through the choice.
The external verification dispatch cannot be part of a Git commit. A newly
committed verifying record therefore doubles as a durable outbox item and
carries a short dispatch lease. The admitting request tries immediately. The
ten-minute lifecycle first searches for the workflow run (covering a crash
after GitHub accepted an ambiguous dispatch), then one reconciler claims an
expired lease and retries. It does not release capacity or declare the dispatch
lost merely because a credential or provider outage needs repair. After three
complete searches and dispatch attempts, an undiscoverable run is irrecoverable:
the scheduled pass records the Palomar-owned dispatch-lost status before
releasing its slot. A
pinned run that GitHub confirms is gone is handled the same way and is never
replaced by a namesake dispatch. Queued or running work remains reserved without
an age timeout.
The full workflow performs authoritative preparation before installing its verification-only toolchain. A failed run first enters a reporting status; the Reviewer ingests the exact run's artifact and atomically records bounded, owner-labelled diagnostics before the status page treats the result as terminal.
Before pointing a fresh or staging Worker at a new state repository, copy the
three files in state-bootstrap/index/ to index/ and commit them. Deploying the
Worker before that initialization deliberately leaves intake unavailable; it
does not silently disable the owner and submitter limits.
Admission is the multi-file transaction described above. Later lifecycle and submitter decisions remain ordered Git commits rather than one global transaction: their operations are idempotent or deliberately leave an earlier durable state that the next request or scheduled pass can finish. Git State continues to be the auditable source of truth; no separate coordinator or export process is required.
index/open.json holds every submission the reviewer is not yet finished with.
This server adds an id when it admits one, and the reviewer drops one when the
record says there is nothing left to do to it, so a reviewer pass costs the
queue rather than the size of the registry. The reviewer can rebuild this
derived queue from the records on its maintenance path, but the server does not
silently reconstruct or overwrite it. A missing or malformed queue makes intake
and affected status transitions unavailable until the reviewer or an operator
restores it.
index/repairs.json is a separate durable outbox for submitter-authorized
metadata repairs. A successful current-policy preliminary check first renders
the exact project-wide project.description that registration would use as the
public abstract, with the selected Comparator configuration and its declarations
shown separately. It is read-only until the submitter chooses Edit
description. That choice opens
the same authenticated pull-request path, restricted to the one description
field. When preliminary checks instead find only field-level
formalization.yaml problems, the intake page renders the complete guided
editor before authentication. Its bounded edit payload is
stored in the private pending intake. After OAuth proves write access, the
Worker re-reads the file at the exact commit, repeats the portable metadata
validation, checks either that the edits cover every current field problem or
that the voluntary request changes only project.description, and verifies
that they produce valid portable metadata. It then atomically creates a
changes-required submission and queued repair. It dispatches the repair
worker instead of the full verification workflow; that worker still runs
authoritative preparation against the generated commit before opening a pull
request.
The later POST /api/repair path requires the submission capability, the
digest of the current failure, a changes-required record, and fields explicitly
marked repairable in that failure. It atomically writes the request, its queue
entry, and the record marker. Profile 3 covers every mechanically required
metadata field, including the multiline project description and structured
people, source, and automation lists, and
requires one complete submitter-confirmed payload before it queues a pull
request. Safe values from an older metadata shape may be shown as editable
prefills, but classifications, maintainers, source relationships, repository
roles, and review claims are never inferred. Malformed YAML, non-mapping roots,
aliases, unsafe paths, and mixed unsupported failures remain manual-only.
index/rate/<digest>.json slows down a submitter who keeps starting and never
finishes. Starting work is the expensive act: it dispatches either verification
or an authenticated repair, both of which consume somebody's runners whether or
not anything comes of them. So the interval is sixty seconds to begin with,
doubles every time work is started, and is put back to that floor only by a completed
registration or by one of the first two preparation failures that asks for repository
changes. Later metadata failures keep the accumulated interval, so the
correction concession cannot turn repeated preparation work into an unbounded
cheap loop. A submission
that fails full verification, or is withdrawn, leaves it where it is, because
those are exactly the loops worth slowing down; ordinary metadata correction is
not treated as abuse.
The Server owns the rate document's current schema_version: 1 contract. A
present document records a positive integer starts, an integer
interval_seconds of at least sixty, and canonical UTC-seconds last_start_at
and next_allowed_at timestamps. It carries nothing that identifies the
submitter: the file name is a peppered digest so that listing the directory
enumerates nobody, and a cleartext login in the body handed that back to anyone
who could read the files.
That is enforced rather than merely intended. Like the dashboard aggregate and
the principal locator, this contract is an exact field-name allowlist, so a
future writer cannot quietly reacquire an identity field: submitter_login
would have to be added here, past these tests, before it could reach a file.
Two fields the Server no longer writes are on the list because older documents
carry them, and refusing those would lock their submitters out of intake rather
than merely losing information: login, and submission_ids from before the
principal locator took that job over. Between them and the current five, that is
every field any version of the Server has ever written to a rate document, so
the allowlist costs nothing in compatibility. Both are accepted on read, neither
survives a projection, and a registration reset names its output field by field
rather than spreading the document it read, which is how those two outlived the
writers that produced them. A login that is present but is not a GitHub login
is a malformed document and still fails closed.
The cost of the allowlist is that a deployment which adds a field cannot be
rolled back past one that does not know it without refusing the documents it
wrote. That is the same append-only discipline the dashboard contracts state
above, and the reason to add a field by bumping schema_version rather than by
widening this quietly.
The State repository's whole-tree validator deliberately treats these producer-owned fields as opaque, so the Server validates all of them before admission and before projecting a reset. A missing file is a first start; a malformed present file makes intake temporarily unavailable rather than silently granting the floor. A malformed file also leaves a registration reset unapplied until repair, but does not hide the already-registered result from its submitter.
There is no ceiling, and that is deliberate rather than an omission to fix.
Twenty starts with nothing registered is already years; nobody submitting in
good faith reaches it, and the failure worth designing for is the other one, a
person locked out with no way back on their own. The escape hatch is an operator
deleting one file, which is why the file records the time beside the interval.
Which file that is comes from the pepper, not from the file: tools/rate-path.js
turns a login into the path. The name is a digest for the same reason
index/tokens/ is, and the body is anonymous for the same reason again, since a
digest for a name buys nothing if the file it names says the name.
This server applies the reset, not the reviewer, even though the reviewer is
what registers. It sees the reset when a status refresh finds the submission
settled at registered, so the good news and the reset arrive on the same
request. Letting the reviewer do it would put TOKEN_PEPPER in reviewer CI, and
that pepper exists so a leaked state repository yields no live links. Somebody
who closes the tab between consenting and that poll waits longer once, and
opening the link again fixes it.
The scheduled workflow in the private PalomarSubmissionState repository runs
palomar-review auto, which is one command that advances everything in the
queue. The commands below are the manual and recovery interface for the same
pipeline, one submission at a time.
palomar-review list # what is awaiting review
palomar-review run --submission <id> --engine codex # writes no state
palomar-review run --submission <id> --engine codex --apply # deliver privately
# the submitter decides, on their status page
palomar-review register --submission <id> # only after consent
palomar-review finalize --submission <id> --pr <n> # after the database PR mergesrun without --apply still runs the review: it calls the model, spends money,
and writes a workspace. What it does not do is touch the state repository, which
is the only thing --apply adds.
The backoff doubles without a ceiling, so somebody who starts submissions and
never finishes one can end up waiting a length of time nothing they can do will
shorten. Deleting their rate document puts them back at the floor. Finding it
needs TOKEN_PEPPER, because the file is named by a peppered digest of the
submitter's numeric GitHub id and its body names nobody:
TOKEN_PEPPER=... tools/rate-path.js --login someone # resolves the id with `gh`
TOKEN_PEPPER=... tools/rate-path.js --id 4242 # when the id is knownIt prints one index/rate/<digest>.json path and nothing else. Delete that file
in a clone of the state repository and push. last_start_at in the document is
what tells you when this submitter last started, if you want to confirm the file
before deleting it.
The pepper is read from the environment and there is deliberately no argument
for it, because a shell history and a process listing both outlive the run. It
is never printed, including on the failure paths, and gh is spawned without
it.
Rate documents written before the Server stopped recording identity carry a
cleartext login, a submission_ids list, or both. A registration reset drops
them the next time it touches a document, but a submitter who never registers
again leaves theirs in place, so the remainder wants one pass over a clone of
the state repository:
tools/strip-rate-logins.js ../PalomarSubmissionState # report only
tools/strip-rate-logins.js ../PalomarSubmissionState --write # rewrite in placeReview the diff, commit, and push. It needs neither the pepper nor the GitHub
API, and it retires exactly the fields the contract tolerates, from the same
list in src/admission-contract.js. It is deliberately not the validator: it
removes those fields and leaves everything else exactly as it found it, because
it edits files by hand outside the Worker.
Removing a field means reserializing the document, which is not in general a byte-preserving operation. So it rewrites a file only when the bytes on disk are already exactly the canonical serialization of what they parse to, which is what the Worker writes. A document that is formatted differently, or that has duplicate keys or an integer too large to survive a JSON round-trip, is reported and left alone, and the run exits nonzero: rewriting it would change more than the field being removed. Those are for a human to edit. Running it again after a successful pass changes nothing.
It is not erasure: the state repository's git history retains the old bodies, and what happens to those is a question for the state repository's retention policy rather than for this pass.
A withdrawal empties the record it closes, but only from the write that closes it. A submission withdrawn before that shipped kept its submitter, its notes, its authorization evidence and its principal's login, and nothing later goes back for them. The same one pass over a clone retires those:
tools/scrub-withdrawn-records.js ../PalomarSubmissionState # report only
tools/scrub-withdrawn-records.js ../PalomarSubmissionState --write # rewrite in placeIt touches only records already withdrawn, empties exactly the fields
withdrawnRecord empties, and appends the scrub event that record would have
carried, stamped with its own status because that is what the State validator
asks of a last event. A record already scrubbed is left alone, so running it
again changes nothing.
The same caveat applies: history keeps what the tip no longer shows.
An account migration rotates TOKEN_PEPPER, invalidating every old raw status
link. GitHub identity recovery continues to work because current records retain
the submitter's numeric principal. Rebuild the pepper-derived locator and reset
rate history in a fresh State checkout, first as a dry run:
TOKEN_PEPPER=... tools/rekey-pepper-indexes.js ../PalomarSubmissionState
TOKEN_PEPPER=... tools/rekey-pepper-indexes.js --write ../PalomarSubmissionStateThe command refuses to write if any nonclosed record lacks a recoverable
principal. It deliberately preserves index/tokens/: those historical pointers
are required by the State repository's integrity relation even though the new
pepper makes their corresponding raw tokens unusable. Validate and review the
complete State diff before merging it.
Pushes to main are deployed automatically after the test suite passes. The
GitHub repository must provide CLOUDFLARE_ACCOUNT_ID as a repository variable
and CLOUDFLARE_API_TOKEN as an Actions secret. The account variable is public
configuration and must equal the account_id pinned in wrangler.jsonc. The
Cloudflare dashboard credential stored under the token secret is named exactly
palomar worker deployment token.
It is not PalomarDatabaseTools GitHub deploy, which is a separate credential
owned by a different repository. palomar worker deployment token must not
have Workers Routes: Edit: this deployment deliberately uploads and promotes
versions without changing the existing route. Before uploading a version, CI
reapplies the reviewed Worker hostname policy; it then uploads and promotes
that version. The version workflow does not change the existing route or cron
trigger.
The Worker imports no package from npm at runtime. The locked npm graph is development tooling: Wrangler and the official Vitest pool's Miniflare/workerd stack used to test, bundle, and deploy the Worker. That still handles source code and, during the deployment steps, runs next to production credentials, so CI rejects every low-or-higher advisory in the complete development graph. A least-permission weekly workflow repeats that registry audit directly from the lockfile, without running dependency install hooks, so an advisory published after the last code change does not wait for another pull request; it can also be run manually. The required audit includes the runtime test harness and its Vitest graph, so an advisory there also blocks deployment.
npm test remains the fast Node contract suite. npm run test:runtime is the
small required integration layer: it loads both real Wrangler configurations
in workerd, invokes the actual Worker entrypoints, and intercepts representative
GitHub traffic at the runtime fetch boundary. CI requires both before the
deployment job can run; its runtime tests receive dummy required bindings, no
credentials, and no remote services. An outbound-service guard refuses any
request that a test does not explicitly intercept. The pinned workerd/Vitest
graph requires Node 22.12 or later. When advancing a Worker's compatibility
date, also advance the pinned Wrangler when needed so its local workerd supports
that date.
npm run lint applies the locked ESLint recommended correctness rules to every
shipped Worker and browser module, the Node and workerd tests, and their
JavaScript configuration. It catches invalid or unreachable code, unresolved
globals, and unused bindings across those environments. This is deliberately a
lint gate, not a claim that the untyped JavaScript has been statically
type-checked; it parses import declarations but does not resolve their targets.
The lint packages are part of the complete audited development graph described
above, so a future low-or-higher advisory in that graph also blocks deployment.
Before the first deployment against a State repository, commit all three files from
state-bootstrap/index/ as described above. Admission and scheduled
reconciliation validate the contracts before using them; /healthz stays a
network-free configuration check so public monitoring cannot spend the shared
GitHub API budget.
Merge the State reporting change and run Refresh private operational report
to create reports/dashboard.json on State main before merging this Server
change. A Server merge deploys automatically; if the report is not ready, the
authenticated route fails closed with a typed 503 until it appears. No OAuth
application callback change is needed: dashboard and intake both return through the existing
/oauth/callback, and their bound state values are disjoint.
To deploy manually:
npm ci
npm run audit:dependencies
npm run lint
npm test
npm run test:runtime
npx wrangler deploy --dry-run
npx wrangler deploy
curl -s https://submit.palomar-registry.org/healthzBoth Worker configurations explicitly disable workers.dev and versioned or
aliased preview URLs. The reviewed hostnames are their only public routes; do
not enable another hostname without giving it the same exposure review. The
main deployment applies just this account-level hostname policy before every
version upload, while the redirect deployment applies its complete configuration
through wrangler deploy.
The separate, secret-free palomar-domain-redirect Worker owns
palomarregistry.org and www.palomarregistry.org. It permanently redirects
every path and query string to the same URL at palomar-registry.org:
npx wrangler deploy --config redirect/wrangler.jsonc --dry-run
npm run deploy:redirect
curl -sSI https://palomarregistry.org/about.htmlThe guided metadata form serves the checked-in arXiv and MSC 2020 taxonomy
snapshots from public/taxonomies/. Their sources, retrieval date, and
third-party licensing terms are recorded in public/taxonomies/LICENSE.md.
Keep that notice with the assets when updating or redistributing them.