Metadata-Version: 2.4
Name: ontobdc-dev
Version: 0.1.0
Summary: Developer CLI utilities for OntoBDC workspaces.
Author: Elias Magalhaes
License: Apache-2.0
Classifier: License :: OSI Approved :: Apache Software License
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: ontobdc>=0.14.0
Dynamic: license-file

# ontobdc-dev

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)

Standalone Python package for OntoBDC development workflows.

## Installation

```bash
pip install -e .
```

This exposes the executable:

```bash
ontobdc-dev --help
```

## Commands

Show repository status:

```bash
ontobdc-dev branch
```

Create a branch across discovered repositories:

```bash
ontobdc-dev branch --create feature/my-branch
```

Checkout a branch across discovered repositories:

```bash
ontobdc-dev branch --checkout feature/my-branch
```

Fetch and pull a branch across discovered repositories:

```bash
ontobdc-dev branch --pull feature/my-branch
```

If omitted, `--pull` uses the current branch of each repository:

```bash
ontobdc-dev branch --pull
```

Run the changelog scaffold:

```bash
ontobdc-dev branch --changelog
```

Commit and push selected registered Git submodules:

```bash
ontobdc-dev commit "feat: your message" --submodule ontobdc-wip,infobim-wip
```

Submodule names may be separated by commas, semicolons, or spaces:

```bash
ontobdc-dev commit "feat: your message" --submodule "ontobdc-wip;infobim-wip"
ontobdc-dev commit "feat: your message" --submodule ontobdc-wip infobim-wip
```

When `--submodule` is omitted, the command selects only the current submodule if the current working directory is exactly its registered root. Otherwise, it selects every submodule registered in `.gitmodules`.

Run the Rich renderer test suite. This instantiates every scenario declared in
[`src/ontobdc_dev/render/plugin/command/scenarios.yaml`](src/ontobdc_dev/render/plugin/command/scenarios.yaml),
resolves the real `ontobdc.view.adapter.response` adapter for each response
class, and prints the resulting Rich message box so you can visually inspect
how `CommandResponse`, `HelpCommandResponse`, `ExceptionCommandResponse`,
`ListCommandResponse`, and `WelcomeCommandResponse` render in the terminal:

```bash
ontobdc-dev render
```

`--test` defaults to `rich` when omitted, so bare `render` is equivalent to
`--test rich`; any other explicit `--test` value is still rejected.

Through the OntoBDC development proxy:

```bash
ontobdc dev render
```

Limit the run to scenarios targeting one exact response class, identified by
its `module.path:ClassName` URI:

```bash
ontobdc dev render --test rich --response ontobdc.cli.domain.response.command:CommandResponse
```

## Semantic test orchestrator

The full architecture and implementation plan for the declarative, state-oriented semantic test orchestrator is documented in:

- [`docs/testing/semantic-test-orchestrator.md`](docs/testing/semantic-test-orchestrator.md)
- [`docs/testing/examples/storage-container-metadata.yaml`](docs/testing/examples/storage-container-metadata.yaml)

That document covers the complete design across nine phases: YAML manifests, state observers, checks, hotfixes, capabilities, fixtures, semantic planning, execution evidence, coverage gates, and migrating every OntoBDC check/hotfix into executable test cases.

The package under [`src/ontobdc_dev/testing/`](src/ontobdc_dev/testing/) implements the slice needed to **execute an existing check and hotfix through a manifest**, corresponding to the document's sections 4 (terminology), 6 (check/hotfix semantics), 7 (manifest model), 8 (state expressions), and 10 (execution cycle) — roughly phases 1 through 3 of section 24. The rest of this section is a usage manual for that slice.

### What this does and does not cover

Implemented:

- loading and validating `StateDefinition`/`TestAction` YAML manifests;
- observing a state by calling an existing `check.py:main` (or a plain filesystem existence check) with no source change to the check itself;
- executing an existing `hotfix.py:main` the same way;
- composite states (`all`/`any`/`not` over other states);
- the `check -> hotfix -> recheck` cycle with a verdict derived only from the recheck.

Not implemented (see `docs/testing/semantic-test-orchestrator.md` for the full design of each):

- fixture/sandbox materialization (section 13) — there is no isolated working copy; `--param` points directly at real paths you prepare yourself;
- a semantic planner or `ExecutionPlan` (section 9) — you must already know which state and which action to run, and satisfy `requires` yourself;
- `TestCase`/`TestFlow`/`TestSuite` grouping, matrices, and invariants (section 7.2, 7.7–7.9);
- evidence persistence and JUnit reporting (section 12);
- inventory, scaffold, and the coverage gate (sections 15–16);
- JSON-Schema-driven structural validation (section 20.1) — validation is hand-written Python, and unrecognized manifest fields (`behavior`, `effects`, `planning`, `evidence`, ...) are accepted but silently ignored instead of rejected, unlike section 7.1's rule for a complete implementation.

