scripts.factory_projection
Reference › scripts › factory_projection
The command-line interface for the SPEC-033 rule-driven projection and completeness check (B5; ADR-0006 and ADR-0025). It uses the analyzer in analyzer and the completeness check in completeness; the factory narrative describes the subsystem. No module or test imports it.
Invocation: main calls the handler of the chosen subcommand. analyze projects the source with a rule base and the given source roots (_cmd_analyze). check runs the completeness check against a list of provided items (_cmd_check, with the list split by _split_provided). Both take the rule base and roots options added by _add_rulebase_and_roots.
At a glance
| Source | scripts/factory_projection.py |
| Lines of code | 143 |
| Classes | 0 |
| Functions | 8 |
| Imports (internal) | orchestrata_ultimate.factory.projection.analyzer, orchestrata_ultimate.factory.projection.completeness |
| Imported by | none |
| Tests that import it | none |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
_split_provided |
_split_provided(raw: str) -> list[str] |
|
_cmd_analyze |
_cmd_analyze(args: argparse.Namespace) -> int |
|
_cmd_check |
_cmd_check(args: argparse.Namespace) -> int |
|
_add_rulebase_and_roots |
_add_rulebase_and_roots(sub_parser: argparse.ArgumentParser) -> None |
|
_add_analyze_subparser |
_add_analyze_subparser(sub: argparse._SubParsersAction) -> None |
|
_add_check_subparser |
_add_check_subparser(sub: argparse._SubParsersAction) -> None |
|
_build_parser |
_build_parser() -> argparse.ArgumentParser |
|
main |
main() -> int |
Parse argv and dispatch to the selected subcommand's handler. |
Source
#!/usr/bin/env python3
"""CLI for the SPEC-033 rule-driven projection + completeness check (B5, ADR-0006/0025).
Subcommands
-----------
analyze Derive the required-predicate set (name/arity) for a rulebase from
one or more ``--root`` predicates; prints it as JSON alongside any
manual ``'%! requires:'`` header cross-check mismatches. Exits
non-zero on :class:`ProjectionUndecidable` (an ADR-0025-banned
construct makes the call graph statically undecidable).
check Same derivation, then asserts completeness against a
caller-supplied ``--provided`` list (ADR-0006's pre-run meta-check);
exits non-zero on :class:`IncompleteProjection`.
Examples
--------
python scripts/factory_projection.py analyze \\
src/orchestrata_ultimate/prolog/policy_layers.pl --root layered_verdict/3
python scripts/factory_projection.py check \\
src/orchestrata_ultimate/prolog/policy_layers.pl --root layered_verdict/3 \\
--provided "constitutional:requires_human_consent/1,resources:resource_access_allowed/4"
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
# Ensure repo root is on path when run directly (mirrors factory_detectors.py):
# `orchestrata_ultimate.*` resolves via the editable install, but the
# analyzer's own lazy `from scripts.prolog_meta_lint import ...` (see its
# module docstring) needs the REPO ROOT itself importable as a namespace
# package too.
_REPO_ROOT = Path(__file__).resolve().parent.parent
if str(_REPO_ROOT) not in sys.path:
sys.path.insert(0, str(_REPO_ROOT))
from orchestrata_ultimate.factory.projection.analyzer import ( # noqa: E402
PredId,
ProjectionUndecidable,
required_predicates,
verify_declaration,
)
from orchestrata_ultimate.factory.projection.completeness import ( # noqa: E402
IncompleteProjection,
assert_projection_complete,
projection_manifest,
)
def _split_provided(raw: str) -> list[str]:
return [spec.strip() for spec in raw.split(",") if spec.strip()]
def _cmd_analyze(args: argparse.Namespace) -> int:
path = Path(args.rulebase)
try:
required = required_predicates(path, args.root)
except ProjectionUndecidable as exc:
print(f"ProjectionUndecidable: {exc}", file=sys.stderr)
return 1
payload = {
"rulebase": str(path),
"roots": args.root,
**projection_manifest(required),
"declaration_mismatches": verify_declaration(path),
}
print(json.dumps(payload, indent=2))
return 0
def _cmd_check(args: argparse.Namespace) -> int:
path = Path(args.rulebase)
try:
required = required_predicates(path, args.root)
except ProjectionUndecidable as exc:
print(f"ProjectionUndecidable: {exc}", file=sys.stderr)
return 1
provided = [PredId.parse(spec) for spec in _split_provided(args.provided)]
try:
assert_projection_complete(required, provided)
except IncompleteProjection as exc:
print(f"IncompleteProjection: {exc}", file=sys.stderr)
return 1
print("projection complete")
print(json.dumps(projection_manifest(required), indent=2))
return 0
def _add_rulebase_and_roots(sub_parser: argparse.ArgumentParser) -> None:
sub_parser.add_argument("rulebase", help="Path to the .pl rulebase file")
sub_parser.add_argument(
"--root",
action="append",
required=True,
help="Root predicate 'name/arity' (repeatable)",
)
def _add_analyze_subparser(sub: argparse._SubParsersAction) -> None:
analyze_p = sub.add_parser(
"analyze", help="Derive + print the required-predicate set as JSON"
)
_add_rulebase_and_roots(analyze_p)
analyze_p.set_defaults(func=_cmd_analyze)
def _add_check_subparser(sub: argparse._SubParsersAction) -> None:
check_p = sub.add_parser(
"check", help="Derive, then assert completeness against --provided"
)
_add_rulebase_and_roots(check_p)
check_p.add_argument(
"--provided",
default="",
help="Comma-separated 'name/arity' predicates the caller is about "
"to project (e.g. 'p/2,q/3')",
)
check_p.set_defaults(func=_cmd_check)
def _build_parser() -> argparse.ArgumentParser:
ap = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
sub = ap.add_subparsers(dest="command", required=True)
_add_analyze_subparser(sub)
_add_check_subparser(sub)
return ap
def main() -> int:
"""Parse argv and dispatch to the selected subcommand's handler."""
args = _build_parser().parse_args()
result: int = args.func(args)
return result
if __name__ == "__main__":
sys.exit(main())