feat(ais_core): implement JSON-Contract + Run-State helpers (#471, Issue #1c)#477
Merged
Conversation
- ais_core/json_contract.py: full implementation of success_envelope,
error_envelope, validate_envelope. Pure functions, no I/O.
- success_envelope(command, data, *, warnings=None, elapsed_ms=None)
- error_envelope(command, code, message, *, hint=None, elapsed_ms=None)
- validate_envelope(envelope) — strict shape validation, raises ValueError
- ais_core/run_state.py: full implementation of Run-ID generation and
state persistence. Pure (no global state, no env-var reads at import).
- make_run_id(owner, repo, timestamp=None) -> str with format
<UTC>-<repo-short>-<8-hex> (SHA-256-based collision-avoidance)
- save_state / load_state with optional base_dir override for tests
- DEFAULT_REPORTS_DIR = Path('reports/runs')
- RunState NamedTuple for typed state records
- tests/test_ais_core/test_json_contract.py: 22 tests covering envelope
shape + validate_envelope rejection cases
- tests/test_ais_core/test_run_state.py: 11 tests covering Run-ID
format + 1000-IDs-uniqueness + state roundtrip + edge cases
Tests: 33 OK in 0.026s (test_json_contract + test_run_state).
Refs #471
Per #477 review feedback: 1. make_run_id hash input switched from seconds-precise UTC string to microsecond-precise ISO-8601 string. Visible prefix remains seconds- precise (YYYYMMDDTHHMMSSZ) so run-IDs stay human-readable and sortable; the hash gains uniqueness within the same wall-clock second. Identical explicit timestamps still produce identical IDs (determinism preserved); timestamp=None calls differ by clock. 2. _sanitize_repo_short: added second .strip('-') after the 20-char truncate (defense-in-depth: trailing separators are possible if input ends in a non-alphanum char at the truncate boundary). 3. save_state: now raises ValueError when run_id argument and state.run_id differ. Prevents accidental metadata.json files whose embedded run_id does not match their on-disk path. Tests added (5): - test_same_second_different_microseconds_produce_different_ids: same second, different microseconds -> different IDs; visible prefix identical. - test_visible_prefix_remains_seconds_precise: microsecond input still produces YYYYMMDDTHHMMSSZ prefix. - test_subsecond_resolution_default_timestamp: two timestamp=None calls produce different IDs (warns if clock is too coarse). - test_save_state_rejects_run_id_mismatch: ValueError on mismatch. - (existing tests unchanged: 1000 unique IDs, basic roundtrip, etc.) Tests: 16 OK in 0.023s (test_run_state). json_contract tests: 21 OK in 0.001s (unchanged). Refs #471, #477
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #471 (Issue #1c — Wave 1b, JSON-Contract + Run-State)
Summary
Replaces the
NotImplementedErrorplaceholders inais_core/json_contract.pyand
ais_core/run_state.pywith full implementations, plus comprehensivebehaviour tests. Pure helpers, no I/O side-effects, no global state, no
env-var reads at import time.
What's in this PR
ais_core/json_contract.pysuccess_envelope(command, data, *, warnings=None, elapsed_ms=None)error_envelope(command, code, message, *, hint=None, elapsed_ms=None)validate_envelope(envelope)— strict shape + schema_version check,raises
ValueErroron bad shapeSCHEMA_VERSION = "1.0"(unchanged)ais_core/run_state.pymake_run_id(owner, repo, timestamp=None)— format<UTC>-<repo-short>-<8-hex>(SHA-256-based collision avoidance)save_state(run_id, state, *, base_dir=None)— writes<base_dir>/<run_id>/metadata.json, creates parentsload_state(run_id, *, base_dir=None)— reads metadata.json backinto a
RunStateDEFAULT_REPORTS_DIR = Path("reports/runs")RunStateNamedTuple (run_id,status,data)Tests
tests/test_ais_core/test_json_contract.py: 22 testsTestSchemaVersionConstant: SCHEMA_VERSION is "1.0"TestSuccessEnvelope: minimal shape, warnings default, withwarnings, with elapsed_ms, arbitrary JSON data
TestErrorEnvelope: minimal shape, with hint, with elapsed_ms,error-body key set
TestValidateEnvelope: accepts valid success/error, rejectsnon-dict, wrong schema_version, missing
ok, non-boolok,success without
data, error withouterror/code/message,non-dict
errortests/test_ais_core/test_run_state.py: 11 testsTestMakeRunIdFormat: basic format, default timestamp UTC,naive timestamp treated as UTC, repo-short sanitization,
repo-short ≤20 chars
TestMakeRunIdUniqueness: same inputs same id, different ownersdifferent ids, 1000 unique IDs
TestSaveLoadRoundtrip: basic roundtrip, parent-dir creation,missing-file FileNotFoundError, empty data roundtrip
Tests
python -m unittest tests.test_ais_core.test_json_contract \ tests.test_ais_core.test_run_state -v→ 33 tests, 0.026s, all OK.
LOC
ais_core/json_contract.pyais_core/run_state.pytests/test_ais_core/test_json_contract.pytests/test_ais_core/test_run_state.pyHelper code: 322 lines (vs. < 300 net soft-budget). Docstrings are
substantial because both modules are public API surfaces with
documented contracts; pure code (function bodies) is ~120 lines.
Out of scope (intentional)
success_envelope/error_envelopedo NOT yet call
redact_dict; that's added when #1d lands.ais_core.run_statemigration from existing scripts (nonecurrently call it; comes with the AIS-CLI in Issue Align README claims with actual analyzer checks #2).
Parent issue
Part of #468 (Release 0.10.0 — AIS Tooling Interface).