← All documents

Raw Intel — the observation object

Version 0.1.0. NavCom is the authority for this object.

Starcom refines raw intel; it does not mint it. So the shape of what enters the grid is defined here and implemented downstream — a consumer conforms to this document rather than negotiating with it, and changes are announced by version. That is not a claim of seniority. It follows from where the object is born: only the application with somebody standing in the actual place can say what being there produced.

A machine-readable form is published at /.well-known/navcom-intel.json, so conformance never requires reading prose. It carries the kind, the required fields, the vocabulary, the obligations on a consumer, and the list of things that will never appear. Its vocabulary and its status are derived — read out of this file and out of kinds.ts rather than retyped — because a declaration maintained by hand is a declaration that drifts.

Nothing emits kind 1911 yet, and the declaration says so in those words until something does. A published contract claiming a capability that does not exist is the failure this project has already had once, on the status page.

Normative for the object. Deliberately not in docs/spec/: that set is scoped to the MVP loop plus the safety-critical ladder, and its README is right that "specs written before the loop is proven are guesses in a more confident format." This specifies a shape, not a schedule. Nothing here is built.

Sibling of directory-schema.md, which is the normative source for records and corrections. Read that first; this reuses its machinery rather than inventing any.

Why this exists

ecosystem.md specifies Starcom as refining raw intel into Intel Reports and Mission Packages. The phrase occurs twice in the whole docs tree and both times it is Starcom's input. Nothing in the network produces it. NavCom is the only component with people standing in the actual place, and its stated contribution is the ninth tribe — "what is only knowable by being present."


1. Observations are not conditions

The distinction is load-bearing and everything below depends on it.

Condition Observation
Example "St Pat's closes intake at 20:30" "On 3 Sept at 23:10 I saw the door locked"
Claim about the present a moment
Over time rots — needs re-checking stays true forever
Object a correction, 30911 an observation, 1911
Handled by volatility.ts decay never decays; may expire unread

Conflating them produces a system that either forgets evidence or trusts stale claims. A correction says what is. An observation says what someone saw.

2. Kind 1911 — Observation

Regular (1000–9999): stored by relays, immutable, never replaced.

Not replaceable (1xxxx) and not addressable (3xxxx), because both permit an author to overwrite what they previously published. An evidence record whose history can be quietly rewritten is the thing the accountability log exists to prevent; the same reasoning applies one layer down. An observation is superseded by a later observation, never edited.

Not ephemeral (2xxxx), which is the deliberate opposite choice from Distress. A distress call must leave no queryable history. An observation is for the record.

Signed with the Contact key. Never the Operational key — the whole point of the split is that "publishing costs no operational exposure."

Anonymity here is field-level, not key-level, and the earlier wording promised both. The callsign field may be the literal anonymous; the event is still Contact-key signed. That is a deliberate limit rather than a shortcut: key-level anonymity would need a fresh throwaway key per observation, which forfeits supersedes entirely, since supersession is scoped to an author's own account. An operator who wants no linkage at all between two observations must also accept that neither can correct the other.

And it is load-bearing for something it was not chosen for. Because the event is still signed with one contact key, two anonymous observations from the same operator share a pubkey and are detectably one source. Starcom's weight model relies on exactly this: an anonymous observation is floored rather than excluded, and cannot corroborate itself.

Key-level anonymity would break that silently — one actor would present as N independent sources, and Sybil corroboration becomes free. So this is recorded here rather than left as a consequence: a future change that "improves" anonymity to a throwaway key per observation reopens the correlated-fabrication hole across the whole grid, in a system whose analysis layer has been told it can trust source independence.

And there is a third consequence, which is a cost to the operator rather than a benefit to the network. One property, three effects: it preserves supersedes, it makes Sybil arithmetically unavailable — and it means an operator publishing under anonymous is pseudonymous, not unlinkable. Every observation they have ever filed joins on that pubkey, for anyone reading a relay, not only for Starcom.

That is not a small caveat. The Doxxer correlates timing and coarse position to unmask an operator, and an anonymous observation set is exactly that material: linkable, timestamped, location-bearing, and larger than the operator believes it to be because they chose the option named after not being identified.

The name is the defect. anonymous promises something the field does not deliver. It omits a callsign; it does not sever a history. A value that described the act — no name attached — would not invite the belief. Renaming it changes the wire format and therefore the contract version, so it is not done here; it is recorded as the thing to fix in 0.2.0, and in the meantime the sentence an operator must actually see is:

