[verified] Enforce delegation adoption at runtime consumers
This commit is contained in:
@@ -24,6 +24,8 @@ Baseline/current inventory를 생성·전달·채택할 때 `check-tests 이전`
|
||||
|
||||
위임 결과 채택 이전에 `harness/lib/delegation_evidence.py`를 [공통 증빙 채택 정본](../../../../operations/repository-cache.md#delegation-evidence-acceptance-preflight)에 따라 실행한다. main이 독립적으로 고정한 context에 source/bundle digest, 허용 작업, 검증 항목과 독립 실행 catalog를 넣으며 worker 자기 선언으로 context를 만들지 않는다. Source line/named test anchor와 실제 보존된 회귀 테스트·RED/GREEN 기록을 대조하고, bundle 추출 범위 밖 부재 주장은 authoritative Git 조회 없이 채택하지 않는다. Import·환경 오류를 RED로 취급하거나 확인되지 않은 위임 주장을 최종 판정·delivery·publication 근거로 옮기지 않는다. Static-only worker가 기존 독립 실행 근거를 인용하는 것과 직접 실행하는 것을 구분한다. BLOCK/입력 거부이면 해당 증빙 채택을 중단하고 main이 실제 기록과 의미를 재검증한다. Gate는 sandbox, 실행 인증 또는 assertion 의미 판정기가 아니며 기존 compare-source/check-tests와 role의 제품 실행 제한을 대체하지 않는다.
|
||||
|
||||
실제 소비 함수에서도 [runtime consumption 정본](../../../../operations/repository-cache.md#delegation-adoption-runtime-consumption)의 `require_adoption`을 통과한다. main은 위임 근거 비사용을 `{"mode":"none"}`, 사용을 `mode=delegated` exact 선언으로 명시하며 누락이나 이전 PASS receipt를 허용하지 않는다. 판정/render, delivery artifact, native publication마다 새 검증을 수행하고 소비 source/stage에 결속한다. 최초 구현 delivery는 `gitea-issue-implementation/scripts/implementation_delivery.py`의 `deliver_implementation(adapter, head=final_head, declaration=main_declaration)`에서 main-owned push/PR adapter를 호출한다. Adapter는 push/read_remote_head/create_or_reuse_pr/read_pr를 제공하고 기존 인증·repository/Issue scope·non-force explicit refspec·remote readback 계약을 지킨다. 새 진입점을 쓰지 않는 직접 push/PR 생성은 이 workflow를 우회하는 것으로 금지한다. Gate 밖 직접 Git/API 실행을 sandbox로 차단한다고 주장하지 않는다. 기존 semantic/source/identity/lease 검증과 외부 readback은 그대로 유지한다.
|
||||
|
||||
## Managed-lock broker gate
|
||||
|
||||
Issue workflow는 bootstrap 이후 반드시 [Repository cache 운영 지침](../../../../operations/repository-cache.md)의 하나의 role-neutral managed-lock broker를 사용한다. `serve`에 canonical Issue identity, role, workflow run-id와 TTL을 전달해 broker-generated runtime을 만들고, ready JSON의 `handle`/`socket` 경로만 사용한다. 경로는 `<issue>/runtime/<workflow-run-id>/broker.handle`과 `broker.sock`이며 verification stage run-id와 독립적이다. Runtime 생성은 approval이나 worktree admission을 대신하지 않는다. Cache bootstrap 이후 broker가 Issue lock 아래 runtime을 준비하고, ready 이후 각 workflow의 진입 검증·승인 조건을 확인한다. Client는 raw token을 inspect/log하지 않으며 public `request` API가 token을 캡슐화한다. Repository lock은 같은 handle의 `acquire_repository`/`release_repository` request로만 중첩하고, TTL 전에 `renew`한다. 정상·오류·cancel 모두 `release_repository`(보유 시) → `release` → broker wait/reap → `probe`의 `free=true` 확인 순서를 완료하기 전에는 완료·로그·보고하지 않는다. Raw `flock ... sleep`과 lock-file unlink는 금지한다.
|
||||
@@ -197,11 +199,11 @@ Discovery가 `신규 작성` 또는 `수정`으로 판정한 문서만 작성한
|
||||
6. 기존 requirement revision이면 final tree에 Contract Preservation Inventory를 다시 대응해 `missing=0`, `ambiguous=0`을 확인한다. 충족하지 못하면 stage하거나 commit하지 않는다.
|
||||
7. 승인된 modified·untracked file을 exact allowlist로 stage하고 staged diff를 다시 읽는다. 범위 밖 파일, credential, generated artifact 또는 예상하지 못한 삭제가 있으면 commit하지 않는다.
|
||||
8. Repository convention에 맞는 non-amended commit을 생성하고 base ancestry, commit tree와 worktree 상태를 검증한다. Push 전에 그 exact commit의 strict clean checkout에서 canonical external verification helper로 최종 검증을 실행·validate하고 manifest의 source SHA가 전달할 commit과 일치함을 확인한다. 실패하면 local commit을 보존하고 push하지 않는다.
|
||||
9. Issue worktree는 delivery 전후 모두 detached HEAD로 유지한다. Issue 전용 remote branch가 없으면 detached final commit을 explicit repository URL과 `<final-sha>:refs/heads/<issue-branch>` refspec으로 push해 생성하고, 있으면 현재 작업이 소유한 branch이며 fast-forward 가능한 경우에만 같은 방식으로 갱신한다. `git switch -c`, `git checkout -b`, `git branch` 또는 동등한 명령으로 canonical mirror의 `refs/heads/*`에 구현용 local branch를 만들지 않는다. Default branch에는 직접 push하지 않고 force-push하지 않으며 canonical cache의 mirror remote 설정을 변경하지 않는다.
|
||||
9. Main은 아래 9–12의 push/readback/PR 동작을 main-owned adapter로 구현하고 `scripts/implementation_delivery.py`의 `deliver_implementation(adapter, head=final_head, declaration=main_declaration)`로 실행한다. Callback을 직접 먼저 실행하거나 이전 PASS로 생략하지 않는다. Adapter의 인증·repository/Issue·branch scope 검증은 아래 규칙을 모두 유지한다. Issue worktree는 delivery 전후 모두 detached HEAD로 유지한다. Issue 전용 remote branch가 없으면 detached final commit을 explicit repository URL과 `<final-sha>:refs/heads/<issue-branch>` refspec으로 push해 생성하고, 있으면 현재 작업이 소유한 branch이며 fast-forward 가능한 경우에만 같은 방식으로 갱신한다. `git switch -c`, `git checkout -b`, `git branch` 또는 동등한 명령으로 canonical mirror의 `refs/heads/*`에 구현용 local branch를 만들지 않는다. Default branch에는 직접 push하지 않고 force-push하지 않으며 canonical cache의 mirror remote 설정을 변경하지 않는다.
|
||||
10. Target repository write 권한이 없으면 같은 push를 반복하지 않는다. Authenticated identity가 fork를 생성할 수 있으면 기존 fork·동명 branch·PR 유무와 fork parent/base SHA를 검증한 뒤 fork의 Issue 전용 branch로 non-force push하고 cross-repository PR을 사용한다. Fork도 쓸 수 없으면 permission blocker와 local commit SHA를 보고한다.
|
||||
11. Remote readback으로 branch owner, repository, branch SHA가 local commit SHA와 exact-match하는지 확인한다.
|
||||
12. Default branch를 base로 PR을 생성한다. 제목은 Issue를 식별하고, 본문에는 `Closes #<number>`, 구현 요약, 문서 변경, 실제 검증 명령·결과, 제한사항과 보안상 주의점을 기록한다. PR 생성 자체로 Issue state를 변경하지 않는다.
|
||||
13. PR 번호를 얻은 뒤 요구사항 문서의 `구현 PR: 미정`을 실제 PR 번호 또는 URL로 교체하고, 문서 검증과 관련 test를 재실행해 별도 commit으로 같은 branch에 non-force push한다.
|
||||
13. 후속 commit의 push에도 새 head/source/context/result를 결속해 동일 delivery workflow를 사용하며 create_or_reuse_pr는 이미 확인된 같은 PR을 재사용한다. PR 번호를 얻은 뒤 요구사항 문서의 `구현 PR: 미정`을 실제 PR 번호 또는 URL로 교체하고, 문서 검증과 관련 test를 재실행해 별도 commit으로 같은 branch에 non-force push한다.
|
||||
14. Gitea에서 PR details, base branch, head repository·branch·SHA, state, changed files와 commit을 다시 읽는다. 최종 remote head SHA가 최종 local commit SHA와 일치할 때만 전달 완료로 판정한다.
|
||||
15. Gitea Actions 또는 commit status가 제공되면 최종 head의 success, failed, pending, run 없음 또는 조회 불가 상태를 읽고 그대로 보고한다.
|
||||
16. PR 생성 또는 후속 push가 부분 실패하면 blind retry하거나 중복 PR을 만들지 않는다. 이미 생성된 fork·branch·PR과 local commit의 exact 상태 및 안전한 복구 지점을 보고한다.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Canonical implementation delivery adoption boundary.
|
||||
|
||||
The caller owns the authenticated transport and all push/readback invariants.
|
||||
This module only gates that transport on fresh delegation evidence.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
_SHA40 = re.compile(r"^[0-9a-f]{40}$")
|
||||
|
||||
|
||||
class AdoptionError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def require_adoption(declaration, *, expected_source, expected_stage):
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
repo_root = Path(__file__).resolve().parents[6]
|
||||
if str(repo_root) not in sys.path:
|
||||
sys.path.insert(0, str(repo_root))
|
||||
from harness.lib.delegation_adoption import require_adoption as checker
|
||||
except Exception as exc:
|
||||
raise AdoptionError("delegation adoption unavailable") from exc
|
||||
try:
|
||||
return checker(declaration, expected_source=expected_source, expected_stage=expected_stage)
|
||||
except Exception as exc:
|
||||
raise AdoptionError("delegation adoption rejected") from exc
|
||||
|
||||
|
||||
def deliver(adapter, *, head, declaration):
|
||||
if not callable(adapter):
|
||||
raise AdoptionError("trusted delivery adapter required")
|
||||
if type(head) is not str or not _SHA40.fullmatch(head):
|
||||
raise AdoptionError("invalid delivery head")
|
||||
# Keep this immediately before the external-write callback: evidence must be fresh.
|
||||
require_adoption(declaration, expected_source={"kind": "git_tree", "revision": head},
|
||||
expected_stage="implementation-delivery")
|
||||
return adapter()
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
"""Initial implementation delivery workflow with independent transport readbacks."""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import re
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any
|
||||
|
||||
|
||||
_SHA40 = re.compile(r"^[0-9a-f]{40}$")
|
||||
_REQUIRED = ("push", "read_remote_head", "create_or_reuse_pr", "read_pr")
|
||||
|
||||
|
||||
def _load_delivery_adoption() -> ModuleType:
|
||||
path = Path(__file__).resolve().parent / "delivery_adoption.py"
|
||||
spec = importlib.util.spec_from_file_location("gitea_delivery_adoption", path)
|
||||
if spec is None or spec.loader is None:
|
||||
raise AdoptionError("delivery adoption unavailable")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
_delivery_adoption = _load_delivery_adoption()
|
||||
AdoptionError = _delivery_adoption.AdoptionError
|
||||
|
||||
|
||||
def _reject(message: str) -> None:
|
||||
raise AdoptionError(message)
|
||||
|
||||
|
||||
def _validate_adapter(adapter: Any) -> None:
|
||||
for name in _REQUIRED:
|
||||
try:
|
||||
value = getattr(adapter, name)
|
||||
except Exception:
|
||||
_reject("trusted delivery adapter required")
|
||||
else:
|
||||
if not callable(value):
|
||||
_reject("trusted delivery adapter required")
|
||||
|
||||
|
||||
def _validate_head(head: Any) -> str:
|
||||
if type(head) is not str or not _SHA40.fullmatch(head):
|
||||
_reject("invalid delivery head")
|
||||
return head
|
||||
|
||||
|
||||
def _validate_pr_number(value: Any) -> int:
|
||||
if type(value) is not int or isinstance(value, bool) or value < 1:
|
||||
_reject("invalid pull request number")
|
||||
return value
|
||||
|
||||
|
||||
def _validate_readback(value: Any, number: int, head: str) -> dict[str, Any]:
|
||||
if type(value) is not dict:
|
||||
_reject("invalid pull request readback")
|
||||
if value.get("number") != number or type(value.get("number")) is not int:
|
||||
_reject("pull request number mismatch")
|
||||
if value.get("head") != head or type(value.get("head")) is not str:
|
||||
_reject("pull request head mismatch")
|
||||
if value.get("state") != "open":
|
||||
_reject("pull request state mismatch")
|
||||
# Bound the returned artifact even when the trusted adapter exposes richer data.
|
||||
return {"number": number, "head": head, "state": "open"}
|
||||
|
||||
|
||||
def deliver_implementation(adapter: Any, *, head: str, declaration: Any) -> dict[str, Any]:
|
||||
"""Push an implementation and create/reuse a PR only after fresh gates."""
|
||||
_validate_head(head)
|
||||
_validate_adapter(adapter)
|
||||
|
||||
# The canonical helper performs the explicit declaration/adoption check
|
||||
# immediately before the first external write.
|
||||
_delivery_adoption.deliver(lambda: adapter.push(head), head=head, declaration=declaration)
|
||||
|
||||
remote_head = adapter.read_remote_head()
|
||||
if remote_head != head or type(remote_head) is not str or not _SHA40.fullmatch(remote_head):
|
||||
_reject("remote head mismatch")
|
||||
|
||||
# Re-check fresh evidence immediately before the PR write.
|
||||
pr_number = _delivery_adoption.deliver(
|
||||
lambda: adapter.create_or_reuse_pr(head), head=head, declaration=declaration
|
||||
)
|
||||
pr_number = _validate_pr_number(pr_number)
|
||||
|
||||
readback = adapter.read_pr(pr_number)
|
||||
bounded_pr = _validate_readback(readback, pr_number, head)
|
||||
return {"remote_head": remote_head, "pr": bounded_pr}
|
||||
@@ -26,6 +26,8 @@ Baseline/current inventory를 생성·전달·채택할 때 `check-tests 이전`
|
||||
|
||||
위임 결과 채택 이전에 `harness/lib/delegation_evidence.py`를 [공통 증빙 채택 정본](../../../../operations/repository-cache.md#delegation-evidence-acceptance-preflight)에 따라 실행한다. main이 독립적으로 고정한 context에 source/bundle digest, 허용 작업, 검증 항목과 독립 실행 catalog를 넣으며 worker 자기 선언으로 context를 만들지 않는다. Source line/named test anchor와 실제 보존된 회귀 테스트·RED/GREEN 기록을 대조하고, bundle 추출 범위 밖 부재 주장은 authoritative Git 조회 없이 채택하지 않는다. Import·환경 오류를 RED로 취급하거나 확인되지 않은 위임 주장을 최종 판정·delivery·publication 근거로 옮기지 않는다. Static-only worker가 기존 독립 실행 근거를 인용하는 것과 직접 실행하는 것을 구분한다. BLOCK/입력 거부이면 해당 증빙 채택을 중단하고 main이 실제 기록과 의미를 재검증한다. Gate는 sandbox, 실행 인증 또는 assertion 의미 판정기가 아니며 기존 compare-source/check-tests와 role의 제품 실행 제한을 대체하지 않는다.
|
||||
|
||||
실제 소비 함수에서도 [runtime consumption 정본](../../../../operations/repository-cache.md#delegation-adoption-runtime-consumption)의 `require_adoption`을 통과한다. main은 위임 근거 비사용을 `{"mode":"none"}`, 사용을 `mode=delegated` exact 선언으로 명시하며 누락이나 이전 PASS receipt를 허용하지 않는다. 판정/render, delivery artifact, native publication마다 새 검증을 수행하고 소비 source/stage에 결속한다. 최초 구현 delivery는 `gitea-issue-implementation/scripts/implementation_delivery.py`의 `deliver_implementation(adapter, head=final_head, declaration=main_declaration)`에서 main-owned push/PR adapter를 호출한다. Adapter는 push/read_remote_head/create_or_reuse_pr/read_pr를 제공하고 기존 인증·repository/Issue scope·non-force explicit refspec·remote readback 계약을 지킨다. 새 진입점을 쓰지 않는 직접 push/PR 생성은 이 workflow를 우회하는 것으로 금지한다. Gate 밖 직접 Git/API 실행을 sandbox로 차단한다고 주장하지 않는다. 기존 semantic/source/identity/lease 검증과 외부 readback은 그대로 유지한다.
|
||||
|
||||
## Managed-lock broker gate
|
||||
|
||||
Bootstrap 이후 이 Issue 재작업도 canonical [Repository cache 운영 지침](../../../../operations/repository-cache.md)의 공통 managed-lock broker 하나만 사용한다. `serve`에 canonical Issue identity, role, workflow run-id와 TTL을 전달해 broker-generated runtime을 만들고 ready JSON의 `handle`/`socket` 경로만 사용한다. 경로는 `<issue>/runtime/<workflow-run-id>/broker.handle`과 `broker.sock`이며 verification run-id와 독립적이다. Runtime 생성은 approval이나 worktree admission을 대신하지 않는다. Cache bootstrap 이후 broker가 Issue lock 아래 runtime을 준비하고, ready 이후 각 workflow의 진입 검증·승인 조건을 확인한다. 같은 handle로만 public `request` API의 `acquire_repository`/`release_repository`를 요청하고 TTL 전에 `renew`한다. Client는 raw token을 inspect/log하지 않으며 broker request API가 token을 캡슐화한다. 정상·오류·cancel cleanup은 보유한 repository lease release, Issue `release`, broker wait/reap, `probe`의 `free=true` 확인까지 동일하며 그 전에는 completed/log/report를 주장하지 않는다. Raw `flock ... sleep`, lock-file unlink와 client-side lock holder는 금지한다.
|
||||
@@ -265,7 +267,7 @@ Reviewer가 rework head를 새 snapshot으로 다시 검토해야 하며 이 com
|
||||
3. Push 결과만 신뢰하지 않고 remote branch SHA와 PR head SHA가 final local HEAD와 exact-match하는지 readback한다.
|
||||
4. PR body 갱신이 필요한 경우 write 직전에 PR 전체 필드, review·comment·commit collection cardinality와 authenticated impl identity를 다시 읽는다. Current body의 `Closes #<issue-number>`와 기존 구현 내용을 보존하고 재작업 review ID·reviewed head·finding별 조치·최종 검증과 unresolved limitation만 반영한 exact body payload를 명시해 한 번만 호출한다. 성공 응답만 신뢰하지 않고 fresh PR과 관련 collection을 읽어 PR author stable account ID, exact body payload, `updated_at`, base·head·commit/ref, state·draft·merge 상태, labels·assignees·reviewer와 collection cardinality를 baseline과 비교한다. Exact body와 provider가 해당 body update로 파생한 `updated_at` 외의 변경은 승인되지 않았으며 인과를 증명할 수 없으면 partial로 중단한다.
|
||||
5. PR changed files, commits, state, merge 상태, statuses와 workflow runs를 다시 읽는다. Local test, commit status와 Actions run을 별도 사실로 기록한다.
|
||||
6. Finding matrix와 최종 검증 기록을 bounded JSON input으로 만들고 `scripts/rework_comment.py render --input <input.json> --comment <comment.md> --expected <expected.json>`를 실행해 canonical 재작업 결과 comment, marker-free digest와 correlation을 생성·검증한다. Comment body와 marker를 수동 조립하지 않는다.
|
||||
6. Finding matrix와 최종 검증 기록을 bounded JSON input으로 만들고 `scripts/rework_comment.py render --input <input.json> --comment <comment.md> --expected <expected.json> --delegation-adoption <absolute-main-declaration.json>`를 실행해 canonical 재작업 결과 comment, marker-free digest와 correlation을 생성·검증한다. Comment body와 marker를 수동 조립하지 않는다.
|
||||
7. Exact PR top-level comment endpoint target과 fresh PR metadata binding을 검증해 Source Issue comments, inline comments, review replies와 native reviews에서 분리하고 scoped collection을 complete pagination한 뒤 canonical deduplication 규칙을 적용한다. Existing exact comment가 없으면 exact PR conversation에 body를 한 번 게시하고, 있으면 write 없이 재사용한다.
|
||||
8. Exact comment와 scoped complete collection을 fresh normalized JSON으로 readback한 뒤 `scripts/rework_comment.py verify --expected <expected.json> --pr-readback <pr.json> --comments-readback <comments.json> --identity-readback <identity.json> --action <create|reuse> --baseline-count <N> --output <handoff-receipt.json>`를 실행한다. Receipt의 comment ID, impl author stable account ID, body, timestamp, final head, digest, correlation과 cardinality가 검증돼야 하며, 신규 게시로 허용된 PR-underlying-issue projection 외의 PR field, Source Issue field와 기존 review collection에 변경이 없어야 한다.
|
||||
9. Native review를 작성·dismiss·reply하거나 새 review를 요청하지 않는다. 기존 `REQUEST_CHANGES`는 reviewer가 새 head를 재검토하기 전까지 자동 해소된 것으로 주장하지 않는다.
|
||||
|
||||
@@ -21,10 +21,16 @@ def read_json(p):
|
||||
return x
|
||||
|
||||
def read_json_any(p):
|
||||
b=Path(p).read_bytes()
|
||||
if len(b)>MAX: raise Bad("input too large")
|
||||
try: return json.loads(b)
|
||||
except Exception as e: raise Bad("invalid json") from e
|
||||
repo_root = Path(__file__).resolve().parents[6]
|
||||
if str(repo_root) not in sys.path:
|
||||
sys.path.insert(0, str(repo_root))
|
||||
try:
|
||||
from harness.lib.delegation_evidence import _json_file
|
||||
value = _json_file(str(p))
|
||||
except Exception as e:
|
||||
raise Bad("invalid json") from e
|
||||
if not isinstance(value, dict): raise Bad("object required")
|
||||
return value
|
||||
|
||||
def text(x, name, pattern=None, maxlen=4096):
|
||||
if not isinstance(x,str) or not x or len(x)>maxlen or "\r" in x or "\n" in x or x.rstrip()!=x: raise Bad(f"invalid {name}")
|
||||
@@ -83,7 +89,17 @@ def atomic(path,data):
|
||||
if os.path.exists(tmp): os.unlink(tmp)
|
||||
|
||||
def do_render(a):
|
||||
d,vals,fs,ex,cs,ar=canon(read_json(a.input))
|
||||
raw = read_json(a.input)
|
||||
declaration = {"mode": "none"} if a.no_delegated_evidence else read_json_any(a.delegation_adoption)
|
||||
try:
|
||||
repo_root = Path(__file__).resolve().parents[6]
|
||||
if str(repo_root) not in sys.path:
|
||||
sys.path.insert(0, str(repo_root))
|
||||
from harness.lib.delegation_adoption import require_adoption
|
||||
require_adoption(declaration, expected_source={"kind": "git_tree", "revision": raw.get("final_head")}, expected_stage="implementation-delivery")
|
||||
except Exception:
|
||||
raise Bad("delegation adoption rejected") from None
|
||||
d,vals,fs,ex,cs,ar=canon(raw)
|
||||
body=render_body(d,vals,fs,ex,cs,ar); bb=body.encode()
|
||||
rh=hashlib.sha256(bb).hexdigest()
|
||||
corr=hashlib.sha256("\0".join([d['owner'],d['repository'],vals['issue'],vals['pr'],vals['review'],d['reviewed_head'],d['final_head'],d['contract_sha256']]).encode()).hexdigest()
|
||||
@@ -137,6 +153,7 @@ def do_verify(a):
|
||||
def main():
|
||||
p=argparse.ArgumentParser(); sp=p.add_subparsers(dest="cmd",required=True)
|
||||
r=sp.add_parser("render"); r.add_argument("--input",required=True); r.add_argument("--comment",required=True); r.add_argument("--expected",required=True)
|
||||
adoption=r.add_mutually_exclusive_group(required=True); adoption.add_argument("--no-delegated-evidence",action="store_true"); adoption.add_argument("--delegation-adoption")
|
||||
v=sp.add_parser("verify"); v.add_argument("--expected",required=True); v.add_argument("--pr-readback",required=True); v.add_argument("--comments-readback",required=True); v.add_argument("--identity-readback",required=True); v.add_argument("--action",choices=("create","reuse"),required=True); v.add_argument("--baseline-count",type=int,required=True); v.add_argument("--output",required=True)
|
||||
args=p.parse_args()
|
||||
try: do_render(args) if args.cmd=="render" else do_verify(args)
|
||||
|
||||
Reference in New Issue
Block a user