Skip to content
Tenbri pangolinTenbri

Action schemas in CI

An action schema says what an entry for one action type must carry: inputs, a rule, an approval, a target pattern, and any member by its dotted path. You will write a tenbri.toml for issue_refund, check a stream against it with tenbri schema check, test that the entries your Python or TypeScript code writes pass it, have tenbrid refuse entries that break it, and run the checks on every pull request.

  • The tenbri CLI, which runs every check:

    Terminal window
    cargo install tenbri-cli --version 0.1.0-beta.1 --locked
  • A stream with refund decisions, such as the one from Record a refund, and TENBRI_KEYS set as there (export TENBRI_KEYS=./keys).

  • To test your own code, the binding for your language:

Nothing more.

  1. Write tenbri.toml.

    tenbri.toml
    # Every action type must be declared here.
    unknown_actions = "reject"
    [actions.issue_refund]
    description = "Refund a card charge through Stripe."
    inputs = ["policy:refund"]
    rule = true
    approval = true
    target = "stripe:charge/*"
    [actions.issue_refund.fields."rule.result"]
    allowed = ["allow"]

    The requirements apply to intents. Outcomes, approvals and other entries that name an action are only checked for a declared action type.

  2. Check a stream.

    schema check reads tenbri.toml from the current directory (or --schema FILE), then every entry of the streams you name: directories, or s3:// and r2:// URLs. It exits 0 when every entry passes, 1 when one breaks its schema, and 2 when the file itself is invalid.

    Terminal window
    tenbri schema check audit/support; echo "exit $?"
    tenbri.toml: 1 action schemas, unknown actions rejected
    audit/support: 5 entries, 0 break their schema
    exit 0

    Append an intent with no rule and no approval, and one for an action the file does not declare, then check again:

    Terminal window
    tenbri append audit/support > /dev/null <<'EOF'
    {"kind":"decision","phase":"intent","source":"sdk",
    "principal":{"kind":"agent","id":"support-assistant"},
    "action":{"type":"issue_refund","target":"stripe:charge/ch_9Zk2"},
    "inputs":[{"ref":"policy:refund","version":"15","digest":"hmac-sha256:9f2c41aa9f2c41aa9f2c41aa9f2c41aa9f2c41aa9f2c41aa9f2c41aa9f2c41aa"}]}
    EOF
    tenbri append audit/support > /dev/null <<'EOF'
    {"kind":"decision","phase":"intent","source":"sdk",
    "principal":{"kind":"agent","id":"support-assistant"},
    "action":{"type":"close_ticket","target":"zendesk:ticket/4821"}}
    EOF
    tenbri schema check audit/support; echo "exit $?"
    tenbri.toml: 1 action schemas, unknown actions rejected
    audit/support seq 6: rule: required
    audit/support seq 6: approval: required
    audit/support seq 7: action.type: `close_ticket` is not declared in tenbri.toml
    audit/support: 7 entries, 2 break their schema
    exit 1

    A typo in tenbri.toml fails before any stream is read. Here rule is misspelled rul:

    tenbri: tenbri.toml: TOML parse error at line 7, column 1
    |
    7 | rul = true
    | ^^^
    unknown field `rul`, expected one of `description`, `inputs`, `rule`, `approval`, `durability`, `target`, `fields`

    With no stream given, schema check only validates the file:

    Terminal window
    tenbri schema check; echo "exit $?"
    tenbri.toml: 1 action schemas, unknown actions rejected
    exit 0
  3. Test the entries your code writes.

    A production stream only shows a broken entry after it was written. A test catches it first: run the code that records a refund against a throwaway stream, then check that stream.

    If your agent writes with tenbri append or tenbrid, point it at a fixture stream in your test suite and run tenbri schema check <fixture> afterwards, as in step 2.

  4. Enforce the schema at write time.

    tenbri append does not apply tenbri.toml; check the stream with tenbri schema check, or write through tenbrid (the REST tab) to refuse bad entries before they are stored.

  5. Run the checks on every pull request.

    This workflow validates tenbri.toml, then runs the test for your language. Add stream paths after schema check (a test fixture, or an s3:// stream with credentials from secrets) to check stored entries too.

    .github/workflows/tenbri-schema.yml
    name: tenbri schema
    on:
    pull_request:
    jobs:
    schema:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@stable
    - name: Install tenbri
    run: cargo install tenbri-cli --version 0.1.0-beta.1 --locked
    - name: Check tenbri.toml
    run: tenbri schema check --schema tenbri.toml

schema check loaded the schema, then checked every entry and printed each violation with its sequence number and member path. It exits 1 when an entry breaks its schema and 2 when the file itself is invalid, so CI fails either way, and the test in your language catches a missing rule or approval before any real entry is written. tenbrid applies the same file at write time and refuses a breaking append with TENBRI_SCHEMA_VIOLATION.