### Concepts

| Term | Meaning here |
|---|---|
| `StateDefinition` | A named, checkable condition of the system. Backed by either an `observer` (a probe) or an `expression` (a composite of other states). |
| Observer | The thing that actually checks a state. Two types are supported: `python-call` (calls an existing `check.py:main`) and `filesystem` (checks a path exists). |
| `TestAction` | A named, executable operation — in practice, a wrapper around an existing `hotfix.py:main`. |
| `requires` / `ensures` | Metadata on a `TestAction` describing which states it needs beforehand and which it is meant to produce. Recorded and reference-checked against the catalog, but **not** automatically satisfied — there is no planner in this slice. |
| `verification.states` | The states the runner actually reobserves right after executing an action. Defaults to `ensures` when omitted. This is what the verdict is computed from. |
| `ExecutionContext` | The `--param key=value` values you pass on the command line, substituted into `${context.key}` placeholders inside the manifest. |
| Verdict | `passed`, `failed`, or `error` in this slice (the full vocabulary also has `blocked`/`skipped`/`inconclusive`, which nothing here produces yet). |

### Directory layout

```text
dev/
├── src/ontobdc_dev/testing/   # the runner: domain models, adapters, catalog loader, CLI commands
└── tests/semantic/
    ├── states/                # StateDefinition manifests
    └── actions/               # TestAction manifests
```

`--manifest <path>` accepts either a single YAML file or a directory, searched
recursively for `*.yaml`. There is no auto-discovery of a default path — you
always pass `--manifest` explicitly.

### Manifest reference

Every document starts with the same header:

```yaml
apiVersion: ontobdc.org/testing/v1alpha1
kind: StateDefinition   # or TestAction
metadata:
  name: my.dotted.state.name   # required, unique across the whole catalog
  title: Human-readable title  # optional
  description: >               # optional
    Longer explanation.
  tags: [optional, list, of, strings]
spec:
  ...
```

**`StateDefinition.spec`** — exactly one of:

```yaml
spec:
  observer:
    type: python-call
    target: package.module.path:callable   # must return an int
    arguments:
      some_argument: "${context.some_param}"
    result:
      satisfiedWhen: { exitCode: [0] }
      unsatisfiedWhen: { exitCode: [1] }
      errorWhen: { exitCode: [2] }
      otherwise: error   # satisfied | unsatisfied | error, used for any code not listed above
```

```yaml
spec:
  observer:
    type: filesystem
    exists: "${context.some_path}"
    kind: directory   # or file; omit to accept either
```

```yaml
spec:
  expression:
    all: [state.name.one, state.name.two]   # every referenced name must already exist in the catalog
    # or: any: [...]
    # or: not: state.name.one
```

**`TestAction.spec`**:

```yaml
spec:
  role: repair   # free text; "repair" is the convention for hotfix-backed actions
  executor:
    type: python-call
    target: package.module.path:callable   # must return an int
    arguments:
      some_argument: "${context.some_param}"
    result:
      succeededWhen: { exitCode: [0] }
      failedWhen: { exitCode: [1] }
      otherwise: error   # succeeded | failed | error
  requires:
    - state: some.precondition.state   # informational + reference-checked only
  ensures:
    - state: some.state.this.fixes
  verification:
    states:
      - some.state.this.fixes   # what actually gets reobserved; defaults to `ensures` if omitted
```

A `TestAction` must end up with at least one `verification` state (explicit or via `ensures`); the loader rejects an action that declares nothing to check afterwards.

### Walkthrough: the shipped example

[`tests/semantic/states/storage.container.metadata.ready.yaml`](tests/semantic/states/storage.container.metadata.ready.yaml),
[`tests/semantic/states/storage.container.directory.ready.yaml`](tests/semantic/states/storage.container.directory.ready.yaml), and
[`tests/semantic/actions/storage.container.metadata.repair.yaml`](tests/semantic/actions/storage.container.metadata.repair.yaml)
wrap the real `ontobdc.storage.plugin.check.is_container_metadata_ready` check
and hotfix — no wrapper code was written on the `ontobdc` side, the manifest
calls `check.py:main`/`hotfix.py:main` directly.

1. Validate the manifests:

   ```bash
   ontobdc-dev test validate --manifest tests/semantic
   ```

2. See what got loaded:

   ```bash
   ontobdc-dev test list states --manifest tests/semantic
   ontobdc-dev test list actions --manifest tests/semantic
   ```

3. Pick (or create) a container directory to test against, then run the bare
   check. Against an empty/nonexistent container this reports `failed`:

   ```bash
   ontobdc-dev test run --manifest tests/semantic \
     --state storage.container.metadata.ready \
     --param root_path=/path/to/workspace \
     --param container_path=/path/to/workspace/my-container
   ```