Anonymous means no name. It does not mean no history.

This is the same failure as the terminal's signature toggle, which reads DOCUMENT while the mode is low signature: a control labelled with a promise it does not keep. Found there by measurement and here by Starcom, and worth noticing that the project produced it twice.

3. Fields

Field Required Notes
anchor yes The entity observed. See §5
observed_at yes When it was seen. Distinct from the event's own timestamp
tags yes One or more from the closed vocabulary (§7). May be nothing_observed
method yes saw · told · inferred. Fact, not judgment. See §8
callsign yes A callsign, or anonymous. Never a legal name [invariant 8]
precision yes area or exact. See §4
supersedes no Event id of an observation this replaces in the author's own account

There is no free-text field, and this is the whole enforcement mechanism. See §6.

4. Publication — coarse now, precise later

An observation publishes immediately at area precision. The exact anchor position publishes 48 hours later, as a second event referencing the first.

Why a delay at all. Real-time relay traffic naming a precise location tells a watcher that an operator is standing there now. That is the acute threat and a delay defeats it completely.

Why 48 and not 24. A 24-hour delay preserves the daily cycle: an operator who patrols nightly would have precise positions surface at the same hour every day, and that rhythm is itself a signal. Forty-eight hours breaks the lock-step while costing strategic analysis — whose horizon is campaigns and patterns — effectively nothing.

What the delay does not do. It does not defeat pattern reconstruction from many observations over time; the event carries observed_at, so nothing about when is hidden. That threat is answered by §9 instead. Two threats, two mechanisms, and neither substitutes for the other.

The encoding, pinned

precision Encoding Cell
area geohash, exactly 4 characters ±20 km
exact full coordinates

Four characters, stated as a count rather than a distance. An earlier draft said "coarse (~20 km)" and gave a five-character example, which is ±2.4 km — an eight-fold disagreement inside the one field whose entire job is preventing an operator from being located. A consumer builds against the example, not the adjective. "Coarse" is not a specification, and the privacy claim here is a function of the character count, so the count is normative.

The refinement replaces the observation. It never adds one.

The exact event carries ["refines", "<event id of the area event>"], and a consumer MUST treat the pair as one observation: the refinement supersedes the original's position and the two never corroborate each other.

Spelled out because the obvious reading is wrong and dangerous. Dedup on event id is correct and does not catch this — the two events are genuinely distinct. Without this rule, one operator reporting one thing once produces two observations ~20 km apart that confirm each other, which is the correlated-fabrication failure arriving through a mechanism built for privacy.

refines is deliberately not supersedes. Supersession means the author changed their account; refinement means the same account, at a resolution that was withheld on purpose. A consumer that conflated them would treat added precision as a correction and, worse, treat a correction as merely more precise.

5. The anchor rule

A report MUST name an entity that could, in principle, respond.

St Patrick's — an organisation
4th & Vine streetlight — infrastructure with an owner
The lot at 212 Elm — a parcel
An encampment behind the Home Depot
People sleeping under the overpass

This is invariant 1 — nothing is recorded about the people being served — reaching a case it had always covered and nobody had noticed: an encampment location is information about people being served, expressed as coordinates. Geography is a descriptor when the subject is a population.

It is also the ethical asymmetry made structural. An institution can dispute a report about itself; a patch of ground cannot, and neither can the people on it. So institutions are reportable and ground is not.

The anchor path must not carry notes. The rule above is enforced by there being no free-text field to put a descriptor in — and the place-creation path has one. PLACE_EXTRAS = ['phone', 'hours', 'notes'], and notes is free text. An observation that creates its anchor could therefore publish exactly what §6 exists to prevent, through the door standing next to it. The observation flow must not expose notes, and must say so in the docblock the way places.ts already names the decisive fields it deliberately omits — so the next person does not restore it as an oversight.

An anchor may be created by the observation, through the existing operator-added place path in directory-schema.md §5 — which already demands a method of in_person, staff_confirmed or phone, and already marks the row as never having been through a maintainer. That path also enforces this rule for free: a new place needs a type from the directory taxonomy, and there is no type an encampment can be filed under.

6. No free text leaves the device

Free text cannot be policed; CLAUDE.md invariant 1 already concedes it and says to guide rather than pretend. So it is not policed — it is not published.

