Introduction
Ironclad records and compares the assumptions your software depends on.
Tests catch many failures, but they do not usually notice when:
- a vendor changes an HTTP response
- a CLI tool starts printing one extra warning line
- a config file gains a field you were not expecting
- an internal dashboard moves the sentence you scrape every Monday morning
Ironclad turns those assumptions into facts, facts into snapshots, and snapshots into material you can review directly.
The basic rhythm
Most Ironclad workflows follow the same loop:
- Define one or more facts.
- Resolve the current state into a snapshot.
- Compare that snapshot with the approved snapshot.
- Review the drift.
- Apply the approved changes.
The command loop is intentionally small. The main flexibility comes from the fact pipeline itself: read a file, fetch a page, split some text, extract the line that matters, and keep only that.
What Ironclad stores
An Ironclad catalog lives in .ironclad/ and usually contains:
.ironclad/
├── facts/
├── index.toml
└── snapshots/
├── actual.json
└── canon.json
facts/holds fact definitions.index.tomlmaps friendly labels to fact IDs.snapshots/canon.jsonis the approved snapshot.snapshots/actual.jsonis the resolved snapshot.
Why this differs from plain diffing
Ironclad does not only diff files. It diffs processed observations.
That means you can compare:
- the second JSON field inside a local file
- the text inside a specific HTML node
- the output of a command after trimming noise
- a section of a file selected by tags
The result is usually more targeted than watching an entire file and diffing every incidental change.
Installation
You can install Ironclad with Cargo.
From local source
cargo install --path crates/ironclad-cli
From GitHub
cargo install --git https://github.com/truecrunchyfrog/ironclad --branch master
Verify the install
ic --help
ic op list
If ic op list prints a list of operation IDs, the CLI is installed and the built-in operations are registered.
Quickstart
This chapter shows the shortest path from an empty directory to a working fact.
1. Initialize a catalog
ic init
This creates .ironclad/ in the current directory.
2. Add a fact
ic add tea-menu
Ironclad prints either the label you chose or a fact ID if you created an unindexed fact.
3. Open the fact
ic edit tea-menu
Put a small pipeline in it:
description = "Track the teas currently advertised in the cafe window."
[[steps]]
use = "seed.file.text"
options.files = ["menu.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
And add a file:
Jasmine Green
Smoked Earl Grey
Ube Oolong
4. Resolve the current state
ic resolve tea-menu --output -
The snapshot should contain three samples.
5. Accept it as the approved snapshot
ic apply tea-menu
If this is the first run, you can also approve everything:
ic apply --all
6. Review later changes
ic resolve
ic diff
ic inspect tea-menu
ic check
At that point you have the basic workflow: capture, compare, inspect, and approve.
Concepts
This section explains the vocabulary Ironclad uses repeatedly.
If you understand catalogs, facts, snapshots, samples, and traces, the rest of the manual becomes much easier to read.
Catalogs
A catalog is the metadata directory where Ironclad keeps facts, indexes, and snapshots.
The catalog directory is .ironclad/.
The directory above it is the container directory.
That distinction matters:
- the catalog directory stores Ironclad data
- the container directory is the workspace your facts usually observe
For example:
project-root/
├── .ironclad/
├── app/
├── config/
└── README.md
Here:
project-root/.ironclad/is the catalog directoryproject-root/is the container directory
Discovery
If you do not pass --catalog-dir, Ironclad searches upward from the current working directory until it finds .ironclad/.
If you do pass --catalog-dir, it must point to the catalog directory itself. Ironclad does not append .ironclad for you.
Catalog layout
.ironclad/
├── .gitignore
├── facts/
├── index.toml
└── snapshots/
├── actual.json
└── canon.json
facts/holds fact files.index.tomlmaps labels to fact IDs.snapshots/actual.jsonstores the resolved snapshot.snapshots/canon.jsonstores the approved snapshot.
Facts
A fact is a named assumption expressed as a small pipeline.
Examples:
- “this config file contains exactly three upstream hosts”
- “the lunch page still says Wednesday is dumpling day”
- “the build tool prints the same version string as yesterday”
Each fact lives in a TOML file under .ironclad/facts/.
Labels and IDs
Facts have two identities:
- a fact ID usually a generated ULID-like string used as the filename
- a label
a human-friendly name stored in
index.toml
Many commands accept a fact selector, which can be either the label or the fact ID.
Fact shape
A fact may include:
descriptionimportsexportsstepssecret
The central part is the steps array. Each step uses an operation and optional options.
description = "Track the names of dragons currently listed in the registry."
[[steps]]
use = "seed.file.text"
options.files = ["dragons.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
Indexed and unindexed facts
Normally you create a fact with a label, which adds it to index.toml.
You can also create one with --no-index. That leaves the fact file on disk but without a label entry in the index.
That is occasionally useful for experimentation, but indexed facts are the normal case.
Snapshots
A snapshot is a map of fact labels to batches of samples.
Ironclad usually deals with two snapshots:
- the resolved snapshot, usually stored in
actual.json - the approved snapshot, usually stored in
canon.json
What snapshots contain
Each fact label points to a batch:
samplescreated
Each sample contains:
contenttraces
The content is what you actually compare.
The traces explain where that content came from.
Approved and resolved snapshots
In review-oriented terms:
- the approved snapshot is the baseline
- the resolved snapshot is the proposal
ic diff compares the two.
ic inspect lets you read one snapshot.
ic apply promotes approved entries from the resolved snapshot into the approved snapshot.
What counts as drift
Drift means the batches differ.
That includes:
- added samples
- removed samples
- changed content
- multiplicity changes
If a batch used to contain one copy of a sample and now contains two, Ironclad treats that as drift.
Samples and Traces
A sample is the atomic piece of observed state in Ironclad.
Each sample has:
content- one or more
traces
Content
The content is the string that operations transform and that snapshots ultimately compare.
It might be:
- one line from a file
- one HTML node
- one JSON value
- one command output
Traces
Traces are small key-value maps that explain provenance.
Examples:
{ "path": "menu.txt" }
{ "json_node_path": "$['dessert']" }
{ "start": "10", "end": "24" }
Each time an operation evolves a sample, it usually appends a new trace.
That means a single sample can tell a small story:
- it came from
menu.txt - then it was split into lines
- then one regex extracted a piece of it
When inspect --trace or diff --trace is useful, it is usually because that story matters as much as the final content.
Pipelines
A fact pipeline is the ordered list of steps in a fact.
Each step:
- chooses an operation with
use - optionally passes
options - receives the output samples of the previous step
A tiny example
[[steps]]
use = "seed.file.text"
options.files = ["observatory.log"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.find"
options.regex = "comet-[0-9]+"
This pipeline:
- reads a file
- splits it into lines
- extracts comet IDs from each line
Per-sample and batch-style behavior
Most operations conceptually run per sample.
For example:
text.trimtext.replacehtml.findjson.find
Some operations act more like batch filters or producers:
seed.*operations usually create samples from nothingslicereorders or narrows the whole input batchcompactremoves empty samples from the whole input batch
You do not usually need to think about the distinction while authoring facts, but it helps explain the shape of the output.
Secrets
Facts can be marked as secret:
secret = true
When a secret fact is resolved normally, Ironclad redacts the sample contents before writing them to snapshots.
Instead of storing the original content, it stores a digest marker.
That means:
- you can still detect drift
- you do not leak the secret value into the snapshot file
If you really need the unredacted values during a run, ic resolve --no-redact disables redaction for that invocation.
Use that flag carefully.
Selectors
Several commands let you choose facts by fact selector.
A fact selector can be:
- a label
- a fact ID
This applies to commands such as:
ic showic editic renameic remove
Include and exclude sets
ic resolve works slightly differently.
It supports:
- positional include labels
--excludelabels
That lets you answer questions such as:
- “resolve only the homepage fact”
- “resolve everything except the chatty HTTP fact”
Missing selectors
If a fact selector does not resolve to a known indexed label or an existing fact file, Ironclad fails explicitly.
That is intentional. Ignoring a misspelled selector would make command results ambiguous and harder to trust.
Guide
This guide walks through the practical use of Ironclad.
The reference chapters later in the book tell you what each command and operation does. This section focuses on how they fit together when you are building and maintaining a real catalog.
Setup
Start in the directory you want Ironclad to observe and initialize a catalog:
ic init
That creates .ironclad/ in the current directory.
.ironclad/
├── .gitignore
├── facts/
├── index.toml
└── snapshots/
The usual habit is:
- keep
.ironclad/in the project root - keep the files you observe next to it in the same container directory
That keeps relative paths in fact files simple and predictable.
Using an explicit catalog
You can point commands at a specific catalog directory with --catalog-dir:
ic --catalog-dir /path/to/workspace/.ironclad inspect
Pass the .ironclad/ path itself, not its parent.
First Fact
Create a fact with a friendly label:
ic add comet-board
Open it:
ic edit comet-board
Give it a description and a pipeline:
description = "Track the currently advertised comet names."
[[steps]]
use = "seed.file.text"
options.files = ["comets.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
Resolve it:
ic resolve comet-board --output -
Show the fact later:
ic show comet-board
ic show comet-board --path
That is enough to establish the rhythm: author, resolve, inspect.
Building Pipelines
Pipelines work best when each step does one small thing clearly.
The most reliable style is usually:
- seed a source
- split it into useful pieces
- normalize noisy whitespace
- narrow it to the parts you care about
Example: from messy bulletin board to clean samples
Imagine a hand-maintained text file:
Neptune Parade
Moonlight Chess Club
Lantern Repair Night
One fact might look like:
[[steps]]
use = "seed.file.text"
options.files = ["bulletin.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
This is more stable than diffing the whole file. You keep the meaningful lines and discard the empty ones.
Prototype with op eval
When a step is tricky, prototype it before baking it into a fact:
ic op eval text.trim --input -
ic op eval text.find --input - --options '{ text = "Neptune" }'
This is often faster than repeatedly editing the fact file.
Imports and Exports
Facts can depend on values produced by other facts.
This happens through exports and imports.
Export a sample
Exports name one sample from a fact output batch.
exports.base_url = { trace_key = "json_node_path", trace_value = "$['base_url']" }
That means:
- find the sample whose trace contains that key/value pair
- export it under the key
base_url
Import a value
Another fact can import that key:
imports = ["base_url"]
And use it in a step option:
[[steps]]
use = "seed.net.http"
options.url = "$(base_url)"
Exact placeholder behavior
Imports are resolved only when the string value is exactly $(key).
That is important:
url = "$(base_url)"resolvesurl = "prefix $(base_url)"does not
This keeps interpolation narrow and predictable.
Export key rules
Export keys must be globally unique across the resolved fact set for a single snapshot.
If two facts export the same key, resolution fails. Ironclad would rather stop loudly than guess.
Review Workflow
The review workflow is the heart of Ironclad.
Resolve
Capture the current state:
ic resolve
That writes .ironclad/snapshots/actual.json, the resolved snapshot.
Inspect
Get an overview:
ic inspect
Inspect one fact in detail:
ic inspect comet-board
ic inspect comet-board --trace
Diff
See which facts changed:
ic diff
See the detailed per-sample change records for one fact:
ic diff comet-board
ic diff comet-board --trace
Check
Use check when you want a clean success/failure exit status:
ic check
It prints either ok (0) or drift (N) and exits 0 or 1.
Apply
Approve one fact:
ic apply comet-board
Approve everything:
ic apply --all
That promotes entries from the resolved snapshot into the approved snapshot.
Working with Multiple Facts
Catalogs become interesting when they have several facts with different levels of noise, cost, and dependency.
Resolve a subset
Resolve only selected facts:
ic resolve homepage hero-sentence footer-links
Resolve everything except a few:
ic resolve --exclude slow-report --exclude weather-page
Dependencies
If facts import exported values from other facts, Ironclad sorts them so dependencies resolve first.
If a dependency cycle exists, resolution fails.
That usually means two facts are trying to derive each other’s assumptions, which is more poetic than practical.
Operating Outside a Catalog
Most commands need a catalog.
Some do not.
op eval
ic op eval can run outside a catalog when the operation does not require catalog files.
For example:
printf '[{"traces":[{}],"content":" hello "}]' \
| ic op eval text.trim --input -
That makes op eval useful as a tiny pipeline laboratory.
When a catalog is still required
Operations that need filesystem context or catalog-backed paths still expect a meaningful working directory, and fact-related commands still require a real catalog directory.
Command Reference
This section documents every CLI command.
Each page describes:
- purpose
- syntax
- arguments and options
- behavior notes
- examples
ic init
Initialize a catalog.
Syntax
ic init [--dir PATH]
Options
--dir PATHCreate the catalog at the given path. If the path already ends in.ironclad, that exact directory is used. Otherwise Ironclad creates.ironclad/inside it.
Notes
ic initcreates the catalog directory and its initial files.- If the target already exists, the command fails.
Example
ic init
ic init --dir /srv/aurora/.ironclad
ic add
Create a fact.
Syntax
ic add <label>
ic add --no-index
Arguments and options
<label>Assign a friendly label and add the fact toindex.toml.--no-indexCreate the fact file without indexing it.
Notes
- When indexed, the command prints the label.
- When unindexed, the command prints the fact ID.
- Duplicate labels are rejected.
Example
ic add tea-menu
ic add --no-index
ic edit
Open a fact in your editor.
Syntax
ic edit <selector>
Arguments
<selector>A fact selector: either a label or a fact ID.
Notes
- Uses
$EDITOR. - The command fails if
$EDITORis unset, empty, or cannot be launched. - The editor exit code is propagated when possible.
ic show
Show a fact summary or its file path.
Syntax
ic show <selector> [--path]
Arguments and options
<selector>A fact selector: either a label or a fact ID.--pathPrint the fact file path instead of its description.
Notes
- Without
--path, the command currently prints the fact description. - If no description is set, it prints an empty line.
ic list
List indexed facts.
Syntax
ic list [--verbose]
Options
--verbosePrintlabel: descriptioninstead of just the label.
Notes
- Only indexed facts are listed.
- The output order is stable and sorted by label.
ic rename
Rename an indexed fact label.
Syntax
ic rename <selector> <new-label>
Arguments
<selector>A fact selector: either a label or a fact ID.<new-label>The new label.
Notes
- The underlying fact file does not change; the index mapping does.
- Reusing another label fails unless it already points to the same fact ID.
ic remove
Remove a fact file and its index entry.
Syntax
ic remove <selector>
Arguments
<selector>A fact selector: either a label or a fact ID.
Notes
- The fact file is deleted.
- If the fact was indexed, the matching label is removed from
index.toml.
ic resolve
Resolve facts into a snapshot.
Syntax
ic resolve [<include> ...] [--exclude <label> ...] [--output FILE|-] [--no-redact]
Arguments and options
<include> ...Resolve only these labeled facts.--exclude <label> ...Resolve all indexed facts except these labels.--output FILE|-Write the resolved snapshot somewhere other thanactual.json.--no-redactDo not redact secret facts.
Notes
- With no include or exclude arguments, all indexed facts are resolved.
- The command prints progress to stderr while steps run.
- Export/import dependencies are sorted automatically.
Example
ic resolve
ic resolve homepage
ic resolve --exclude noisy-banner --output -
ic inspect
Inspect one snapshot.
Syntax
ic inspect [<label>] [--trace] [--snapshot FILE|-] [--raw]
Arguments and options
<label>Show detailed samples for one fact.--traceInclude trace lines in detailed output.--snapshot FILE|-Read a snapshot from a file or stdin instead of the default approved snapshot,canon.json.--rawPrint the entire snapshot as JSON.
Behavior
- Without a label,
inspectprints one overview line per fact: label, sample count, created timestamp. - With a label, it prints structured sample records.
Example
ic inspect
ic inspect tea-menu
ic inspect tea-menu --trace
ic diff
Compare two snapshots.
Syntax
ic diff [<label>] [--trace] [--proposal FILE|-] [--baseline FILE|-] [--raw]
Arguments and options
<label>Show detailed change records for one fact.--traceInclude traces in detailed output.--proposal FILE|-Read the resolved snapshot from somewhere other thanactual.json.--baseline FILE|-Read the approved snapshot from somewhere other thancanon.json.--rawPrint the whole diff structure as JSON.
Behavior
- Without a label,
diffprints one overview line per changed fact. - With a label, it prints numbered change records with explicit
beforeandaftersections. - Unchanged facts are omitted from the overview.
Example
ic diff
ic diff tea-menu
ic diff tea-menu --trace
ic check
Check whether two snapshots are identical.
Syntax
ic check [--proposal FILE|-] [--baseline FILE|-]
Options
--proposal FILE|-Read the resolved snapshot from somewhere other thanactual.json.--baseline FILE|-Read the approved snapshot from somewhere other thancanon.json.
Behavior
- Prints
ok (0)when nothing drifted. - Prints
drift (N)whenNfacts drifted. - Exits
0for no drift and1for any drift.
This is the command you want in CI.
ic apply
Promote snapshot entries into the approved snapshot.
Syntax
ic apply <label> ...
ic apply --all
Options
<label> ...Promote only these facts.--allReplace the approved snapshot with the full resolved snapshot.--promotion FILE|-Use a snapshot other thanactual.jsonas the resolved source.--baseline FILE|-Use a snapshot other thancanon.jsonas the approved source.--output FILE|-Write the updated approved snapshot somewhere other thancanon.json.
Notes
- Applying selected labels can add, replace, or remove those labels in the approved snapshot.
- If a requested label is absent from both the resolved snapshot and the approved snapshot, the command fails.
ic op list
List registered operation IDs.
Syntax
ic op list
Behavior
- Prints one operation ID per line.
- Output is sorted.
Useful when you remember the general idea of an operation but not its exact ID.
ic op show
Show operation metadata.
Syntax
ic op show <operation-id>
Behavior
The command prints:
- the operation ID
- the operation description
- a TOML template of its default options, if any
Example
ic op show seed.run
ic op show text.find
ic op eval
Evaluate a single operation by hand.
Syntax
ic op eval <operation-id> [--input FILE|-] [--options TOML|-]
Options
--input FILE|-A JSON batch of samples. Defaults to an empty batch.--options TOML|-A TOML value passed as operation options.
Notes
- This command is excellent for prototyping pipelines.
- It can run outside a catalog for operations that do not require catalog-backed files.
- Avoid reading both
--inputand--optionsfrom stdin in the same invocation.
Fact File Reference
A fact file is TOML with a small, regular shape.
Fields
description
Optional human-readable text.
description = "Watch the currently listed moon phase."
imports
A list of export keys this fact depends on.
imports = ["base_url", "api_token"]
exports
A map of export keys to trace-match rules.
[exports.base_url]
trace_key = "json_node_path"
trace_value = "$['base_url']"
Ironclad finds the sample whose trace contains that exact key/value pair and exports it.
steps
An ordered array of operations.
[[steps]]
use = "seed.file.text"
options.files = ["status.txt"]
secret
Marks the fact as sensitive.
secret = true
Full example
description = "Track all creature names announced by the observatory."
secret = false
[[steps]]
use = "seed.file.text"
options.files = ["observatory-board.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
Notes
- Unknown operation options are rejected by most operations through
deny_unknown_fields. - Import interpolation only happens for exact strings like
$(key).
Snapshot Format Reference
Snapshots are JSON objects keyed by fact label.
Shape
{
"fact-label": {
"samples": [
{
"traces": [{ "path": "file.txt" }],
"content": "hello"
}
],
"created": "..."
}
}
Batch fields
samplesordered list of samplescreatedtimestamp for when the batch was created
Sample fields
tracesordered list of trace objectscontentstring content being tracked
Stability notes
- Snapshot files are a practical storage format, not a public network protocol.
- They are suitable for
inspect,diff,check,apply, andop evalexperiments.
Operations
Operations are the verbs used inside fact steps.
Each operation page documents:
- what the operation does
- its options
- whether it works per sample or over a batch
- examples
ic op list shows the available IDs.
ic op show <id> shows the description and default options template.
seed.file.text
Read text from one or more files.
Options
files = []
fileslist of glob patterns relative to the container directory
Behavior
- Produces one sample per matched file
- Adds a trace with
path=<relative-path> - Fails if a file cannot be read as UTF-8 text
Example
[[steps]]
use = "seed.file.text"
options.files = ["config/*.toml"]
seed.net.http
Fetch a URL with HTTP GET.
Options
url = ""
user_agent = "Mozilla/5.0 ..."
urlrequest targetuser_agentHTTP user agent string
Behavior
- Produces one sample containing the response body
- Fails on HTTP error status codes
seed.run
Run one program and capture its stdout as a sample.
Options
program = ""
args = []
Behavior
- Runs once for the whole step
- Uses the operation working directory
- Produces one sample from stdout
- Fails on non-zero exit
run
Run a program once per sample, piping the sample content to stdin.
Options
program = ""
args = []
Behavior
- Runs once per input sample
- Writes sample content to child stdin
- Replaces content with child stdout
- Adds a new empty trace step to preserve lineage
Example
[[steps]]
use = "run"
options.program = "rev"
hello becomes olleh.
compact
Remove samples whose content is empty.
Options
None.
Behavior
- Operates over the whole batch
- Keeps sample order
- Removes only samples whose content is exactly
""
This pairs naturally with text.trim.
slice
Take a slice of the current batch.
Options
drop = 0
take = 0
dropnumber of samples to skip from the starttakenumber of samples to keep after dropping
If take is omitted, Ironclad keeps the rest.
text.lines
Split each sample into lines.
Options
None.
Behavior
- Runs per sample
- Produces one sample per line
text.find
Find text matches inside each sample.
Options
text = ""
regex = ""
expand = ""
The supported options are:
textplain substring matchregexregular expression matchexpandoptional regex expansion template
Behavior
- Produces one sample per match
- Adds
startandendtrace entries
text.replace
Replace text inside each sample.
Options
text = ""
regex = ""
replacement = ""
max = 0
- choose either
textorregex replacementreplacement stringmaxoptional maximum replacement count
text.split
Split each sample into multiple samples.
Options
The operation supports several modes:
at_index = 0
on_text.text = ""
on_text.max = 0
on_text_inclusive.text = ""
Behavior
at_indexsplit at a byte index when validon_textsplit on a delimiter, optionally with a limiton_text_inclusivekeep the delimiter attached to each piece
text.tag
Extract text using Ironclad tags.
Options
tag = ""
tagthe tag ID to select
Behavior
- Runs per sample
- Produces one sample per matching tag occurrence
- Removes the tag itself from the output
See the dedicated Tags chapter for full syntax and examples.
text.trim
Trim leading and trailing whitespace from each sample.
Options
None.
Behavior
- Runs per sample
- Removes surrounding spaces, tabs, and newlines
This is the most common companion to text.lines.
html.find
Find HTML elements by CSS selector.
Options
selector = ""
document = false
selectorCSS selectordocumentparse as a full HTML document instead of a fragment
Behavior
- Produces one sample per matching node
- Adds a
node_idtrace entry
html.attribute
Extract one attribute from the first element in a fragment.
Options
attribute = ""
If the attribute is missing, the result is an empty string.
html.inner.html
Extract the inner HTML of the first element in a fragment.
Options
None.
html.inner.text
Extract the inner text of the first element in a fragment.
Options
None.
json.find
Find values in JSON using a JSONPath expression.
Options
path = "$"
patha JSONPath expression
Behavior
- Produces one sample per matched value
- Adds a
json_node_pathtrace entry - Strings stay strings; other JSON values are serialized back to text
Tags
Tags are Ironclad’s small embedded selection language for text.
They are meant for situations where the source text itself can carry a marker and the interesting region is relative to that marker.
This is especially handy for:
- config files you control
- internal notes
- fixture files
- documentation snippets that are easier to mark than to parse
Tag Syntax
The base syntax is:
~ic=<id>
Or with rules:
~ic=<id>=(...)
The ID is the value used by text.tag.
Minimal example
The moon is calm ~ic=moon-line
And the fact step:
[[steps]]
use = "text.tag"
options.tag = "moon-line"
Selection Boundaries
Tag rules use three boundary types:
- line boundaries:
1L,3L - byte boundaries:
4B,20B - text boundaries:
'boundary text'
Lines
1L means one line boundary away.
Bytes
4B means four bytes away.
Use this carefully with Unicode text.
Text
'marker' means search for a text marker.
Escapes supported in text boundaries include:
\\\'\n\r\t
Left and Right Rules
Rules describe how much text to keep to the left or right of the tag.
Arrows:
<-select leftward->select rightward
Pipes change inclusivity:
|<-exclude the boundary when selecting leftward->|exclude the boundary when selecting rightward
Without a pipe, the boundary is included.
Each tag can carry up to two rules:
- one left rule
- one right rule
If omitted, defaults are used.
Tag Examples
Select only the tagged line
ordinary line
chosen line ~ic=pick
ordinary line
Select three lines above
line 1
line 2
line 3
current ~ic=history=(3L<-1L)
line 5
Select until a text boundary
before
START
this is the part you want
~ic=chunk=('START'|<-1L)
after
Select to the right until a marker
~ic=menu=(1L->|'dessert')
soup
salad
dessert
cake
The tag operation removes the tag text itself from the output.
Pitfalls
Bytes are not characters
B counts bytes, not graphemes. This matters for Unicode text.
Text boundary misses can be surprising
If a text boundary is absent, the selection logic falls back in a way that may not match your first guess. Test unusual rules with ic op eval text.tag.
Tags are powerful but local
Tags are great when you control the source text. They are usually the wrong tool for arbitrary external HTML or JSON, where dedicated parsers are clearer.
Configuration
Ironclad configuration comes from:
- CLI flags
- environment variables prefixed with
IC_ - a config file
CLI flags
Global flags:
-v,-vv,-vvvincrease log verbosity--config-file PATHuse a specific config file--catalog-dir PATHpoint at the exact catalog directory
Config file
By default Ironclad looks for:
~/.config/ironclad/config.toml
Environment variables
Environment variables are read with the IC_ prefix.
Examples:
IC_CATALOG_DIRIC_VERBOSE
Practical use
Use CLI flags when you need an explicit override.
The simplest rule is:
- use
--catalog-dirwhen you want one command to target a specific catalog directory - use environment variables or a config file for longer-lived defaults
Troubleshooting
catalog not found
Ironclad could not discover .ironclad/ from the current working directory.
Fixes:
cdinto the right container directory- pass
--catalog-dir /path/to/.ironclad
label not found
You asked for a fact or snapshot entry that does not exist.
Fixes:
- run
ic list - check spelling
- if you meant a fact file directly, use the fact ID instead
fact selector not found
The fact selector is neither:
- an indexed label
- nor an existing fact ID file
Import/export failures
Typical causes:
- an imported key was never exported
- two facts exported the same key
- the export trace match no longer identifies any sample
op eval with stdin
Remember that --input - consumes stdin for the batch itself.
Avoid also trying to source --options from stdin in the same invocation.
Subprocess failures
Operations like seed.run and run surface:
- non-zero exits
- stderr
- signal termination when possible
That usually gives you enough information to debug the underlying command.
Recipes
Clean a line-oriented text file
[[steps]]
use = "seed.file.text"
options.files = ["guest-list.txt"]
[[steps]]
use = "text.lines"
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
Scrape one HTML fragment
[[steps]]
use = "seed.net.http"
options.url = "https://example.com"
[[steps]]
use = "html.find"
options.selector = "main .headline"
options.document = true
[[steps]]
use = "html.inner.text"
Run a classic Unix filter per sample
[[steps]]
use = "text.lines"
[[steps]]
use = "run"
options.program = "rev"
Keep only interesting JSON values
[[steps]]
use = "seed.file.text"
options.files = ["status.json"]
[[steps]]
use = "json.find"
options.path = "$.checks[*].name"
Trim then remove empties
This is a common cleanup pattern:
[[steps]]
use = "text.trim"
[[steps]]
use = "compact"
Appendix
This section collects shorter reference material that is useful but not central enough for the main flow.
Glossary
- catalog directory
the
.ironclad/directory - container directory the directory above the catalog directory
- fact a TOML pipeline describing one tracked assumption
- fact selector
a fact label or fact ID accepted by commands such as
show,edit, andremove - fact ID the file-based identifier of a fact
- label the human-friendly indexed name of a fact
- sample one unit of tracked content
- trace provenance metadata attached to a sample
- batch of samples the set of samples produced by one fact
- resolved snapshot
the latest captured snapshot, usually stored in
actual.json - approved snapshot
the reviewed snapshot, usually stored in
canon.json
Command Aliases
Ironclad provides a few short aliases:
ic rm->ic removeic sh->ic showic ls->ic listic r->ic resolveic i->ic inspectic d->ic diffic c->ic checkic up->ic applyic op ls->ic op listic op sh->ic op show
Operation Index
Current built-in operations:
compacthtml.attributehtml.findhtml.inner.htmlhtml.inner.textjson.findrunseed.file.textseed.net.httpseed.runslicetext.findtext.linestext.replacetext.splittext.tagtext.trim
Catalog Layout
Typical catalog tree:
.ironclad/
├── .gitignore
├── facts/
│ ├── 01...
│ └── 01...
├── index.toml
└── snapshots/
├── actual.json
└── canon.json
The fact directory stores TOML files named by fact ID. The index maps labels to those IDs.