Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
20411da
test(scope): add scope_order fixture for narrowing contract
fraware Jun 29, 2026
ae501c7
test(scope): add valid_narrowing_pairs fixture
fraware Jun 29, 2026
8cd0d37
feat(akta): load scope order and narrowing from fixtures
fraware Jun 29, 2026
98459d7
refactor(akta): derive review-loop scope ranks from fixtures
fraware Jun 29, 2026
37ce3ac
test(contracts): cover fixture-driven scope narrowing
fraware Jun 29, 2026
78142aa
fix(scope): require AKTA record in akta-review CLI mode
fraware Jun 29, 2026
4579616
test(contracts): assert akta-review requires AKTA record
fraware Jun 29, 2026
f4f8c5f
test(contracts): pass record in akta-review summary validation test
fraware Jun 29, 2026
aa0b355
feat(pcs): export scope_review_summary in PCS bundles
fraware Jun 29, 2026
72485b7
test(contracts): PCS full chain includes scope_review_summary
fraware Jun 29, 2026
02f0172
feat(scripts): add cross-repo reconstructable verification
fraware Jun 29, 2026
375276c
feat(scripts): extend reconstructable demo for v0.8 SCOPE summary
fraware Jun 29, 2026
9cbc26f
test(integration): assert scope_review_summary in reconstructable demo
fraware Jun 29, 2026
b7edf69
test(integration): update reconstructable demo expectations for v0.8
fraware Jun 29, 2026
355d950
ci(make): add cross-repo reconstructable demo and verify targets
fraware Jun 29, 2026
fe2ae34
feat(scripts): include scope summary in protocol drift demo
fraware Jun 29, 2026
a82a7bf
chore(examples): sync protocol_drift active-update decision artifact
fraware Jun 29, 2026
45efb47
chore(examples): sync protocol_drift PCS PF obligation member
fraware Jun 29, 2026
7792798
chore(examples): sync protocol_drift PCS akta decision member
fraware Jun 29, 2026
3d810b6
chore(examples): sync protocol_drift PCS akta record member
fraware Jun 29, 2026
a6b85a7
chore(examples): sync protocol_drift PCS review trigger member
fraware Jun 29, 2026
d65fcda
chore(examples): sync protocol_drift PF obligation sidecar
fraware Jun 29, 2026
0088b9a
chore(examples): sync protocol_drift review trigger sidecar
fraware Jun 29, 2026
687b5e1
chore(examples): sync protocol_drift active-update record artifact
fraware Jun 29, 2026
2ac35f1
chore(examples): update protocol_drift PCS manifest hashes
fraware Jun 29, 2026
fc76e0a
chore(examples): add protocol_drift PCS scope_review_summary member
fraware Jun 29, 2026
85a2567
chore(evals): refresh canonical_5 report baseline
fraware Jun 29, 2026
38fe05a
chore(evals): refresh public_100 report baseline
fraware Jun 29, 2026
da7c949
test(api): expect health endpoint version 0.8.0
fraware Jun 29, 2026
b1b28fb
chore(release): bump package version to 0.8.0
fraware Jun 29, 2026
4cfb2ad
docs(changelog): add AKTA v0.8.0 release notes
fraware Jun 29, 2026
add81b3
docs(release): document v0.8.0 release process
fraware Jun 29, 2026
11d35c7
docs: document v0.8 limitations and scope assumptions
fraware Jun 29, 2026
37a05ec
docs: update README for v0.8 integration highlights
fraware Jun 29, 2026
3114fab
chore(evals): align canonical_5 after CI run
fraware Jun 29, 2026
26211dc
chore(evals): align public_100 after CI run
fraware Jun 29, 2026
e24cee5
chore(examples): CI-sync protocol_drift akta_decision_active_update.json
fraware Jun 29, 2026
85ecb22
chore(examples): CI-sync protocol_drift akta_record_active_update.json
fraware Jun 29, 2026
a062f03
chore(examples): CI-sync protocol_drift review_trigger.json
fraware Jun 29, 2026
70d5fce
chore(examples): CI-sync protocol_drift pf_obligation.json
fraware Jun 29, 2026
671bcfc
chore(examples): CI-sync protocol_drift akta_decision.json
fraware Jun 29, 2026
be29670
chore(examples): CI-sync protocol_drift akta_record.json
fraware Jun 29, 2026
96629e0
chore(examples): CI-sync protocol_drift review_trigger.json
fraware Jun 29, 2026
50c4350
chore(examples): CI-sync protocol_drift pf_obligation.json
fraware Jun 29, 2026
fbc0d1e
chore(examples): CI-sync protocol_drift manifest.json
fraware Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ All notable changes to AKTA are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [0.8.0] - 2026-06-29