An operator's own notes stay in the Wipeable tier, on the device, destroyed by panic wipe, exactly as now. A published observation is composed entirely of selected values.

This single mechanism enforces both hard prohibitions at once:

  • No physical descriptors. Race, clothing, build, vehicle — the machinery of profiling, and what turned Citizen and Nextdoor into instruments of harm. There is no field to put one in.
  • No locating vulnerable people. §5 refuses the anchor; §6 refuses the phrasing.

Cost, stated plainly: with a closed vocabulary this is not fully raw intel. It is structured field observation, which is the safe subset. Roughly a third of the value of unstructured reporting lives in the part we are refusing, and all of the harm does.

7. The vocabulary — STUB, needs human authorship

Not agent work. Extending the taxonomy needs people with local knowledge, and a wrong vocabulary is worse than a thin one because it shapes what operators think to look at.

Seed below is a placeholder for correction, not a proposal:

access      fenced · locked · demolished · rebuilt · blocked
service     closed · moved · hours_changed · capacity_full · reopened
infra       light_out · camera_new · barrier_new · transit_changed
artifact    flyer_posted · notice_posted · sticker_qr
threat      scam_targeting_community · predatory_operation
nil         nothing_observed

Failure is safe by construction: what the vocabulary cannot express does not publish and stays on the device. Nothing is lost, and nobody is blocked waiting for a maintainer.

Fetching it — cache 3600 seconds, and degrade permissive

The list is published at /.well-known/navcom-intel.json under vocabulary.tags. A consumer fetches it; it is never vendored into fixtures, because these twenty are a placeholder and the real list arrives the day somebody with local knowledge writes it.

Cache TTL 3600 s — it is a static file; refetching is free
Stale ceiling 86400 s, after which the list must not be enforced at all
On expiry or fetch failure fall back to shape-only validation, and say so in the drop reason

Degrade permissive, not restrictive, and that direction is the whole rule. Enforcing a list you cannot confirm is current rejects valid observations and they vanish looking exactly like malformed input — the worst failure available here, because it is silent and it discards the thing the network is shortest of. Accepting a tag you cannot verify is a mild correctness cost that shows up in analysis. Prefer the visible error to the invisible loss.

Bounds: ttl_seconds is never below 60 or above 86400. A consumer that reads a value outside them should refuse it and use its own default, because a ttl of zero means refetch on every call — a denial of service against us, published by us. Starcom had to invent that check unprompted; a contract able to instruct a consumer to hurt its publisher should state what it will never ask for rather than leaving every consumer to guess a floor.

Verifying the vocabulary came from us

This file decides what a consumer accepts, so whoever controls the origin controls their allowlist — a compromise here means a consumer enforcing an attacker's list, believing it is ours. The staleness ceiling caps that at 24 hours. This closes it.

vocabulary.cid is a raw CIDv1 (sha2-256) over the canonical bytes, and the same CID is announced as kind 30078, d: navcom:intel-vocabulary, signed by the node key whose pubkey is at /.well-known/navcom-node.json, published to relays the web origin does not control.

1. fetch /.well-known/navcom-intel.json
2. canonical = JSON.stringify(vocabulary.tags) with namespaces sorted,
   members sorted within each, utf8, no whitespace
3. recompute raw CIDv1(sha2-256) over those bytes
4. fetch kind 30078, d=navcom:intel-vocabulary, from a relay
5. verify its signature against the node pubkey — pinned, or learned once
6. the two CIDs must match

Do not take vocabulary.cid as evidence of itself. A hash published beside the thing it describes proves nothing: an attacker rewriting the vocabulary rewrites the hash in the same request. The check is only worth anything against the relay copy, which requires the node key, which the origin does not hold. And step 5 is the one that carries it — a pubkey read from the same compromised origin is no better than no check at all.

The node key's authority is unchanged and is stated in its own descriptor: "this key attests origin, never truth." It says this vocabulary came from NavCom's build. It says nothing about whether the vocabulary is any good.

A vocabulary change does not bump the contract version. The list is data, the version covers the shape, and the two move independently — so a consumer that refetches only when the version changes would never refetch at all. This is the sentence most likely to be assumed away by whoever wires it up.

8. method, and why NavCom never grades

method is saw · told · inferred. This is a fact about how the observer came to know, requiring no self-assessment, and it is the single most valuable thing a raw report carries.

