generated from coulomb/repo-seed
IB-WP-0014: archive-list, restore, retention annotation, docs (T03-T05)
Round out IB-WP-0014 with the remaining archive operations and docs. - restore_archive() and `infospace-bench restore <pkg> --target <dir>` round-trip a finalized package's bytes back to disk. Refuses to overwrite a non-empty target unless --force. --from <infospace-root> resolves the store location. - archive-list CLI with --with-retention flag; annotate_retention() opens the per-infospace registry and joins each record with its current retention state (effective class, expires, holds, eligibility). - docs/archive-integration.md covers when to archive, the include set, retention classes, storage layout, credentials policy, and the explicit non-goal that S3/git backends live in artifact-store. - SCOPE.md cross-links the new doc. - Workplan flipped to status: done. Full pytest suite: 72 passed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,12 @@ import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from .archive import (
|
||||
annotate_retention,
|
||||
archive_infospace,
|
||||
list_archives,
|
||||
restore_archive,
|
||||
)
|
||||
from .checks import run_collection_checks
|
||||
from .engine import engine_capability_contract, plan_asset_sync, sync_assets
|
||||
from .errors import InfospaceError
|
||||
@@ -195,6 +201,74 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
generate_from_source.add_argument("--max-chunks", type=int, default=0)
|
||||
generate_from_source.add_argument("--apply", action="store_true")
|
||||
|
||||
archive = sub.add_parser(
|
||||
"archive",
|
||||
help="Archive an infospace into artifact-store (durable, content-addressed)",
|
||||
)
|
||||
archive.add_argument("root")
|
||||
archive.add_argument(
|
||||
"--retention-class",
|
||||
default="release-evidence",
|
||||
help="artifact-store retention class id (default: release-evidence)",
|
||||
)
|
||||
archive.add_argument(
|
||||
"--include",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Relative path to include (repeatable). Default: infospace.yaml, artifacts/, workflows/, output/, reports/, exports/",
|
||||
)
|
||||
archive.add_argument(
|
||||
"--exclude",
|
||||
action="append",
|
||||
default=[],
|
||||
help="Relative path or glob to exclude (repeatable)",
|
||||
)
|
||||
archive.add_argument("--note", default="", help="Free-text note for the archive record")
|
||||
archive.add_argument(
|
||||
"--store-root",
|
||||
default="",
|
||||
help="Override the artifact-store location (default: <root>/output/archives/.store)",
|
||||
)
|
||||
|
||||
archive_list = sub.add_parser(
|
||||
"archive-list",
|
||||
help="List recorded archives for an infospace",
|
||||
)
|
||||
archive_list.add_argument("root")
|
||||
archive_list.add_argument(
|
||||
"--with-retention",
|
||||
action="store_true",
|
||||
help="Annotate each archive with its current retention state",
|
||||
)
|
||||
archive_list.add_argument(
|
||||
"--store-root",
|
||||
default="",
|
||||
help="Override the artifact-store location for retention lookups",
|
||||
)
|
||||
|
||||
restore = sub.add_parser(
|
||||
"restore",
|
||||
help="Restore an archived infospace package into a target directory",
|
||||
)
|
||||
restore.add_argument("package_id")
|
||||
restore.add_argument("--target", required=True, help="Directory to restore into")
|
||||
restore.add_argument(
|
||||
"--from",
|
||||
dest="from_root",
|
||||
default="",
|
||||
help="Source infospace whose archive store holds the package",
|
||||
)
|
||||
restore.add_argument(
|
||||
"--store-root",
|
||||
default="",
|
||||
help="Direct path to the artifact-store location",
|
||||
)
|
||||
restore.add_argument(
|
||||
"--force",
|
||||
action="store_true",
|
||||
help="Overwrite into a non-empty target directory",
|
||||
)
|
||||
|
||||
engine = sub.add_parser("engine", help="Inspect and sync engine boundary state")
|
||||
engine_sub = engine.add_subparsers(dest="engine_command", required=True)
|
||||
|
||||
@@ -423,6 +497,36 @@ def main(argv: list[str] | None = None) -> int:
|
||||
_write_json(plan_generation(infospace.root, stage=args.stage))
|
||||
else:
|
||||
parser.error(f"Unhandled generate command: {args.generate_command}")
|
||||
elif args.command == "archive":
|
||||
record = archive_infospace(
|
||||
Path(args.root),
|
||||
retention_class=args.retention_class,
|
||||
include=args.include or None,
|
||||
exclude=args.exclude or None,
|
||||
note=args.note,
|
||||
store_root=args.store_root or None,
|
||||
)
|
||||
_write_json(record.to_dict())
|
||||
elif args.command == "archive-list":
|
||||
archives = list_archives(Path(args.root))
|
||||
if args.with_retention:
|
||||
payload = annotate_retention(
|
||||
archives,
|
||||
store_root=args.store_root or None,
|
||||
source_infospace=Path(args.root) if not args.store_root else None,
|
||||
)
|
||||
_write_json({"archives": payload})
|
||||
else:
|
||||
_write_json({"archives": [rec.to_dict() for rec in archives]})
|
||||
elif args.command == "restore":
|
||||
result = restore_archive(
|
||||
args.package_id,
|
||||
target=Path(args.target),
|
||||
store_root=args.store_root or None,
|
||||
source_infospace=Path(args.from_root) if args.from_root else None,
|
||||
force=args.force,
|
||||
)
|
||||
_write_json(result.to_dict())
|
||||
elif args.command == "engine":
|
||||
if args.engine_command == "inspect":
|
||||
_write_json(
|
||||
|
||||
Reference in New Issue
Block a user