--- name: axone-logic-predicate description: "Add or change predicates in the AXONE logic module. Use when working on x/logic predicate behavior, Prolog libraries, predicate docs, VFS-backed logic capabilities, or feature scenarios. Follows the current architecture direction: new predicates should be written in Prolog, either pure Prolog or Prolog backed by the path-based logic VFS, not as new native Go predicates." compatibility: Requires go, make, and docker for validation and doc generation. --- # Axone Logic Predicate Use this skill when changing predicate behavior in `x/logic`. ## Architectural direction New predicates should not be introduced as native Go predicates in `x/logic/predicate` or registered through `x/logic/interpreter/registry.go`. The target model is: 1. pure Prolog predicates implemented in `x/logic/lib/*.pl`, or 2. Prolog predicates implemented in `x/logic/lib/*.pl` and backed by the path-based logic VFS for host I/O. For transactional endpoints, use the existing device-file model: path-based VFS access plus a half-duplex request/commit/response interaction pattern. Treat `x/logic/predicate` and the interpreter registry as existing runtime primitives and legacy surface area. Change them only for maintenance, bug fixes, or truly unavoidable interpreter-level work. If you need to touch them for new functionality, call that out explicitly as an architectural exception. ## Choose the implementation shape - If the behavior can be expressed by composing existing predicates and libraries, implement it as pure Prolog in `x/logic/lib/*.pl`. - If the behavior needs host data or external capabilities, expose that capability through the VFS under `x/logic/fs/**`, then wrap it with a Prolog predicate in `x/logic/lib/*.pl`. - If the behavior is an interactive transactional endpoint, expose it under `/v1/dev/...` and use the device helpers from `x/logic/lib/dev.pl`. - Do not add a new Go predicate when a Prolog wrapper over `open/4`, `read_term/3`, `write_term/3`, or `dev_call/4` would solve the problem. ## VFS model The logic VFS is the boundary between logical evaluation and host capabilities. - `/v1/lib`: embedded Prolog libraries - `/v1/run`: invocation-scoped runtime resources - `/v1/var/lib`: persistent host-managed resources - `/v1/dev`: interactive device-like capabilities Choose the path family that matches the semantics: - Snapshot or read-only runtime state: prefer `/v1/run/...` - Persistent queryable resources: prefer `/v1/var/lib/...` - Request-response endpoints: prefer `/v1/dev/...` For `/v1/dev/...` endpoints, keep the protocol aligned with the existing half-duplex model: 1. writes build the request 2. the first read commits the transaction 3. subsequent reads stream the response ## Implementation patterns ### Public textual representation Public domain predicates must use atoms as the canonical representation for textual inputs and outputs. Do not make a public predicate accept atoms, character lists, and character code lists through a broad `text` contract unless representation conversion is the predicate's explicit purpose. - Use `must_be(atom, Value)` at public predicate boundaries for textual values. - Return textual values as atoms. - Keep byte payloads as `list(byte)` when the API is explicitly byte-oriented. - Leave conversion predicates such as `atom_chars/2`, `atom_codes/2`, `string_bytes/3`, and stream-reading helpers responsible for representation conversion. - Require callers to perform explicit conversions before calling domain predicates when they hold character lists or code lists. ### Pure Prolog predicate - Add or update a library file in `x/logic/lib/*.pl` - Validate inputs with `with_context/2` and `must_be/2` when appropriate - Use `setup_call_cleanup/3` around stream access - Write PlDoc comments because `make doc-predicate` depends on them ### VFS-backed predicate - Implement the host-facing filesystem in `x/logic/fs/**` - Mount it through the standard VFS if it is part of the canonical host surface - Expose an ergonomic Prolog predicate in `x/logic/lib/*.pl` - Prefer returning logical terms from Prolog wrappers, not leaking protocol details unless the predicate is intentionally low-level ### Transactional device-backed predicate - Reuse `dev_call/4`, `dev_write_bytes/2`, and `dev_read_bytes/2` from `x/logic/lib/dev.pl` - Encode protocol errors in-band when designing the device protocol - Keep transport details inside the VFS device and Prolog wrapper; keep the public predicate ergonomic - Follow the pattern used by `wasm_query/3` and codec-backed helpers ## Documentation contract For Prolog predicates, documentation is not optional. The generated predicate docs depend on the source comments. - Put a `%!` signature line immediately above the predicate definition, for example `%! wasm_query(+Address, +RequestBytes, -ResponseBytes) is det.` - Continue the doc block with `%` comment lines directly above the predicate head. - Keep the doc block attached to the predicate. The generator reads the contiguous `%` block and associates it with the next predicate head. - Document the public predicate, not just helper predicates. - Include enough description for the generated `## Description` section to stand on its own. - Mention loading expectations when relevant, but do not duplicate the auto-generated `consult('/v1/lib/...')` section in prose. ## Feature contract Every predicate change should come with feature coverage in `x/logic/tests/predicate/features`. - Name the feature file after the predicate, using the generated doc naming convention: `name_arity.feature` for `name/arity`. - Start with `Feature: name/arity`. - Add scenarios for the main success path and important failure paths. - Mark the scenarios that should appear in generated docs with `@great_for_documentation`. - Treat features as both executable tests and documentation examples. Keep them readable and domain-oriented. - When the predicate is provided by a library file under `/v1/lib`, include the explicit `consult('/v1/lib/...').` step in the scenario program unless the scenario is specifically testing availability before consult. ## Files to update - Predicate API: `x/logic/lib/*.pl` - Host capability surface: `x/logic/fs/**` - Predicate feature coverage: `x/logic/tests/predicate/features/*.feature` - Predicate docs generator inputs: PlDoc in `x/logic/lib/*.pl` Only touch these for exceptions or maintenance: - `x/logic/predicate/*.go` - `x/logic/interpreter/registry.go` ## Validation - Run `make doc-predicate` - Run `make test-go` - If you changed Go code under `x/logic/fs/**` or related plumbing, also run `make lint-go` ## Gotchas - `make doc-predicate` still scans both Go predicates and Prolog predicates. That is a generator detail, not a design recommendation. - New `.pl` files under `x/logic/lib` are embedded automatically and become available under `/v1/lib/...`. - Feature files under `x/logic/tests/predicate/features` are both behavioral tests and documentation inputs. Only scenarios tagged `@great_for_documentation` are rendered into generated docs. - `dev_call/4` is the preferred low-level abstraction for transactional endpoints. Build a higher-level domain predicate on top of it instead of exposing raw device usage directly to callers when possible. - If a predicate should be auto-loaded as part of the interpreter bootstrap rather than explicitly consulted from `/v1/lib/...`, treat that as a special case and justify it before editing bootstrap files. ## Read first - `x/logic/fs/std_fs.go` - `x/logic/lib/dev.pl` - `x/logic/lib/wasm.pl` - `x/logic/tests/predicate/features/dev_call_4.feature` - `scripts/generate_predicates_doc.go`