NavCom does not grade its own operators. Scoring people is a reputation system by another name, and "let the agent judge or decide" is a standing anti-pattern.

Grading is Starcom's, using the Admiralty two-axis code (NATO AJP-2.1), and the two axes fall either side of the boundary by their own doctrine — which insists they be judged independently:

Axis Answers From
A–F source reliability has this source been right before? the provenance NavCom attaches
1–6 information credibility does anything else support this? corroboration, which is Starcom's function

F6 — untested source, unverifiable information — is a valid grade, not a rejection. The doctrine notes such information "may still be accurate, actionable, and valuable." A first-night operator is F6. There is therefore no quality bar on submission, ever: the grade carries the caveat so the gate does not have to.

9. Retention — uncorroborated intel is dropped, not deleted

An observation that is never corroborated and never cited is dropped from local stores after 90 days. Corroborated or cited observations are kept.

This is a retention rule, not a property of the event, and the distinction is not pedantic. A regular Nostr event is immutable and held by whatever relay chose to store it; §11 forbids deletion; nothing here can reach across the network and remove something already published. An earlier draft of this section said observations "expire", which described something no relay would honour and no client could enforce — a rule the code could never satisfy.

What is actually specified: devices and nodes drop what they hold, on the shape escalation.ts's reap already uses. A relay that keeps everything forever is not in violation; it is simply not participating in the part we control. The Doxxer's dataset shrinks wherever this runs, which is the honest claim, and it is smaller than the one it replaces.

Why 90. It reuses the window the Accountability tier already has rather than inventing a new one, and it is long enough for a second operator to visit the same place in an ordinary patrol cycle.

One rule doing two jobs: it keeps the pool clean with no moderator, and it shrinks the stored set to the observations that mattered to somebody. An observation is permanently true; permanent retention is a separate choice, and it is the only half of that we can decide.

Citation pins, so the provenance chain can never break — anything a report depends on is by definition cited.

10. The valve

Refines ecosystem.md's table for this object. C35 forbids operator records and permits patterns; those are not opposites, and an observation is neither. A Contact-key observation about an institution is raw and is not an operator record, so it crosses intact — aggregating it first would be NavCom doing Starcom's job badly while destroying the evidence chain.

Direction Crosses Never
NavCom → Starcom Observations, whole, with provenance Operational-key anything; positions; board state; query text

On the callsign, because two published contracts disagreed about it. navcom-refusals.json says callsigns never cross; this object declares callsign required. Both were shipped on the same night and could not both be true as written.

The resolution is that they describe different acts. The valve governs what NavCom discloses — hands over, correlates, answers questions about. It cannot govern what an operator already published, because a Contact-key observation goes to public relays and Starcom can subscribe to those without asking anyone. A rule forbidding the handover of something already readable is not a protection, it is a fig leaf, and this project does not get to ship one.

So the control is operator-side and always was: anonymous is available on every observation, and an operator who does not want their callsign on a public fact does not put it there. That is a weaker guarantee than the refusals file implied, and it is the true one. | Starcom → NavCom | Grades and citations for observations | Anything reaching the Field Terminal; anything as a feed |

Provenance must survive refinement. An Intel Report cites the event ids it was built from. Because observations are public and durable, the chain is walkable by third parties — a journalist can take a published Report back to its F6 sources without asking Starcom or us. That is the only verification that means anything, and ecosystem.md founds the whole network on the failure it prevents: "the tribes exchange conclusions stripped of their evidence chain, which is why the exchange doesn't compound."

11. What this deliberately is not

  • No photographs. Every threat re-enters through one — faces, plates, EXIF, reflections — and an operator cannot see what is in frame at 1am. Deferred, named, not forgotten
  • No evaluation in NavCom. Not confidence, not triage, not priority
  • No feed. Submission is an act. Results appear when an operator looks, never as a notification; the field terminal stays silent
  • No automatic publication. The default is that nothing leaves
  • No deletion. Retraction is a new observation, and a source who self-corrects is more reliable, not less
  • No reward a reporter can produce alone. Submissions, streaks and speed are all farmable and all banned. Cited, corroborated and stood-up-after-reporting-early are conferred by others and cannot be farmed. Incentivised volume does not add noise to an analysis layer, it manufactures correlated false signal

docs/product/raw-intel.md