SITUSDemoDashboardMailboxesPolicyMandatesGrantsReceiptsVerifyPasskeysDocs

Docs

What Situs Pilot 1 is, and how to build against it.

Write a plugin

A plugin is how a new use case joins Situs without changing what a proof means. Write it, check it, package it, sign it, submit it by pull request.

Three words

The package layout

filewhat it holds
manifest.jsonname, major, version, author, license, counterparty kind, minimum level, mock
facades.jsonthe façades: name, kind (read, fetch, protected), params schema, connector op
tools.jsonthe agent's tools in OpenAI function format, each mapped to a façade
schema/profile_rules.json, schema/mandate_rules.jsonJSON Schemas of the rules blocks
rules.pythe pure rules module: binds PLUGIN
connector.jsonhow the enforcement point reaches the counterparty; declarative
vocabulary.jsonthe plugin's domain words, which core must never use
wording.jsonevery word people see: findings, approval cards, labels, reasons
regulatory_map.jsonwhich rules map to which regulation (may declare "none")
fixtures/the profile fixture and an example mandate
scenarios/*.jsonnamed calls with expected outcomes; one must be injected
storyline.yamlthe demo storyline, format situs.storyline/1
mock/optional: a mock counterparty the demo runs against

The plugin API

rules.py implements situs_core.plugin_api.PluginRules: validate_profile_rules, validate_mandate_rules, evaluate(mandate, profile, canonical_action, now, ledger) returning pass, deny or escalate with a reason code, ledger_keys, fingerprint (what "the same action" means for duplicate detection) and describe (who, what, how much, in what unit, for people).

Purity and narrowing: what a bad plugin can and cannot do

The rules module is pure Python under a static import allowlist: no I/O, no network, no clock, no randomness, no model, no service calls, a read-only ledger. Core checks run first; a plugin only sees what survived and can only deny or escalate it. Approval for the covered façade is required unless the signed profile sets approval.delegate_to_plugin: true, and the receipt records that delegation.

situs-plugin: new, check, package, sign

uv run situs-plugin new <name>              # a plugin that passes check, to start from
uv run situs-plugin check plugins/<name>    # the conformance suite; live items need make up
uv run situs-plugin package plugins/<name>  # writes plugin.lock with the package hash
uv run situs-plugin sign plugins/<name> --key author.pem   # the author's signature in the manifest

The conformance suite, item by item

situs-plugin check runs these, in this order, with these titles (one shared list, situs_core/plugins/conformance_items.py). Items that need the stack skip without one.

#idcheckneeds the stack
1files_validateevery file validates against its meta-schema
2vocabulary_neutralvocabulary declared and absent from core
3purityrules.py passes the purity check
4determinismevaluation is deterministic; a shifted now changes only time-dependent checks
5narrowingwhatever core denies stays denied under this plugin (the narrowing fuzz)
6storyline_receiptsthe storyline runs on the local stack and every receipt passes every verifier checkyes
7injected_scenario_deniedthe injected scenario in scenarios/ is denied with one of the plugin's own reason codes
8approval_over_hashthe approval path exercises approval over the action hash through the approval APIyes
9ack_binds_action_hashthe counterparty acknowledgment binds the action hashyes
10wording_covers_findingswording covers every finding id or inherits
11regulatory_map_presentregulatory_map.json present (it may declare "none")
12package_hash_reproducesthe package hash reproduces

Tiers

tierlabelhow
localunlisted pluginany package not in the bundle
listedlisted pluginreviewed, merged, its package hash in situs_core/plugins_bundle.json
certifiedcertified pluginlisted, and passed the conformance suite under review

The tier is a label in the console and the verifier, resolved from the verifier's own bundle. Never a check.

Submitting

Open a pull request to situs-plugins. CI runs situs-plugin check. A merged package enters the static bundle by its package hash; there is no registry service and no marketplace.

Worked example

The plugin written to test that core is general, walked through from its own files:

plugin: records

records (plugins/records/): a support agent looks up regulated customer records and releases them to approved third parties for an allowed purpose. Three façades: records.lookup and requester.verify (read) and records.release (protected, the covered façade). rules.py has four reason codes, fingerprints a release by record, recipient and purpose, and counts releases per day in the ledger; describe() names the recipient, the record and its sensitivity. The connector reaches a mock recipient on port 8004 that signs every delivery, echoing the action hash and the idempotency key. Its injected scenario (scenarios/injected_new_address.json) is a postscript asking to send the complete file to a new address: denied recipient_not_approved. Its example mandate routes restricted releases to a records officer at hardware assurance, so a key file is refused assurance_insufficient. Writing it found the generality bugs listed in docs/DECISIONS.md D34; the pep, the authority's policy, grants, receipts, the approval API and the verifier needed no change. make demo PLUGIN=records plays its storyline; situs-plugin check plugins/records passes every item.