{
  "spec_name": "Still OS Notary Trust Vocabulary",
  "doc_version": "1.3",
  "schema_version": "1.0",
  "published_by": "Still OS Digital Holdings",
  "published_at": "2026-07-04",
  "updated_at": "2026-07-05",
  "purpose": "A public, versioned, machine-readable description of every trust primitive this notary exposes: claim verdicts, disputes against those verdicts, and pre-action spending authorization. Published in response to a design-partner thread raising claim-vocabulary fragmentation across notaries as the real bottleneck to composable trust -- this is our half of that: what we check, and how, stated plainly enough that another notary or an agent evaluating us cold can compare against it without asking us first. Extended 2026-07-04 to cover dispute and authorization shapes once it became clear the same fragmentation problem recurs one layer up -- an agent authorized by our gate or granted recourse through our dispute path means nothing to a counterparty running an incompatible one unless the shape is public too.",
  "not_a_claim_of": "industry-wide convergence -- this describes what THIS notary does, not a standard other notaries have agreed to. Convergence requires more than one party publishing a spec.",
  "commit_receipt_shape": {
    "agent": "string, caller-supplied, free-form, unauthenticated",
    "claim_sha256": "sha256 hex of the claim text (or caller-supplied pre-hash)",
    "ts": "ISO8601 timestamp, moment of commit",
    "prev_hash": "sha256 hex of the immediately prior receipt in the global chain -- this, not per-agent sequencing, is what makes the whole ledger tamper-evident: see commitment_completeness below",
    "notary_fp": "this notary instance's public key fingerprint",
    "resolver_hash": "sha256 hex or absent -- present only if a resolver spec was supplied at commit time, locking the settlement methodology before the outcome is known",
    "reasoning_trace_hash": "sha256 hex or absent, added 2026-07-05 (RFC v0.1 §4 Approach A). Caller hashes their OWN reasoning trace client-side and supplies only the hash -- the trace itself is never sent or stored, preserving confidentiality. Binding it into receipt_hash proves a trace existed unchanged at commit time. It does NOT prove the trace was the actual execution path that produced the claim -- an agent can hash a plausible trace it never ran. §4's harder question (verifiable linkage to the real execution path, e.g. via TEE attestation) stays open.",
    "receipt_hash": "sha256 hex, this receipt's own hash over the fields above in fixed key order",
    "signature": "Ed25519 signature of receipt_hash, base64",
    "verify": "URL to re-check this receipt"
  },
  "verdict_object_shape": {
    "kind": "claim_verdict",
    "agent": "string, caller-supplied, free-form, unauthenticated",
    "claim_receipt_hash": "sha256 hex, the commit receipt for the raw claim text",
    "partner_receipt_hash": "sha256 hex or null, optional counterparty receipt to bind to",
    "verdict": "CONFIRMED | REFUTED | ERROR | PENDING | DISPUTED | ATTESTED",
    "outcome": "boolean or null -- null unless verdict is CONFIRMED/REFUTED",
    "settles_against": "string, the specific external source this verdict resolved against",
    "resolver_type": "one of the resolver_types below",
    "resolver_confidence": "number 0-1, static per resolver_type today, not yet dynamic",
    "observed": "object, resolver-specific raw data supporting the verdict",
    "resolved_at": "ISO8601 timestamp",
    "action_class": "read-only -- always, by construction; this notary never writes on the caller's behalf",
    "schema_version": "string, this spec's version -- '1.0' as of 2026-07-04",
    "freshness": "object {kind, note, stale_after} or absent -- present only when verdict is CONFIRMED/REFUTED/ATTESTED. stale_after is an ISO8601 timestamp or null (null = permanent/attestation kinds never go stale); for point_in_time kinds it is a conservative default (60s) since we cannot know how fast an arbitrary caller-specified source drifts; for price_oracle it is a real, params-derived window (samples * sample_interval_ms), not a guess. See freshness_semantics below",
    "failure_class": "string or absent -- present only when verdict is ERROR/PENDING/DISPUTED, see failure_class_taxonomy below",
    "epistemic_status": "string or absent -- present only on physical_attestation; explicitly flags attestation-only evidence, never confuse with independently-verified",
    "resolver_independent": "boolean, added 2026-07-05. false ONLY for broker_position/broker_order/ibkr_balance -- resolver types that check the COMMITTING PARTY'S OWN brokerage/IBKR account state, so the audited entity and the audit source are the same party. true for every other resolver_type, where the source is a genuine third party (GitHub, an on-chain RPC, a public price feed, Kalshi). This is a label, not a fix -- see known_gaps_not_covered_by_this_spec"
  },
  "resolver_types": {
    "github_pr": { "spec": "{owner, repo, number}", "checks": "is the PR merged, closed-unmerged, or still open", "source": "api.github.com", "freshness_kind": "permanent", "resolver_independent": true },
    "onchain_tx": { "spec": "{chain: 'eth'|'base'|'arbitrum', txhash, min_confirmations?: number}", "checks": "is the tx mined and successful, optionally past a confirmation depth", "source": "public RPC (keyless)", "freshness_kind": "permanent_after_confirmation", "resolver_independent": true },
    "url_json": { "spec": "{url, path: 'a.b.c', expect}", "checks": "does a JSON field at a public endpoint equal an expected value", "source": "any public HTTP endpoint", "freshness_kind": "point_in_time", "resolver_independent": true },
    "http_status": { "spec": "{url, expect_code}", "checks": "does the URL return the expected HTTP status", "source": "any public HTTP endpoint", "freshness_kind": "point_in_time", "resolver_independent": true },
    "kalshi_market": { "spec": "{ticker, side: 'yes'|'no'}", "checks": "did the market settle, and did the named side win", "source": "api.elections.kalshi.com", "freshness_kind": "permanent_after_settlement", "resolver_independent": true },
    "price_oracle": { "spec": "{sources: [{url, path}] (>=3), comparator: 'gte'|'lte'|'eq', threshold, tolerance_pct?, samples?, sample_interval_ms?}", "checks": "N-source, time-sampled, deviation-gated price consensus against a threshold", "source": ">=3 independent public price feeds (caller-supplied)", "freshness_kind": "point_in_time_window", "resolver_independent": true },
    "physical_attestation": { "spec": "{claim_text, attester_ids: string[] (>=1), min_attesters?: number (default 2)}", "checks": "do >=min_attesters distinct named parties corroborate the same claim text -- NOT a verification of the underlying physical fact, only that named parties asserted it", "source": "caller-supplied attester identities, no independent external source (structurally cannot have one for a physical-world claim)", "freshness_kind": "attestation_only", "resolver_confidence": 0.3, "resolver_independent": true },
    "erc8004_reputation": { "spec": "{chain?: 'base', agentId, clientAddresses: string[] (>=1), tag1?, tag2?, comparator, threshold}", "checks": "on-chain ERC-8004 reputation registry read against a threshold, client-filtered to resist Sybil aggregation", "source": "public Base RPC (keyless)", "freshness_kind": "point_in_time", "resolver_independent": true },
    "broker_position": { "spec": "{account_id, symbol, comparator?, expected_qty}", "checks": "does the named brokerage account currently hold the expected quantity of a symbol", "source": "the committing party's OWN linked brokerage account (SnapTrade)", "freshness_kind": "point_in_time", "resolver_independent": false },
    "broker_order": { "spec": "{account_id, brokerage_order_id, expect_status}", "checks": "did a specific brokerage order reach the expected terminal status", "source": "the committing party's OWN linked brokerage account (SnapTrade)", "freshness_kind": "permanent", "resolver_independent": false },
    "ibkr_balance": { "spec": "{account_id, comparator?, expected_amount}", "checks": "does the named IBKR account balance satisfy a comparison", "source": "the committing party's OWN linked IBKR account", "freshness_kind": "point_in_time", "resolver_independent": false }
  },
  "freshness_semantics": {
    "permanent": "once resolved, this fact does not change",
    "permanent_after_confirmation": "stable once a caller-specified confirmation depth is met; unconfirmed can still reorg",
    "permanent_after_settlement": "volatile and non-final while open; permanent once finalized",
    "point_in_time": "true only at the moment the call ran; the source can change immediately after",
    "point_in_time_window": "reflects a short multi-sample window at commit time, not a permanent value",
    "attestation_only": "records that named parties asserted a claim; never becomes stronger evidence of the underlying physical fact over time"
  },
  "failure_class_taxonomy": {
    "SOURCE_UNREACHABLE": "the external source could not be reached (network, timeout, or bad response)",
    "SOURCE_NOT_FOUND": "the named target does not exist at the source",
    "RATE_LIMITED": "the source is throttling requests -- retry later",
    "AWAITING_FINALITY": "the claim exists but has not yet reached a final, resolvable state",
    "QUORUM_NOT_MET": "not enough independent sources responded to reach a verdict",
    "SOURCE_DISAGREEMENT": "independent sources disagree beyond the allowed tolerance",
    "MALFORMED_RESPONSE": "the source responded but not in the expected shape",
    "UNSUPPORTED_RESOLVER": "no resolver exists for the requested claim type"
  },
  "dispute_object_shape": {
    "endpoint": "POST /notary/dispute — bonded, $1 x402, non-refundable either way (anti-spam)",
    "kind": "verdict_dispute",
    "original_receipt_hash": "sha256 hex of the verdict receipt being disputed",
    "agent": "string, the disputing party",
    "reason": "string, free-form, max 500 chars",
    "original_verdict": "the disputed receipt's original verdict label (CONFIRMED|REFUTED|ATTESTED|ERROR|PENDING|DISPUTED)",
    "original_outcome": "boolean or null, copied from the original verdict",
    "fresh_verdict": "the verdict label computed by re-running the SAME resolver spec right now",
    "fresh_outcome": "boolean or null, from the fresh re-resolution",
    "fresh_status": "the raw resolver status string from the fresh re-resolution",
    "fresh_observed": "object, the fresh resolver's raw observed data",
    "upheld": "boolean -- true means fresh_verdict differs from original_verdict (the original is OVERTURNED); false means they match (dispute REJECTED, original stands)",
    "resolved_at": "ISO8601 timestamp",
    "mechanics": "verifies the supplied verdict_object hashes to match claim_sha256 on the disputed receipt (rejects fabricated verdict objects), enforces a 48h window from the original receipt's ts, and requires the resolver spec's settles_against to match the original (rejects swapping in an easier target). Compares verdict LABELS, not just a boolean outcome, so any resolver type -- including non-binary ones like physical_attestation's ATTESTED -- can be genuinely overturned, not just CONFIRMED/REFUTED ones. The original receipt is never rewritten; a dispute appends a new linked receipt, it does not edit history."
  },
  "authorization_decision_object_shape": {
    "endpoint": "POST /notary/authorize (after POST /notary/register-policy) — free, no x402: charging per-check would incentivize skipping the safety gate to save money",
    "kind": "pre_action_authorization",
    "agent": "string, the requesting party",
    "action": "string, free-form label for what is being authorized",
    "amount": "number, the amount being requested for this action",
    "decision": "ALLOW | DENY",
    "reason": "string -- 'within_policy', or the specific limit that was exceeded, or 'no_policy_registered'",
    "policy_snapshot": "object {max_per_action, max_per_day, currency}, the registered policy in effect at decision time",
    "already_spent_today": "number, cumulative ALLOWed amount for this agent on the current Phoenix-local calendar day before this request",
    "day": "YYYY-MM-DD, Phoenix-local date bucket used for the cumulative check",
    "decided_at": "ISO8601 timestamp",
    "mechanics": "deterministic, no model judgment: amount > max_per_action -> DENY; cumulative same-day total > max_per_day -> DENY; no policy registered for this agent -> DENY (fails closed, never ALLOW-by-default). Every decision, ALLOW or DENY, is signed and hash-chained into the same ledger as every other receipt -- a denial is a real checkable artifact, not a silent drop. Scoped to spending-limit gating only, not a general action/tool/resource authorization engine."
  },
  "known_gaps_not_covered_by_this_spec": [
    "cross-notary claim-vocabulary convergence -- this is one notary's vocabulary, not a shared standard, and the same fragmentation risk now applies to the dispute and authorization shapes too, not just claims",
    "source-independence enforcement for price_oracle -- flagged (observed.independence), not enforced",
    "physical_attestation narrows the attester-said vs fact-is-true gap, it does not close it -- a colluding or compromised set of attesters still corroborates a false claim; this is structural, not a missing feature",
    "resolver_independent (added 2026-07-05) LABELS self-referential resolution, it does not CLOSE it -- broker_position/broker_order/ibkr_balance still resolve against the committing party's own account; there is no independent resolver for account-state claims today. What the flag DOES do: claim_verdict.cjs's optional own-account execution path now checks it and REFUSES to fire a real trade when resolver_independent is false, the same way it already refused on REFUTED/PENDING/ERROR verdicts -- a circular-trust verdict can no longer move real money, even though the underlying resolver independence gap is unchanged",
    "recursive disputes -- no meta-arbitration if a dispute's own fresh re-resolution itself comes back DISPUTED or is disputed a second time",
    "aggregate/systemic exposure across many agents -- authorization checks one agent's own limit; correlated authorized spend across many distinct agents against a shared resource is not monitored",
    "emergency policy revocation -- registerPolicy only overwrites; there is no distinct, signed 'suspend this policy now' action separate from re-registering"
  ],
  "commitment_completeness": "answers RFC v0.1 §7.1 ('should all commitments be visible, not just resolved ones'). GET /notary/export returns EVERY receipt and EVERY verdict ever committed, for every agent, unfiltered, in one hash-chained sequence (each receipt_hash embeds prev_hash). No agent -- including us -- can selectively publish only favorable resolutions: pulling any agent's slice out of /export and checking prev_hash continuity proves nothing between two of that agent's entries was deleted or hidden. This was already live infrastructure; this spec update is the first place it is named as the answer to that specific objection.",
  "verify": "https://nolawealthfinancial.com/notary/export returns the live ledger, public key, and hash-chain verification procedure. This spec describes the shape; /notary/export proves the instance."
}
