Sharing with Jadpo HQ
On this page
Sharing is how Jadpo learns which signals are right: which findings people take, which they ignore and why, and what the scan missed. Jadpo HQ is the service that collects them.
Sharing is on unless it is turned off, and nothing goes unless someone
runs share. Only hashes go without a person's approval; a snippet's
text never does.
$ jadpo suggest share✓ Sent 3 hashes to Jadpo HQ kinds 1 accepted, 1 found, 1 ignored asked nothing more token new, in app.jadpo: commit it so every copy counts as one projectSharing is on Only hashes went. To stop sharing, add `share: false` to `suggestions { }`.
The project token
app.jadpo holds suggestions { project_token: "…" }: 32 random hex
digits tied to nothing, no repository, person or machine. It is committed,
so a team counts as one project, and HQ counts distinct projects without
knowing which they are.
Whenever it is missing, share writes it before sending, made from the
operating system's random generator. With nothing pending it writes none.
share --new-token replaces it and sends nothing. A fork shares its
parent's token until it does. Only 32 lower-case hex digits read as a
token: any other value is type.suggestions_value_invalid, so a
repository's name can't be used as one.
Turning it off
suggestions { share: false } turns sharing off for the project.
JADPO_SUGGEST_SHARE=off in the environment turns it off for a whole
machine or organisation; any value but on or empty turns it off, so a
misspelling never sends. share: false counts in whichever file the
suggestions { } is in. Neither can turn sharing back on over the other.
Either way, every form of share, --approve and --new-token included,
stops at once. It sends nothing, writes nothing, keeps the pending
entries and says where sharing was turned off. The scan's report drops its
instruction to share. The scan, ignore and found still record entries.
A suggestions { } block with a problem stops share too.
What share sends
share sends every pending entry's text hash with its kind (found,
accepted or ignored), its signal and its suggestion, and the token. It
does so at once, with no prompt: the agent runs it itself at the end of a
suggest session, as the report's instruction says. The signal and
suggestion go because HQ reassesses a signal by how often it is ignored,
and most entries never send their text. Nothing else goes, not even
Jadpo's version.
HQ replies with the hashes whose text it wants: those seen from enough
distinct projects. The command writes the request's address and body to
.jadpo/suggest_wanted, shows exactly what share --approve would send,
and stops.
$ jadpo suggest share✓ Sent 3 hashes to Jadpo HQ kinds 1 accepted, 1 found, 1 ignored asked 1 snippetWhat `jadpo suggest share --approve` sends POST https://hq.example.test/v1/suggestions/snippets { "snippets": [ { "kind": "ignored", "reason": "Basis points, not money", "signal": "conversion", "suggestion": "Money", "text": "total_price * 100" } ] }◆ decision Jadpo HQ asks for 1 snippet of this project's code next Ask the person whether to send it, showing them the payload above. Run `jadpo suggest share --approve` only after they say yes; otherwise run `jadpo suggest share --decline`. (human)
Here (human) means the choice is yours: whether a snippet of your code
leaves your machine.
Approving or declining
A person approves, directly or through the agent asking them in its
conversation. Only then does share --approve send those bytes to that
address, whatever JADPO_SUGGEST_HQ says by then. It sends the snippets,
each with its kind, signal and suggestion and, for an ignored one, its
reason; a found entry's reason stays local. HQ recomputes their hashes.
share --decline drops them and sends nothing. The next share replaces
what waits, unsent.
$ jadpo suggest share --approve✓ Sent 1 snippet to Jadpo HQ$ JADPO_SUGGEST_SHARE=off jadpo suggest share✓ Sent nothing: sharing is turned off where JADPO_SUGGEST_SHARE=off in the environment
--json gives the counts, the hashes HQ asked for, the payload, the
address it goes to, and an approval object an agent relays: the
question, the payload, the two commands, and the rule that --approve is
only valid after the person said yes. The command can't check that they
did, so asking is the agent's job, written into the decision's next.
Unattended runs
An unattended run (--unattended, or CI set to anything) sends only
hashes and skips HQ's request for text. HQ asks the next project that
sends that hash, and the next share drops a request left waiting. An
unattended run can't --approve (cli.suggest_arguments), and share's
flags given without share are refused rather than ignored.
Afterwards
The pending file is wiped, whether HQ wanted anything or not; a send that
fails keeps it. The first time a checkout sends hashes, share says so in
one line, with how to turn sharing off. That first time is noted in
.jadpo/suggest_shared, so nothing is written outside the project.
While HQ isn't running
No HQ address is configured by default, so a suggest session never ends
in a failure while HQ doesn't exist. share says
Sharing isn't available yet: Jadpo HQ isn't running. Nothing was sent; your entries are kept., sends nothing, keeps the entries and succeeds.
The scan's report leaves out its instruction to share, as it does when
sharing is off (The protocol).
The protocol
This is the contract the real HQ implements unchanged: HTTPS and JSON, UTF-8, under a versioned path.
-
Address.
JADPO_SUGGEST_HQin the environment. There is no default yet: the release that launches HQ, with the registry, sets the real address as the default.http://is accepted only for a loopback host (localhost,127.0.0.1,[::1]), which is how the tests reach the fake. Anything else must behttps://, through OpenSSL, with the platform's certificate store and the host name checked. Only thejadpotool links OpenSSL; no deployable does. -
Headers.
Content-Type: application/json,Accept: application/json,User-Agent: jadpo, without a version, andConnection: close: the client is a small HTTP/1.1 one ofjadpo's own. The token in the body is the only identifier the client sends. The client logs nothing about a request: no repository name, user, machine or address. -
POST /v1/suggestions/hashessends at most 500 entries;sharesends more in several requests.{ "token": "5f0c2a9e7b1d4c38a6e2f9b0d7c4a1e3", "entries": [ { "hash": "8c1d0e5a4f3b2a19", "kind": "ignored", "signal": "conversion", "suggestion": "Money" } ]}
hashis the text hash (16 hex digits of BLAKE3 of the snippet's text afterjadpo fmt, comments removed).kindisfound,acceptedorignored.signalis a signal's word (taughtfor afoundentry).suggestionis a standard type or operation. HQ answers200with the entries whose text it wants, each a hash and kind it was sent, so an ignored entry's reason can be asked for without the accepted ones' texts. It names at most 20, so an approval stays readable:{ "wanted": [ { "hash": "8c1d0e5a4f3b2a19", "kind": "ignored" } ] }For each hash, kind, signal and suggestion, HQ counts the distinct tokens it has seen it from, so sending the same entry twice changes nothing and a retry is safe. It wants a hash's text once that count reaches its threshold and it doesn't hold the text yet; for an ignored entry, until it holds a few reasons. A hash below the threshold is never asked for.
-
POST /v1/suggestions/snippets, only after approval, carries no token: a snippet is never tied to a project.{ "snippets": [ { "kind": "ignored", "reason": "Basis points, not money", "signal": "conversion", "suggestion": "Money", "text": "total_price * 100" } ] }textis the snippet's text without comments, whose hash is the one HQ asked for.reasonis sent only for an ignored snippet. HQ recomputes each hash, keeps the snippets it asked for and refuses the rest, answering200with{ "received": 1, "refused": 0 }. -
Errors. Any other status has the body
{ "error": { "code": "…", "message": "…" } }:400 invalid_request(with the field),413 too_large(a body over 1 MiB),410 version_retired(this Jadpo is too old to share; update it),429 rate_limitedwithRetry-Afterin seconds, and500,502,503or504for HQ's own trouble. -
Timeouts and retries. 5 seconds to connect and 15 for each answer. A failed connection, a timeout,
429, and500,502,503and504are tried again up to three times: after half a second, 1 and 2 seconds (at most three and a half seconds of waiting), or afterRetry-Afterwhen HQ gives one. ARetry-Afterover 60 seconds ends the attempt. Nothing else is retried. When the last try fails,sharefails withsuggest.share_failed, sends nothing more and keeps the pending entries for the next run. -
What HQ keeps. For each hash, the set of tokens it came from and its kinds, signals and suggestions; and the texts and reasons it asked for, without a token. Nothing else: addresses are used only to rate-limit, in memory, and never written down.
HQ's agent
HQ runs an agent over what is shared. It turns shared patterns into signal data for the next release: new constants, words and validation texts on a type's entry, a threshold, the tests each new signal is generated with, and the release notes saying what changed.
It reassesses a signal that is ignored often across distinct projects. It reads the ignored snippets and their reasons beside the accepted ones, then narrows the signal or removes it, tested against the accepted snippets so it keeps catching what it got right. Where a pattern points at something the language lacks, it proposes a language feature for a person to decide on, and never builds one.
Not built yet: Jadpo HQ itself, which comes later, with the registry.
Sharing is built against a local fake of HQ that speaks the protocol
above. jadpo new writes the project token; for a project without one,
share writes it.
Markdown for agents: /docs/sharing-with-jadpo-hq.md.Built from Jadpo main at a707efb.