Skip to main content
Version: 1.0.0

Handler Supply Chain

Include installed packages​

In a project using packages, touba supply-chain generate adds package_installation to the execution lock. This field pins the installation lock's digest; validation then checks each package's files, including host modules and files unused by the current entry point. Changes require another validation and lock review. Keep the execution lock at the project root to include the touba.packages.lock.json inventory.

One lock for multiple modules​

A shared project manifest can bind tools from several modules. Explicitly supply all required assembled AIR files with repeated --air options:

touba supply-chain generate \
--air .touba/build/prod/air/app/expenses.air.json \
--air .touba/build/prod/air/app/support.air.json \
--manifest .touba/build/prod/touba.manifest.json \
--out touba.lock.json

The first AIR is recorded in air; subsequent files appear in additional_air, each with a lock-relative path and digest. Validation from any listed AIR verifies every declared AIR, then checks bindings against their combined tool declarations. Changed, missing, duplicate or unconfined files are refused. There is no implicit project scan, new grant or automatic approval. A binding absent from all AIRs remains an error. After genuine review, the same lock can be required for each prod entrypoint.

A Touba handler is host code. It can call a network, SDK, API, or external system when the manifest authorizes it. This power creates a risk: the executed file can drift from the expected file.

The handler supply-chain lock (SC-1) adds a coherence check between:

  • compiled AIR;
  • runtime manifest;
  • handler files;
  • declared SDK metadata;
  • declared handler package identity.

The dependency artifact extension (SC-2) adds verification of handler package files and explicitly declared dependency artifacts. It does not scan the project and does not perform vulnerability auditing.

The provenance and review extension (SC-3) adds declared provenance and review for the lock. It can require an approved review before accepting a lock; cryptographic signing and code review remain separate controls.

What the Lock Verifies​

Handler supply-chain verification checks explicitly supplied artifacts.

ElementVerification
AIRsha256 digest
Manifestsha256 digest
Handlersha256 digest
Handler commandcommand, cwd, args
SDKdeclarative name, version, handler protocol, and status
Handler packagedeclared identity, root, declared files
Dependency artifactsdigests of declared files
Provenancedeclared preparer, source, timestamp
Reviewreview status, reviewers, declared evidence
Driftok, missing, changed, unknown

This verification does not execute the handler. The dependency artifact extension (SC-2) does not resolve transitive dependencies. The provenance and review extension (SC-3) does not sign artifacts.

Generate a Lock from Real Files​

After compiling AIR and reviewing the runtime manifest:

Terminal
touba supply-chain generate \
--air program.air.json \
--manifest touba.manifest.json \
--out touba.lock.json

Generation hashes the exact bytes of AIR, manifest, handler entrypoints, package identity and dependency lockfiles. Static local JavaScript and Python imports are followed transitively without executing the code. Add other assets with repeated --file <package-relative-file> options. Declared dependencies require a package-manager lock; Touba hashes it without resolving or installing its packages. Pure programs need no manifest or handler package.

Python handlers​

Place a pyproject.toml at the package root (the lock directory by default):

[project]
name = "business-handlers"
version = "0.1.0"

Identity must be static. Declare application dependencies there and retain the lock generated by your manager: pylock.toml, uv.lock or poetry.lock. Touba inventories these files; it does not verify that the installed venv matches their resolution. The interpreter, bytecode caches and native modules remain the host's responsibility. The bundled SDK wheel stays in vendor/touba-sdk; include it with --file and supply inspected SDK metadata with --sdk-metadata-json when needed.

Script analysis uses isolated Python 3.11+, with no site, PYTHONPATH, handler import or project venv loading. --python <interpreter> selects this trusted tool, never a binding command. Adjacent modules, packages, internal relative imports and cycles are inventoried. Files must remain within --package-root, including through symlinks. Detected dynamic loading and import-path changes are refused. This static check is not a Python sandbox; python -m package.module bindings resolve from their explicit cwd; local packages have __init__.py files. Custom loaders and modified import paths remain refused. See the Python tutorial.

The output remains review.status: pending. Generation neither approves a review nor overwrites an existing lock. Use a new filename for an updated lock, review its changes, then select it explicitly. Strict validation refuses a pending review. SDK metadata is declarative: after inspecting the installed dependency, provide its toubaSdkMetadata() result with --sdk-metadata-json. Without it, SDK identity remains unknown and strict validation refuses. The generator never imports or executes handler/package code.

Path rules:

  • handler.cwd is manifest-relative; args are relative to that cwd.
  • Lock artifact paths, handler.path and package_root are lock-directory-relative.
  • entrypoint, files and dependency_artifacts are package-root-relative.
  • The inventory must include its entrypoint. Files and symlinks must remain confined.

The default entrypoint argument is index zero. Use --entrypoint-arg <index> for interpreter flags, or command for a directly executed local binary. Validation checks that the declared handler is an executable or explicit argument path; it does not infer arbitrary command semantics. Missing commands, contradictory paths and entrypoints omitted from the inventory are refused.

Relocate the whole lock root, keeping relative paths and file bytes unchanged. Absolute interpreter paths remain machine-specific and must exist at the new location. Changing the manifest or command requires a new lock and review, never automatic approval.

