Controls and policy
Two kinds of configuration can shape a run, and they carry opposite amounts of trust.
The repository policy is the one input read from the scanned tree itself, and it is
correspondingly weak. .amiss/scanner-policy.json can add directories to scan, list
protected paths whose removal is always a finding, declare exact source-to-document
projections, and raise the disposition of
explicit-target-missing, explicit-target-type-mismatch, and invalid-reference. Raise
only: repository policy combines with the built-in profile by maximum, so it can promote an
observe warning to fail and can never downgrade or suppress it. An unknown
field makes the whole file invalid and the run incomplete, which is what keeps the policy
from growing into a plugin system one field at a time.
The complete grammar in one example, with projection_assertions optional for compatibility
with policies written before projections existed and the file valid only whole:
{
"schema": "amiss/scanner-policy",
"document_includes": [
{ "path": "build/docs", "kind": "tree" },
{ "path": "docs", "kind": "tree", "suffix": ".txt", "adapter": "rst" },
{ "path": "notes/ARCHITECTURE", "kind": "document", "adapter": "markdown" }
],
"projection_assertions": [
{
"document": "docs/api.md",
"name": "request-shape",
"projection": "code-text-v1",
"sink": "previous-code",
"source": {
"kind": "blob-lines",
"path": "examples/request.json",
"first_line": 1,
"last_line": 12
}
}
],
"protected_inventory": ["docs/install.md"],
"finding_dispositions": [
{ "finding_kind": "explicit-target-missing", "disposition": "fail" }
]
}
The first tree include readmits a subtree the built-in skip list would drop, which is
Discovery’s monorepo lever. The second admits only .txt descendants of
docs and reads them as reStructuredText. The document include reads one extensionless file
under the markdown grammar. The protected path makes its removal a finding, and the disposition
row promotes one kind to fail. The
scanner-policy schema
closes the grammar, and each array keeps the sort order the schema states. The strictness
also sets the upgrade order: an engine that predates a policy field refuses the whole file
and leaves the run incomplete, so a repository grows its policy only after every engine
reading it has learned the field.
A projection assertion is owned by the policy, under the stable identity (document, name).
The code-text-v1 sources select either an inclusive one-based line interval from a tracked regular
or executable blob, or bytes between distinct exact start_marker and end_marker lines. Each
printable-ASCII marker line is at most 256 bytes, occurs exactly once, and is itself excluded. Amiss
does not parse a source language or its comment syntax. Duplicate, missing, reversed, same-line,
or non-UTF-8 regions are typed drift, while edits outside the selected region are irrelevant.
The source’s code-text-v1 projection is compared with the semantic Markdown or MDX code block
immediately before [amiss:<name>]: <amiss:projection>. It converts CRLF and bare CR to LF and
removes exactly one final line ending; it does not normalize indentation or any other byte. The
document must be in the scanner’s discovered set. Missing, duplicate, or non-adjacent sinks, and
absent, non-blob, LFS, or otherwise invalid sources, produce one projection-drift finding. A
marker with no matching policy row remains an unsupported reserved capability and makes the run
incomplete. Removing the marker while the policy row survives therefore cannot disable the
relation, while removing the policy row is policy-weakened even when the marker is removed too.
A record-value source applies code-text-v1 to one key in a named record-set@1 semantic
envelope. Each such envelope carries exactly one record-set object, including empty complete
sets, and its records are strictly ordered and unique by key. Keys are nonempty, control-free
UTF-8 of at most 4,096 bytes; display values have the same character law and a 65,536-byte cap.
The producer’s strings remain inert data: Amiss runs no formatter or template. A row that is
present can attest its value even when the envelope says the set is partial. A missing key means
source-record-absent only for a complete set; in a partial set it is
source-record-unproven, and a missing named set is source-record-set-absent. Evidence derived
from an earlier scanner report is not admitted as a projection source.
For a complete envelope, a record-set source applies sorted-rows-v1 to every display value in
UTF-8 byte order, or applies decimal-count-v1 to the exact number of records. Duplicate display
values remain distinct records because keys, not values, own identity. A partial set produces
source-record-set-incomplete for both projections before any equality, count, extra-row, or
absence conclusion is attempted. Row-difference previews remain byte-sorted and bounded.
The sorted-rows-v1 projection pairs the same sink with a tree-paths source: one existing tree
root, an optional exact suffix, and a positive maximum relative depth. It filters the complete
ordered snapshot map without another Git walk or object read, excludes directory entries, and
projects all other tracked paths relative to the root. A qualifying non-UTF-8 path or path with a
control character is typed drift; an invalid Git path already makes the candidate incomplete. Row
mismatches carry exact counts, a pure-ordering flag, and at most 32 rows and 32 KiB of byte-sorted
preview on each side, with exact omitted counts.
decimal-count-v1 applies to that same complete tree-paths source and emits only the canonical
unsigned ASCII member count. It includes qualifying non-UTF-8 and control-containing path names
because none are rendered. A sign, leading zero, grouping separator, label, or whitespace makes
the visible value noncanonical rather than a second spelling of the same count.
A document include names one exact path. A tree include names that path and descendants
separated by /; specs therefore covers specs/api.md but not specs-old/api.md. Matching
is bytewise, including for paths JSON cannot represent as text. A tree may carry one suffix:
2–64 UTF-8 bytes beginning with ., with no slash, backslash, or NUL. It selects only non-tree
entries at or below that root whose raw path ends in those exact bytes. There are no globs,
wildcards, regexes, excludes, normalization, or case folding, and built-in classifications still
win. The stable selector identity remains (path, kind), so changing or removing the suffix—or
replacing it with a broader tree—reports policy weakening instead of disguising the old selector
as a new one.
amiss policy-include prints a validated canonical row for the suffixed-tree form
without touching the policy file. Its optional staged-index preview applies this same matching
implementation and reports exact path identities; it does not invent excludes or merge the row
into existing controls.
Document includes, projection assertions, inventory members, and disposition rows share each
snapshot’s published repository-policy entry ceiling, so
the base/candidate classification union can contain twice that many distinct roots. Tree roots
and suffix roots are indexed separately; lookup probes path ancestors and suffix components,
never every policy row. The
policy tests
pin the semantic boundaries, and the release
eligibility test
checks the maximum union without scanning every policy row for every discovered path. The
amiss-scan controls benchmark tracks tree matching, suffix matching, and policy-set comparison
as the entry count grows.
External controls come from outside the repository, because anything stored inside it could
be rewritten by the very pull request under review. The contract defines five nullable controls: an
organization floor (tightens ceilings and dispositions across many repositories), an
adoption debt snapshot (a recorded list of known failures being worked off, mintable
from a real evaluation by amiss adopt), a waiver
bundle (time-limited permission to pass despite a named failure), trusted time, and an
execution constraint. The sealed request also carries a bounded set of
semantic-evidence envelopes, each paired with an independently planned
context digest, candidate-bound, and interpreted only by a compiled consumer. An ordinary
amiss check may instead bind one caller-selected candidate-free template after resolving its
exact candidate; that convenience input remains self-asserted and is not an external control.
Every control identity, and the release manifest’s, uses one open repository grammar: a
caller-canonical host, a slash-joined owner when the forge supports nested groups, and a
repository name. That admits enterprise and self-hosted instances without making them
impersonate a public host. In the evaluation request, candidate_ref is the candidate or
source branch used to recognize same-repository links; target_ref is the protected branch to
which the organization floor, trusted time, debt snapshot, and waiver bundle bind. They are
equal for an ordinary branch update but may differ for a pull or merge request.
default_branch_ref remains URL-resolution context and does not stand in for the protected
target. The
organization-floor,
debt-snapshot, and
waiver-bundle schemas, the
control parsers, and their
open-forge contract tests pin that grammar and
the exact repository/target-ref binding. The execution constraint additionally pins the action
tree, release manifest, platform, declared required-status name, and bootstrap in its
dedicated parser.
A status name is data, not proof of which provider integration published it; source-bound
enforcement remains an adapter responsibility.
Trusted time binds more than a timestamp. Its current parser requires the repository and protected target ref, a provider namespace, an opaque bounded provider run ID and positive attempt, and the candidate-identity digest. That candidate identity includes both candidate and target refs, the selected URL dialect, the repository, and the snapshots, so changing any of those cannot replay a statement for the same Git trees. The controls request must repeat the same provider/run tuple, and the verification gate compares it byte-for-byte before using the time.
These are binding rules, not authentication. The controller must authenticate provider input before constructing requests for the exact run. Its provider-neutral sequence and durable retry contract are documented in Controller delivery. The concrete provider lanes load organization policy and their execution constraint outside the checked repository, authenticate a signed webhook or policy-job token, refresh provider-owned change and merge-rule state, acquire the exact trees, derive trusted time, and run the sealed bootstrap. Their separate pages describe the Check Run, policy-job result, or dedicated review that carries provider evidence.
The request’s forge value remains only the URL dialect used by link resolution and is separate
from the controller’s provider namespace and instance identity. Debt must reproduce its adoption
tree, and a waiver item for another candidate tree is simply not selected. The commit and
staged-index paths share one
trusted-time, debt, and waiver pipeline.
Debt and waiver require verified trusted time and a complete Git candidate. An item
carries its accepted fact, and that fact is the sole source of the finding kind and the
key-input preimage; finding_key is recomputed from the nested key. The fact can name
only explicit-target-missing or explicit-target-type-mismatch. Selection needs an
exact current finding key with a candidate fact; a resolved projection or an absent key
is not an exception target. An exact forge commit is part of the normalized target intent, so an
exception for the same path at another immutable commit does not match. Matching also requires the
exact fact digest. When
everything lines up, active unchanged debt records tolerance at warn, and an applied
waiver changes only fail to warn. Invalid, expired, worsened, or overlapping items
suppress nothing, and an overlap makes evaluation incomplete. Both controls travel only
in the sealed request: amiss adopt mints a debt snapshot from the public grammar, but
no public flag supplies one back, so consumption belongs to the provider lanes.
The wrapper tests
pin binding, trusted-time, expiry, fact-drift, wrong-tree selection, resolved-target, and
overlap behavior. The published complete-findings, debt-items, and waiver-items
ceilings bound the accepted sets, and the amiss-scan pipeline benchmark
tracks matching as findings and exception items grow.
One asymmetry remains in the current control contract: the report can carry a finding on a document whose name is raw bytes, but waiver and debt items spell paths as text. Such a finding is reportable yet cannot be waived or adopted.
The machine-facing evaluation and controls requests are closed by the evaluation-request schema and controls-request schema, with matching strict parser tests. Their unversioned names are intentional: before 1.0 the shipped schema, parser, examples, and report form one rolling contract and move together.
In the public command and GitHub composite Action, all five external controls are absent and no
protected target ref is authenticated. The Action supplies no semantic evidence; a direct public
check may carry one self-asserted template. The report records
status: "none" separately for
organization floor, debt snapshot, waiver bundle, execution constraint, and trusted time; its
sandbox assurance is self-asserted. There is no aggregate provider_verified field. The
exact projection is built in the
report writer.
The sealed bootstrap path can carry all five controls and the bounded semantic-evidence set to the
engine. Its report acceptance
binds the requested profile; exact organization-floor, debt-snapshot, and waiver-bundle
presence, digest, and trust source; the execution-constraint digest, trust source, and
recomputed descriptor semantics; the trusted-time digest, provider run, instant, and recomputed
statement semantics; every semantic envelope’s payload and producer/input identities after its
planned context matches; and the
candidate identity and honest sandbox projection. The public
CLI shell still supplies
each nullable external control as None; its optional template is bound only inside the candidate
evaluation and leaves sandbox assurance self-asserted. A report control row with
status: "verified" means the engine verified
the supplied value’s digest and identity relationships. It does not prove that a provider
authenticated or supplied the value: neither the report nor its enum authenticates its own
source. The honest reading of a local or convenience-Action report remains: these findings, under
this repository policy, with no outside authority consulted. In a provider lane, origin is
evidenced separately by the App-owned Check Run, protected policy job, or dedicated review and
the matching merge rule; copied report bytes do not become an attestation.
The control-plane finding family closes the loop from the other side. When a candidate
weakens its own policy file or drops required coverage, the comparison raises
policy-weakened or coverage-reduced. With a verified organization floor,
control-plane-changed reports a protected control path unless both base and candidate are
present supported blobs with identical path, mode, and raw-content evidence. Loosening the
rules is reported under the rules being loosened.