### Added

- `akta-review-cli` mode requires AKTA record before subprocess (`--akta-record`); fails closed with clear error
- First-class `04_scope_review_summary.json` artifact in reconstructable demo; PCS bundle includes `scope_review_summary.json`
- Fixture-driven SCOPE narrowing: `tests/fixtures/scope_scope_order.json`, `scope_valid_narrowing.json`
- Makefile targets: `demo-reconstructable-cross-repo`, `verify-reconstructable-cross-repo`
- Tests: `test_akta_review_requires_record.py`, `test_reconstructable_summary_artifact.py`, `test_scope_narrowing_fixture.py`

### Changed

- `validate_approval_grant()` uses fixture narrowing pairs instead of hard-coded `active_protocol_update` → `protocol_draft` only
- Reconstructable demo artifact numbering 00–12 with `10_pcs_bundle/`; cross-repo output at `dist/reconstructable_cross_repo/`
- `reconstruction_report.md` cites `summary.json` contract and checks assurance levels and tool lists
- `ScopeAdapterResult` includes `summary` for all adapter modes

## [0.7.1] - 2026-06-28

### Added
Expand Down
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: install test eval-canonical eval-public eval-public-100 eval-oracle eval-holdout eval-v06 demo-weak-evidence demo-akta-weak-evidence demo-akta-scope-protocol-drift demo-reconstructable ci
.PHONY: install test eval-canonical eval-public eval-public-100 eval-oracle eval-holdout eval-v06 demo-weak-evidence demo-akta-weak-evidence demo-akta-scope-protocol-drift demo-reconstructable demo-reconstructable-cross-repo verify-reconstructable-cross-repo ci

PYTHON ?= python

Expand Down Expand Up @@ -26,6 +26,13 @@ eval-v06:
demo-reconstructable:
$(PYTHON) scripts/demo_reconstructable_experiment.py

demo-reconstructable-cross-repo:
$(PYTHON) -c "import os,sys; ok=bool(os.environ.get('SCOPE_REPO_PATH') or os.environ.get('SCOPE_CLI')); (print('Set SCOPE_REPO_PATH or SCOPE_CLI for live SCOPE') if not ok else None); sys.exit(0 if ok else 1)"
$(PYTHON) scripts/demo_reconstructable_experiment.py --cross-repo

verify-reconstructable-cross-repo:
$(PYTHON) scripts/verify_reconstructable_cross_repo.py

demo-weak-evidence:
$(PYTHON) scripts/demo_weak_evidence.py

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ REST API, MCP server, and batch evaluation are available for integrators — see
| **PF-Core** | Runtime proof obligations tied to admissibility decisions | [PF-Core bridge](docs/pf_core_bridge.md) |
| **PCS** | Packages decisions, records, and hashes for release verification | [PCS export](docs/pcs_export.md) |

SCOPE connects in several modes — offline simulation (default), Python import from a sibling checkout, subprocess CLI, or a one-shot review command. Details and conformance checks: [scope_live_conformance.md](docs/scope_live_conformance.md).
SCOPE connects in several modes — offline simulation (default), Python import from a sibling checkout, subprocess CLI, or a one-shot review command (`scope akta review` requires both AKTA trigger and record). Reconstructable cross-repo demo: `make demo-reconstructable-cross-repo` then `make verify-reconstructable-cross-repo`. Details: [scope_live_conformance.md](docs/scope_live_conformance.md).

Cross-repo environment variables for optional CI jobs: [.github/CROSS_REPO_CI.md](.github/CROSS_REPO_CI.md).