Validate the Lock​

Terminal
touba supply-chain validate \
--lock touba.lock.json \
--air program.air.json \
--manifest touba.manifest.json

Expected text output:

Output
Touba Supply Chain touba-supply-chain-validation-v1
Status ok
Lock touba-lock-v1
AIR ok
Manifest ok
Provenance ok
Review approved evidence=ok required=no
Handlers 1
- tool:examples.search/SearchDatasets drift=ok sdk=ok package=ok package_files=ok dependencies=ok
Diagnostics ok

To retrieve a JSON report:

Terminal
touba supply-chain validate \
--lock touba.lock.json \
--air program.air.json \
--manifest touba.manifest.json \
--output json

Strict mode also fails on warnings:

Terminal
touba supply-chain validate \
--lock touba.lock.json \
--air program.air.json \
--manifest touba.manifest.json \
--strict

To require an approved review:

Terminal
touba supply-chain validate \
--lock touba.lock.json \
--air program.air.json \
--manifest touba.manifest.json \
--require-review

This command refuses a lock without review, with status: "pending", or with status: "rejected". It also verifies declared review evidence by digest when review.evidence is present.

Run with the Lock​

touba run can use the lock as an explicit gate before handler dispatch:

Terminal
touba run program.air.json \
--manifest touba.manifest.json \
--supply-chain-lock touba.lock.json \
--require-supply-chain-lock

In this mode, the runtime refuses before execution if the lock is absent, if lock validation produces an error, or if it produces a warning. Without --require-supply-chain-lock, a warning remains visible but non-blocking.

This gate grants no runtime capability and does not replace the manifest. It verifies alignment between AIR, manifest, handlers, SDK metadata, handler_package, package files, and declared dependency artifacts before launching host code.

Project prod Profile​

A project initialized by touba init also contains a prod run profile in touba.project.json:

touba.project.json
{
"run_profiles": {
"prod": {
"require_supply_chain_lock": true,
"supply_chain_lock": "touba.lock.json"
}
}
}

You can then run:

Terminal
touba run program.air.json \
--profile prod \
--manifest touba.manifest.json

The touba facade turns this profile into explicit runtime options. It does not generate the lock and does not automatically search for a manifest or handler. The dev profile does not force the lock.

Inspect the Supply-Chain Lock​

To read a lock:

Terminal
touba inspect --supply-chain touba.lock.json

To read a validation report:

Terminal
touba inspect --supply-chain supply-chain.report.json

inspect displays paths, digests, commands, SDK status, handler_package identity, provenance, review, and diagnostics. It does not display handler content or review evidence content.

Drift Statuses​

StatusMeaning
okobserved artifact matches the lock
missingexpected artifact is not found
changeddigest or metadata differs
unknownvalidation cannot conclude

unknown is not a hidden success. It is a signal to examine.

The same applies to sdk.status = missing, unknown, or unsupported: in simple validation, this is a warning; in strict mode, the run is refused before the handler.

The same rule applies to handler_package_status = unknown: simple validation remains readable, but a strict profile refuses execution because the handler package is not explicitly identified.

handler_package_files_status and handler_dependency_artifacts_status can be unknown when no file is declared, ok when digests match, missing when a file is absent, changed when a digest differs, or invalid when the shape or path is refused.

Relation with the Manifest​

The manifest remains the runtime authority for:

  • handler;
  • grants;
  • secrets;
  • memories;
  • limits;
  • payload constraints;
  • triggers.

The lock grants no additional right. It only verifies that the supplied artifacts match what is expected.

Bounded constraint example:

{
"bindings": {
"tool:examples.fetch_source/FetchSource": {
"payload_constraints": {
"input": {
"request.query": {
"non_empty": true,
"max_length": 120
},
"request.mode": {
"allowed_values": ["dry_run", "review", "execute"]
}
}
}
}
}
}

This constraint is verified by the runtime before handler launch. It does not replace the AIR schema and does not become a host expression.

Assurance Scope​

Use the supply-chain lock as a coherence guard: it makes declared artifacts, digests, command metadata, package identity, dependency artifacts, provenance, and review state inspectable before host code is launched.

Cryptographic signing, registry distribution, package installation, vulnerability auditing, dependency graph resolution, system sandboxing, proof that a handler is safe, prevention of exfiltration by an authorized handler, and Verified / Unverified status are separate supply-chain and platform layers.

Share a Python module​

Organize handlers as a local Python package with __init__.py files:

pyproject.toml
handlers/
__init__.py
shared.py
invoices/
__init__.py
apply.py
payments/
__init__.py
apply.py

Both apply.py files can use from handlers.shared import …. Each binding selects the project interpreter, cwd: "." and, for example, args: ["-m", "handlers.invoices.apply"]. The generator resolves the module from that cwd, inventories the __init__.py files and follows local imports. The lock includes shared.py; changing it invalidates the lock.

The package root must contain all these files. Use --package-root . when generating the lock if this root is the project directory. This organization keeps one copy of shared helpers and works with both run and serve.