A snapshot: read at commit 707a04c7ce8 on 2026-10-18T00:06:01Z by majordomus 0.29.0. Nothing on this page is read again. See it live
test/cases/76_capabilities_projections.sh
An object of kind test
What it is
- URI
majordomus://test/test/cases/76_capabilities_projections.sh- Kind
test- Identity
test/cases/76_capabilities_projections.sh- Path
test/cases/76_capabilities_projections.sh- Provenance
- declared
- Media type
text/plain- As a capability
- test.test/cases/76_capabilities_projections.sh
Front matter
{}Content
# majordomus-covers: none
# claims: generated-projections-checked, interfaces-are-projections, schema-driven-kinds
# One capability, every interface. The Rust executable is built from this tree and started
# inside a repository the shell tool's own `init` wrote; then the same capability is read
# back through the registry's introspection on the command line, through MCP over pipes,
# through the OpenAPI document, and through the generated reference, with nothing but data
# changed between the checks. The HTTP socket and the Swagger shell are exercised by the
# crate's own black-box suites, which this case runs, so that no network client is needed
# here (rule project.no-network-no-eval).
#
# Skips itself when there is neither cargo nor MAJORDOMUS_BIN, as the site cases do for zola.
. "$ROOT/test/lib.sh"
MANIFEST="$ROOT/apps/majordomus-cli/Cargo.toml"
S="$(mktemp -d "${TMPDIR:-/tmp}/mj76.XXXXXX")"; trap 'rm -rf "$S"' EXIT
RB="$(rust_bin)" || rust_bin_exit $?
MAJORDOMUS_SHARE="$ROOT/share"; export MAJORDOMUS_SHARE
"$MJ" init >/dev/null
git add -A >/dev/null && git commit -qm install
# --- the registry builds over the layer init writes, and says so check by check
expect_exit 0 "$RB" capabilities validate
expect_grep '^OK registry '
expect_grep '^OK mcp '
expect_grep '^OK http '
expect_grep '^OK openapi '
expect_grep 'validate: 0 failure\(s\)'
# --- one executable capability, described: its id, and every projection it declares
run_quiet "$S/cap.err" "$RB" capabilities describe objects.get --format json > "$S/cap.json"
jq -e '.id == "objects.get" and .kind == "query" and .exposure.mcp.tool == "majordomus_get" and .exposure.http.method == "GET" and .exposure.http.path == "/api/v1/object" and .provenance.source == "builtin"' "$S/cap.json" >/dev/null \
|| { echo " capabilities describe objects.get is not the descriptor"; cat "$S/cap.json"; exit 1; }
# the same entry under MCP: the tool carries the id, and calling it answers from the layer
{
printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"case76","version":"0"}}}\n'
printf '{"jsonrpc":"2.0","id":2,"method":"tools/list"}\n'
printf '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"majordomus_get","arguments":{"uri":"majordomus://prompt/continue"}}}\n'
} > "$S/session.in"
rc=0; "$RB" mcp < "$S/session.in" > "$S/session.out" 2>/dev/null || rc=$?
[ "$rc" = 0 ] || { echo " mcp exited $rc"; exit 1; }
sed -n 2p "$S/session.out" | jq -e '.result.tools[] | select(.name == "majordomus_get") | ._meta.majordomus.id == "objects.get"' >/dev/null \
|| { echo " the MCP tool does not carry the canonical id"; exit 1; }
sed -n 3p "$S/session.out" | jq -e '.result.structuredContent.id == "prompt.continue" and .result.structuredContent.provenance.path == ".ai/repo/prompts/continue.md"' >/dev/null \
|| { echo " the MCP call did not answer from the layer"; exit 1; }
# the same entry in the OpenAPI document and in the generated reference
expect_exit 0 "$RB" generate --out "$S/gen"
jq -e '.paths["/api/v1/object"].get.operationId == "objects.get" and .paths["/api/v1/object"].get["x-majordomus-mcp"].tool == "majordomus_get" and .openapi == "3.1.0"' "$S/gen/docs/generated/openapi.json" >/dev/null \
|| { echo " the OpenAPI operation is not the same capability"; exit 1; }
grep -q '^| `objects.get` |' "$S/gen/docs/generated/capabilities.md" || { echo " the reference lacks objects.get"; exit 1; }
# every OpenAPI operation is a registry entry and every HTTP exposure is an operation: counted both ways
ops="$(jq '[.paths[] | keys[]] | length' "$S/gen/docs/generated/openapi.json")"
routes="$("$RB" capabilities list --exposure http --format json 2> "$S/routes.err" | jq '.count')"
[ "$ops" = "$routes" ] || { echo " $ops OpenAPI operations against $routes HTTP exposures"; exit 1; }
# --- the committed projections are derived: in sync after generation, refused when tampered
expect_exit 0 "$RB" generate --check --out "$S/gen"
expect_grep 'generate --check: in sync'
printf '\n' >> "$S/gen/docs/generated/openapi.json"
expect_exit 10 "$RB" generate --check --out "$S/gen"
expect_grep 'openapi.json \(differs\)'
# and the shell tool's own allow-lists are derived from the schemas this tree ships
expect_exit 0 "$RB" generate allow --check
# --- the command line, the one projection that is declared twice, agrees with the registry
# Every other interface is built by walking the registry and cannot carry an entry it does
# not have. clap owns the command line, so `CliExposure` is a claim about a declaration that
# lives elsewhere; these two assertions are the comparison.
expect_exit 0 "$RB" capabilities validate
expect_grep '^OK projection '
"$RB" capabilities projections --format json 2>/dev/null > "$S/proj.json"
jq -e '[.rows[] | select(.closed == false)] | length == 0' "$S/proj.json" >/dev/null \
|| { echo " a capability claims a command line the clap declaration does not answer"; jq '[.rows[]|select(.closed==false)]' "$S/proj.json"; exit 1; }
# Every row that reports a command line is a command the built executable actually runs.
# Read into a file first: a `while read` on the right of a pipe runs in a subshell, and an
# exit there would leave the case passing.
jq -r '.rows[] | select(.cli != null) | .cli | sub("^majordomus "; "")' "$S/proj.json" > "$S/clis.txt"
[ -s "$S/clis.txt" ] || { echo " no capability reports a command line"; exit 1; }
while read -r words; do
[ -n "$words" ] || continue
# shellcheck disable=SC2086
"$RB" $words --help >/dev/null 2>&1 \
|| { echo " 'majordomus $words' is reported as a command line and does not run"; exit 1; }
done < "$S/clis.txt"
jq -e '(.rows | length) > 0 and (.unbacked | type) == "array"' "$S/proj.json" >/dev/null \
|| { echo " the projection matrix is not shaped as declared"; exit 1; }
# --- data only: a declarative object of a known kind, and a kind the repository defines with its schema
cat > .ai/repo/rules/project/example.v1.md <<'R'
---
id: project.example
version: 1
kind: rule
title: Example
description: An example project rule.
statement: Do the example thing.
status: active
class: advisory
depends_on: []
tags: [example]
---
R
# a schema's identity fixes its path: project.note/v1 lives at project/note/note.v1.schema.json
mkdir -p .ai/repo/knowledge/schemas/project/note .ai/repo/notes
printf 'schema: majordomus-kinds/v1\nkinds:\n note:\n format: markdown\n front_matter: required\n schema: project.note/v1\n identity: [id]\n title: title\n' > .ai/repo/knowledge/kinds.yaml
printf '{ "type": "object", "additionalProperties": false, "required": ["id", "title"], "properties": { "id": { "type": "string" }, "title": { "type": "string" } } }\n' > .ai/repo/knowledge/schemas/project/note/note.v1.schema.json
printf '\n - id: note\n kind: note\n discovery: vcs\n pathspec: '"'"':(glob).ai/repo/notes/*.md'"'"'\n required: false\n' >> .ai/repo/knowledge/sources.yaml
printf -- '---\nid: first\ntitle: The first note\n---\n\nBody.\n' > .ai/repo/notes/first.md
printf -- '---\nid: second\ntitle: T\ncolour: red\n---\n' > .ai/repo/notes/second.md
git add -A >/dev/null
run_quiet "$S/list.err" "$RB" capabilities list --kind resource --format json > "$S/list.json"
jq -e '[.capabilities[].id] | index("rule.project.example@1") != null and index("note.first") != null and index("note.second") == null' "$S/list.json" >/dev/null \
|| { echo " the added rule and the repository-defined note are not both listed, or the broken note is"; exit 1; }
"$MJ" rules list | grep -q '^project.example ' || { echo " the shell tool does not see the same rule"; exit 1; }
expect_exit 10 "$RB" mcp --inspect
expect_grep '^FAIL unknown_key +\.ai/repo/notes/second\.md'
expect_grep '^resource majordomus://note/first$'
# a repository may add a kind, not redefine one the distribution declares
printf 'schema: majordomus-kinds/v1\nkinds:\n rule:\n format: yaml\n' > .ai/repo/knowledge/kinds.yaml
git add -A >/dev/null
expect_exit 10 "$RB" capabilities validate
expect_grep "kind 'rule' is declared by both"
# --- the HTTP socket, the Swagger shell and MCP/HTTP parity, through the crate's own black-box
# suites; these are cargo's, so a prebuilt executable alone cannot run them
if command -v cargo >/dev/null 2>&1; then
RUSTFLAGS='' cargo test -q --manifest-path "$MANIFEST" --test http_serve --test projections 2>"$S/cargo.log" >"$S/cargo.out" \
|| { tail -n 40 "$S/cargo.log" "$S/cargo.out"; echo " the crate's HTTP and projection suites failed"; exit 1; }
else
echo " skip: cargo not installed (the crate's HTTP and projection suites; the executable half ran)"
fi