Expand Down Expand Up @@ -200,7 +200,7 @@ Read [CONTRIBUTING.md](CONTRIBUTING.md) for pull-request expectations, [GOVERNAN

## Project status

**v0.7.1** (`akta-protocol`) is the current reference kernel. It implements the full admissibility loop — gate, record, review triggers, grant re-gating, PF export, and PCS packaging — with 385+ tests and a green `make ci` suite. Biology, chemistry, and clinical domain overlays are experimental and not deployment-ready without institutional governance. Fully autonomous scientific operator profiles are defined for taxonomy only and are not supported at runtime. AKTA is a reference implementation under the MIT license, not a safety certification. Known gaps and non-goals: [docs/limitations.md](docs/limitations.md).
**v0.8.0** (`akta-protocol`) is the current reference kernel. Biology, chemistry, and clinical domain overlays are experimental and not deployment-ready without institutional governance. Fully autonomous scientific operator profiles are defined for taxonomy only and are not supported at runtime. AKTA is a reference implementation under the MIT license, not a safety certification. Known gaps and non-goals: [docs/limitations.md](docs/limitations.md).

---

Expand Down
10 changes: 10 additions & 0 deletions adapters/pcs/export_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def build_pcs_manifest(
*,
file_hashes: dict[str, str] | None = None,
include_review_trigger: bool = False,
include_scope_summary: bool = False,
include_scope_packet: bool = False,
include_scope_decision: bool = False,
include_scope_grant: bool = False,
Expand All @@ -42,6 +43,8 @@ def build_pcs_manifest(
files = list(CORE_FILES)
if include_review_trigger:
files.append("review_trigger.json")
if include_scope_summary:
files.append("scope_review_summary.json")
if include_scope_packet:
files.append("scope_review_packet.json")
if include_scope_decision:
Expand Down Expand Up @@ -107,6 +110,7 @@ def validate_pcs_bundle(bundle_dir: str | Path) -> None:
json.loads((bundle_dir / "akta_decision.json").read_text(encoding="utf-8")),
file_hashes=file_hashes,
include_review_trigger=(bundle_dir / "review_trigger.json").exists(),
include_scope_summary=(bundle_dir / "scope_review_summary.json").exists(),
include_scope_packet=(bundle_dir / "scope_review_packet.json").exists(),
include_scope_decision=(bundle_dir / "scope_decision.json").exists(),
include_scope_grant=(bundle_dir / "scope_grant.json").exists(),
Expand All @@ -122,6 +126,7 @@ def export_pcs_bundle(
out_dir: str | Path,
*,
decision: dict[str, Any] | None = None,
scope_review_summary: dict[str, Any] | None = None,
scope_review_packet: dict[str, Any] | None = None,
scope_decision: dict[str, Any] | None = None,
scope_grant: dict[str, Any] | None = None,
Expand Down Expand Up @@ -190,6 +195,10 @@ def _write(name: str, content: str) -> None:
if review_trigger:
_write("review_trigger.json", json.dumps(review_trigger, indent=2))

has_scope_summary = scope_review_summary is not None
if scope_review_summary is not None:
_write("scope_review_summary.json", json.dumps(scope_review_summary, indent=2))

has_scope_packet = scope_review_packet is not None
if scope_review_packet is not None:
_write("scope_review_packet.json", json.dumps(scope_review_packet, indent=2))
Expand Down Expand Up @@ -220,6 +229,7 @@ def _write(name: str, content: str) -> None:
decision_payload,
file_hashes=artifacts,
include_review_trigger=has_review_trigger,
include_scope_summary=has_scope_summary,
include_scope_packet=has_scope_packet,
include_scope_decision=has_scope_decision,
include_scope_grant=has_scope_grant,
Expand Down
64 changes: 63 additions & 1 deletion adapters/scope/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class ScopeAdapterResult:
review_packet: dict[str, Any] | None = None
grant: dict[str, Any] | None = None
decision: dict[str, Any] | None = None
summary: dict[str, Any] | None = None
error: str | None = None


Expand Down Expand Up @@ -176,7 +177,7 @@ def _decision_input_for_grant(granted: str, trigger: dict[str, Any]) -> dict[str
requested = trigger.get("requested_scope", "protocol_draft")
if granted == requested:
return {
"type": "approve_scope",
"type": "approve",
"approved_scope": granted,
"rationale": "Approved at requested scope.",
}
Expand Down Expand Up @@ -255,6 +256,13 @@ def _python_import_scope(
review_packet=packet,
grant=grant,
decision=decision,
summary=_synthesize_scope_summary(
adapter_mode=ADAPTER_MODE_PYTHON_IMPORT,
trigger=trigger,
packet=packet,
decision=decision,
grant=grant,
),
)


Expand Down Expand Up @@ -296,12 +304,51 @@ def _validate_akta_review_summary(summary: dict[str, Any]) -> None:
validate_against_schema(summary, "scope_akta_review_summary.schema.json")


def _synthesize_scope_summary(
*,
adapter_mode: str,
trigger: dict[str, Any],
packet: dict[str, Any],
decision: dict[str, Any],
grant: dict[str, Any],
) -> dict[str, Any]:
"""Build summary.json-compatible contract for non-akta-review adapter modes."""
auth = grant.get("authorization") or {}
approved = auth.get("approved_scope") or grant.get("granted_scope") or ""
requested = (
grant.get("requested_scope")
or (grant.get("source") or {}).get("requested_scope")
or trigger.get("requested_scope")
or ""
)
return {
"status": "completed",
"approved_scope": approved,
"requested_scope": requested,
"allowed_tools": list(auth.get("allowed_tools") or grant.get("allowed_tools") or []),
"blocked_tools": list(auth.get("blocked_tools") or grant.get("blocked_tools") or []),
"adapter_contract_version": "scope-akta-review-v0.8",
"identity_assurance_level": "IAL0",
"signing_assurance_level": "SAL1",
"adapter_mode": adapter_mode,
"packet_id": packet.get("packet_id"),
"decision_id": decision.get("decision_id"),
"grant_id": grant.get("grant_id"),
}


def _akta_review_cli_scope(
trigger: dict[str, Any],
record: dict[str, Any] | None,
granted: str,
reviewer_id: str,
) -> ScopeAdapterResult:
if record is None:
return ScopeAdapterResult(
adapter_mode=ADAPTER_MODE_AKTA_REVIEW_CLI,
error="SCOPE akta-review CLI mode requires an AKTA record.",
)

cli = os.environ.get("SCOPE_CLI", "scope")
with tempfile.TemporaryDirectory(prefix="akta-scope-akta-review-") as tmp:
tmp_path = Path(tmp)
Expand Down Expand Up @@ -388,6 +435,7 @@ def _akta_review_cli_scope(
review_packet=packet,
grant=grant,
decision=decision,
summary=summary,
)


Expand Down Expand Up @@ -492,6 +540,13 @@ def _cli_scope(
review_packet=packet,
grant=grant,
decision=decision,
summary=_synthesize_scope_summary(
adapter_mode=ADAPTER_MODE_CLI,
trigger=trigger,
packet=packet,
decision=decision,
grant=grant,
),
)


Expand Down Expand Up @@ -532,6 +587,13 @@ def _simulated_scope(
review_packet=packet,
grant=grant,
decision=decision,
summary=_synthesize_scope_summary(
adapter_mode=ADAPTER_MODE_SIMULATED,
trigger=trigger,
packet=packet,
decision=decision,
grant=grant,
),
)


Expand Down
43 changes: 27 additions & 16 deletions akta/review_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,38 +17,49 @@
enforce_grant_expiry,
is_review_expired,
)
from akta.scope_contract import _scope_grant_approved_scope, _scope_grant_requested_scope
from akta.scope_contract import (
_scope_grant_approved_scope,
_scope_grant_requested_scope,
is_valid_narrowing_grant,
scope_rank,
)
from akta.scope_mapping import VALID_REQUESTED_SCOPES

SCOPE_RANK_ORDER: dict[str, int] = {
"protocol_draft": 1,
"active_protocol_update": 2,
"single_validation_plan": 3,
"single_validation_run_draft": 3,
"single_run_queue_priority": 4,
"robot_queue_submission": 5,
"execution_payload_preparation": 5,
"publication_claim": 6,
"scientific_memory_import": 4,
"draft_only": 0,
scope: scope_rank(scope)
for scope in (
"protocol_draft",
"active_protocol_update",
"single_validation_plan",
"single_validation_run_draft",
"single_run_queue_priority",
"robot_queue_submission",
"execution_payload_preparation",
"publication_claim",
"scientific_memory_import",
"draft_only",
)
}


def _scope_rank(scope: str) -> int:
try:
return scope_rank(scope)
except FileNotFoundError:
return SCOPE_RANK_ORDER.get(scope, 99)


def _evidence_rank(evidence_state: str) -> int:
if evidence_state.startswith("E") and len(evidence_state) > 1 and evidence_state[1].isdigit():
return int(evidence_state[1])
return 0


def _scope_rank(scope: str) -> int:
return SCOPE_RANK_ORDER.get(scope, 99)


def grant_scope_covers_action(granted_scope: str, requested_scope: str) -> bool:
"""Return True when granted scope covers the requested scope (narrow grants only)."""
if granted_scope == requested_scope:
return True
if requested_scope == "active_protocol_update" and granted_scope == "protocol_draft":
if is_valid_narrowing_grant(granted_scope=granted_scope, requested_scope=requested_scope):
return True
return _scope_rank(granted_scope) >= _scope_rank(requested_scope)

Expand Down
Loading