4. Run the full `check -> hotfix -> recheck` cycle. The hotfix creates/repairs
   the container metadata, and the verdict comes from reobserving
   `storage.container.metadata.ready` afterwards — never from the hotfix's own
   exit code (semantic-test-orchestrator.md, section 6.1: "o retorno do
   hotfix não comprova estado"):

   ```bash
   ontobdc-dev test run --manifest tests/semantic \
     --action storage.container.metadata.repair \
     --param root_path=/path/to/workspace \
     --param container_path=/path/to/workspace/my-container
   ```

5. Running the bare check again now reports `passed`.

### Writing a test for another check/hotfix

1. Pick the check, e.g. `ontobdc.storage.plugin.check.is_container_manifest_synced.check:main`. Read its signature to know which arguments it takes and confirm it returns `int`.
2. Add a `StateDefinition` under `tests/semantic/states/`, named after the check (dotted, lower-case), with a `python-call` observer pointing at `module:main` and an `arguments` map using `${context.<name>}` for every parameter the check needs.
3. If there is a matching `hotfix.py`, add a `TestAction` under `tests/semantic/actions/` with role `repair`, an executor pointing at `hotfix.py:main`, `ensures` pointing back at the `StateDefinition` from step 2, and `requires` for any precondition state (add a `StateDefinition` for it too if one does not exist yet — a `filesystem` observer is usually enough for "does this directory exist").
4. Run `ontobdc-dev test validate --manifest tests/semantic` — it will catch a typo'd state reference or a missing `metadata.name` immediately.
5. Run `ontobdc-dev test run --state ... --param ...` against a real (or intentionally broken) path first, to see the check fail the way you expect, then `ontobdc-dev test run --action ... --param ...` to see the repair cycle.

### Reading the output

`test run` returns a `CommandResponse` whose `content` always has this shape:

```jsonc
{
  "mode": "state" | "action",
  "target": "the state or action name you passed",
  "verdict": "passed" | "failed" | "error",
  "before": { "<state name>": { "status": "...", "observed_at": "...", "detail": "...", "evidence": {...} } },
  "action_result": null | { "action": "...", "status": "succeeded|failed|error", "executed_at": "...", "detail": "..." },
  "after": { "<state name>": { ... } },   // empty for `--state` runs; only populated after a non-erroring action
  "detail": "short human-readable summary"
}
```

`before` is always populated (for `--action`, it is the pre-execution
observation, informational only). `after` is only populated once the action
itself did not error or fail. Pass `--json` on the outer `ontobdc-dev`
invocation to get this as plain JSON instead of a Rich box.

### Troubleshooting

| Symptom | Cause |
|---|---|
| `Missing context value for '${context.foo}'. Provide it with --param foo=<value>.` | The manifest references a `${context.foo}` placeholder you did not supply with `--param foo=...`. |
| `Unknown state 'x'.` / `Unknown action 'x'.` | The name passed to `--state`/`--action`, or referenced by `requires`/`ensures`/`verification.states`/an expression, does not match any `metadata.name` in the loaded manifests. Check for typos or a missing `--manifest` path. |
| `TestAction 'x' references unknown state 'y'.` | Add the missing `StateDefinition`, or fix the reference. |
| `StateDefinition 'x' must declare exactly one of 'observer' or 'expression'.` | A state manifest has both, or neither, under `spec`. |
| `'<target>' returned <value>; a python-call target must return an int exit code.` | The wrapped `check.py`/`hotfix.py` function did not return a plain `int` — every OntoBDC check/hotfix `main()` is expected to. |
| Verdict is `error` instead of `failed` | Something in the observer/executor itself broke (bad target, exception, wrong argument), as opposed to the state simply being unsatisfied. The `detail`/`evidence` fields carry the underlying message. |
| `TestAction 'x' declares no 'ensures' or 'verification.states' to reobserve after execution.` | Add at least one `ensures` entry or an explicit `verification.states` list — the runner has nothing to reobserve otherwise. |

## Root Resolution

`RootDirStrategy` owns workspace-root resolution inside `ontobdc-dev`.

An explicit root may be passed in any position:

```bash
ontobdc-dev --root-dir /path/to/workspace branch
ontobdc-dev branch --root-dir /path/to/workspace
```

The explicit directory must exist and contain `.gitmodules`.

When `--root-dir` is omitted, the strategy starts at the current working directory and walks through its parents until it finds `.gitmodules`. The resolved value is stored in the CLI context as `pathlib.Path` and is consumed by workspace commands and dependent strategies.

The `ontobdc dev` proxy only locates and executes the `ontobdc-dev` package. It forwards arguments and preserves the current working directory; it does not resolve or inject the workspace root.
